13.3.1.5. Eventos¶
As páginas até aqui chamam a cam: enviar um script, ler um quadro, escrever em um canal. Cada uma dessas operações é iniciada pelo host – o host pede, a cam responde. O protocolo também funciona na outra direção. A cam pode enviar eventos ao host sem ser solicitada, e o SDK do host entrega cada um a um callback que a aplicação pode sobrescrever.
Esta é a ferramenta certa sempre que a aplicação quer reagir a algo que a cam percebeu antes de pedir. Sem eventos, a única maneira de descobrir é continuar chamando read_status() em um loop.
13.3.1.5.1. O callback padrão¶
Camera já se inscreve em eventos internamente. _handle_event() é o callback que o transporte executa sempre que um pacote de evento chega. O comportamento padrão trata três eventos de sistema:
CHANNEL_REGISTERED– um novo canal apareceu na cam depois que o host conectou. O framework atualiza seu cache de canais para que a próxima consulta dehas_channel()o encontre.CHANNEL_UNREGISTERED– um canal desapareceu.SOFT_REBOOT– a cam reiniciou por conta própria (watchdog, hard fault,machine.reset()intencional).
Ele também rastreia o evento de quadro pronto do canal stream para o caminho de streaming e o início/parada de script do canal stdin para o buffering de stdout. O padrão events=True do construtor mantém tudo isso ativado; uma aplicação que não quer nada disso pode passar events=False para Camera e o subsistema de eventos permanece silencioso.
13.3.1.5.2. Criando subclasses para reagir¶
Para tratar eventos específicos da aplicação que a cam dispara, crie uma subclasse de Camera e sobrescreva _handle_event(). Chame o método pai primeiro para manter o comportamento padrão e, em seguida, despache os eventos que importam à aplicação:
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 de sistema e, caso contrário, o ID numérico do canal que o disparou; event é um inteiro escolhido pelo script do lado da cam. O enum EventType dá nomes aos três eventos de sistema; os eventos de canal usam quaisquer valores que o backend do lado da cam defina.
Os eventos de canal voltam indexados por ID numérico, não por nome. O dicionário channels_by_id em cache é o que a sobrescrita acima usa para procurar o nome; channels_by_name é o seu espelho, indexado no sentido oposto.
13.3.1.5.3. A metade do lado da cam¶
O script do lado da cam dispara um evento chamando send_event() no handle retornado 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 a sobrescrita do host esteja preparada para tratar é válido; a camada de protocolo o trata como payload opaco. Por padrão, a chamada dispara e esquece; passe wait_ack=True para bloquear até que o host reconheça, quando saber que o evento chegou importa mais do que a latência da viagem de ida e volta.
Um canal que apenas dispara eventos e não transporta dados legíveis é um padrão válido – size retorna 0 e read retorna bytes vazios. A biblioteca de protocolo ainda precisa que ambos os métodos estejam presentes para marcar o canal como legível; o script do lado da cam simplesmente nunca coloca dados nele.
13.3.1.5.4. Movimentando o caminho de recepção enquanto ocioso¶
Os eventos chegam pela mesma conexão que tudo o mais, então qualquer chamada do host que envie ou receba bytes dá ao transporte a chance de processar eventos pendentes em linha. Um loop de polling que já chama read_status() ou read_frame() uma vez por ciclo não precisa de nada extra.
Para programas que passam minutos sem outras operações de E/S, poll_events() executa o caminho de recepção uma vez sem enviar um comando. Ele retorna assim que o buffer de entrada está vazio, então um loop apertado em torno dele – ou um timer curto em um loop de eventos de GUI – é o que mantém os handlers reativos.
13.3.1.5.5. Um loop completo¶
De ponta a ponta, o padrão é: o script do lado da cam registra um canal e chama send_event() quando algo acontece; a subclasse do lado do host sobrescreve _handle_event() e despacha. Um loop de host que não faz nada além de atender eventos se parece com:
with MyCamera('/dev/ttyACM0') as cam:
cam.stop()
cam.exec(open('motion_cam.py').read())
while True:
cam.poll_events()
A cam captura, decide e dispara eventos. O host fica dentro de poll_events() até que um chegue, e então on_motion é executado. Nenhuma chamada de read_status() é feita quando nada aconteceu, e nenhum quadro é puxado pelo USB quando a cam não tem nada a reportar.