12.6. Nazwane kanały

Identyfikator kanału w nagłówku każdego pakietu pozwala, aby do 32 niezależnych strumieni współdzieliło ten sam fizyczny transport. Warstwa kanałów zamienia te numeryczne identyfikatory w nazwane, widoczne dla aplikacji punkty końcowe, do których kod hosta może odwoływać się za pomocą łańcucha znaków.

Jeden przewód transportowy po lewej stronie rozgałęziający się na cztery oznaczone kanały po stronie kamery -- stdin, stdout, stream oraz zarejestrowany przez użytkownika kanał statusu -- każdy pokazany jako niezależny blok.

12.6.1. Cztery wbudowane kanały

Kamera rejestruje cztery kanały podczas rozruchu, zanim uruchomi się jakikolwiek kod aplikacji:

  • stdin – bajty skryptu, które host wysyła do kamery do wykonania. IDE używa tego kanału do wysyłania edytowanego skryptu; exec() w SDK hosta jest równoważnym wywołaniem z programu Pythona.

  • stdout – bajty z wywołań print() kamery i śladów stosu nieprzechwyconych wyjątków. Konsola szeregowa IDE odczytuje ten kanał.

  • stream – kanał podglądu na żywo. IDE pobiera z niego ramki JPEG; dowolny skrypt hosta może zrobić to samo za pomocą read_frame().

  • profile – zdarzenia profilera, obecne tylko wtedy, gdy kamera została zbudowana z włączonym profilowaniem. Większość kompilacji wydaniowych go pomija.

Kod aplikacji rzadko potrzebuje dotykać którychkolwiek z wbudowanych kanałów; ciekawa praca odbywa się na kanałach, które aplikacja rejestruje sama.

12.6.2. Rejestrowanie kanału

Skrypt po stronie kamery rejestruje nowy kanał, wywołując protocol.register() z nazwą i obiektem backendu w Pythonie:

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 obiektu backendu decydują o tym, co kanał może robić. Backend mający tylko size i read jest kanałem danych tylko do odczytu; dodaj write, a stanie się dwukierunkowy; dodaj poll, a host może zapytać, czy nowe dane są gotowe, zanim poniesie koszt odczytu. Próbkowanie danych wewnątrz size to najprostszy wzorzec, gdy ładunek jest na tyle mały, że mieści się w jednym fragmencie – bufor jest generowany na żądanie, nigdy nie buforowany, nigdy nie narażony na wyścig. Większe ładunki – ramki obrazu, ślady sensorów – potrzebują wzorca zatrzaskiwania, który przytrzymuje bufor, dopóki host nie zakończy odczytu wielofragmentowego, omówionego przy kanale ramek.

Niewielka ilość ewidencjonowania odbywa się automatycznie:

  • Biblioteka przydziela kolejny wolny identyfikator kanału (między 0 a 31).

  • Flagi zdolności są wyprowadzane z obecnych metod: CHANNEL_FLAG_READ, jeśli zdefiniowano read, CHANNEL_FLAG_WRITE, jeśli zdefiniowano write, CHANNEL_FLAG_LOCK, jeśli zdefiniowano lock / unlock.

  • Pakiet zdarzenia CHANNEL_REGISTERED jest wysyłany do każdego podłączonego hosta, aby jego lista kanałów się zaktualizowała.

Wartością zwracaną jest uchwyt protocol.ProtocolChannel, który aplikacja może zachować. Metoda uchwytu send_event() to zaczep po stronie kamery służący do informowania hosta „coś się stało na tym kanale bez zmiany danych do odczytu” – wyzwolono wyzwalacz, naciśnięto przycisk, osiągnięto kamień milowy liczby próbek.

12.6.3. Odczytywanie kanałów z hosta

SDK hosta dostarczany jest jako pakiet openmv w PyPI (pip install openmv), zbudowany na pyserial dla transportu. Jego klasa openmv.camera.Camera udostępnia nazwane kanały kamery poprzez metody wysokiego poziomu:

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)

Ostrzeżenie

Pakiet openmv wymaga CPython 3.12 lub nowszego. Wcześniejsze interpretery nie mają funkcji, na których SDK polega; zainstaluj kompilację 3.12+ przed pip install openmv.

Kilka rzeczy, na które warto zwrócić uwagę przy konfiguracji:

  • Łańcuch znaków portu szeregowego – tutaj /dev/ttyACM0 – ma postać COM3 w systemie Windows, /dev/cu.usbmodemXXXX w macOS i /dev/ttyACM* w systemie Linux. Rzeczywisty numer zależy od tego, jako który port kamera została wyliczona.

  • Szybkość transmisji (baud) to magiczna wartość protokołu 921600, którą stos USB-CDC kamery rozpoznaje jako „ten klient mówi w protokole, a nie w REPL”. Każda inna szybkość powoduje powrót do zwykłej linii szeregowej.

  • Menedżer kontekstu with Camera(...) as cam: otwiera transport, uruchamia PROTO_SYNC, wymienia zdolności, a przy wyjściu czysto zamyka port. Jawne wywołanie update_channels() po wejściu odświeża lokalną listę kanałów o wszelkie kanały zarejestrowane przez aplikację po rozruchu.

channel_size() i channel_read() to robocze metody; channel_write() przesyła bufor w obie strony do kamery, jeśli backend ma metodę write; has_channel() to bezpieczny sposób sprawdzenia, czy nazwa jest zarejestrowana przed jej użyciem. Nazwa kanału jest jednorazowo wyszukiwana i zamieniana na identyfikator kanału, który kamera przydzieliła podczas register, i od tej pory używana w każdym pakiecie.

Każda para channel_size() / channel_read() kosztuje dwie rundy w obie strony: jeden pakiet, aby zapytać o rozmiar, jeden, aby poprosić o bajty. Przez USB-CDC obie kończą się łącznie w około milisekundę; przez UART ta sama wymiana trwa dłużej proporcjonalnie do szybkości transmisji (baud) linii szeregowej. Kod aplikacji odczytujący w ciasnej pętli powinien wywoływać channel_size() tylko wtedy, gdy rozmiar może się faktycznie zmienić – dla danych o stałym rozmiarze rozmiar z pierwszego wywołania można zbuforować.

12.6.4. Niezależność między kanałami

Trzy rzeczy warto wiedzieć o tym, jak kanały współdziałają:

  • Niezależna kontrola przepływu. Każdy kanał ma własny stan oczekującego odczytu, własne dane i własne wywołania zwrotne size / read / write. Długotrwały odczyt na kanale stream nie blokuje odczytów na kanale config aplikacji.

  • Sekwencyjność na kanał. W obrębie pojedynczego kanału pakiety są dostarczane w kolejności. Warstwa niezawodności gwarantuje to nawet wtedy, gdy w grę wchodzą retransmisje.

  • Współdzielony transport, współdzielony budżet retransmisji. Wszystkie kanały współdzielą jedno fizyczne łącze, więc nawał ruchu na jednym kanale spowalnia pozostałe, zajmując przewód. Mechanizm CHANNEL_LOCK pozwala jednemu kanałowi zarezerwować przewód dla atomowego odczytu wielu pakietów; backend włącza się, implementując wywołania zwrotne lock / unlock.

Kanał to minimalna powierzchnia, na której program hosta i program kamery zgadzają się współpracować. Nazwa, kierunkowość (odczyt, zapis lub oba), metody wywołań zwrotnych po stronie kamery oraz odpowiadające im wywołania metod po stronie hosta stanowią cały kontrakt.