13.3.1.4. Canais personalizados¶
Um canal é um fluxo de bytes bidirecional com nome entre um script na câmara e o anfitrião. A câmara regista um canal e fornece callbacks que produzem ou consomem dados; o anfitrião lê e escreve nesse canal pelo nome. O mesmo mecanismo que o pacote usa internamente para o canal stream que transporta fotogramas, o canal stdout que transporta a saída do script e o canal stdin que transporta o carregamento do script está exposto aos scripts do utilizador, pelo que quaisquer dados específicos da aplicação de que o anfitrião necessite podem usar a mesma ligação USB sem inventar um segundo protocolo.
Esta é a funcionalidade mais útil do pacote e aquela que a documentação padrão cobre menos bem, pelo que esta página a desenvolve do início ao fim.
13.3.1.4.1. As duas metades¶
Um canal personalizado precisa de código cooperante em ambos os lados. O script na câmara 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 com 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() devolve quantos bytes o canal tem atualmente disponíveis. read() é o produtor: dado um offset e size solicitados pelo anfitrião, devolve os bytes (ou uma string que a camada de protocolo codifica). poll() devolve True quando há algo para ler – a camada de protocolo usa isto para sinalizar o canal como pronto em read_status().
O programa no anfitrião usa quatro métodos de openmv.Camera: has_channel() para verificar se o canal existe, channel_size() para perguntar quantos dados estão em espera, channel_read() para retirar bytes, e channel_write() para enviar 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 ciclo do anfitrião consulta read_status(); quando o canal ticks está pronto, chama channel_read() sem size para retirar o que estiver disponível. O TicksChannel.poll() da câmara devolve True em cada verificação, pelo que o canal está sempre «pronto» e o anfitrião obtém um valor de ticks atualizado a cada consulta.
13.3.1.4.2. Um canal bidirecional¶
Para um anfitrião que precisa de enviar dados de volta, a classe na câmara 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 anfitrião escreve para o canal com channel_write() e lê a resposta pelo padrão habitual 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 isto oferece à aplicação¶
Os canais personalizados são a ferramenta certa sempre que uma aplicação quer usar a ligação USB existente para dados que não sejam fotogramas nem impressões: contadores de telemetria, parâmetros de configuração transmitidos ao vivo a partir de uma interface no anfitrião, comandos de controlo enviados na direção inversa, resultados de uma medição que a câmara calculou e que não se enquadra no formato de «imagem» que o canal de fluxo pressupõe. A camada de protocolo trata do enquadramento, fragmentação, reconhecimento e repetição; o script só precisa de implementar o backend de quatro métodos, e o anfitrião só precisa de conhecer o nome do canal e o formato dos dados.
A opção --channel NAME da CLI é uma forma rápida de verificar um canal personalizado a partir do terminal sem escrever um programa no lado do anfitrião: a CLI consulta o canal com o nome indicado e imprime os primeiros dez bytes de cada atualização.
O limite de tamanho numa única chamada a channel_read() ou channel_write() é o max_payload negociado pelo protocolo – 4096 bytes por predefinição. Os métodos do lado do anfitrião dividem automaticamente escritas maiores no número certo de pacotes, pelo que a aplicação pode passar buffers arbitrariamente grandes; a fragmentação é transparente.