12.6. Canais nomeados

O ID de canal no cabeçalho de cada pacote permite que até 32 fluxos independentes compartilhem o mesmo transporte físico. A camada de canais transforma esses IDs numéricos em endpoints nomeados e visíveis à aplicação, aos quais o código do host pode se referir por uma string.

Um fio de transporte à esquerda se ramificando em quatro canais rotulados no lado da câmera -- stdin, stdout, stream e um canal de status registrado pelo usuário -- cada um exibido como uma caixa independente.

12.6.1. Os quatro canais integrados

A câmera registra quatro canais na inicialização, antes de qualquer código de aplicação ser executado:

  • stdin – bytes de script que o host envia à câmera para executar. A IDE usa esse canal para enviar o script que está sendo editado; exec() no SDK do host é a chamada equivalente a partir de um programa Python.

  • stdout – bytes das chamadas print() da câmera e tracebacks de exceções não capturadas. O console serial da IDE lê esse canal.

  • stream – o canal de pré-visualização ao vivo. A IDE extrai quadros JPEG dele; qualquer script do host pode fazer o mesmo com read_frame().

  • profile – eventos de profiler, presentes apenas quando a câmera foi compilada com profiling habilitado. A maioria das builds de release o omite.

O código de aplicação raramente precisa tocar em qualquer um dos canais integrados; o trabalho interessante acontece em canais que a própria aplicação registra.

12.6.2. Registrando um canal

Um script do lado da câmera registra um novo canal chamando protocol.register() com um nome e um objeto Python de backend

import json
import protocol
import time

trigger_count = 0

class StatusChannel:
    def size(self):
        # Refresh the snapshot on every host query.
        self._buf = json.dumps({
            'uptime_s': time.ticks_ms() // 1000,
            'triggers': trigger_count,
        }).encode()
        return len(self._buf)

    def read(self, offset, size):
        return self._buf[offset:offset + size]

protocol.register(name='status', backend=StatusChannel())

Os métodos do objeto de backend decidem o que o canal pode fazer. Um backend com apenas size e read é um canal de dados somente leitura; adicione write e ele se torna bidirecional; adicione poll e o host pode perguntar se há dados novos prontos antes de pagar por uma leitura. Amostrar os dados dentro de size é o padrão mais simples quando o payload é pequeno o suficiente para caber em um único fragmento – o buffer é gerado sob demanda, nunca em cache, nunca em condição de corrida. Payloads maiores – quadros de imagem, traços de sensor – precisam de um padrão de travamento que segura o buffer até o host concluir sua leitura de múltiplos fragmentos, abordado no canal de quadros.

Uma pequena quantidade de contabilidade acontece automaticamente:

  • A biblioteca atribui o próximo ID de canal livre (entre 0 e 31).

  • Os flags de capacidade são derivados dos métodos presentes: CHANNEL_FLAG_READ se read estiver definido, CHANNEL_FLAG_WRITE se write estiver definido, CHANNEL_FLAG_LOCK se lock / unlock estiverem definidos.

  • Um pacote de evento CHANNEL_REGISTERED é enviado a qualquer host conectado para que sua lista de canais se atualize.

O valor de retorno é um handle protocol.ProtocolChannel que a aplicação pode manter. O método send_event() do handle é o gancho do lado da câmera para dizer ao host “algo aconteceu neste canal sem alterar os dados legíveis” – um gatilho disparou, um botão foi pressionado, um marco de contagem de amostras foi atingido.

12.6.3. Lendo canais a partir do host

O SDK do host é distribuído como o pacote openmv no PyPI (pip install openmv), construído sobre pyserial para o transporte. Sua classe openmv.camera.Camera expõe os canais nomeados da câmera por meio de métodos de alto nível:

from openmv.camera import Camera

with Camera('/dev/ttyACM0', baudrate=921600) as cam:
    cam.update_channels()
    if cam.has_channel('status'):
        size = cam.channel_size('status')
        data = cam.channel_read('status', size)

Aviso

O pacote openmv requer CPython 3.12 ou mais recente. Interpretadores anteriores não possuem recursos dos quais o SDK depende; instale uma build 3.12+ antes de pip install openmv.

Algumas coisas a observar sobre a configuração:

  • A string da porta serial – /dev/ttyACM0 aqui – é no estilo COM3 no Windows, /dev/cu.usbmodemXXXX no macOS e /dev/ttyACM* no Linux. O número real depende de qual porta a câmera enumerou.

  • A taxa de transmissão (baud rate) é o valor mágico do protocolo 921600, que a pilha USB-CDC da câmera reconhece como “este cliente fala o protocolo, não o REPL”. Qualquer outra taxa cai de volta para uma linha serial comum.

  • O gerenciador de contexto with Camera(...) as cam: abre o transporte, executa PROTO_SYNC, troca capacidades e, ao sair, fecha a porta de forma limpa. A chamada explícita a update_channels() após a entrada atualiza a lista local de canais com quaisquer canais que a aplicação registrou após a inicialização.

channel_size() e channel_read() são os métodos de trabalho principais; channel_write() faz a ida e volta de um buffer para a câmera se o backend tiver um método write; has_channel() é a forma segura de verificar se um nome está registrado antes de usá-lo. O nome do canal é resolvido uma vez para o ID de canal que a câmera atribuiu durante register e usado em cada pacote a partir de então.

Cada par channel_size() / channel_read() custa duas idas e voltas: um pacote para pedir o tamanho, outro para pedir os bytes. Por USB-CDC, ambos terminam em cerca de um milissegundo combinados; por UART, a mesma troca leva mais tempo em proporção à taxa de transmissão (baud rate) da linha serial. Código de aplicação que lê em um laço apertado deve chamar channel_size() apenas quando o tamanho pode realmente mudar – para dados de tamanho fixo, o tamanho da primeira chamada pode ser armazenado em cache.

12.6.4. Independência entre canais

Três coisas valem a pena saber sobre como os canais interagem:

  • Controle de fluxo independente. Cada canal tem seu próprio estado de leitura pendente, seus próprios dados e seus próprios callbacks size / read / write. Uma leitura demorada no canal stream não bloqueia leituras no canal config da aplicação.

  • Sequencial por canal. Dentro de um único canal, os pacotes são entregues em ordem. A camada de confiabilidade garante isso mesmo quando há retransmissões envolvidas.

  • Transporte compartilhado, orçamento de retransmissão compartilhado. Todos os canais compartilham o único enlace físico, então uma torrente de tráfego em um canal desacelera os outros ao monopolizar o fio. O mecanismo CHANNEL_LOCK permite que um canal reserve o fio para uma leitura atômica de múltiplos pacotes; o backend adere implementando os callbacks lock / unlock.

Um canal é a área de superfície mínima sobre a qual um programa do host e um programa da câmera concordam em cooperar. O nome, a direcionalidade (leitura, escrita ou ambas), os métodos de callback no lado da câmera e as chamadas de método correspondentes no lado do host constituem todo o contrato.