13.3.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.3.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ăutare has_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.3.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.3.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.3.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.3.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.