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ă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.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.