12.9. Dubbelriktat flöde

Kanaler är inte enkelriktade. En backend som implementerar write låter värden skicka byte till kameran, och kameran reagerar. Det är mönstret bakom varje verkligt interaktivt verktyg: operatören vrider på ett vred i värdens gränssnitt, värden skriver det nya värdet till en konfigurationskanal, och kameran läser det nästa gång den fångar en bild.

12.9.1. En konfigurationskanal

Lägg till i det strömmande skriptet på kamerasidan och exponera en andra kanal för JPEG-kvaliteten:

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)

Fångstslingan läser från config.quality varje gång den komprimerar en bildruta:

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

Värden har nu ett vred. Ställ in det på 50 så blir nästa bildruta mindre (och fulare); ställ in det på 95 så blir nästa bildruta större (och skarpare). Kameran fortsätter fånga utan att starta om; värden behöver inte skicka ett nytt skript.

12.9.2. Skrivanropet från värden

På värdsidan skickar channel_write() byte till en namngiven kanal:

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

Värd-SDK:n kodar byten som ett enda (eller fragmenterat) CHANNEL_WRITE-paket, protokollagret levererar det till kameran, kamerans write(offset=0, data=...) körs och kamerans sida bekräftar. När anropet returnerar har kameran tagit emot och accepterat det nya värdet.

Skrivningen är atomär ur kamerans synvinkel – protokollbiblioteket garanterar att backendens write körs till slut innan någon annan operation på den kanalen fortsätter. Applikationskoden kan läsa config.quality inifrån fångstslingan utan att oroa sig för att värden trampar in mitt under en stillbild.

12.9.3. Stubbstorlek och läsning på en skrivskyddad kanal

En ren skrivkanal behöver fortfarande size och read definierade, även om de är stubbar som returnerar 0 och b''. Biblioteket använder förekomsten av metoder för att härleda kanalens kapacitetsflaggor; en backend som saknar read får inte CHANNEL_FLAG_READ satt och värden kommer att vägra ett läsförsök.

De byte som returneras från read på en skrivskyddad kanal är dock användbara för ett annat ändamål: att eka tillbaka det aktuella värdet så att en värd som just anslutit kan fråga kameran ”vad är den aktuella inställningen?” i stället för att utgå från ett standardvärde. För att det ska fungera måste båda riktningarna komma överens om en serialisering. Tolkningen av råa byte int(bytes(data)) i det tidigare exemplet fungerar för ett enda heltalsfält men skalar inte när det finns ett andra vred att ställa in. Att byta write till att tolka JSON och para ihop det med en read som returnerar den matchande JSON-dumpen förvandlar kanalen till en äkta tur-och-retur-konfigurationslagring:

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)

Värden skriver nu cam.channel_write('config', b'{"quality": 50}') för att ställa in ett värde och cam.channel_read('config') för att läsa tillbaka det aktuella tillståndet. Kameran serialiserar en färsk JSON-dump vid varje läsning så att värden alltid ser de senaste värdena, och att lägga till ytterligare ett vred (threshold, exposure, orientation) är en rad i JSON-ordboken på varje sida.

12.9.4. En komplett slinga

Med en bildkanal för data från kamera → värd, en konfigurationskanal för styrning från värd → kamera och en liten mängd limkod är applikationen ett interaktivt verktyg:

  • Värden öppnar kameran, börjar hämta bildrutor och visar dem i ett fönster.

  • När operatören drar i ett reglage skriver värden det nya värdet på config.

  • Kamerans fångstslinga plockar upp värdet vid nästa bildruta.

  • De nya bildrutorna flödar genom samma frame-kanal.

Det är hela modellen. Två kanaler, två återanrop var, en fångstslinga på kameran och en läs-och-skriv-slinga på värden. Ingen synlig inramningslogik, ingen synlig felhantering – protokollbiblioteket får den tillförlitliga byteförflyttningen att försvinna.

Allt bortom denna punkt är applikationskod. Att lägga till en tredje kanal för ett histogram, en fjärde för telemetri eller en femte för sensorutlösare är samma recept med backend-klass och protocol.register, upprepat. När ett kameraprojekt väl når denna punkt slutar protokollet vara det intressanta problemet; applikationens egen logik tar över.