12.9. Kétirányú adatáramlás

A csatornák nem egyirányúak. Egy write metódust megvalósító backend lehetővé teszi, hogy a gazdagép bájtokat küldjön a kamera felé, és a kamera reagáljon. Ez a minta áll minden valódi interaktív eszköz mögött: a kezelő elforgat egy gombot a gazdagép grafikus felületén, a gazdagép az új értéket egy konfigurációs csatornára írja, a kamera pedig a következő rögzítéskor beolvassa.

12.9.1. Egy konfigurációs csatorna

A streamelő kamera-oldali szkriptet kiegészítve tegyél elérhetővé egy második csatornát a JPEG-minőséghez:

class ConfigChannel:
    def __init__(self):
        self.quality = 85

    def size(self):
        return 0

    def read(self, offset, size):
        # Not used for "host writes to cam" -- but the library
        # still needs the method present.
        return b''

    def write(self, offset, data):
        # data is a bytearray view into the protocol buffer.
        # Copy out the contents before doing anything with it.
        new_q = int(bytes(data))
        if 1 <= new_q <= 100:
            self.quality = new_q
        return len(data)

config = ConfigChannel()
protocol.register(name='config', backend=config)

A rögzítési ciklus a config.quality értékről olvas minden alkalommal, amikor egy képkockát tömörít:

while True:
    img = csi0.snapshot()
    latest_jpeg = bytes(
        img.compress(quality=config.quality).bytearray()
    )
    ch.send_event(0x01)

A gazdagépnek immár van egy gombja. Állítsd 50-re, és a következő képkocka kisebb (és csúnyább) lesz; állítsd 95-re, és a következő képkocka nagyobb (és élesebb) lesz. A kamera újraindítás nélkül folytatja a rögzítést; a gazdagépnek nem kell új szkriptet feltöltenie.

12.9.2. Az írási hívás a gazdagép felől

A gazdagép oldalán a channel_write() bájtokat küld egy megnevezett csatornára:

cam.channel_write('config', b'50')

A gazda-SDK a bájtokat egyetlen (vagy fragmentált) CHANNEL_WRITE csomagként kódolja, a protokollréteg kézbesíti a kamerának, lefut a kamera write(offset=0, data=...) hívása, és a kamera oldala visszaigazol. Mire a hívás visszatér, a kamera megkapta és elfogadta az új értéket.

Az írás a kamera szemszögéből atomi – a protokollkönyvtár garantálja, hogy a backend write metódusa teljesen lefut, mielőtt bármely más művelet folytatódna azon a csatornán. Az alkalmazáskód a rögzítési cikluson belülről olvashatja a config.quality értéket anélkül, hogy aggódnia kellene amiatt, hogy a gazdagép pillanatkép közben beletapos.

12.9.3. Csonk méret és olvasás egy csak írható csatornán

Egy tisztán írható csatornának is szüksége van a size és a read definícióira, még akkor is, ha ezek 0-t és b''-t visszaadó csonkok. A könyvtár a metódusok jelenléte alapján vezeti le a csatorna képességjelzőit; egy read nélküli backendnél nem lesz beállítva a CHANNEL_FLAG_READ, és a gazdagép elutasít minden olvasási kísérletet.

A csak írható csatornán a read-ből visszaadott bájtok azonban más célra hasznosak: a jelenlegi érték visszatükrözésére, hogy egy éppen csatlakozott gazdagép megkérdezhesse a kamerától, „mi az aktuális beállítás?”, ahelyett, hogy egy alapértelmezésből indulna ki. Hogy ez működjön, mindkét iránynak meg kell egyeznie egy szerializálásban. A korábbi példában szereplő nyers bájtos int(bytes(data)) elemzés egyetlen egész mező esetén működik, de nem skálázódik, amint van egy második beállítandó gomb. Ha a write metódust átállítod JSON elemzésére, és párosítod egy olyan read metódussal, amely az ennek megfelelő JSON-kiírást adja vissza, a csatorna valódi, oda-vissza működő konfigurációs tárrá válik:

import json

class ConfigChannel:
    def __init__(self):
        self.quality = 85
        self._buf = b''
    def size(self):
        self._buf = json.dumps({'quality': self.quality}).encode()
        return len(self._buf)
    def read(self, offset, size):
        return self._buf[offset:offset + size]
    def write(self, offset, data):
        new = json.loads(bytes(data))
        if 'quality' in new:
            self.quality = int(new['quality'])
        return len(data)

A gazdagép immár a cam.channel_write('config', b'{"quality": 50}') hívással állít be egy értéket, és a cam.channel_read('config') hívással olvassa vissza az aktuális állapotot. A kamera minden olvasáskor friss JSON-kiírást szerializál, így a gazdagép mindig a legutóbbi értékeket látja, és egy újabb gomb hozzáadása (threshold, exposure, orientation) mindkét oldalon egyetlen sor a JSON-szótárban.

12.9.4. Egy teljes ciklus

Egy képkocka-csatornával a kamera → gazdagép adatokhoz, egy konfigurációs csatornával a gazdagép → kamera vezérléshez, és egy kevés összekötő kóddal az alkalmazás interaktív eszközzé válik:

  • A gazdagép megnyitja a kamerát, elkezdi lehúzni a képkockákat, és egy ablakban megjeleníti őket.

  • Amikor a kezelő elhúz egy csúszkát, a gazdagép az új értéket a config csatornára írja.

  • A kamera rögzítési ciklusa a következő képkockánál veszi át az értéket.

  • Az új képkockák ugyanazon a frame csatornán keresztül áramlanak.

Ez az egész modell. Két csatorna, mindegyiken két visszahívás, egy rögzítési ciklus a kamerán, egy olvasó-és-író ciklus a gazdagépen. Semmilyen látható keretezési logika, semmilyen látható hibakezelés – a protokollkönyvtár eltünteti a megbízható bájtmozgatást.

Ezen a ponton túl minden alkalmazáskód. Egy harmadik csatorna hozzáadása egy hisztogramhoz, egy negyediké telemetriához, vagy egy ötödiké érzékelő-triggerekhez ugyanaz a backend-osztály-és-protocol.register recept, megismételve. Amint egy kameraprojekt eléri ezt a pontot, a protokoll megszűnik az érdekes probléma lenni; az alkalmazás saját logikája veszi át a helyét.