12.6. Benoemde channels

De channel-ID in de header van elk pakket laat tot 32 onafhankelijke streams hetzelfde fysieke transport delen. De channellaag zet die numerieke ID’s om in benoemde, voor de applicatie zichtbare eindpunten waar de hostcode via een string naar kan verwijzen.

Eén transportdraad links die uitwaaiert naar vier gelabelde channels aan de cam-zijde -- stdin, stdout, stream, en een door de gebruiker geregistreerd statuschannel -- elk weergegeven als een onafhankelijk vak.

12.6.1. De vier ingebouwde channels

De cam registreert bij het opstarten vier channels, voordat enige applicatiecode wordt uitgevoerd:

  • stdin – scriptbytes die de host naar de cam pusht om uit te voeren. De IDE gebruikt dit channel om het script dat wordt bewerkt te versturen; exec() op de host-SDK is de equivalente aanroep vanuit een Python-programma.

  • stdout – bytes van print()-aanroepen van de cam en tracebacks van niet-opgevangen excepties. De seriële console van de IDE leest dit channel.

  • stream – het live-previewchannel. De IDE haalt er JPEG-frames uit; elk hostscript kan hetzelfde doen met read_frame().

  • profile – profiler-events, alleen aanwezig wanneer de cam met profiling ingeschakeld is gebouwd. De meeste release-builds laten dit weg.

Applicatiecode hoeft zelden een van de ingebouwde channels aan te raken; het interessante werk gebeurt op channels die de applicatie zelf registreert.

12.6.2. Een channel registreren

Een script aan de cam-zijde registreert een nieuw channel door protocol.register() aan te roepen met een naam en een Python backend-object:

import json
import protocol
import time

trigger_count = 0

class StatusChannel:
    def size(self):
        # Refresh the snapshot on every host query.
        self._buf = json.dumps({
            'uptime_s': time.ticks_ms() // 1000,
            'triggers': trigger_count,
        }).encode()
        return len(self._buf)

    def read(self, offset, size):
        return self._buf[offset:offset + size]

protocol.register(name='status', backend=StatusChannel())

De methoden van het backend-object bepalen wat het channel kan doen. Een backend met alleen size en read is een alleen-lezen datachannel; voeg write toe en het wordt bidirectioneel; voeg poll toe en de host kan vragen of er nieuwe data klaar is voordat hij betaalt voor een lees-actie. Het bemonsteren van de data binnen size is het eenvoudigste patroon wanneer de payload klein genoeg is om in één fragment te passen – de buffer wordt op verzoek gegenereerd, nooit gecachet, nooit in een race-conditie betrokken. Grotere payloads – beeldframes, sensorsporen – vereisen een latch-patroon dat de buffer vasthoudt totdat de host zijn lees-actie over meerdere fragmenten voltooit, behandeld bij het frame-channel.

Er gebeurt automatisch een kleine hoeveelheid administratie:

  • De bibliotheek wijst de volgende vrije channel-ID toe (tussen 0 en 31).

  • De capability-vlaggen worden afgeleid van de aanwezige methoden: CHANNEL_FLAG_READ als read is gedefinieerd, CHANNEL_FLAG_WRITE als write is gedefinieerd, CHANNEL_FLAG_LOCK als lock / unlock zijn gedefinieerd.

  • Een CHANNEL_REGISTERED-eventpakket wordt naar elke verbonden host gestuurd zodat diens channellijst wordt bijgewerkt.

De retourwaarde is een protocol.ProtocolChannel-handle die de applicatie kan vasthouden. De send_event()-methode van de handle is de cam-zijdige hook om de host te vertellen “er is iets gebeurd op dit channel zonder dat de leesbare data is veranderd” – een trigger werd geactiveerd, een knop werd ingedrukt, een mijlpaal in het aantal samples werd bereikt.

12.6.3. Channels lezen vanaf de host

De host-SDK wordt geleverd als het openmv-pakket op PyPI (pip install openmv), gebouwd op pyserial voor het transport. De klasse openmv.camera.Camera stelt de benoemde channels van de cam beschikbaar via methoden op hoog niveau:

from openmv.camera import Camera

with Camera('/dev/ttyACM0', baudrate=921600) as cam:
    cam.update_channels()
    if cam.has_channel('status'):
        size = cam.channel_size('status')
        data = cam.channel_read('status', size)

Waarschuwing

Het openmv-pakket vereist CPython 3.12 of nieuwer. Eerdere interpreters missen functies waarvan de SDK afhankelijk is; installeer een 3.12+-build vóór pip install openmv.

Een paar dingen om op te merken aan de opzet:

  • De seriële-poortstring – hier /dev/ttyACM0 – is van het type COM3 op Windows, /dev/cu.usbmodemXXXX op macOS, en /dev/ttyACM* op Linux. Het feitelijke nummer hangt af van welke poort de cam als enumeratie kreeg.

  • De baudrate is de magische waarde 921600 van het protocol, die de USB-CDC-stack van de cam herkent als “deze client spreekt het protocol, niet de REPL.” Elke andere snelheid valt terug op een gewone seriële lijn.

  • De contextmanager with Camera(...) as cam: opent het transport, voert PROTO_SYNC uit, wisselt capabilities uit, en sluit bij het verlaten de poort netjes af. De expliciete aanroep van update_channels() na het betreden vernieuwt de lokale channellijst met eventuele channels die de applicatie na het opstarten heeft geregistreerd.

channel_size() en channel_read() zijn de werkpaardmethoden; channel_write() stuurt een buffer heen en weer naar de cam als de backend een write-methode heeft; has_channel() is de veilige manier om te controleren of een naam is geregistreerd voordat je hem gebruikt. De channelnaam wordt eenmaal opgezocht in de channel-ID die de cam tijdens register heeft toegewezen en daarna in elk pakket gebruikt.

Elk paar van channel_size() / channel_read() kost twee heen-en-weerritten: één pakket om naar de grootte te vragen, één om naar de bytes te vragen. Over USB-CDC zijn beide samen in ongeveer een milliseconde voltooid; over UART duurt dezelfde uitwisseling langer, in verhouding tot de baudrate van de seriële lijn. Applicatiecode die in een strakke lus leest, zou channel_size() alleen moeten aanroepen wanneer de grootte daadwerkelijk kan veranderen – voor data met vaste grootte kan de grootte uit de eerste aanroep worden gecachet.

12.6.4. Onafhankelijkheid tussen channels

Drie dingen zijn de moeite waard om te weten over hoe channels op elkaar inwerken:

  • Onafhankelijke flow control. Elk channel heeft zijn eigen openstaande leesstatus, zijn eigen data en zijn eigen size / read / write-callbacks. Een langlopende lees-actie op het stream-channel blokkeert leesacties op het config-channel van de applicatie niet.

  • Sequentieel per channel. Binnen één channel worden pakketten in volgorde afgeleverd. De betrouwbaarheidslaag garandeert dit zelfs wanneer er hertransmissies bij betrokken zijn.

  • Gedeeld transport, gedeeld hertransmissiebudget. Alle channels delen de ene fysieke verbinding, dus een stortvloed aan verkeer op één channel vertraagt de andere doordat het de draad in beslag neemt. Het CHANNEL_LOCK-mechanisme laat één channel de draad reserveren voor een atomaire lees-actie over meerdere pakketten; de backend kiest hiervoor door de lock / unlock-callbacks te implementeren.

Een channel is het minimale raakvlak waarop een hostprogramma en een cam-programma overeenkomen om samen te werken. De naam, de richting (lezen of schrijven of beide), de callback-methoden aan de cam-zijde en de bijbehorende methode-aanroepen aan de host-zijde vormen het volledige contract.