13.4.1.4. Vlastní kanály#
Kanál je pojmenovaný obousměrný bajtový stream mezi skriptem na straně kamery a hostitelem. Kamera kanál registruje a poskytuje callbacky, které data produkují nebo spotřebovávají; hostitel z tohoto kanálu čte a do něj zapisuje podle jména. Tentýž mechanismus, který balíček interně používá pro kanál stream přenášející snímky, kanál stdout přenášející výstup skriptu a kanál stdin přenášející nahrávání skriptu, je vystaven i uživatelským skriptům, takže jakákoli data specifická pro aplikaci, která hostitel potřebuje, mohou jezdit po témže USB připojení bez vymýšlení druhého protokolu.
Je to nejužitečnější funkce balíčku a zároveň ta, kterou standardní dokumentace pokrývá nejhůře, takže tato stránka ji prochází od začátku do konce.
13.4.1.4.1. Dvě poloviny#
Vlastní kanál potřebuje spolupracující kód na obou stranách. Skript na straně kamery importuje protocol, definuje třídu se třemi metodami (size(), read(), poll()) a volitelnou write() a volá protocol.register(name=..., backend=...) pro publikování kanálu pod zvoleným jménem:
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())
Metoda size() vrací, kolik bajtů má kanál aktuálně k dispozici. read() je producent: dostane offset a size požadované hostitelem a vrátí bajty (nebo řetězec, který protokolová vrstva zakóduje). poll() vrací True, když je k dispozici něco ke čtení – protokolová vrstva to používá k označení kanálu jako připraveného v read_status().
Program na straně hostitele používá čtyři metody třídy openmv.Camera: has_channel() pro kontrolu existence kanálu, channel_size() pro dotaz, kolik dat čeká, channel_read() pro vytažení bajtů ven a channel_write() pro vložení bajtů dovnitř. read_status() dotazuje všechny kanály najednou:
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()}")
Hostitelská smyčka dotazuje read_status(); když je kanál ticks připraven, zavolá channel_read() bez size, aby vytáhla cokoli je k dispozici. Metoda TicksChannel.poll() kamery vrací True při každé kontrole, takže kanál je vždy „připraven“ a hostitel získá čerstvou hodnotu ticku při každém dotazu.
13.4.1.4.2. Obousměrný kanál#
Pro hostitele, který potřebuje posílat data zpět, přidá třída na straně kamery metodu write(), která přijímá příchozí bajty:
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())
Hostitel zapisuje do kanálu pomocí channel_write() a čte odpověď zpět obvyklým vzorem read_status() / channel_read()
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.4.1.4.3. Co tím aplikace získá#
Vlastní kanály jsou ten správný nástroj, kdykoli chce aplikace využít stávající USB připojení pro data, která nejsou snímky ani výpisy: telemetrické čítače, konfigurační nastavení streamovaná živě z uživatelského rozhraní na hostiteli, řídicí příkazy posílané opačným směrem, výsledky měření, které kamera vypočítala a které se nehodí do „obrazového“ rámcování předpokládaného kanálem stream. Protokolová vrstva se stará o rámcování, fragmentaci, potvrzování a opakování; skript musí pouze implementovat backend se čtyřmi metodami a hostitel potřebuje znát jen jméno kanálu a tvar dat.
Příznak --channel NAME v CLI je rychlý způsob, jak ověřit vlastní kanál z terminálu bez psaní programu na straně hostitele: CLI dotazuje pojmenovaný kanál a vypisuje prvních deset bajtů každé aktualizace.
Limit velikosti jednoho volání channel_read() nebo channel_write() je protokolem vyjednaná hodnota max_payload – výchozí 4096 bajtů. Metody na straně hostitele automaticky rozdělí větší zápisy na správný počet paketů, takže aplikace může předávat libovolně velké buffery; fragmentace je neviditelná.