12.6. Pojmenované kanály

ID kanálu v hlavičce každého paketu umožňuje, aby si až 32 nezávislých streamů sdílelo tentýž fyzický přenosový kanál. Vrstva kanálů převádí tyto číselné ID na pojmenované, pro aplikaci viditelné koncové body, na které se kód hostitele může odkazovat řetězcem.

Jeden přenosový drát vlevo se rozvětvuje do čtyř označených kanálů na straně kamery – stdin, stdout, stream a uživatelsky registrovaný stavový kanál – přičemž každý je zobrazen jako samostatný blok.

12.6.1. Čtyři vestavěné kanály

Kamera registruje při startu čtyři kanály, ještě než se spustí jakýkoli aplikační kód:

  • stdin – bajty skriptu, které hostitel posílá kameře k vykonání. IDE tento kanál používá k odeslání právě editovaného skriptu; exec() v hostitelském SDK je ekvivalentní volání z programu v Pythonu.

  • stdout – bajty z volání print() na kameře a tracebacky neodchycených výjimek. Sériová konzole IDE čte tento kanál.

  • stream – kanál živého náhledu. IDE z něj stahuje JPEG snímky; jakýkoli hostitelský skript může udělat totéž pomocí read_frame().

  • profile – události profileru, přítomné pouze tehdy, byla-li kamera sestavena s povoleným profilováním. Většina release buildů jej vynechává.

Aplikační kód jen zřídka potřebuje sahat na některý z vestavěných kanálů; zajímavá práce probíhá na kanálech, které si aplikace registruje sama.

12.6.2. Registrace kanálu

Skript na straně kamery registruje nový kanál voláním protocol.register() s názvem a backend objektem v Pythonu:

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

Metody backend objektu rozhodují o tom, co kanál umí. Backend pouze s metodami size a read je datový kanál určený jen ke čtení; přidejte write a stane se obousměrným; přidejte poll a hostitel se může před zaplacením za čtení zeptat, zda jsou připravena nová data. Odběr vzorků dat uvnitř metody size je nejjednodušší vzorec, když je užitečná zátěž dostatečně malá, aby se vešla do jednoho fragmentu – buffer se generuje na vyžádání, nikdy se neukládá do mezipaměti a nikdy nedojde k souběhu. Větší užitečné zátěže – obrazové snímky, senzorové průběhy – potřebují záchytný (latching) vzorec, který buffer podrží, dokud hostitel nedokončí své čtení napříč více fragmenty, jak je popsáno u snímkového kanálu.

Automaticky proběhne malé množství evidence:

  • Knihovna přidělí další volné ID kanálu (mezi 0 a 31).

  • Příznaky schopností jsou odvozeny od přítomných metod: CHANNEL_FLAG_READ, je-li definována read, CHANNEL_FLAG_WRITE, je-li definována write, CHANNEL_FLAG_LOCK, jsou-li definovány lock / unlock.

  • Každému připojenému hostiteli je odeslán paket události CHANNEL_REGISTERED, aby se jeho seznam kanálů aktualizoval.

Návratovou hodnotou je handle typu protocol.ProtocolChannel, který si aplikace může podržet. Metoda send_event() tohoto handlu je háček na straně kamery, jímž lze hostiteli sdělit „na tomto kanálu se něco stalo, aniž by se změnila čitelná data“ – spustil se trigger, bylo stisknuto tlačítko, byl dosažen milník v počtu vzorků.

12.6.3. Čtení kanálů ze strany hostitele

Hostitelské SDK je dodáváno jako balíček openmv na PyPI (pip install openmv) a je postaveno na knihovně pyserial pro přenos. Jeho třída openmv.camera.Camera zpřístupňuje pojmenované kanály kamery prostřednictvím vysokoúrovňových metod:

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)

Varování

Balíček openmv vyžaduje CPython 3.12 nebo novější. Starším interpretům chybí funkce, na nichž SDK závisí; před pip install openmv nainstalujte build 3.12+.

Pár věcí, kterých si u tohoto nastavení všimnout:

  • Řetězec sériového portu – zde /dev/ttyACM0 – má na Windows podobu COM3, na macOS /dev/cu.usbmodemXXXX a na Linuxu /dev/ttyACM*. Skutečné číslo závisí na tom, jako který port se kamera vyčíslila.

  • Přenosová rychlost (baud rate) je magická hodnota protokolu 921600, kterou USB-CDC stack kamery rozpozná jako „tento klient mluví protokolem, nikoli REPL“. Jakákoli jiná rychlost se vrátí k běžné sériové lince.

  • Kontextový manažer with Camera(...) as cam: otevře přenosový kanál, spustí PROTO_SYNC, vymění si schopnosti a při ukončení port čistě uzavře. Explicitní volání update_channels() po vstupu obnoví lokální seznam kanálů o všechny kanály, které aplikace zaregistrovala po startu.

channel_size() a channel_read() jsou hlavní pracovní metody; channel_write() přenese buffer tam i zpět ke kameře, má-li backend metodu write; has_channel() je bezpečný způsob, jak před použitím ověřit, že je název registrován. Název kanálu se jednou vyhledá na ID kanálu, které kamera přidělila během register, a od té chvíle se používá v každém paketu.

Každá dvojice channel_size() / channel_read() stojí dva obousměrné přenosy: jeden paket na dotaz na velikost, jeden na dotaz na bajty. Přes USB-CDC oba dohromady doběhnou přibližně za milisekundu; přes UART tatáž výměna trvá déle úměrně přenosové rychlosti (baud rate) sériové linky. Aplikační kód, který čte v těsné smyčce, by měl volat channel_size() pouze tehdy, když se velikost skutečně může změnit – u dat pevné velikosti lze velikost z prvního volání uložit do mezipaměti.

12.6.4. Nezávislost mezi kanály

O tom, jak kanály vzájemně interagují, stojí za to vědět tři věci:

  • Nezávislé řízení toku. Každý kanál má svůj vlastní stav čekajícího čtení, svá vlastní data a své vlastní callbacky size / read / write. Dlouho běžící čtení na kanálu stream neblokuje čtení na aplikačním kanálu config.

  • Sekvenční v rámci kanálu. V rámci jednoho kanálu jsou pakety doručovány v pořadí. Vrstva spolehlivosti to zaručuje i tehdy, dochází-li k opětovným přenosům.

  • Sdílený přenosový kanál, sdílený rozpočet opětovných přenosů. Všechny kanály sdílejí jeden fyzický spoj, takže záplava provozu na jednom kanálu zpomaluje ostatní tím, že si urve drát pro sebe. Mechanismus CHANNEL_LOCK umožňuje jednomu kanálu rezervovat si drát pro atomické čtení napříč více pakety; backend se k tomu přihlásí implementací callbacků lock / unlock.

Kanál je minimální plocha, na níž se hostitelský program a program kamery dohodnou na spolupráci. Název, směrovost (čtení nebo zápis nebo obojí), callback metody na straně kamery a odpovídající volání metod na straně hostitele tvoří celý kontrakt.