13.4.1.5. Eventi#
Le pagine viste finora effettuano chiamate verso la cam: caricano uno script, leggono un frame, scrivono su un canale. Ognuna di queste operazioni è avviata dall’host: l’host chiede, la cam risponde. Il protocollo funziona anche nella direzione opposta. La cam può inviare eventi all’host senza che le siano richiesti, e l’SDK host consegna ciascuno di essi a un callback che l’applicazione può sovrascrivere.
Questo è lo strumento giusto ogni volta che l’applicazione vuole reagire a qualcosa che la cam ha notato prima ancora di chiederlo. Senza eventi, l’unico modo per scoprirlo è continuare a chiamare read_status() in un ciclo.
13.4.1.5.1. Il callback predefinito#
Camera si iscrive già internamente agli eventi. _handle_event() è il callback che il transport esegue ogni volta che arriva un pacchetto di evento. Quello predefinito gestisce tre eventi di sistema:
CHANNEL_REGISTERED– un nuovo canale è apparso sulla cam dopo la connessione dell’host. Il framework aggiorna la sua cache dei canali in modo che la successiva ricercahas_channel()lo trovi.CHANNEL_UNREGISTERED– un canale è scomparso.SOFT_REBOOT– la cam si è riavviata da sola (watchdog, hard fault,machine.reset()intenzionale).
Tiene inoltre traccia dell’evento di frame pronto del canale stream per il percorso di streaming e dell’avvio / arresto dello script del canale stdin per il buffering dello stdout. Il valore predefinito events=True del costruttore mantiene attivo tutto questo; un’applicazione che non ne vuole nulla può passare events=False a Camera e il sottosistema degli eventi rimane silenzioso.
13.4.1.5.2. Sottoclassi per reagire#
Per gestire gli eventi specifici dell’applicazione generati dalla cam, crea una sottoclasse di Camera e sovrascrivi _handle_event(). Chiama prima il metodo padre per mantenere il comportamento predefinito, quindi smista gli eventi a cui l’applicazione tiene:
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")
La firma è (channel_id, event). channel_id è 0 per gli eventi di sistema e altrimenti l’ID numerico del canale che lo ha generato; event è un intero scelto dallo script lato cam. L’enum EventType assegna i nomi ai tre eventi di sistema; gli eventi di canale usano qualsiasi valore definito dal backend lato cam.
Gli eventi di canale vengono restituiti con chiave l’ID numerico, non il nome. Il dizionario channels_by_id in cache è ciò che l’override sopra usa per cercare il nome; channels_by_name è il suo speculare, con chiavi nell’altra direzione.
13.4.1.5.3. La metà lato cam#
Lo script lato cam genera un evento chiamando send_event() sull’handle restituito da 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)
Il numero dell’evento è un intero scelto dall’applicazione. Qualsiasi valore che l’override dell’host è pronto a gestire è ammissibile; il livello di protocollo lo tratta come payload opaco. Per impostazione predefinita la chiamata invia e dimentica; passa wait_ack=True per bloccare fino a quando l’host conferma, quando sapere che l’evento è arrivato conta più della latenza del viaggio di andata e ritorno.
Un canale che genera solo eventi e non trasporta dati leggibili è uno schema valido: size restituisce 0 e read restituisce byte vuoti. La libreria del protocollo richiede comunque la presenza di entrambi i metodi per contrassegnare il canale come leggibile; lo script lato cam semplicemente non vi inserisce mai dati.
13.4.1.5.4. Pilotare il percorso di ricezione durante l’inattività#
Gli eventi arrivano sulla stessa connessione di tutto il resto, quindi qualsiasi chiamata host che invia o riceve byte dà al transport la possibilità di elaborare gli eventi in sospeso in linea. Un ciclo di polling che chiama già read_status() o read_frame() una volta per ciclo non necessita di nulla di aggiuntivo.
Per i programmi che restano minuti senza altro I/O, poll_events() esegue il percorso di ricezione una volta senza inviare un comando. Ritorna non appena il buffer in entrata è vuoto, quindi un ciclo serrato attorno ad esso, o un breve timer in un ciclo di eventi GUI, è ciò che mantiene reattivi i gestori.
13.4.1.5.5. Un ciclo completo#
Dall’inizio alla fine, lo schema è: lo script lato cam registra un canale e chiama send_event() quando accade qualcosa; la sottoclasse lato host sovrascrive _handle_event() e smista. Un ciclo host che non fa altro che servire gli eventi ha questo aspetto:
with MyCamera('/dev/ttyACM0') as cam:
cam.stop()
cam.exec(open('motion_cam.py').read())
while True:
cam.poll_events()
La cam acquisisce, decide e genera eventi. L’host resta all’interno di poll_events() finché non ne arriva uno, quindi viene eseguito on_motion. Nessuna chiamata a read_status() viene eseguita quando non è successo nulla, e nessun frame viene trasferito su USB quando la cam non ha nulla da segnalare.