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.
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 zdefiniowanoread,CHANNEL_FLAG_WRITE, jeśli zdefiniowanowrite,CHANNEL_FLAG_LOCK, jeśli zdefiniowanolock/unlock.Pakiet zdarzenia
CHANNEL_REGISTEREDjest 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ćCOM3w systemie Windows,/dev/cu.usbmodemXXXXw 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, uruchamiaPROTO_SYNC, wymienia zdolności, a przy wyjściu czysto zamyka port. Jawne wywołanieupdate_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 kanalestreamnie blokuje odczytów na kanaleconfigaplikacji.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_LOCKpozwala jednemu kanałowi zarezerwować przewód dla atomowego odczytu wielu pakietów; backend włącza się, implementując wywołania zwrotnelock/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.