13.3.1.4. Canale personalizate

Un canal este un flux de octeți bidirecțional, denumit, între un script de pe cameră și gazdă. Camera înregistrează un canal și furnizează funcții de retroapelare (callback) care produc sau consumă date; gazda citește din și scrie în acel canal după nume. Același mecanism pe care pachetul îl folosește intern pentru canalul stream care transportă cadre, canalul stdout care transportă ieșirea scriptului și canalul stdin care transportă încărcarea scriptului este expus scripturilor utilizatorului, astfel încât orice date specifice aplicației de care gazda are nevoie pot călători pe aceeași conexiune USB fără a inventa un al doilea protocol.

Aceasta este cea mai utilă funcționalitate a pachetului și cea pe care documentația standard o tratează cel mai puțin bine, așa că această pagină o parcurge de la un capăt la altul.

13.3.1.4.1. Cele două jumătăți

Un canal personalizat are nevoie de cod care cooperează pe ambele părți. Scriptul de pe cameră importă protocol, definește o clasă cu trei metode (size(), read(), poll()) plus un write() opțional și apelează protocol.register(name=..., backend=...) pentru a publica canalul sub un nume ales:

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() returnează câți octeți are canalul disponibili în prezent. read() este producătorul: dat fiind un offset și o dimensiune size solicitate de gazdă, returnează octeții (sau un șir pe care stratul de protocol îl codifică). poll() returnează True atunci când există ceva de citit – stratul de protocol folosește acest lucru pentru a marca canalul ca pregătit în read_status().

Programul de pe gazdă folosește patru metode openmv.Camera: has_channel() pentru a verifica existența canalului, channel_size() pentru a întreba câte date așteaptă, channel_read() pentru a extrage octeți și channel_write() pentru a introduce octeți. read_status() interoghează toate canalele deodată:

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

Bucla gazdei interoghează read_status(); când canalul ticks este pregătit, apelează channel_read() fără size pentru a extrage tot ce este disponibil. Metoda TicksChannel.poll() a camerei returnează True la fiecare verificare, așa că acel canal este întotdeauna „pregătit”, iar gazda obține o valoare de tic proaspătă la fiecare interogare.

13.3.1.4.2. Un canal bidirecțional

Pentru o gazdă care are nevoie să trimită date înapoi, clasa de pe cameră adaugă o metodă write() care acceptă octeții de intrare:

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

Gazda scrie în canal cu channel_write() și citește răspunsul înapoi prin tiparul obișnuit 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. Ce obține aplicația din asta

Canalele personalizate sunt instrumentul potrivit ori de câte ori o aplicație dorește să folosească conexiunea USB existentă pentru date care nu sunt cadre și nici tipăriri: contoare de telemetrie, butoane de configurare transmise în direct dintr-o interfață de pe gazdă, comenzi de control trimise în cealaltă direcție, rezultate ale unei măsurători calculate de cameră care nu se încadrează în formatul de „imagine” pe care îl presupune canalul stream. Stratul de protocol gestionează încadrarea, fragmentarea, confirmarea și reîncercarea; scriptul trebuie doar să implementeze backend-ul cu patru metode, iar gazda trebuie doar să cunoască numele canalului și forma datelor.

Indicatorul --channel NAME al interfeței de linie de comandă este o modalitate rapidă de a verifica un canal personalizat din terminal fără a scrie un program pe gazdă: interfața de linie de comandă interoghează canalul numit și afișează primii zece octeți ai fiecărei actualizări.

Limita de dimensiune pentru un singur apel channel_read() sau channel_write() este valoarea max_payload negociată de protocol – 4096 de octeți în mod implicit. Metodele de pe gazdă împart automat scrierile mai mari în numărul corect de pachete, astfel încât aplicația poate transmite tampoane de dimensiuni arbitrar de mari; fragmentarea este invizibilă.