12.9. Fluxo bidirecional¶
Os canais não são de mão única. Um backend que implementa write permite que o host envie bytes para a câmera, e a câmera reage. Esse é o padrão por trás de toda ferramenta interativa real: o operador gira um botão na GUI do host, o host escreve o novo valor em um canal de configuração, e a câmera o lê na próxima vez que captura.
12.9.1. Um canal de configuração¶
Adicionando ao script do lado da câmera de streaming, exponha um segundo canal para a qualidade JPEG:
class ConfigChannel:
def __init__(self):
self.quality = 85
def size(self):
return 0
def read(self, offset, size):
# Not used for "host writes to cam" -- but the library
# still needs the method present.
return b''
def write(self, offset, data):
# data is a bytearray view into the protocol buffer.
# Copy out the contents before doing anything with it.
new_q = int(bytes(data))
if 1 <= new_q <= 100:
self.quality = new_q
return len(data)
config = ConfigChannel()
protocol.register(name='config', backend=config)
O laço de captura lê de config.quality sempre que comprime um quadro:
while True:
img = csi0.snapshot()
latest_jpeg = bytes(
img.compress(quality=config.quality).bytearray()
)
ch.send_event(0x01)
O host agora tem um botão de controle. Defina-o como 50 e o próximo quadro fica menor (e mais feio); defina-o como 95 e o próximo quadro fica maior (e mais nítido). A câmera continua capturando sem reiniciar; o host não precisa enviar um novo script.
12.9.2. A chamada de escrita a partir do host¶
No lado do host, channel_write() envia bytes para um canal nomeado:
cam.channel_write('config', b'50')
O SDK do host codifica os bytes como um único pacote CHANNEL_WRITE (ou fragmentado), a camada de protocolo o entrega à câmera, o write(offset=0, data=...) da câmera é executado, e o lado da câmera confirma. No momento em que a chamada retorna, a câmera já recebeu e aceitou o novo valor.
A escrita é atômica do ponto de vista da câmera – a biblioteca do protocolo garante que o write do backend é executado até a conclusão antes que qualquer outra operação naquele canal prossiga. O código da aplicação pode ler config.quality de dentro do laço de captura sem se preocupar com o host interferindo no meio de um snapshot.
12.9.3. Tamanho stub e leitura em um canal somente de escrita¶
Um canal puramente de escrita ainda precisa de size e read definidos, mesmo que sejam stubs retornando 0 e b''. A biblioteca usa a presença de métodos para derivar as flags de capacidade do canal; um backend que não tem read não receberá CHANNEL_FLAG_READ ativado e o host recusará uma tentativa de leitura.
Os bytes retornados de read em um canal somente de escrita são úteis para um propósito diferente, no entanto: ecoar de volta o valor atual para que um host que acabou de se conectar possa perguntar à câmera “qual é a configuração atual?” em vez de partir de um padrão. Para que isso funcione, ambas as direções têm que concordar sobre uma serialização. A análise de bytes brutos int(bytes(data)) no exemplo anterior funciona para um único campo inteiro, mas não escala assim que houver um segundo botão a definir. Trocar write para analisar JSON e combiná-lo com um read que retorna o despejo JSON correspondente transforma o canal em um verdadeiro armazenamento de configuração de ida e volta:
import json
class ConfigChannel:
def __init__(self):
self.quality = 85
self._buf = b''
def size(self):
self._buf = json.dumps({'quality': self.quality}).encode()
return len(self._buf)
def read(self, offset, size):
return self._buf[offset:offset + size]
def write(self, offset, data):
new = json.loads(bytes(data))
if 'quality' in new:
self.quality = int(new['quality'])
return len(data)
O host agora escreve cam.channel_write('config', b'{"quality": 50}') para definir um valor e cam.channel_read('config') para ler o estado atual de volta. A câmera serializa um novo despejo JSON a cada leitura, de modo que o host sempre vê os valores mais recentes, e adicionar outro botão (threshold, exposure, orientation) é uma linha no dicionário JSON de cada lado.
12.9.4. Um laço completo¶
Com um canal de quadro para dados câmera → host, um canal de configuração para controle host → câmera e uma pequena quantidade de cola, a aplicação é uma ferramenta interativa:
O host abre a câmera, começa a puxar quadros e os exibe em uma janela.
Quando o operador arrasta um controle deslizante, o host escreve o novo valor em
config.O laço de captura da câmera capta o valor no próximo quadro.
Os novos quadros fluem pelo mesmo canal
frame.
Esse é o modelo inteiro. Dois canais, dois callbacks cada, um laço de captura na câmera, um laço de leitura e escrita no host. Nenhuma lógica de enquadramento visível, nenhum tratamento de erro visível – a biblioteca do protocolo faz a movimentação confiável de bytes desaparecer.
Tudo a partir deste ponto é código de aplicação. Adicionar um terceiro canal para um histograma, um quarto para telemetria ou um quinto para gatilhos de sensor é a mesma receita de classe-de-backend-e-protocol.register, repetida. Uma vez que um projeto de câmera atinge este ponto, o protocolo deixa de ser o problema interessante; a própria lógica da aplicação passa a ser.