12.9. Kaksisuuntainen virtaus

Kanavat eivät ole yksisuuntaisia. write-metodin toteuttava taustaosa antaa isännän työntää tavuja kameralle, ja kamera reagoi. Tämä on jokaisen oikean vuorovaikutteisen työkalun takana oleva malli: käyttäjä kääntää nuppia isännän graafisessa käyttöliittymässä, isäntä kirjoittaa uuden arvon asetuskanavaan, ja kamera lukee sen seuraavan kerran kuvaa ottaessaan.

12.9.1. Asetuskanava

Lisää virtautukseen kameran puolen skriptissä toinen kanava JPEG-laadulle:

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)

Kaappaussilmukka lukee config.quality-arvosta aina, kun se pakkaa kehyksen:

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

Isännällä on nyt nuppi. Aseta se arvoon 50, niin seuraava kehys on pienempi (ja rumempi); aseta se arvoon 95, niin seuraava kehys on suurempi (ja terävämpi). Kamera jatkaa kuvaamista käynnistymättä uudelleen; isännän ei tarvitse työntää uutta skriptiä.

12.9.2. Kirjoituskutsu isännältä

Isännän puolella channel_write() lähettää tavuja nimettyyn kanavaan:

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

Isännän SDK koodaa tavut yhdeksi (tai pirstotuksi) CHANNEL_WRITE-paketiksi, protokollakerros toimittaa sen kameralle, kameran write(offset=0, data=...) suoritetaan, ja kameran puoli kuittaa. Kun kutsu palaa, kamera on vastaanottanut ja hyväksynyt uuden arvon.

Kirjoitus on kameran näkökulmasta atominen – protokollakirjasto takaa, että taustaosan write suoritetaan loppuun ennen kuin mikään muu toiminto kyseisellä kanavalla etenee. Sovelluskoodi voi lukea config.quality-arvon kaappaussilmukan sisältä ilman huolta siitä, että isäntä sotkisi sitä kesken tilannekuvan.

12.9.3. Tynkäkoko ja luku vain kirjoitettavalla kanavalla

Pelkkä kirjoituskanavakin tarvitsee size- ja read-metodit määriteltyinä, vaikka ne olisivat tynkiä, jotka palauttavat 0 ja b''. Kirjasto johtaa kanavan kyvykkyysliput metodien läsnäolosta; taustaosa, jolta puuttuu read, ei saa CHANNEL_FLAG_READ-lippua, ja isäntä kieltäytyy lukuyrityksestä.

Vain kirjoitettavan kanavan read-metodin palauttamat tavut ovat kuitenkin hyödyllisiä toiseen tarkoitukseen: nykyisen arvon kaiuttamiseen takaisin, jotta juuri liittynyt isäntä voi kysyä kameralta ”mikä on nykyinen asetus?” sen sijaan, että aloittaisi oletusarvosta. Jotta tämä toimisi, molempien suuntien on sovittava sarjallistuksesta. Aiemman esimerkin raakatavujen int(bytes(data))-jäsennys toimii yhdelle kokonaislukukentälle mutta ei skaalaudu, kun asetettavia nuppeja on toinenkin. Kun write vaihdetaan jäsentämään JSON-muotoa ja pariutetaan read-metodin kanssa, joka palauttaa vastaavan JSON-tulosteen, kanavasta tulee aito edestakainen asetustallennus:

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)

Isäntä kirjoittaa nyt cam.channel_write('config', b'{"quality": 50}') asettaakseen arvon ja cam.channel_read('config') lukeakseen nykyisen tilan takaisin. Kamera sarjallistaa tuoreen JSON-tulosteen jokaisella luvulla, joten isäntä näkee aina viimeisimmät arvot, ja toisen nupin (threshold, exposure, orientation) lisääminen on yksi rivi JSON-sanakirjaan kummallakin puolella.

12.9.4. Täydellinen silmukka

Kun kehyskanava hoitaa kamera → isäntä -datan, asetuskanava hoitaa isäntä → kamera -ohjauksen ja pieni määrä liimaa, sovelluksesta tulee vuorovaikutteinen työkalu:

  • Isäntä avaa kameran, alkaa vetää kehyksiä ja näyttää ne ikkunassa.

  • Kun käyttäjä vetää liukusäädintä, isäntä kirjoittaa uuden arvon config-kanavalle.

  • Kameran kaappaussilmukka poimii arvon seuraavalla kehyksellä.

  • Uudet kehykset virtaavat saman frame-kanavan kautta.

Siinä on koko malli. Kaksi kanavaa, kaksi takaisinkutsua kumpaakin, kaappaussilmukka kameralla, luku- ja kirjoitussilmukka isännällä. Kehystyslogiikkaa ei näy, virheenkäsittelyä ei näy – protokollakirjasto saa luotettavan tavujen siirron katoamaan näkyvistä.

Kaikki tämän pisteen jälkeen on sovelluskoodia. Kolmannen kanavan lisääminen histogrammille, neljännen telemetrialle tai viidennen sensorilaukaisimille on sama taustaosaluokan ja protocol.register-resepti toistettuna. Kun kameraprojekti saavuttaa tämän pisteen, protokolla lakkaa olemasta kiinnostava ongelma; sovelluksen oma logiikka on se.