13.1.10. Eigenständige Terminalfenster

Tools → Open Terminal öffnet unabhängige Terminalfenster – jedes davon eine Miniatursitzung der OpenMV IDE in einem eigenen Fenster, mit einem Framebuffer-Viewer, einem Histogramm und einem interaktiven Terminal, verbunden über einen Transport Ihrer Wahl. Die Verbindung des Hauptfensters bleibt davon unberührt, sodass Sie mit einem eigenständigen Terminal eine zweite Kamera beobachten können, während die erste verbunden bleibt, oder eine Kamera am anderen Ende eines Netzwerks debuggen können.

Ein eigenständiges Terminalfenster: links das interaktive Terminal mit seiner Symbolleiste, rechts der Framebuffer und das Histogramm

Ein eigenständiges Terminalfenster über einen seriellen Anschluss: links das interaktive Terminal mit seiner Run-/Stop-/Reset-Symbolleiste, rechts der Framebuffer und das Histogramm – sie werden aktiv, wenn die Kamera Einzelbilder im selben Datenstrom (in-band) sendet.

New Terminal fragt nach einem von drei Transporten:

  • Serieller Anschluss – jeder serielle Anschluss mit beliebiger Baudrate (Standard 115.200). Dies deckt den USB-Anschluss einer zweiten Kamera ab, eine über eine USB-zu-UART-Brücke verkabelte Kamera, eine serielle Bluetooth-Verbindung (die als gewöhnlicher serieller Anschluss erscheint) oder jedes serielle Gerät, das kein OpenMV-Gerät ist und ein Terminal benötigt. An einem USB-Anschluss begrenzt die Baudrate die Geschwindigkeit nicht – die Daten bewegen sich stets mit der Geschwindigkeit der USB-Verbindung –, aber am USB-Anschluss einer Kamera sollten Sie 921.600 und 12.000.000 vermeiden, da diese die Kamera vom REPL in das Debug-Protokoll der IDE umschalten.

  • TCP – Verbindung zu einem Server an einem gewählten Host und Port, oder Lauschen als Server an einem gewählten Port.

  • UDP – dasselbe Rollenpaar, über UDP.

Die IDE merkt sich die letzten zehn Konfigurationen und listet sie im Untermenü Open Terminal zum erneuten Öffnen mit einem Klick auf; Clear Menu vergisst sie.

Anders als das reine Ausgabefenster des Hauptfensters ist ein eigenständiges Terminal vollständig interaktiv: Es ist ein REPL. Geben Sie an der Eingabeaufforderung etwas ein, und Python führt es Zeile für Zeile auf der verbundenen Kamera aus, mit Verlauf und Tab-Vervollständigung, die MicroPython selbst bereitstellt. Die Symbolleiste fügt Ein-Klick-Entsprechungen der gängigen Steuersequenzen hinzu – das aktuelle Editor-Skript ausführen, das laufende Skript stoppen und Soft-Reset – sowie dieselben Steuerelemente zum Leeren, Speichern und Umbrechen wie im Haupt-Terminalfenster.

Der Framebuffer über dem Terminal ist ebenfalls live. Wenn die verbundene Kamera komprimierte Einzelbilder im selben Datenstrom (in-band) sendet – eingebettet in denselben Strom wie ihre Druckausgabe –, dekodiert und zeigt das Terminal sie an, und die Schaltflächen Record und Zoom funktionieren genau wie im Hauptfenster. Diese Kombination ist das Netzwerk-Debugging-Konzept der IDE: Eine Kamera, die ihr REPL über Wi-Fi bereitstellt, erhält die vollständige Schleife aus Bearbeiten, Ausführen und Vorschau, ganz ohne USB-Kabel.

13.1.10.1. Einzelbilder im selben Datenstrom (in-band) senden

Die Kodierung, die das Terminal versteht, ist einfach: Ein 0xFE-Byte eröffnet ein Einzelbild, ein zweites 0xFE schließt es, und jedes Nutzdaten-Byte dazwischen hat das oberste Bit gesetzt und trägt sechs Bit des komprimierten Bildes – aus drei Bildbytes werden vier Nutzdaten-Bytes. Reiner Text verwendet diese Bytewerte niemals, sodass Einzelbilder und print()-Ausgabe sich den Datenstrom teilen, ohne zu kollidieren: Das Terminal zeigt den Text an und stellt die Einzelbilder dar.

Das folgende Skript erfasst Bilder, wandelt jedes Einzelbild in JPEG um und gibt es in dieser Form aus. Das Bit-Packing läuft über ulab (das keine Shift-Operatoren besitzt, weshalb die Verschiebungen als Multiplikationen und Divisionen geschrieben sind) und ist schnell genug, um mit der Kamera Schritt zu halten:

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

Dies funktioniert in einem eigenständigen Terminal, weil das REPL-Drucken wartet: Die Kamera blockiert, bis das Terminal die Daten übernommen hat, sodass jedes Byte des Einzelbilds ankommt, und ein JPEG bewegt sich schnell über eine USB-Full-Speed- oder High-Speed-Verbindung. Dasselbe Skript funktioniert unverändert über ein TCP-Terminal, was der oben beschriebene Netzwerk-Debugging-Pfad ist. Die einzige Stelle, an der es nicht funktioniert, ist die Haupt-Debug-Verbindung der IDE, deren Ausgabekanal sich bei Überlauf selbst zurücksetzt, anstatt zu warten – dort zeigt der Framebuffer-Viewer die Einzelbilder der Kamera bereits nativ an, sodass nichts verloren geht.