13.3.1.5. Eventos¶
As páginas anteriores chamam a câmara: carregam um script, leem um fotograma, escrevem num canal. Todas essas operações são iniciadas pelo anfitrião – o anfitrião pergunta, a câmara responde. O protocolo também funciona na direção inversa. A câmara pode enviar eventos para o anfitrião sem ser solicitada, e o SDK do anfitrião entrega cada um a um callback que a aplicação pode substituir.
Esta é a ferramenta certa sempre que a aplicação quer reagir a algo que a câmara detetou antes de perguntar. Sem eventos, a única forma de descobrir é continuar a chamar read_status() num ciclo.
13.3.1.5.1. O callback predefinido¶
Camera já subscreve eventos internamente. _handle_event() é o callback que o transporte executa sempre que chega um pacote de evento. O predefinido trata de três eventos do sistema:
CHANNEL_REGISTERED– apareceu um novo canal na câmara após a ligação do anfitrião. A estrutura atualiza a sua cache de canais para que a próxima pesquisahas_channel()o encontre.CHANNEL_UNREGISTERED– um canal desapareceu.SOFT_REBOOT– a câmara reiniciou por conta própria (watchdog, falha grave,machine.reset()intencional).
Também rastreia o evento de fotograma pronto do canal stream para o caminho de transmissão e o início/fim do script do canal stdin para o buffering de stdout. A predefinição events=True do construtor mantém tudo isto ativo; uma aplicação que não queira nada disto pode passar events=False para Camera e o subsistema de eventos fica silencioso.
13.3.1.5.2. Criar subclasses para reagir¶
Para tratar eventos específicos da aplicação que a câmara levanta, crie uma subclasse de Camera e substitua _handle_event(). Chame primeiro o pai para manter o comportamento predefinido, depois despache os eventos que a aplicação tem interesse:
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")
A assinatura é (channel_id, event). channel_id é 0 para eventos do sistema e, caso contrário, o ID numérico do canal que o levantou; event é um inteiro que o script na câmara escolheu. O enum EventType dá nomes aos três eventos do sistema; os eventos de canal usam os valores que o backend na câmara definir.
Os eventos de canal chegam indexados pelo ID numérico, não pelo nome. O dicionário channels_by_id em cache é o que o método substituído acima usa para procurar o nome; channels_by_name é o seu espelho, indexado ao contrário.
13.3.1.5.3. A metade na câmara¶
O script na câmara levanta um evento chamando send_event() no handle devolvido 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)
O número do evento é um inteiro escolhido pela aplicação. Qualquer valor que o método substituído do anfitrião esteja preparado para tratar é válido; a camada de protocolo trata-o como payload opaco. Por predefinição a chamada é do tipo «disparar e esquecer»; passe wait_ack=True para bloquear até o anfitrião reconhecer, quando saber que o evento chegou importa mais do que a latência da viagem de ida e volta.
Um canal que apenas levanta eventos e não contém dados legíveis é um padrão válido – size devolve 0 e read devolve bytes vazios. A biblioteca de protocolo ainda precisa de ambos os métodos presentes para marcar o canal como legível; o script na câmara simplesmente nunca coloca dados nele.
13.3.1.5.4. Manter o caminho de receção ativo quando inativo¶
Os eventos chegam na mesma ligação que tudo o resto, pelo que qualquer chamada do anfitrião que envie ou receba bytes dá ao transporte uma oportunidade de processar eventos pendentes inline. Um ciclo de consulta que já chama read_status() ou read_frame() uma vez por ciclo não precisa de nada extra.
Para programas que ficam minutos sem outra E/S, poll_events() executa o caminho de receção uma vez sem enviar um comando. Retorna assim que o buffer de entrada estiver vazio, pelo que um ciclo fechado à sua volta – ou um temporizador curto num ciclo de eventos GUI – é o que mantém os handlers reativos.
13.3.1.5.5. Um ciclo completo¶
Do início ao fim, o padrão é: o script na câmara regista um canal e chama send_event() quando algo acontece; a subclasse no anfitrião substitui _handle_event() e despacha. Um ciclo no anfitrião que apenas serve eventos tem este aspeto:
with MyCamera('/dev/ttyACM0') as cam:
cam.stop()
cam.exec(open('motion_cam.py').read())
while True:
cam.poll_events()
A câmara captura, decide e levanta eventos. O anfitrião permanece dentro de poll_events() até que um chegue, depois on_motion é executado. Nenhuma chamada a read_status() é feita quando nada aconteceu, e nenhum fotograma é transferido via USB quando a câmara nada tem a reportar.