13.3.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.3.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.3.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.3.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.3.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.3.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.