13.1.10. Janelas de terminal independentes

Tools → Open Terminal abre janelas de terminal independentes – cada uma é uma sessão em miniatura da OpenMV IDE em sua própria janela, com um visualizador de frame buffer, um histograma e um terminal interativo, conectados por um transporte de sua escolha. A conexão da janela principal não é afetada, então um terminal independente permite observar uma segunda câmera enquanto a primeira permanece conectada, ou depurar uma câmera do outro lado de uma rede.

Uma janela de terminal independente: terminal interativo à esquerda com sua barra de ferramentas, frame buffer e histograma à direita

Uma janela de terminal independente sobre uma porta serial: o terminal interativo à esquerda com sua barra de ferramentas de executar / parar / reiniciar, o frame buffer e o histograma à direita – eles se acendem quando a câmera transmite quadros embutidos no fluxo.

New Terminal solicita um de três transportes:

  • Serial port – qualquer porta serial em qualquer taxa de transmissão (baud rate) (padrão 115.200). Isso abrange a porta USB de uma segunda câmera, uma câmera ligada por uma ponte USB-para-UART, um enlace serial Bluetooth (que aparece como uma porta serial comum) ou qualquer dispositivo serial não OpenMV que precise de um terminal. Em uma porta USB, a taxa de transmissão (baud rate) não limita a velocidade – os dados sempre se movem na velocidade do enlace USB –, mas na porta USB de uma câmera evite 921.600 e 12.000.000, que alternam a câmera do REPL para o protocolo de depuração da IDE.

  • TCP – conecte-se a um servidor em um host e porta escolhidos, ou escute como um deles em uma porta escolhida.

  • UDP – o mesmo par de papéis, sobre UDP.

A IDE lembra as últimas dez configurações e as lista no submenu Open Terminal para reabertura com um clique; Clear Menu as esquece.

Diferentemente do painel de saída-apenas da janela principal, um terminal independente é totalmente interativo: é um REPL. Digite no prompt e o Python é executado na câmera conectada linha por linha, com histórico e autocompletar por tabulação fornecidos pelo próprio MicroPython. A barra de ferramentas adiciona equivalentes de um clique para as sequências de controle comuns – executar o script atual do editor, parar o script em execução e fazer soft reset – e os mesmos controles de limpar, salvar e quebrar de linha do painel de terminal principal.

O frame buffer acima do terminal também é ao vivo. Quando a câmera conectada transmite quadros comprimidos embutidos no fluxo – inseridos no mesmo fluxo de sua saída de impressão –, o terminal os decodifica e exibe, e os botões Record e Zoom funcionam exatamente como na janela principal. Essa combinação é a proposta de depuração em rede da IDE: uma câmera que expõe seu REPL por Wi-Fi obtém o ciclo completo de editar-executar-pré-visualizar sem nenhum cabo USB envolvido.

13.1.10.1. Transmitindo quadros embutidos no fluxo

A codificação que o terminal entende é simples: um byte 0xFE abre um quadro, um segundo 0xFE o fecha, e cada byte de carga útil entre eles tem seu bit mais alto definido e carrega seis bits da imagem comprimida – três bytes de imagem tornam-se quatro bytes de carga útil. O texto puro nunca usa esses valores de byte, então quadros e a saída de print() compartilham o fluxo sem colidir: o terminal mostra o texto e exibe os quadros.

O script abaixo captura, converte cada quadro para JPEG e o imprime nessa forma. O empacotamento de bits passa pelo ulab (que não possui operadores de deslocamento, então os deslocamentos são escritos como multiplicações e divisões) e é rápido o suficiente para acompanhar a câmera:

import csi
import sys
import time
from ulab import numpy as np

csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.QVGA)

clock = time.clock()


def encode_for_ide(data):
    n = len(data)
    n3 = n - (n % 3)
    m = (n3 // 3) * 4
    out = bytearray(((n * 8) + 5) // 6 + 2)
    out[0] = 0xFE
    out[-1] = 0xFE
    if n3:
        src = np.frombuffer(data, dtype=np.uint8)
        dst = np.frombuffer(out, dtype=np.uint8)
        b0 = src[0:n3:3]
        b1 = src[1:n3:3]
        b2 = src[2:n3:3]
        dst[1:m + 1:4] = (b0 & 0x3F) | 0x80
        dst[2:m + 2:4] = (b0 // 64) | ((b1 & 0x0F) * 4) | 0x80
        dst[3:m + 3:4] = (b1 // 16) | ((b2 & 0x03) * 16) | 0x80
        dst[4:m + 4:4] = (b2 // 4) | 0x80
    if n % 3 == 2:
        x = data[n - 2] | (data[n - 1] << 8)
        out[m + 1] = 0x80 | (x & 0x3F)
        out[m + 2] = 0x80 | ((x >> 6) & 0x3F)
        out[m + 3] = 0x80 | ((x >> 12) & 0x3F)
    elif n % 3 == 1:
        out[m + 1] = 0x80 | (data[n - 1] & 0x3F)
        out[m + 2] = 0x80 | (data[n - 1] >> 6)
    return out


while True:
    clock.tick()
    img = csi0.snapshot().to_jpeg(quality=80)
    sys.stdout.write(encode_for_ide(img.bytearray()))
    print(clock.fps())

Isso funciona em um terminal independente porque a impressão do REPL espera: a câmera bloqueia até que o terminal tenha recebido os dados, então cada byte do quadro chega, e um JPEG se move rapidamente sobre um enlace USB full-speed ou high-speed. O mesmo script funciona sem alterações sobre um terminal TCP, que é o caminho de depuração em rede descrito acima. O único lugar onde ele não funciona é a conexão de depuração principal da IDE, cujo canal de saída se reinicia ao transbordar em vez de esperar – ali o visualizador de frame buffer já mostra os quadros da câmera nativamente, então nada é perdido.