13.3.1.4. Canais personalizados¶
Um canal é um fluxo de bytes nomeado e bidirecional entre um script do lado da cam e o host. A cam registra um canal e fornece callbacks que produzem ou consomem dados; o host lê e escreve nesse canal pelo nome. O mesmo mecanismo que o pacote usa internamente para o canal stream que transporta os quadros, o canal stdout que transporta a saída do script e o canal stdin que transporta o upload do script é exposto aos scripts do usuário, de modo que quaisquer dados específicos da aplicação que o host precise possam trafegar pela mesma conexão USB sem inventar um segundo protocolo.
Este é o recurso mais útil do pacote e o que a documentação padrão cobre pior, então esta página trabalha-o de ponta a ponta.
13.3.1.4.1. As duas metades¶
Um canal personalizado precisa de código cooperante em ambos os lados. O script do lado da cam importa protocol, define uma classe com três métodos (size(), read(), poll()) mais um write() opcional, e chama protocol.register(name=..., backend=...) para publicar o canal sob um nome escolhido:
import protocol
import time
class TicksChannel:
def size(self):
return 10
def read(self, offset, size):
return f'{time.ticks_ms():010d}'
def poll(self):
return True
protocol.register(name='ticks', backend=TicksChannel())
O método size() retorna quantos bytes o canal tem disponíveis no momento. read() é o produtor: dados um offset e um size solicitados pelo host, retorna os bytes (ou uma string que a camada de protocolo codifica). poll() retorna True quando há algo para ler – a camada de protocolo usa isso para sinalizar o canal como pronto em read_status().
O programa do lado do host usa quatro métodos de openmv.Camera: has_channel() para verificar se o canal existe, channel_size() para perguntar quantos dados estão aguardando, channel_read() para extrair bytes e channel_write() para inserir bytes. read_status() consulta todos os canais de uma vez:
from openmv import Camera
with Camera('/dev/ttyACM0') as cam:
cam.stop()
cam.exec(open('ticks_cam.py').read())
while True:
status = cam.read_status()
if status.get('ticks'):
data = cam.channel_read('ticks')
print(f"ticks: {data.decode()}")
O loop do host consulta read_status(); quando o canal ticks está pronto, ele chama channel_read() sem size para extrair o que estiver disponível. O método TicksChannel.poll() da cam retorna True em toda verificação, então o canal está sempre “pronto” e o host obtém um valor de tick atualizado a cada polling.
13.3.1.4.2. Um canal bidirecional¶
Para um host que precisa enviar dados de volta, a classe do lado da cam adiciona um método write() que aceita os bytes recebidos:
import protocol
class CommandChannel:
def __init__(self):
self.last_command = b''
self.replied = False
def size(self):
return len(self.last_command)
def read(self, offset, size):
self.replied = True
return self.last_command
def write(self, offset, data):
self.last_command = b'echo: ' + bytes(data)
self.replied = False
def poll(self):
return not self.replied and len(self.last_command) > 0
protocol.register(name='echo', backend=CommandChannel())
O host escreve no canal com channel_write() e lê a resposta de volta pelo padrão habitual de read_status() / channel_read()
with Camera('/dev/ttyACM0') as cam:
cam.stop()
cam.exec(open('echo_cam.py').read())
cam.channel_write('echo', b'hello')
while True:
if cam.read_status().get('echo'):
print(cam.channel_read('echo').decode())
break
13.3.1.4.3. O que isso traz para a aplicação¶
Canais personalizados são a ferramenta certa sempre que uma aplicação quer aproveitar a conexão USB existente para dados que não são quadros nem impressões: contadores de telemetria, parâmetros de configuração transmitidos ao vivo a partir de uma interface no host, comandos de controle enviados no sentido oposto, resultados de uma medição que a cam computou e que não se encaixam no enquadramento de “imagem” que o canal de stream pressupõe. A camada de protocolo cuida do enquadramento, da fragmentação, do reconhecimento e da retentativa; o script só precisa implementar o backend de quatro métodos, e o host só precisa conhecer o nome do canal e o formato dos dados.
A flag --channel NAME da CLI é uma maneira rápida de verificar um canal personalizado a partir do terminal sem escrever um programa do lado do host: a CLI consulta o canal nomeado e imprime os primeiros dez bytes de cada atualização.
O limite de tamanho em uma única chamada de channel_read() ou channel_write() é o max_payload negociado pelo protocolo – 4096 bytes por padrão. Os métodos do lado do host dividem automaticamente escritas maiores no número certo de pacotes, de modo que a aplicação pode passar buffers arbitrariamente grandes; a fragmentação é invisível.