13.1.10. Finestre di terminale autonome

Tools → Open Terminal apre finestre di terminale indipendenti – ciascuna una sessione in miniatura di OpenMV IDE nella propria finestra, con un visualizzatore del frame buffer, un istogramma e un terminale interattivo, connessa tramite un trasporto a tua scelta. La connessione della finestra principale non viene influenzata, quindi un terminale autonomo ti consente di osservare una seconda camera mentre la prima rimane connessa, oppure di eseguire il debug di una camera dall’altra parte di una rete.

Una finestra di terminale autonoma: il terminale interattivo a sinistra con la sua barra degli strumenti, il frame buffer e l'istogramma a destra

Una finestra di terminale autonoma su una porta seriale: il terminale interattivo a sinistra con la sua barra degli strumenti run / stop / reset, il frame buffer e l’istogramma a destra – si attivano quando la camera trasmette i frame in-band.

New Terminal richiede uno di tre trasporti:

  • Serial port – qualsiasi porta seriale a qualsiasi baud rate (predefinito 115.200). Questo copre la porta USB di una seconda camera, una camera collegata tramite un ponte USB-to-UART, un collegamento seriale Bluetooth (che appare come una normale porta seriale), o qualsiasi dispositivo seriale non OpenMV che necessiti di un terminale. Su una porta USB il baud rate non limita la velocità – i dati si spostano sempre alla velocità del collegamento USB – ma sulla porta USB di una camera evita 921.600 e 12.000.000, che commutano la camera dal REPL al protocollo di debug dell’IDE.

  • TCP – connettiti a un server su un host e una porta scelti, oppure rimani in ascolto come server su una porta scelta.

  • UDP – la stessa coppia di ruoli, tramite UDP.

L’IDE ricorda le ultime dieci configurazioni e le elenca nel sottomenu Open Terminal per riaprirle con un clic; Clear Menu le dimentica.

A differenza del riquadro di sola uscita della finestra principale, un terminale autonomo è completamente interattivo: è un REPL. Digita al prompt e Python esegue sulla camera connessa riga per riga, con cronologia e completamento con tab forniti da MicroPython stesso. La barra degli strumenti aggiunge equivalenti con un solo clic delle sequenze di controllo comuni – esegui lo script corrente dell’editor, ferma lo script in esecuzione e soft reset – e gli stessi controlli di clear, save e wrap del riquadro del terminale principale.

Anche il frame buffer sopra il terminale è in tempo reale. Quando la camera connessa trasmette frame compressi in-band – incorporati nello stesso stream del suo output di stampa – il terminale li decodifica e li visualizza, e i pulsanti Record e Zoom funzionano esattamente come nella finestra principale. Questa combinazione è la soluzione di debug di rete dell’IDE: una camera che espone il proprio REPL tramite Wi-Fi ottiene il ciclo completo di modifica-esecuzione-anteprima senza alcun cavo USB coinvolto.

13.1.10.1. Trasmissione di frame in-band

La codifica che il terminale comprende è semplice: un byte 0xFE apre un frame, un secondo 0xFE lo chiude, e ogni byte di payload tra di essi ha il bit più alto impostato e trasporta sei bit dell’immagine compressa – tre byte di immagine diventano quattro byte di payload. Il testo normale non usa mai quei valori di byte, quindi i frame e l’output di print() condividono lo stream senza collidere: il terminale mostra il testo e visualizza i frame.

Lo script seguente cattura, converte ogni frame in JPEG e lo stampa in quella forma. L’impacchettamento dei bit avviene tramite ulab (che non dispone di operatori di shift, quindi gli shift sono scritti come moltiplicazioni e divisioni), ed è abbastanza veloce da tenere il passo con la camera:

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

Questo funziona in un terminale autonomo perché la stampa nel REPL attende: la camera si blocca finché il terminale non ha preso i dati, quindi ogni byte del frame arriva, e un JPEG si sposta rapidamente su un collegamento USB full-speed o high-speed. Lo stesso script funziona senza modifiche tramite un terminale TCP, che è il percorso di debug di rete descritto sopra. L’unico caso in cui non funziona è la connessione di debug principale dell’IDE, il cui canale di uscita si resetta da solo in caso di overflow invece di attendere – lì il visualizzatore del frame buffer mostra già i frame della camera nativamente, quindi nulla viene perso.