12.6. Namngivna kanaler

Kanal-ID:t i varje pakets header gör att upp till 32 oberoende strömmar kan dela samma fysiska transport. Kanallagret omvandlar de numeriska ID:na till namngivna, applikationssynliga slutpunkter som värdkoden kan referera till med en sträng.

En transporttråd till vänster som grenar ut sig i fyra namngivna kanaler på kamerasidan -- stdin, stdout, stream och en användarregistrerad statuskanal -- var och en visad som en oberoende ruta.

12.6.1. De fyra inbyggda kanalerna

Kameran registrerar fyra kanaler vid uppstart, innan någon applikationskod körs:

  • stdin – skriptbyte som värden skickar till kameran för att köras. IDE:n använder denna kanal för att skicka skriptet som redigeras; exec() på värd-SDK:n är motsvarande anrop från ett Python-program.

  • stdout – byte från print()-anrop på kameran och spårningar för oavfångade undantag. IDE:ns seriekonsol läser denna kanal.

  • stream – kanalen för direktförhandsvisning. IDE:n hämtar JPEG-bildrutor från den; vilket värdskript som helst kan göra detsamma med read_frame().

  • profile – profilerarhändelser, närvarande endast när kameran byggdes med profilering aktiverad. De flesta utgåvebyggen utelämnar den.

Applikationskod behöver sällan röra någon av de inbyggda; det intressanta arbetet sker på kanaler som applikationen själv registrerar.

12.6.2. Registrera en kanal

Ett skript på kamerasidan registrerar en ny kanal genom att anropa protocol.register() med ett namn och ett Python-backend-objekt:

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())

Backend-objektets metoder avgör vad kanalen kan göra. En backend med endast size och read är en skrivskyddad datakanal; lägg till write så blir den dubbelriktad; lägg till poll så kan värden fråga om ny data är klar innan en läsning betalas. Att sampla data inuti size är det enklaste mönstret när nyttolasten är liten nog att rymmas i ett fragment – bufferten genereras vid behov, cachas aldrig, drabbas aldrig av kapplöpning. Större nyttolaster – bildrutor, sensorspår – behöver ett spärrmönster som håller kvar bufferten tills värden avslutar sin flerfragmentläsning, vilket behandlas i avsnittet om bildruterkanalen.

En liten mängd bokföring sker automatiskt:

  • Biblioteket tilldelar nästa lediga kanal-ID (mellan 0 och 31).

  • Förmågeflaggorna härleds från de metoder som finns: CHANNEL_FLAG_READ om read är definierad, CHANNEL_FLAG_WRITE om write är definierad, CHANNEL_FLAG_LOCK om lock / unlock är definierade.

  • Ett CHANNEL_REGISTERED-händelsepaket skickas till varje ansluten värd så att dess kanallista uppdateras.

Returvärdet är ett protocol.ProtocolChannel-handtag som applikationen kan behålla. Handtagets metod send_event() är kamerasidans krok för att tala om för värden att ”något hände på denna kanal utan att den läsbara datan ändrades” – en trigger utlöstes, en knapp trycktes ned, en milstolpe i antal sampel passerades.

12.6.3. Läsa kanaler från värden

Värd-SDK:n levereras som paketet openmv på PyPI (pip install openmv), byggt på pyserial för transporten. Dess klass openmv.camera.Camera exponerar kamerans namngivna kanaler genom högnivåmetoder:

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)

Varning

Paketet openmv kräver CPython 3.12 eller senare. Tidigare tolkar saknar funktioner som SDK:n är beroende av; installera ett 3.12+-bygge innan pip install openmv.

Några saker att lägga märke till om uppsättningen:

  • Serieportssträngen – /dev/ttyACM0 här – är i COM3-stil på Windows, /dev/cu.usbmodemXXXX på macOS och /dev/ttyACM* på Linux. Det faktiska numret beror på vilken port kameran räknades upp som.

  • Baudhastigheten är protokollets magiska värde 921600, som kamerans USB-CDC-stack känner igen som ”den här klienten talar protokollet, inte REPL:en”. Vilken annan hastighet som helst faller tillbaka på en vanlig seriell linje.

  • Kontexthanteraren with Camera(...) as cam: öppnar transporten, kör PROTO_SYNC, utbyter förmågor och stänger porten rent vid avslut. Det explicita anropet update_channels() efter inträde uppdaterar den lokala kanallistan med eventuella kanaler som applikationen registrerade efter uppstart.

channel_size() och channel_read() är arbetshästmetoderna; channel_write() skickar en buffert tur och retur till kameran om backenden har en write-metod; has_channel() är det säkra sättet att kontrollera att ett namn är registrerat innan det används. Kanalnamnet slås upp en gång till det kanal-ID som kameran tilldelade under register och används i varje paket från och med då.

Varje par av channel_size() / channel_read() kostar två tur-och-retur-resor: ett paket för att fråga efter storleken, ett för att fråga efter byten. Över USB-CDC avslutas båda på cirka en millisekund tillsammans; över UART tar samma utbyte längre tid i proportion till den seriella linjens baudhastighet. Applikationskod som läser i en tät slinga bör anropa channel_size() endast när storleken faktiskt kan ändras – för data av fast storlek kan storleken från det första anropet cachas.

12.6.4. Oberoende mellan kanaler

Tre saker är värda att veta om hur kanaler samverkar:

  • Oberoende flödeskontroll. Varje kanal har sitt eget tillstånd för väntande läsning, sin egen data och sina egna återanrop size / read / write. En långvarig läsning på kanalen stream blockerar inte läsningar på applikationens config-kanal.

  • Sekventiellt per kanal. Inom en enda kanal levereras paket i ordning. Tillförlitlighetslagret garanterar detta även när omsändningar är inblandade.

  • Delad transport, delad budget för omsändning. Alla kanaler delar den enda fysiska länken, så en störtflod av trafik på en kanal saktar ned de andra genom att lägga beslag på tråden. Mekanismen CHANNEL_LOCK låter en kanal reservera tråden för en atomär flerpaketsläsning; backenden ansluter sig genom att implementera återanropen lock / unlock.

En kanal är den minsta gränsyta på vilken ett värdprogram och ett kameraprogram kommer överens om att samverka. Namnet, riktningen (läsa eller skriva eller båda), återanropsmetoderna på kamerasidan och de matchande metodanropen på värdsidan är hela kontraktet.