13.3.1.4. Benutzerdefinierte Kanäle¶
Ein Kanal ist ein benannter, bidirektionaler Byte-Stream zwischen einem kameraseitigen Skript und dem Host. Die Kamera registriert einen Kanal und stellt Callbacks bereit, die Daten erzeugen oder verbrauchen; der Host liest aus diesem Kanal und schreibt in ihn anhand des Namens. Derselbe Mechanismus, den das Paket intern für den stream-Kanal (der Einzelbilder transportiert), den stdout-Kanal (der die Skriptausgabe transportiert) und den stdin-Kanal (der den Skript-Upload transportiert) verwendet, steht Benutzerskripten zur Verfügung. So können beliebige anwendungsspezifische Daten, die der Host benötigt, dieselbe USB-Verbindung nutzen, ohne dass ein zweites Protokoll erfunden werden muss.
Dies ist das nützlichste Feature des Pakets und gleichzeitig dasjenige, das die Standarddokumentation am schlechtesten abdeckt. Daher arbeitet diese Seite es von Anfang bis Ende durch.
13.3.1.4.1. Die beiden Hälften¶
Ein benutzerdefinierter Kanal benötigt zusammenarbeitenden Code auf beiden Seiten. Das kameraseitige Skript importiert protocol, definiert eine Klasse mit drei Methoden (size(), read(), poll()) sowie einer optionalen write()-Methode und ruft protocol.register(name=..., backend=...) auf, um den Kanal unter einem gewählten Namen zu veröffentlichen:
import protocol
import time
class TicksChannel:
def size(self):
return 10
def read(self, offset, size):
return f'{time.ticks_ms():010d}'
def poll(self):
return True
protocol.register(name='ticks', backend=TicksChannel())
Die size()-Methode gibt zurück, wie viele Bytes der Kanal aktuell zur Verfügung hat. read() ist der Produzent: Bei einem vom Host angeforderten offset und size gibt sie die Bytes zurück (oder eine Zeichenkette, die die Protokollschicht kodiert). poll() gibt True zurück, wenn es etwas zu lesen gibt – die Protokollschicht verwendet dies, um den Kanal in read_status() als bereit zu kennzeichnen.
Das hostseitige Programm verwendet vier openmv.Camera-Methoden: has_channel(), um zu prüfen, ob der Kanal existiert, channel_size(), um abzufragen, wie viele Daten warten, channel_read(), um Bytes herauszuziehen, und channel_write(), um Bytes hineinzuschieben. read_status() fragt alle Kanäle auf einmal ab:
from openmv import Camera
with Camera('/dev/ttyACM0') as cam:
cam.stop()
cam.exec(open('ticks_cam.py').read())
while True:
status = cam.read_status()
if status.get('ticks'):
data = cam.channel_read('ticks')
print(f"ticks: {data.decode()}")
Die Host-Schleife fragt read_status() ab; wenn der ticks-Kanal bereit ist, ruft sie channel_read() ohne size auf, um alles abzuholen, was verfügbar ist. Die TicksChannel.poll()-Methode der Kamera gibt bei jeder Prüfung True zurück, sodass der Kanal immer „bereit“ ist und der Host bei jeder Abfrage einen frischen Tick-Wert erhält.
13.3.1.4.2. Ein bidirektionaler Kanal¶
Für einen Host, der Daten zurückschieben muss, fügt die kameraseitige Klasse eine write()-Methode hinzu, die die eingehenden Bytes annimmt:
import protocol
class CommandChannel:
def __init__(self):
self.last_command = b''
self.replied = False
def size(self):
return len(self.last_command)
def read(self, offset, size):
self.replied = True
return self.last_command
def write(self, offset, data):
self.last_command = b'echo: ' + bytes(data)
self.replied = False
def poll(self):
return not self.replied and len(self.last_command) > 0
protocol.register(name='echo', backend=CommandChannel())
Der Host schreibt mit channel_write() in den Kanal und liest die Antwort über das übliche Muster aus read_status() / channel_read() zurück:
with Camera('/dev/ttyACM0') as cam:
cam.stop()
cam.exec(open('echo_cam.py').read())
cam.channel_write('echo', b'hello')
while True:
if cam.read_status().get('echo'):
print(cam.channel_read('echo').decode())
break
13.3.1.4.3. Was die Anwendung dadurch gewinnt¶
Benutzerdefinierte Kanäle sind das richtige Werkzeug, wann immer eine Anwendung die bestehende USB-Verbindung für Daten nutzen möchte, die weder Einzelbilder noch Ausgaben sind: Telemetriezähler, Konfigurationsregler, die live von einer Benutzeroberfläche auf dem Host gestreamt werden, Steuerbefehle, die in die andere Richtung gesendet werden, Ergebnisse einer von der Kamera berechneten Messung, die nicht in das „Bild“-Format passen, das der Stream-Kanal voraussetzt. Die Protokollschicht kümmert sich um Framing, Fragmentierung, Bestätigung und Wiederholung; das Skript muss nur das Vier-Methoden-Backend implementieren, und der Host muss nur den Kanalnamen und die Datenform kennen.
Das --channel NAME-Flag des CLI ist ein schneller Weg, um einen benutzerdefinierten Kanal vom Terminal aus zu überprüfen, ohne ein hostseitiges Programm zu schreiben: Das CLI fragt den benannten Kanal ab und gibt die ersten zehn Bytes jeder Aktualisierung aus.
Die Größenbeschränkung für einen einzelnen Aufruf von channel_read() oder channel_write() ist das vom Protokoll ausgehandelte max_payload – standardmäßig 4096 Bytes. Die hostseitigen Methoden teilen größere Schreibvorgänge automatisch in die richtige Anzahl von Paketen auf, sodass die Anwendung beliebig große Puffer übergeben kann; die Fragmentierung ist unsichtbar.