13.4.1.5. Evenimente#
Paginile de până acum apelează către cameră: încarcă un script, citesc un cadru, scriu într-un canal. Fiecare dintre aceste operațiuni este inițiată de gazdă – gazda întreabă, camera răspunde. Protocolul rulează și în cealaltă direcție. Camera poate trimite evenimente către gazdă fără să fie întrebată, iar SDK-ul gazdei livrează fiecare eveniment unei funcții de retroapelare (callback) pe care aplicația o poate suprascrie.
Acesta este instrumentul potrivit ori de câte ori aplicația dorește să reacționeze la ceva ce a observat camera înainte ca aplicația să întrebe. Fără evenimente, singura modalitate de a afla este să apelezi read_status() într-o buclă.
13.4.1.5.1. Funcția de retroapelare implicită#
Camera se abonează deja la evenimente intern. _handle_event() este funcția de retroapelare pe care o rulează transportul ori de câte ori sosește un pachet de eveniment. Implicit gestionează trei evenimente de sistem:
CHANNEL_REGISTERED– un canal nou a apărut pe cameră după ce gazda s-a conectat. Cadrul de lucru își reîmprospătează memoria cache a canalelor, astfel încât următoarea căutarehas_channel()îl găsește.CHANNEL_UNREGISTERED– un canal a dispărut.SOFT_REBOOT– camera a repornit de la sine (watchdog, eroare gravă,machine.reset()intenționat).
De asemenea, urmărește evenimentul de cadru-pregătit al canalului stream pentru calea de transmisie și pornirea / oprirea scriptului canalului stdin pentru tamponarea stdout. Valoarea implicită events=True a constructorului păstrează toate acestea active; o aplicație care nu dorește nimic din toate acestea poate transmite events=False către Camera, iar subsistemul de evenimente rămâne tăcut.
13.4.1.5.2. Crearea de subclase pentru a reacționa#
Pentru a gestiona evenimentele specifice aplicației pe care le ridică camera, creează o subclasă a Camera și suprascrie _handle_event(). Apelează mai întâi clasa părinte pentru a păstra comportamentul implicit, apoi distribuie evenimentele care contează pentru aplicație:
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")
Semnătura este (channel_id, event). channel_id este 0 pentru evenimentele de sistem și, în rest, ID-ul numeric al canalului care l-a ridicat; event este un întreg ales de scriptul de pe cameră. Enumerarea EventType dă nume celor trei evenimente de sistem; evenimentele de canal folosesc orice valori definește backend-ul de pe cameră.
Evenimentele de canal revin indexate după ID numeric, nu după nume. Dicționarul channels_by_id din cache este ceea ce folosește suprascrierea de mai sus pentru a căuta numele; channels_by_name este oglinda sa, indexată invers.
13.4.1.5.3. Jumătatea de pe cameră#
Scriptul de pe cameră ridică un eveniment apelând send_event() pe identificatorul returnat de 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)
Numărul evenimentului este un întreg ales de aplicație. Orice valoare pe care suprascrierea gazdei este pregătită să o gestioneze este permisă; stratul de protocol o tratează ca sarcină utilă opacă. În mod implicit, apelul declanșează și uită; transmite wait_ack=True pentru a bloca până când gazda confirmă, atunci când a ști că evenimentul a ajuns contează mai mult decât latența traseului dus-întors.
Un canal care doar declanșează evenimente și nu transportă date care pot fi citite este un tipar valid – size returnează 0, iar read returnează octeți goi. Biblioteca de protocol are totuși nevoie ca ambele metode să fie prezente pentru a marca canalul ca putând fi citit; scriptul de pe cameră pur și simplu nu pune niciodată date în el.
13.4.1.5.4. Acționarea căii de recepție în starea de repaus#
Evenimentele sosesc pe aceeași conexiune ca tot restul, așa că orice apel al gazdei care trimite sau primește octeți oferă transportului șansa de a procesa în linie evenimentele în așteptare. O buclă de interogare care apelează deja read_status() sau read_frame() o dată pe ciclu nu are nevoie de nimic în plus.
Pentru programele care petrec minute fără alte operațiuni de I/O, poll_events() rulează calea de recepție o dată fără a trimite o comandă. Revine de îndată ce tamponul de intrare este gol, așa că o buclă strânsă în jurul ei – sau un temporizator scurt într-o buclă de evenimente GUI – este ceea ce menține gestionarele reactive.
13.4.1.5.5. O buclă completă#
De la un capăt la altul, tiparul este: scriptul de pe cameră înregistrează un canal și apelează send_event() când se întâmplă ceva; subclasa de pe gazdă suprascrie _handle_event() și distribuie. O buclă de pe gazdă care nu face nimic altceva decât să deservească evenimentele arată astfel:
with MyCamera('/dev/ttyACM0') as cam:
cam.stop()
cam.exec(open('motion_cam.py').read())
while True:
cam.poll_events()
Camera captează, decide și ridică evenimente. Gazda stă în interiorul poll_events() până când sosește unul, apoi rulează on_motion. Niciun apel read_status() nu rulează când nu s-a întâmplat nimic și niciun cadru nu este extras prin USB atunci când camera nu are nimic de raportat.