13.1.10. Op zichzelf staande terminalvensters

Tools → Open Terminal opent onafhankelijke terminalvensters – elk een miniatuur OpenMV IDE-sessie in een eigen venster, met een framebuffer-viewer, een histogram en een interactieve terminal, verbonden via een transport naar keuze. De verbinding van het hoofdvenster blijft ongemoeid, dus met een op zichzelf staande terminal kun je een tweede camera bekijken terwijl de eerste verbonden blijft, of een camera aan de andere kant van een netwerk debuggen.

Een op zichzelf staand terminalvenster: links de interactieve terminal met zijn werkbalk, rechts de framebuffer en het histogram

Een op zichzelf staand terminalvenster over een seriële poort: links de interactieve terminal met zijn run-/stop-/reset-werkbalk, rechts de framebuffer en het histogram – ze lichten op wanneer de camera frames in-band streamt.

New Terminal vraagt om een van drie transporten:

  • Serial port – elke seriële poort op elke baudrate (standaard 115.200). Dit dekt de USB-poort van een tweede camera, een camera die is aangesloten via een USB-naar-UART-bridge, een Bluetooth seriële verbinding (die als een gewone seriële poort verschijnt), of elk niet-OpenMV serieel apparaat dat een terminal nodig heeft. Op een USB-poort beperkt de baudrate de snelheid niet – gegevens bewegen altijd op de snelheid van de USB-verbinding – maar vermijd op de USB-poort van een camera 921.600 en 12.000.000, die de camera van de REPL overschakelen naar het debugprotocol van de IDE.

  • TCP – verbind met een server op een gekozen host en poort, of luister er zelf als een op een gekozen poort.

  • UDP – hetzelfde paar rollen, maar via UDP.

De IDE onthoudt de laatste tien configuraties en toont ze in het submenu Open Terminal voor heropenen met één klik; Clear Menu vergeet ze.

Anders dan het alleen-uitvoer-paneel van het hoofdvenster is een op zichzelf staande terminal volledig interactief: het is een REPL. Typ op de prompt en Python voert regel voor regel uit op de verbonden camera, met geschiedenis en tab-aanvulling die door MicroPython zelf worden geleverd. De werkbalk voegt equivalenten met één klik toe van de gangbare besturingssequenties – het huidige editorscript uitvoeren, het draaiende script stoppen en soft reset – en dezelfde wis-, opslaan- en wrap-besturingen als het hoofdterminal-paneel.

De framebuffer boven de terminal is ook live. Wanneer de verbonden camera gecomprimeerde frames in-band streamt – ingebed in dezelfde stream als zijn print-uitvoer – decodeert en toont de terminal ze, en de knoppen Record en Zoom werken precies zoals in het hoofdvenster. Die combinatie vormt het netwerk-debugverhaal van de IDE: een camera die zijn REPL via Wi-Fi blootstelt, krijgt de volledige bewerk-uitvoer-voorbeeld-lus zonder dat er een USB-kabel aan te pas komt.

13.1.10.1. Frames in-band streamen

De codering die de terminal begrijpt is eenvoudig: een 0xFE-byte opent een frame, een tweede 0xFE sluit het, en elke payload-byte ertussen heeft zijn hoogste bit gezet en draagt zes bits van de gecomprimeerde afbeelding – drie afbeeldingsbytes worden vier payload-bytes. Platte tekst gebruikt die bytewaarden nooit, dus frames en print()-uitvoer delen de stream zonder te botsen: de terminal toont de tekst en geeft de frames weer.

Het onderstaande script maakt een opname, converteert elk frame naar JPEG en print het in die vorm. De bit-packing verloopt via ulab (dat geen shift-operatoren heeft, dus de shifts zijn geschreven als vermenigvuldigingen en delingen) en is snel genoeg om de camera bij te houden:

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

Dit werkt in een op zichzelf staande terminal omdat het REPL-printen wacht: de camera blokkeert totdat de terminal de gegevens heeft opgehaald, zodat elke byte van het frame aankomt, en een JPEG beweegt snel over een USB full-speed- of high-speed-verbinding. Hetzelfde script werkt ongewijzigd over een TCP-terminal, wat het netwerk-debugpad hierboven is. De enige plek waar het niet werkt is de hoofd-debugverbinding van de IDE, waarvan het uitvoerkanaal zichzelf bij overflow reset in plaats van te wachten – daar toont de framebuffer-viewer de frames van de camera al native, dus er gaat niets verloren.