13.3.1.4. Egna kanaler

En kanal är en namngiven, dubbelriktad byteström mellan ett skript på kamerasidan och värden. Kameran registrerar en kanal och tillhandahåller återanrop som producerar eller konsumerar data; värden läser från och skriver till den kanalen via namn. Samma mekanism som paketet använder internt för stream-kanalen som bär bildrutor, stdout-kanalen som bär skriptutdata och stdin-kanalen som bär skriptuppladdning exponeras för användarskript, så att all applikationsspecifik data som värden behöver kan färdas på samma USB-anslutning utan att man uppfinner ett andra protokoll.

Detta är paketets mest användbara funktion och den som standarddokumentationen täcker sämst, så den här sidan går igenom den från början till slut.

13.3.1.4.1. De två halvorna

En egen kanal kräver samverkande kod på båda sidor. Skriptet på kamerasidan importerar protocol, definierar en klass med tre metoder (size(), read(), poll()) plus en valfri write(), och anropar protocol.register(name=..., backend=...) för att publicera kanalen under ett valt namn:

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

size()-metoden returnerar hur många byte kanalen för närvarande har tillgängliga. read() är producenten: givet en offset och size som begärts av värden returnerar den byten (eller en sträng som protokolllagret kodar). poll() returnerar True när det finns något att läsa – protokolllagret använder detta för att flagga kanalen som redo i read_status().

Programmet på värdsidan använder fyra openmv.Camera-metoder: has_channel() för att kontrollera att kanalen finns, channel_size() för att fråga hur mycket data som väntar, channel_read() för att hämta ut byte, och channel_write() för att skicka in byte. read_status() pollar alla kanaler samtidigt:

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

Värdslingan pollar read_status(); när ticks-kanalen är redo anropar den channel_read() utan size för att hämta vad som än finns tillgängligt. Kamerans TicksChannel.poll() returnerar True vid varje kontroll, så kanalen är alltid ”redo” och värden får ett färskt tick-värde vid varje pollning.

13.3.1.4.2. En dubbelriktad kanal

För en värd som behöver skicka data tillbaka lägger klassen på kamerasidan till en write()-metod som tar emot de inkommande byten:

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

Värden skriver till kanalen med channel_write() och läser tillbaka svaret genom det vanliga mönstret 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.3.1.4.3. Vad detta ger applikationen

Egna kanaler är rätt verktyg närhelst en applikation vill använda den befintliga USB-anslutningen för data som varken är bildrutor eller utskrifter: telemetriräknare, konfigurationsrattar som strömmas live från ett gränssnitt på värden, styrkommandon som skickas åt andra hållet, eller resultat av en mätning som kameran beräknat och som inte passar in i den ”bild”-inramning som stream-kanalen förutsätter. Protokolllagret hanterar inramning, fragmentering, bekräftelse och återförsök; skriptet behöver bara implementera fyrametoders-backenden, och värden behöver bara känna till kanalnamnet och dataformatet.

CLI:ts --channel NAME-flagga är ett snabbt sätt att verifiera en egen kanal från terminalen utan att skriva ett program på värdsidan: CLI:t pollar den namngivna kanalen och skriver ut de första tio byten av varje uppdatering.

Storleksgränsen för ett enskilt anrop till channel_read() eller channel_write() är protokollets förhandlade max_payload – 4096 byte som standard. Metoderna på värdsidan delar automatiskt upp större skrivningar i rätt antal paket, så att applikationen kan skicka godtyckligt stora buffertar; fragmenteringen är osynlig.