13.3.1.5. Zdarzenia¶
Dotychczasowe strony wywołują operacje na kamerze: przesyłają skrypt, odczytują ramkę, zapisują do kanału. Każda z tych operacji jest inicjowana przez hosta – host pyta, kamera odpowiada. Protokół działa też w drugą stronę. Kamera może wypychać do hosta zdarzenia bez pytania, a SDK hosta dostarcza każde z nich do wywołania zwrotnego, które aplikacja może nadpisać.
To właściwe narzędzie, gdy aplikacja chce zareagować na coś, co kamera zauważyła, zanim sama o to zapyta. Bez zdarzeń jedynym sposobem na dowiedzenie się o tym jest ciągłe wywoływanie read_status() w pętli.
13.3.1.5.1. Domyślne wywołanie zwrotne¶
Klasa Camera już wewnętrznie subskrybuje zdarzenia. _handle_event() to wywołanie zwrotne, które transport uruchamia za każdym razem, gdy nadejdzie pakiet zdarzenia. Domyślnie obsługuje trzy zdarzenia systemowe:
CHANNEL_REGISTERED– na kamerze pojawił się nowy kanał po połączeniu hosta. Framework odświeża swoją pamięć podręczną kanałów, aby następne wyszukaniehas_channel()go znalazło.CHANNEL_UNREGISTERED– kanał zniknął.SOFT_REBOOT– kamera samodzielnie się zrestartowała (watchdog, błąd krytyczny, celowemachine.reset()).
Śledzi również zdarzenie gotowości ramki kanału stream dla ścieżki strumieniowania oraz uruchomienie / zatrzymanie skryptu kanału stdin dla buforowania stdout. Domyślna wartość events=True w konstruktorze utrzymuje to wszystko włączone; aplikacja, która nie chce nic z tego, może przekazać events=False do Camera, a podsystem zdarzeń pozostanie cichy.
13.3.1.5.2. Tworzenie podklas w celu reagowania¶
Aby obsłużyć zdarzenia specyficzne dla aplikacji, które zgłasza kamera, utwórz podklasę Camera i nadpisz _handle_event(). Najpierw wywołaj metodę rodzica, aby zachować domyślne zachowanie, a następnie rozdzielaj zdarzenia, na których zależy aplikacji:
from openmv import Camera
class MyCamera(Camera):
def _handle_event(self, channel_id, event):
super()._handle_event(channel_id, event)
name = self.channels_by_id.get(
channel_id, {}).get('name')
if name == 'motion' and event == 1:
self.on_motion()
def on_motion(self):
print("motion detected")
Sygnatura to (channel_id, event). channel_id wynosi 0 dla zdarzeń systemowych, a w przeciwnym razie jest to numeryczny identyfikator kanału, który je zgłosił; event to liczba całkowita wybrana przez skrypt po stronie kamery. Wyliczenie EventType nadaje nazwy trzem zdarzeniom systemowym; zdarzenia kanałów używają dowolnych wartości zdefiniowanych przez backend po stronie kamery.
Zdarzenia kanałów wracają zindeksowane numerycznym identyfikatorem, a nie nazwą. Słownik z pamięci podręcznej channels_by_id jest tym, czego powyższe nadpisanie używa do wyszukania nazwy; channels_by_name jest jego lustrzanym odbiciem, zindeksowanym w drugą stronę.
13.3.1.5.3. Połowa po stronie kamery¶
Skrypt po stronie kamery zgłasza zdarzenie, wywołując send_event() na uchwycie zwróconym przez protocol.register()
import protocol
class MotionChannel:
def size(self):
return 0
def read(self, offset, size):
return b''
def poll(self):
return False
ch = protocol.register(
name='motion', backend=MotionChannel())
while True:
if detect_motion():
ch.send_event(1)
Numer zdarzenia to liczba całkowita wybrana przez aplikację. Dopuszczalna jest dowolna wartość, którą nadpisanie po stronie hosta jest gotowe obsłużyć; warstwa protokołu traktuje ją jako nieprzezroczysty ładunek. Domyślnie wywołanie działa na zasadzie „wyślij i zapomnij”; przekaż wait_ack=True, aby zablokować się do czasu potwierdzenia przez hosta, gdy pewność, że zdarzenie dotarło, jest ważniejsza niż opóźnienie pełnej wymiany.
Kanał, który wyłącznie wyzwala zdarzenia i nie przenosi żadnych odczytywalnych danych, jest poprawnym wzorcem – size zwraca 0, a read zwraca puste bajty. Biblioteka protokołu nadal wymaga obecności obu metod, aby oznaczyć kanał jako odczytywalny; skrypt po stronie kamery po prostu nigdy nie umieszcza w nim danych.
13.3.1.5.4. Sterowanie ścieżką odbioru w stanie bezczynności¶
Zdarzenia przychodzą tym samym połączeniem co wszystko inne, więc każde wywołanie hosta, które wysyła lub odbiera bajty, daje transportowi szansę na przetworzenie oczekujących zdarzeń przy okazji. Pętla odpytująca, która już raz na cykl wywołuje read_status() lub read_frame(), nie wymaga niczego dodatkowego.
Dla programów, które przez całe minuty obywają się bez innych operacji we/wy, poll_events() uruchamia ścieżkę odbioru raz, bez wysyłania polecenia. Zwraca sterowanie, gdy tylko bufor przychodzący jest pusty, więc ciasna pętla wokół niej – lub krótki licznik czasu (timer) w pętli zdarzeń GUI – to coś, co utrzymuje reaktywność procedur obsługi.
13.3.1.5.5. Kompletna pętla¶
Od początku do końca wzorzec wygląda tak: skrypt po stronie kamery rejestruje kanał i wywołuje send_event(), gdy coś się wydarzy; podklasa po stronie hosta nadpisuje _handle_event() i rozdziela zdarzenia. Pętla hosta, która nie robi nic poza obsługą zdarzeń, wygląda następująco:
with MyCamera('/dev/ttyACM0') as cam:
cam.stop()
cam.exec(open('motion_cam.py').read())
while True:
cam.poll_events()
Kamera przechwytuje, decyduje i zgłasza zdarzenia. Host pozostaje wewnątrz poll_events(), dopóki któreś nie nadejdzie, a wtedy uruchamia się on_motion. Żadne wywołanie read_status() nie jest uruchamiane, gdy nic się nie wydarzyło, i żadna ramka nie jest przesyłana przez USB, gdy kamera nie ma nic do zgłoszenia.