Már egy ideje az íróasztal fiókom mélyén tanyázik egy MCP23017 chip, amivel például a Raspberry Pi GPIO portjait lehet bővíteni, szám szerint 16 darab új csatlakozással. A 16 darab csatlakozás pedig pont elég arra, hogy 16 darab LED segítségével megjelenítsünk egy bináris órát (bár sajnos csak 12 órás megjelenítéssel).

Hardver és összedrótozás

Először is talán ismerkedjünk meg egy kicsit közelebbről az MCP23017-tel. I²C buszon lehet vele kommunikálni, a hardver címét az A0-2 lábak határozzák meg.

A GPA0-7 és GPB0-7 lábakra fogjuk rákötni a ledeket (esetleg a LED-hez kellhet még egy ellenállás is, de én attól eltekintettem), A VSS-re és az A0-2 lábakra megy a föld (ez a 0x20-as hardvercímet fogja eredményezni), a VDD-re és a RESET-re a 3.3V, az SLC és SDA pedig a Raspberry Pi azonos nevű kivezetéseivel lesz összekötve.

Katt a nagyobb változatért.

Bónuszként a kábelrengeteg miatt még úgy is fog tűnni, mintha valami bonyolult dolog lenne. Ezen kívül, ami még érdekel minket, azok az MCP23017 regiszterei és azok címei. Legalábbis egy részük:

0x00 IODIRA A GPA0-7 lábak inputként vagy outputként működjenek
0x01 IODIRB A GPB0-7 lábak inputként vagy outputként működjenek
0x12 GPIOA Ennek a segítségével lehet a GPA0-7 lábakat elérni
0x13 GPIOB Ennek a segítségével lehet a GPB0-7 lábakat elérni

A chip dokumentációjában utána lehet nézni a többinek is, ha valakit esetleg részletesebben is érdekelne.

Konfigurálás

Ennyit a hardverről, végre rátérhetünk a dolog szoftveres részére. Mint azt már fentebb említettem, a chip I²C-t használ, ami alapból nincsen bekapcsolva az RPi-n. Először is a /etc/modules fájlba kell két új sor, mondjuk a végére:

i2c-bcm2708
i2c-dev

Aztán a /etc/modprobe.d/raspi-blacklist.conf fájlban a blacklist i2c-bcm2708 sort kell törölni (vagy kikommentelni). Szükség van ezen kívül még néhány csomagra telepítésére is:

$ sudo apt-get install python-smbus i2c-tools

Ha ez is végzett, akkor már csak egy újraindítás, és el is kezdhetjük használatba venni a szerkezetet.

Életre keltés

Először is szükségünk lesz a chip címére, amit az i2cdetect parancs segítségével tudunk kideríteni. A fenti drótozással elvileg ezt kell kapnunk:

$ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: 20 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

Amiből kiderül, hogy 0x20 az eszköz címe (régebbi RPi-n előfordulhat, hogy sudo i2cdetect -y 0 a nyerő parancs), úgyhogy rá is térhetünk a kódra.

import time
import smbus

Az már a konfigurálás során kiderült, hogy a Python smbus modulját fogjuk használni (meg egy kis időt, ha már egyszer órát csinálunk).

i2c = 1
addr = 0x20

Néhány beállítás: az i2c értéke ugyanaz, mint amit az i2cdetect parancsban is használtunk -y paraméterként, az addr értéke pedig az i2cdetect válaszából kinyert hardver cím.

bus = smbus.SMBus(i2c)
bus.write_byte_data(addr, 0x00, 0x00)
bus.write_byte_data(addr, 0x01, 0x00)

Kezdődhet a kommunikáció. A chip 0x00-s és 0x01-es regisztereit is kinullázzuk, ami mindkét nyolcas csatlakozó csoportot output-ra állítja.

try:
  while True:
    now = time.localtime()
    h = now.tm_hour
    if h > 12:
      h = h - 12
    
    x = '{:04b}{:06b}{:06b}'.format(h, now.tm_min, now.tm_sec)
    
    bus.write_byte_data(addr, 0x12, int(x[8:], 2))
    bus.write_byte_data(addr, 0x13, int(x[:8], 2))
    time.sleep(0.01)
except KeyboardInterrupt:
    bus.write_byte_data(addr, 0x12, 0x00)
    bus.write_byte_data(addr, 0x13, 0x00)
    print

Először átvarázsoljuk az aktuális időt egy nekünk megfelelő bináris formátumba, aztán beírjuk az egyes byte-okat a 0x12-es és 0x13-as regiszterekbe. Aztán ez megy tovább a végtelenségig. Vagyis amíg Ctrl+C-t nem nyomunk, akkor kinullázzuk mindkét regisztert, ami az összes ledet lekapcsolja. A végeredmény pedig:

A minap eszembe jutott, hogy milyen jó is lenne, ha a Sublime jelezné, ha van valami elgépelés az éppen szerkesztett fájlba. És bár létezik elég sok megoldás a problémára, ami ráadásul még több nyelvet is (Python, CSS, JavaScript, stb.) támogat, de azért mégis csak sokkal mókásabb egy sajátot írni.

A megoldás lényege, hogy valahol a vinyón van egy PHP bináris, amit megfuttatunk a -l kapcsolóval, ami nem futtatja a fájlt, hanem csak szintaxisra ellenőriz. A válasz a PHP szokásos "parse error" szövege lesz, ami azt írja ki, hogy hol hasalt el a fordító, ami nem feltétlen esik egybe azzal, hogy hol van a hiba. A kódot többféleképpen meg lehetne valósítani (pl. parancsként, amit menüből, vagy billentyűkombinációra lehet futtatni), én azt választottam, hogy mentés eseményre automatikusan fusson le, ha megfelelő a fájl kiterjesztése:

import sublime, sublime_plugin, os, subprocess, re

class PhpLintEventListener(sublime_plugin.EventListener):
    key = "php_lint_syntax_error"
    
    def on_post_save(self, view):
        if not view.settings().has("php_lint"):
            return
        
        config = view.settings().get("php_lint")
        
        bin_path = config.get("binary", "")
        name, ext = os.path.splitext(view.file_name())
        
        if not bin_path or ext not in config.get("extensions", []):
            return
        
        view.erase_regions(self.key)
        view.erase_status(self.key)
        
        shell = sublime.platform() == "windows"
        p = subprocess.Popen([bin_path, "-l",  view.file_name()], shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        (stdout, stderr) = p.communicate()
        
        if "" == stderr:
            data = stdout
        else:
            data = stderr
        
        result = re.match(r"(.+?) in (.+?) on line ([0-9]+).*", data, re.M | re.S)
        if result:
            error = result.group(1)
            line = result.group(3)
            
            view.add_regions(self.key, [view.full_line(view.text_point(int(line) - 1, 0))], "markup.deleted", "dot")
            view.set_status(self.key, error + " on line " + line)

Nem egy bonyolult jószág, ha létezik a config és a fájl kiterjesztése benne van a configban megadott listában, akkor megfuttatjuk a PHP binárist és megkeressük a kimenetében, hogy hányadik sorban volt a hiba. A hibás sort kiemeljük és a hiba szövegét a státuszba ki is írjuk. A szükséges config pedig valahogy így néz ki:

"php_lint" : {
    "binary" : "d:\\php\\5.2\\php.exe",
    "extensions" : [".php"]
}

Ezt lehet pakolni a Preferences > Settings - User részbe vagy akár a projekt beállításai közé is (a .sublime-project fájlon belül a "settings" blokkba).

Érdekes módon a deadlime kedvenc kódszerkesztő alkalmazása a Sublime. Egyrészt azért, mert a többszörös kijelölés az bizony hihetetlenül fantasztikus egy találmány. Másrészt pedig Python-ban lehet hozzá plugin-eket írni.

Kedves kollégám említette a minap, hogy egyik régebben használt szerkesztőjében volt valamiféle funkció, amivel a vágólap korábbi tartalmait lehetett újra felhasználni. Aztán elkezdtem gondolkodni rajta, hogy vajon lehetne-e egy ilyen plugin-t írni Sublime-hoz. Aztán megírtam. :)

Ha már így alakult, ragadjuk meg az alkalmat és nézzük meg kicsit, hogyan is néz ki egy Sublime plugin. A funkció meglepően kevés kódból megoldható: először is felül kell csapnunk a beépített copy és cut utasításokat sajáttal úgy, hogy megmaradjon az eredeti funkcionalitás is, de közbe mentsük egy tömbbe az adatokat.

class ClipboardHistoryCopyCommand(sublimeplugin.TextCommand):
    def run(self, view, args):
        saveSelections(view)
        view.runCommand('copy');

Egy kis XML-lel meg is mondjuk a proginak, hogy a mi parancsunkat használja a Ctrl+C nyomkodása esetén:

<bindings>
    <binding key="ctrl+c" command="clipboardHistoryCopy" />
</bindings>

A cut nem túl meglepő módon elég hasonlóan néz ki (csak a beépített cut parancsot hívjuk meg a végén). A saveSelections végzi a munka javát. Végigmegy az összes kijelölésen és amelyik nem üres, azt elpakolja egy tömbbe, aminél még arra is figyelünk, hogy ne lehessen túl nagy:

clipboardMaxSize = 100
clipboardData = []

def saveSelections(view):
    for region in view.sel():
        if region.begin() != region.end():
            clipboardData.insert(0, view.substr(region))
            if len(clipboardData) > clipboardMaxSize:
                clipboardData.pop()

Annyi maradt már csak, hogy valami kellően kézreálló billentyűkombinációra meg is jelenítse az előzményeket és tudjunk belőle kedvünkre válogatni. Legnagyobb szerencsénkre tudjuk használni a beépített lista ablakot, amiben még kereső is van (ugyanaz, ami a Ctrl+P vagy a Ctrl+R kombinációkra is bejön):

class ClipboardHistoryPasteCommand(sublimeplugin.TextCommand):
    def pasteSelected(self, view, index):
        for region in view.sel():
            view.insert(region.begin(), clipboardData[index])
    
    def run(self, view, args):
        view.window().showSelectPanel(
            clipboardData,
            functools.partial(self.pasteSelected, view),
            None,
            sublime.SELECT_PANEL_MONOSPACE_FONT
        );

És természetesen a hozzá tartozó kis XML részlet:

<binding key="alt+shift+q" command="clipboardHistoryPaste" />

Nem a legtökéletesebb megoldás, például plugin újratöltéseknél elveszik a clipboardData tömb tartalma (lehetne például fájlba menteni menet közben), de kezdetnek megteszi. A teljes csomagot innen le lehet rántani (érdekes módon ClipboardHistory.sublime-package fájlnévvel nem mentek volna a billentyűparancs felüldefiniálások, mivel az Sublime ABC sorrendben tölti be a plugineket és a Default később van... :)).

[a képet köszönjük ShutterBugChef-nek]

Az úgy volt, hogy elgondolkoztam azon, hogy vajon miért jó az, ha a kiszolgálás folyamatát több részre bontják. Ha egyáltalán jó az. Offline, bolti kiszolgálásról van szó, mielőtt bárki - a blog témáját figyelembe véve - jogosan másra gondolna.

Ha vesszük az értékesítő-pénztáros-árukiadó modellt, érzésre úgy tűnik, hogy a több sor végigjárása miatt többet is várunk. De a sorbanállás pszichológiája fura egy dolog. Jó példa erre a hipermarket, ahol N pénztárnak van N sora azért, mert az N pénztár 1 sor felállás zavaró lehet az embereknek a hosszú sor miatt, holott a várakozási idő úgy kevesebb lenne.

Tovább...

Mostanság parancssori scripteket írogatok Python-ban, ha éppen nincs jobb dolgom (ritka). Gondoltam hát, hogy ezzel kapcsolatos tapasztalataimat és a nyelvvel való ismerkedésemet megosztom egy - egyelőre még nem tudni hány részes - bejegyzéssorozatban. Az első rész témája az alkalmazásnak átadott paraméterek feldolgozása a getopt és az optparse modulok segítségével.

A programunk egy egyszerű "Hello World!" alkalmazás lesz parancssori stílusban. Két fajta bemenő paramétert fogad el. Az egyik a név, akinek köszönni fog, a másikra pedig megjeleníti a súgót, hogy hogyan is kell paraméterezni ezt a rettentő bonyolult programot.

Tovább...

Néha a lustaságomon felül kerekedik az a vágy, hogy új programozási nyelveket ismerjek meg. Ennek lett a legfrissebb áldozata a Python. Bár már volt vele dolgom régebben is, de körülbelül csak egy Fibonacci-számokat számoló rekurzív függvény megvalósításáig jutottam (az is nagy valószínűséggel valami példakód volt valamelyik segédletből).

Magáról a nyelvről azt lehet elmondani, hogy feltűnően szépnek mondható ahhoz képest, hogy a Pascal-stílusú nyelvek közé tartozik. És nem csak egyszerűen szép, hanem törekszik arra, hogy a benne írt kód is szép legyen (ahogy az a Python filozófiáinak listájában is szerepel: "Beautiful is better than ugly."). Persze ehhez az is kell, hogy ne csak Python nyelven, de Python stílusban is programozunk.
A kinézetet tekintve legszembetűnőbb különbség más nyelvekhez képest a kód kötelező indentálása. Nincsenek sorvégi pontok, pontosvesszők, if-eket és egyéb blokkszerkezeteket lezáró hullámos zárójelek vagy kulcsszavak. Minden az új sor karakterek és az indentálás alapján dől el. De mivel kódolás közben a nagy többség amúgy is szokott új sorokat és tabokat használni (és mivel a kötelező indentálás az esetek közel 100%-ában megegyezik azzal, amúgy is használnánk), ezért elég gyorsan hozzá lehet szokni ahhoz, hogy tulajdonképpen kevesebbet kell gépelni. Persze ez még kevés ahhoz, hogy használható is legyen valamire. Mivel napjaim nagy részét a webprogramozás teszi ki, ezért a szintaktikai ismerkedés után első dolgom az volt, hogy utánanézzek, mire is képes a kígyó a weben...

Tovább...