13.1.10. Janelas de terminal independentes

Tools → Open Terminal abre janelas de terminal independentes – cada uma é uma mini-sessão do OpenMV IDE na sua própria janela, com um visualizador de buffer de fotograma, um histograma e um terminal interativo, ligados através de um transporte à sua escolha. A ligação da janela principal não é afetada, pelo que um terminal autónomo permite observar uma segunda câmara enquanto a primeira continua ligada, ou depurar uma câmara num lado distante de uma rede.

A standalone terminal window: interactive terminal on the left with its toolbar, frame buffer and histogram on the right

Uma janela de terminal autónoma sobre uma porta série: o terminal interativo à esquerda com a sua barra de ferramentas executar/parar/reiniciar, o buffer de fotograma e o histograma à direita – acendem-se quando a câmara transmite fotogramas em banda.

New Terminal pede um de três transportes:

  • Porta série – qualquer porta série a qualquer taxa de baud (predefinição 115 200). Isto abrange a porta USB de uma segunda câmara, uma câmara ligada por uma ponte USB para UART, uma ligação série Bluetooth (que aparece como uma porta série normal), ou qualquer dispositivo série não-OpenMV que necessite de um terminal. Numa porta USB, a taxa de baud não limita a velocidade – os dados movem-se sempre à velocidade da ligação USB – mas numa porta USB de uma câmara evite 921 600 e 12 000 000, que fazem a câmara passar do REPL para o protocolo de depuração do IDE.

  • TCP – ligar a um servidor num host e porta à escolha, ou escutar como servidor numa porta à escolha.

  • UDP – o mesmo par de funções, sobre UDP.

O IDE memoriza as últimas dez configurações e lista-as no submenu Open Terminal para reabertura com um clique; Clear Menu apaga-as.

Ao contrário do painel de apenas saída da janela principal, um terminal autónomo é totalmente interativo: é um REPL. Escreva no prompt e o Python executa na câmara ligada linha por linha, com histórico e completação por tabulação fornecidos pelo próprio MicroPython. A barra de ferramentas adiciona equivalentes de um clique para as sequências de controlo mais comuns – executar o script atual do editor, parar o script em execução e reinicialização suave – bem como os mesmos controlos de limpar, guardar e quebra de linha do painel do terminal principal.

O buffer de fotograma acima do terminal também está ativo. Quando a câmara ligada transmite fotogramas comprimidos em banda – incorporados no mesmo fluxo que a sua saída de print – o terminal descodifica-os e apresenta-os, e os botões Record e Zoom funcionam exatamente como na janela principal. Essa combinação é a solução de depuração em rede do IDE: uma câmara que expõe o seu REPL via Wi-Fi obtém o ciclo completo de editar-executar-pré-visualizar sem necessitar de cabo USB.

13.1.10.1. Transmissão de fotogramas em banda

A codificação que o terminal entende é simples: um byte 0xFE abre um fotograma, um segundo 0xFE fecha-o, e cada byte de payload entre eles tem o bit mais significativo a 1 e transporta seis bits da imagem comprimida – três bytes de imagem tornam-se quatro bytes de payload. O texto simples nunca utiliza esses valores de byte, pelo que os fotogramas e a saída de print() partilham o fluxo sem colisões: o terminal mostra o texto e apresenta os fotogramas.

O script abaixo captura, converte cada fotograma para JPEG e imprime-o nessa forma. O empacotamento de bits é feito através do ulab (que não tem operadores de deslocamento, pelo que os deslocamentos são escritos como multiplicações e divisões), e é suficientemente rápido para acompanhar a câmara:

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())

Isto funciona num terminal autónomo porque a impressão do REPL espera: a câmara bloqueia até o terminal ter recebido os dados, garantindo que todos os bytes do fotograma chegam, e um JPEG move-se rapidamente sobre uma ligação USB full-speed ou high-speed. O mesmo script funciona sem alterações num terminal TCP, que é o caminho de depuração em rede acima. O único caso em que não funciona é a ligação de depuração principal do IDE, cujo canal de saída se reinicia em caso de overflow em vez de aguardar – nesse caso, o visualizador de buffer de fotograma já mostra os fotogramas da câmara de forma nativa, pelo que nada se perde.