12.9. Bidirectionele stroom

Kanalen zijn niet eenrichtingsverkeer. Een backend die write implementeert laat de host bytes naar de cam sturen, en de cam reageert. Dat is het patroon achter elk echt interactief hulpmiddel: de operator draait aan een knop in de host-GUI, de host schrijft de nieuwe waarde naar een config-kanaal, en de cam leest die de volgende keer dat hij vastlegt.

12.9.1. Een config-kanaal

Voeg aan het streamende cam-zijdige script toe om een tweede kanaal voor de JPEG-kwaliteit bloot te stellen:

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)

De vastleglus leest uit config.quality telkens wanneer hij een frame comprimeert:

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

De host heeft nu een knop. Zet hem op 50 en het volgende frame is kleiner (en lelijker); zet hem op 95 en het volgende frame is groter (en scherper). De cam blijft vastleggen zonder opnieuw te starten; de host hoeft geen nieuw script te pushen.

12.9.2. De write-aanroep vanaf de host

Aan de host-zijde stuurt channel_write() bytes naar een benoemd kanaal:

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

De host-SDK codeert de bytes als een enkel (of gefragmenteerd) CHANNEL_WRITE-pakket, de protocollaag levert het af bij de cam, de write(offset=0, data=...) van de cam draait, en de cam-zijde bevestigt. Tegen de tijd dat de aanroep terugkeert heeft de cam de nieuwe waarde ontvangen en geaccepteerd.

De write is atomair vanuit het oogpunt van de cam – de protocolbibliotheek garandeert dat de write van de backend tot voltooiing draait voordat een andere bewerking op dat kanaal verdergaat. Applicatiecode kan config.quality lezen vanuit de vastleglus zonder zich zorgen te maken dat de host er midden in een momentopname overheen schrijft.

12.9.3. Stub-grootte en read op een alleen-schrijven-kanaal

Een puur schrijfkanaal heeft nog steeds size en read nodig, zelfs als het stubs zijn die 0 en b'' retourneren. De bibliotheek gebruikt de aanwezigheid van methoden om de capability-vlaggen van het kanaal af te leiden; een backend die geen read heeft krijgt CHANNEL_FLAG_READ niet gezet en de host zal een leespoging weigeren.

De bytes die read op een alleen-schrijven-kanaal retourneert zijn echter nuttig voor een ander doel: het terugkaatsen van de huidige waarde zodat een host die net is gekoppeld de cam kan vragen “wat is de huidige instelling?” in plaats van vanuit een standaardwaarde te starten. Om dat te laten werken moeten beide richtingen het eens worden over een serialisatie. De int(bytes(data))-parse van ruwe bytes in het eerdere voorbeeld werkt voor een enkel integer-veld maar schaalt niet zodra er een tweede knob is om in te stellen. Door write te laten parsen naar JSON en het te koppelen aan een read die de bijbehorende JSON-dump retourneert, wordt het kanaal een echte heen-en-weer-configuratieopslag:

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)

De host schrijft nu cam.channel_write('config', b'{"quality": 50}') om een waarde in te stellen en cam.channel_read('config') om de huidige status terug te lezen. De cam serialiseert bij elke lezing een verse JSON-dump zodat de host altijd de nieuwste waarden ziet, en een andere knop toevoegen (threshold, exposure, orientation) is één regel in de JSON-dict aan elke zijde.

12.9.4. Een complete lus

Met een frame-kanaal voor data van cam → host, een config-kanaal voor besturing van host → cam, en een kleine hoeveelheid lijm, is de applicatie een interactief hulpmiddel:

  • De host opent de cam, begint frames op te halen, en toont ze in een venster.

  • Wanneer de operator een schuifregelaar versleept, schrijft de host de nieuwe waarde op config.

  • De vastleglus van de cam pikt de waarde op bij het volgende frame.

  • De nieuwe frames stromen door hetzelfde frame-kanaal.

Dat is het hele model. Twee kanalen, elk twee callbacks, een vastleglus op de cam, een lees-en-schrijflus op de host. Geen framinglogica zichtbaar, geen foutafhandeling zichtbaar – de protocolbibliotheek laat de betrouwbare byteverplaatsing verdwijnen.

Alles voorbij dit punt is applicatiecode. Een derde kanaal toevoegen voor een histogram, een vierde voor telemetrie, of een vijfde voor sensortriggers is hetzelfde recept van backend-klasse-en-protocol.register, herhaald. Zodra een cam-project dit punt bereikt houdt het protocol op het interessante probleem te zijn; de eigen logica van de applicatie wordt dat.