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á.