13.3.1.5. Eventos¶
Las páginas anteriores invocan a la cámara: subir un script, leer un fotograma, escribir en un canal. Cada una de esas operaciones se inicia desde el anfitrión: el anfitrión pregunta, la cámara responde. El protocolo también funciona en el otro sentido. La cámara puede enviar eventos al anfitrión sin que se le pida, y el SDK del anfitrión entrega cada uno a una función de retorno (callback) que la aplicación puede anular.
Esta es la herramienta adecuada siempre que la aplicación quiera reaccionar a algo que la cámara detectó antes de preguntarlo. Sin eventos, la única forma de averiguarlo es seguir llamando a read_status() en bucle.
13.3.1.5.1. La función de retorno por defecto¶
Camera ya se suscribe a los eventos internamente. _handle_event() es la función de retorno (callback) que el transporte ejecuta cada vez que llega un paquete de evento. La implementación por defecto maneja tres eventos del sistema:
CHANNEL_REGISTERED– apareció un nuevo canal en la cámara después de que el anfitrión se conectara. El framework actualiza su caché de canales para que la siguiente búsqueda dehas_channel()lo encuentre.CHANNEL_UNREGISTERED– desapareció un canal.SOFT_REBOOT– la cámara se reinició por sí sola (watchdog, fallo grave,machine.reset()intencionado).
También rastrea el evento de fotograma-listo del canal stream para la ruta de transmisión y el inicio / parada del script del canal stdin para el almacenamiento en búfer de stdout. El valor por defecto events=True del constructor mantiene todo esto activo; una aplicación que no quiera nada de ello puede pasar events=False a Camera y el subsistema de eventos permanece en silencio.
13.3.1.5.2. Crear subclases para reaccionar¶
Para manejar los eventos específicos de la aplicación que la cámara genera, crea una subclase de Camera y anula _handle_event(). Llama primero al padre para mantener el comportamiento por defecto, y luego despacha los eventos que le interesan a la aplicación:
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 es (channel_id, event). channel_id es 0 para los eventos del sistema y, en caso contrario, el ID numérico del canal que lo generó; event es un entero que eligió el script del lado de la cámara. La enumeración EventType da nombres a los tres eventos del sistema; los eventos de canal usan los valores que defina el backend del lado de la cámara.
Los eventos de canal regresan indexados por ID numérico, no por nombre. El diccionario en caché channels_by_id es lo que la anulación anterior usa para buscar el nombre; channels_by_name es su espejo, indexado al revés.
13.3.1.5.3. La mitad del lado de la cámara¶
El script del lado de la cámara genera un evento llamando a send_event() sobre el manejador devuelto por 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)
El número de evento es un entero elegido por la aplicación. Cualquier valor que la anulación del anfitrión esté preparada para manejar es válido; la capa de protocolo lo trata como carga útil opaca. Por defecto la llamada dispara y olvida; pasa wait_ack=True para bloquear hasta que el anfitrión acuse recibo, cuando saber que el evento llegó importa más que la latencia del viaje de ida y vuelta.
Un canal que solo dispara eventos y no transporta datos legibles es un patrón válido: size devuelve 0 y read devuelve bytes vacíos. La biblioteca de protocolo aún necesita ambos métodos presentes para marcar el canal como legible; el script del lado de la cámara simplemente nunca pone datos en él.
13.3.1.5.4. Impulsar la ruta de recepción mientras está inactivo¶
Los eventos llegan por la misma conexión que todo lo demás, así que cualquier llamada del anfitrión que envíe o reciba bytes le da al transporte la oportunidad de procesar eventos pendientes en línea. Un bucle de sondeo que ya llama a read_status() o read_frame() una vez por ciclo no necesita nada adicional.
Para programas que pasan minutos sin otra E/S, poll_events() ejecuta la ruta de recepción una vez sin enviar un comando. Regresa en cuanto el búfer de entrada está vacío, de modo que un bucle cerrado a su alrededor (o un temporizador corto en un bucle de eventos de GUI) es lo que mantiene reactivos los manejadores.
13.3.1.5.5. Un bucle completo¶
De extremo a extremo, el patrón es: el script del lado de la cámara registra un canal y llama a send_event() cuando algo sucede; la subclase del lado del anfitrión anula _handle_event() y despacha. Un bucle del anfitrión que no hace más que atender eventos tiene este aspecto:
with MyCamera('/dev/ttyACM0') as cam:
cam.stop()
cam.exec(open('motion_cam.py').read())
while True:
cam.poll_events()
La cámara captura, decide y genera eventos. El anfitrión permanece dentro de poll_events() hasta que llega uno, y entonces se ejecuta on_motion. No se ejecuta ninguna llamada a read_status() cuando no ha pasado nada, y no se extrae ningún fotograma por USB cuando la cámara no tiene nada que informar.