13.3.1.4. Canali personalizzati

Un canale è un flusso di byte bidirezionale e con nome tra uno script lato cam e l’host. La cam registra un canale e fornisce callback che producono o consumano dati; l’host legge da e scrive su quel canale tramite il nome. Lo stesso meccanismo che il pacchetto utilizza internamente per il canale stream che trasporta i frame, il canale stdout che trasporta l’output dello script e il canale stdin che trasporta il caricamento dello script è esposto agli script utente, così qualsiasi dato specifico dell’applicazione di cui l’host ha bisogno può viaggiare sulla stessa connessione USB senza inventare un secondo protocollo.

Questa è la funzionalità più utile del pacchetto e quella che la documentazione standard tratta meno bene, quindi questa pagina la illustra dall’inizio alla fine.

13.3.1.4.1. Le due metà

Un canale personalizzato necessita di codice cooperante su entrambi i lati. Lo script lato cam importa protocol, definisce una classe con tre metodi (size(), read(), poll()) più un write() opzionale, e chiama protocol.register(name=..., backend=...) per pubblicare il canale con un nome scelto:

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

Il metodo size() restituisce quanti byte il canale ha attualmente disponibili. read() è il produttore: dati un offset e una size richiesti dall’host, restituisce i byte (o una stringa che il livello di protocollo codifica). poll() restituisce True quando c’è qualcosa da leggere: il livello di protocollo lo usa per contrassegnare il canale come pronto in read_status().

Il programma lato host usa quattro metodi di openmv.Camera: has_channel() per verificare che il canale esista, channel_size() per chiedere quanti dati sono in attesa, channel_read() per estrarre i byte e channel_write() per inviare byte. read_status() interroga tutti i canali contemporaneamente:

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

Il ciclo dell’host interroga read_status(); quando il canale ticks è pronto, chiama channel_read() senza size per estrarre tutto ciò che è disponibile. Il metodo TicksChannel.poll() della cam restituisce True a ogni controllo, quindi il canale è sempre «pronto» e l’host ottiene un nuovo valore di tick a ogni polling.

13.3.1.4.2. Un canale bidirezionale

Per un host che deve rimandare indietro dei dati, la classe lato cam aggiunge un metodo write() che accetta i byte in arrivo:

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

L’host scrive sul canale con channel_write() e rilegge la risposta tramite il consueto schema 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. Cosa ottiene l’applicazione

I canali personalizzati sono lo strumento giusto ogni volta che un’applicazione vuole usare la connessione USB esistente per dati che non sono frame né stampe: contatori di telemetria, manopole di configurazione trasmesse in tempo reale da una UI sull’host, comandi di controllo inviati nell’altra direzione, risultati di una misura calcolata dalla cam che non rientrano nell’inquadratura «immagine» che il canale stream presuppone. Il livello di protocollo gestisce l’inquadramento, la frammentazione, la conferma e i tentativi; lo script deve solo implementare il backend a quattro metodi, e l’host deve solo conoscere il nome del canale e la forma dei dati.

Il flag --channel NAME della CLI è un modo rapido per verificare un canale personalizzato dal terminale senza scrivere un programma lato host: la CLI interroga il canale indicato e stampa i primi dieci byte di ogni aggiornamento.

Il limite di dimensione su una singola chiamata a channel_read() o channel_write() è il max_payload negoziato del protocollo, ovvero 4096 byte per impostazione predefinita. I metodi lato host suddividono automaticamente le scritture più grandi nel giusto numero di pacchetti, così l’applicazione può passare buffer arbitrariamente grandi; la frammentazione è invisibile.