protocol — Kanały protokołu OpenMV#
Moduł protocol udostępnia w Pythonie protokół hosta OpenMV. Pozwala on na zainicjalizowanie i skonfigurowanie stosu protokołu po stronie oprogramowania układowego oraz umożliwia kodowi użytkownika rejestrowanie niestandardowych kanałów logicznych opartych na obiekcie Pythona implementującym interfejs kanału (read, write, size, poll itd.). To z nim komunikują się towarzyszące narzędzia desktopowe, gdy strumieniują dane obrazu lub udostępniają interaktywne widżety podłączonej kamerze.
Przykłady#
Strumieniowanie obrazu RGB565 do narzędzia hosta przy użyciu niestandardowego backendu implementującego surowy interfejs kanału (backend.size(), backend.shape(), backend.poll(), backend.read()):
import csi
import protocol
csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.HD)
img = csi0.snapshot()
img_mv = memoryview(img.bytearray())
frame_ready = True
class FrameChannel:
def size(self):
return len(img_mv)
def shape(self):
return (img.height(), img.width(), len(img_mv))
def poll(self):
return frame_ready
def read(self, offset, size):
global frame_ready
end = offset + size
chunk = img_mv[offset:end]
if end >= len(img_mv):
frame_ready = False
return chunk
protocol.register(name="frame", backend=FrameChannel())
while True:
if not frame_ready:
img = csi0.snapshot()
img_mv = memoryview(img.bytearray())
frame_ready = True
Odpowiadający mu skrypt po stronie hosta, używający pakietu Pythona openmv (pip install openmv) do połączenia, przesłania skryptu na kamerę i pobierania kolejnych ramek:
import cv2
import numpy as np
from openmv.camera import Camera
# The on-cam script above, stored as a string (or read from a file).
SCRIPT = open("frame_streamer_on_cam.py").read()
with Camera("/dev/ttyACM0", baudrate=921600) as cam:
cam.stop()
cam.exec(SCRIPT)
while True:
status = cam.read_status()
if not cam.has_channel("frame") or not status.get("frame"):
continue
h, w, size = cam._channel_shape(cam.get_channel(name="frame"))
if cam.channel_size("frame") < size:
continue
data = cam.channel_read("frame", size)
rgb565 = np.frombuffer(data, dtype="<u2").reshape(h, w)
# Unpack RGB565 to an HxWx3 uint8 RGB image.
r = ((rgb565 >> 11) & 0x1F) << 3
g = ((rgb565 >> 5) & 0x3F) << 2
b = ( rgb565 & 0x1F) << 3
frame = np.dstack([r, g, b]).astype(np.uint8)
# Display with OpenCV (cv2 expects BGR, not RGB).
cv2.imshow("OpenMV", cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))
if cv2.waitKey(1) == ord("q"):
break
cv2.destroyAllWindows()
Zastąp /dev/ttyACM0 portem szeregowym kamery (np. COM3 w systemie Windows). Konstruktor openmv.camera.Camera akceptuje te same parametry protokołu co init (crc / seq / ack / events / max_payload / max_retry / timeout), gdy stos po stronie kamery został odpowiednio przekonfigurowany.
Funkcje#
- protocol.init(crc: bool = True, seq: bool = True, ack: bool = True, events: bool = True, max_payload: int = ..., rtx_retries: int = 3, rtx_timeout_ms: int = 500, lock_interval_ms: int = 10, poll_ms: int = 0) None#
Inicjalizuje (lub rekonfiguruje) stos protokołu i rejestruje domyślne logiczne kanały danych (
stdin,stdout,streamoraz, jeśli został wkompilowany,profile). ZgłaszaRuntimeError, jeśli inicjalizacja się nie powiedzie. Oprogramowanie układowe uruchamia się z już działającym domyślnym stosem protokołu USB, więc wywołanie tej funkcji jest potrzebne tylko do zmiany transportu lub nadpisania domyślnych parametrów ramkowania. Ponowne wywołanie reinicjalizuje stos: poprzedni timer odpytywania jest usuwany, a kanały są rejestrowane od nowa.crcwłącza walidację CRC ramek protokołu.seqwłącza śledzenie numerów sekwencyjnych.ackwłącza potwierdzenia poszczególnych ramek.eventswłącza powiadomienia o zdarzeniach kanału.max_payloadto maksymalny rozmiar ładunku w bajtach. Jeśli zostanie pominięty, używana jest poniższa wartość domyślna dla danej kamery; jest ona wyprowadzana z rozmiaru bufora protokołu danej płytki jakobuffer - 10 (header) - 4 (CRC).Kamera
Rozmiar bufora
Maksymalny ładunek
OpenMV Cam M4 (
OPENMV2)512
498
OpenMV Cam M7 (
OPENMV3)512
498
OpenMV Cam H7 (
OPENMV4)512
498
OpenMV Cam H7 Plus (
OPENMV4P)4096
4082
OpenMV Pure Thermal (
OPENMVPT)4096
4082
OpenMV Cam RT1062 (
OPENMV_RT1060)4096
4082
OpenMV Cam N6 (
OPENMV_N6)8192
8178
OpenMV AE3 (
OPENMV_AE3)8192
8178
Arduino Portenta H7 (
ARDUINO_PORTENTA_H7)4096
4082
Arduino Giga (
ARDUINO_GIGA)4096
4082
Arduino Nicla Vision (
ARDUINO_NICLA_VISION)4096
4082
rtx_retriesto liczba prób retransmisji. Domyślnie3.rtx_timeout_msto limit czasu retransmisji w milisekundach (podwajany po każdym przekroczeniu limitu). Domyślnie500.lock_interval_msto minimalny interwał blokady w milisekundach. Domyślnie10.poll_msto interwał odpytywania w milisekundach.0(wartość domyślna) wyłącza odpytywanie za pomocą timera.
- protocol.is_active() bool#
Zwraca
True, jeśli host jest aktualnie połączony, a stos protokołu jest aktywny, w przeciwnym razieFalse.
- protocol.poll() int#
Wykonuje jedną iterację zadania protokołu: opróżnia bufor odbiorczy transportu, parsuje wszystkie kompletne ramki, rozdziela polecenia i obsługuje zdarzenia kanałów. Oprogramowanie układowe zwykle robi to za Ciebie z przerwania USB oraz z timera
poll_ms, więc jest to potrzebne tylko dla niestandardowych transportów, które nie są sterowane przez żadne z nich – na przykład gdypoll_ms=0, a transport jest odpytywany z głównej pętli skryptu.Zwraca
0w przypadku powodzenia oraz-1, jeśli nie zarejestrowano żadnego aktywnego transportu lub wywołanie nastąpiło z kontekstu przerwania.
- protocol.register(name: str, *, backend: object, flags: int = 0) ProtocolChannel#
Rejestruje obiekt Pythona
backendjako nowy kanał logiczny i zwraca uchwytProtocolChannel. Dostępne metody obiektubackend(zobacz Interfejs backendu poniżej) określają możliwości kanału; flagiprotocol.CHANNEL_FLAG_READ,protocol.CHANNEL_FLAG_WRITEiprotocol.CHANNEL_FLAG_LOCKsą dodawane doflagsautomatycznie, gdy odpowiednie metody są zaimplementowane.nameto nazwa kanału jako ciąg znaków. Przycinana do rozmiaru bufora nazwy kanału w oprogramowaniu układowym. Wymagane.backendto obiekt Pythona implementujący interfejs backendu. Wymagane. Zazwyczaj przekazywany jako argument słowa kluczowego (backend=...).flagsto dodatkowe bity flag kanału (zobacz stałeCHANNEL_FLAG_*). Opcjonalne; domyślnie0.Zgłasza
RuntimeError, jeśli kanału nie można zarejestrować (np. brak wolnych gniazd kanałów).
Klasy#
- class protocol.ProtocolChannel#
Uchwyt zwracany przez
protocol.register. Instancje nie są tworzone bezpośrednio.
Interfejs backendu#
Obiekt backendu przekazywany do protocol.register może implementować dowolny podzbiór poniższych metod. Tylko metody obecne w obiekcie są podłączane do warstwy protokołu w C; brakujące metody pozostawiają odpowiednią funkcjonalność wyłączoną.
- class protocol.backend#
Obiekt backendu kanału przekazywany do
protocol.register. Poniższe metody opisują opcjonalny interfejs, który może zaimplementować backend w Pythonie.- init() object#
Wywoływana jednorazowo podczas inicjalizacji kanału. Zwraca dowolną wartość różną od
Nonew przypadku powodzenia; wyjątek lub brak zwracanej wartości jest traktowany jako błąd.
- shape() tuple#
Zwraca krotkę składającą się z maksymalnie czterech liczb całkowitych opisujących kształt danych (np. wymiary obrazu). Warstwa protokołu zużywa maksymalnie cztery elementy.
- flush() object#
Wypróżnia wszelkie oczekujące dane. Zwraca dowolną wartość różną od
Nonew przypadku powodzenia.
- read(offset: int, size: int) bytes#
Zwraca maksymalnie
sizebajtów począwszy od pozycjioffsetjako obiekt typubytesobsługujący protokół bufora.
- readp(offset: int, size: int) bytes#
Wariant
readbez kopiowania (zero-copy). Zwraca bufor, którego pamięć jest odczytywana bezpośrednio przez warstwę protokołu; bufor musi pozostać ważny przez cały czas trwania transferu.
- write(offset: int, data: bytearray) int#
Zapisuje
datana pozycjioffset.datatobytearrayodwołujący się bezpośrednio do bufora C. Zwraca liczbę zapisanych bajtów lub0w przypadku domyślnego powodzenia.
- class protocol.CBORChannel(on_read: Callable | None = None, on_write: Callable | None = None)#
Backend Pythona wyższego poziomu (dostarczany przez zamrożony pakiet
protocol), który serializuje nazwane pola do CBOR przy użyciu kluczy całkowitych zgodnych z SenML. Obsługuje widżety wyświetlania (label,text,depth,waveform) oraz interaktywne elementy sterujące (toggle,pushbutton,slider,spinbox,select,radio,lineedit) z wywołaniami zwrotnymion_read/on_write.on_readto opcjonalny obiekt wywoływalnyon_read(channel)wywoływany przed serializacją kanału dla hosta. Użyj go do odświeżenia wartości pól.on_writeto opcjonalny obiekt wywoływalnyon_write(channel, name, value)wywoływany, gdy host zapisuje nową wartość dla nazwanego pola.- add(name: str, type: str, value: Any = None, unit: str | None = None, min: int | float | None = None, max: int | float | None = None, step: int | float | None = None, options: list | None = None, width: int | None = None, height: int | None = None, sample_rate: float | None = None, series: int = 1, typecode: str | None = None) None#
Dodaje nazwane pole do kanału.
nameto nazwa wyświetlana; musi być unikalna w obrębie tego kanału.typeto typ widżetu:"label"– wyświetlacz liczbowy tylko do odczytu z opcjonalną jednostkąunit."text"– wyświetlacz ciągu znaków tylko do odczytu."toggle"– przełącznik logiczny."pushbutton"– działanie chwilowe (wyzwolenie, kalibracja, …). Host zapisujeTruepo kliknięciu i nie wyświetla żadnego stanu."slider"– liczbowy element sterujący zmin/max/step."spinbox"– precyzyjne krokowe wprowadzanie liczb; współdzieli pola suwaka oraz formę aktualizacji krotką(min, max, value)."select"– lista rozwijana ciągów znaków zoptions."radio"–optionswyświetlane jako grupa przycisków opcji."lineedit"– zapisywalne pole tekstowe."depth"– wyświetlacz mapy głębi 2D o wymiarachwidthxheight."waveform"– wykres szeregu czasowego 1D z przeplatanych próbek.
valueto wartość początkowa. Wartość domyślna zależy odtype:0dlalabel,""dlatext/lineedit/select/radio,Falsedlatoggle/pushbutton,mindlaslider/spinbox.unitto ciąg jednostki dlalabel/slider/spinbox/waveform(np."Cel","%RH").minto wartość minimalna (zakres slider/spinbox lub zakres wyświetlania depth/waveform).maxto wartość maksymalna (zakres slider/spinbox lub zakres wyświetlania depth/waveform).stepto wielkość kroku (slider/spinbox).optionsto lista ciągów opcji (select/radio) lub lista nazw poszczególnych serii (waveform).widthto szerokość w pikselach (depth).heightto wysokość w pikselach (depth).sample_rateto częstotliwość próbkowania w próbkach na sekundę (waveform). Wysyłana do hosta jako okres próbkowania.seriesto liczba przeplatanych serii w każdym bloku próbek (waveform), np.3dla przebiegu akcelerometru XYZ.typecodeto kod typuarraypróbek przebiegu (np."H","h","f"). Domyślnie"H"(16-bitowy bez znaku).
- __getitem__(name: str) object#
Zwraca bieżącą wartość nazwanego pola. Dla pól
depthiwaveformzwracany jest binarny bufor danych, w przeciwnym razie wartość skalarna.
- __setitem__(name: str, value: Any) None#
Ustawia wartość nazwanego pola. Dla pól
sliderispinboxkrotka(min, max, value)aktualizuje jednocześnie zakres i bieżącą wartość. Dla póldepthvaluejest binarnym buforem danych. Dla pólwaveformvaluejest blokiem bajtopodobnym przeplatanych próbek wtypecodepola; liczba próbek na serię jest wyprowadzana z jego długości i dołączany jest monotoniczny znacznik czasu (w mikrosekundach, akumulowany ztime.ticks_us()), aby host mógł umieścić fragment na swojej osi czasu.
- size() int#
Metoda interfejsu backendu. Wywołuje
on_read(jeśli ustawione) i zwraca rozmiar zserializowanego bufora.
Stałe#
Bity flag kanału (łączone bitowo; przekazywane do protocol.register poprzez flags lub ustawiane automatycznie na podstawie metod backendu).
- protocol.CHANNEL_FLAG_PHYSICAL: int#
Kanał reprezentuje fizyczny transport (w przeciwieństwie do logicznego kanału danych).
Wbudowane identyfikatory kanałów.