12.6. Canale denumite

ID-ul canalului din antetul fiecărui pachet permite ca până la 32 de fluxuri independente să partajeze același transport fizic. Nivelul de canal transformă acele ID-uri numerice în puncte finale denumite, vizibile pentru aplicație, la care codul gazdei se poate referi prin șir de caractere.

Un fir de transport în stânga care se ramifică în patru canale etichetate pe partea camerei -- stdin, stdout, stream și un canal de stare înregistrat de utilizator -- fiecare apărând ca o casetă independentă.

12.6.1. Cele patru canale încorporate

Camera înregistrează patru canale la pornire, înainte ca orice cod de aplicație să ruleze:

  • stdin – octeții de script pe care gazda îi trimite camerei pentru execuție. IDE-ul folosește acest canal pentru a trimite scriptul care se editează; exec() din SDK-ul gazdei este apelul echivalent dintr-un program Python.

  • stdout – octeții proveniți din apelurile print() ale camerei și din traseele de stivă ale excepțiilor necapturate. Consola serială a IDE-ului citește acest canal.

  • stream – canalul de previzualizare în timp real. IDE-ul extrage cadre JPEG din el; orice script gazdă poate face același lucru cu read_frame().

  • profile – evenimente de profilare, prezente doar atunci când camera a fost compilată cu profilarea activată. Majoritatea versiunilor de lansare îl omit.

Codul de aplicație rareori are nevoie să atingă vreunul dintre canalele încorporate; lucrul interesant se întâmplă pe canalele pe care aplicația le înregistrează ea însăși.

12.6.2. Înregistrarea unui canal

Un script de pe partea camerei înregistrează un canal nou apelând protocol.register() cu un nume și un obiect backend Python:

import json
import protocol
import time

trigger_count = 0

class StatusChannel:
    def size(self):
        # Refresh the snapshot on every host query.
        self._buf = json.dumps({
            'uptime_s': time.ticks_ms() // 1000,
            'triggers': trigger_count,
        }).encode()
        return len(self._buf)

    def read(self, offset, size):
        return self._buf[offset:offset + size]

protocol.register(name='status', backend=StatusChannel())

Metodele obiectului backend decid ce poate face canalul. Un backend cu doar size și read este un canal de date doar pentru citire; adaugă write și devine bidirecțional; adaugă poll și gazda poate întreba dacă sunt gata date noi înainte de a plăti pentru o citire. Eșantionarea datelor în interiorul metodei size este cel mai simplu tipar atunci când sarcina utilă este suficient de mică pentru a încăpea într-un singur fragment – tamponul (buffer) este generat la cerere, niciodată memorat în cache, niciodată supus unei condiții de cursă. Sarcinile utile mai mari – cadre de imagine, urme de senzor – au nevoie de un tipar de blocare care reține tamponul până când gazda își termină citirea pe mai multe fragmente, tratat la canalul de cadre.

O cantitate mică de evidență contabilă se întâmplă automat:

  • Biblioteca atribuie următorul ID de canal liber (între 0 și 31).

  • Indicatorii de capabilitate sunt derivați din metodele prezente: CHANNEL_FLAG_READ dacă este definit read, CHANNEL_FLAG_WRITE dacă este definit write, CHANNEL_FLAG_LOCK dacă sunt definite lock / unlock.

  • Un pachet de eveniment CHANNEL_REGISTERED este trimis oricărei gazde conectate, astfel încât lista sa de canale se actualizează.

Valoarea returnată este un descriptor protocol.ProtocolChannel pe care aplicația îl poate păstra. Metoda send_event() a descriptorului este cârligul de pe partea camerei pentru a anunța gazda că „s-a întâmplat ceva pe acest canal fără a schimba datele citibile” – s-a declanșat un eveniment, s-a apăsat un buton, s-a depășit un prag de număr de eșantioane.

12.6.3. Citirea canalelor de la gazdă

SDK-ul gazdei este livrat ca pachetul openmv pe PyPI (pip install openmv), construit pe pyserial pentru transport. Clasa sa openmv.camera.Camera expune canalele denumite ale camerei prin metode de nivel înalt:

from openmv.camera import Camera

with Camera('/dev/ttyACM0', baudrate=921600) as cam:
    cam.update_channels()
    if cam.has_channel('status'):
        size = cam.channel_size('status')
        data = cam.channel_read('status', size)

Atenționare

Pachetul openmv necesită CPython 3.12 sau mai nou. Interpretoarele anterioare nu au caracteristici de care depinde SDK-ul; instalează o versiune 3.12+ înainte de pip install openmv.

Câteva lucruri de remarcat despre configurare:

  • Șirul portului serial – aici /dev/ttyACM0 – este de tip COM3 pe Windows, /dev/cu.usbmodemXXXX pe macOS și /dev/ttyACM* pe Linux. Numărul efectiv depinde de portul pe care camera l-a enumerat.

  • Rata baud este valoarea magică a protocolului 921600, pe care stiva USB-CDC a camerei o recunoaște ca „acest client vorbește protocolul, nu REPL-ul.” Orice altă rată revine la o linie serială simplă.

  • Managerul de context with Camera(...) as cam: deschide transportul, rulează PROTO_SYNC, schimbă capabilitățile și, la ieșire, închide portul curat. Apelul explicit update_channels() după intrare reîmprospătează lista locală de canale cu orice canale pe care aplicația le-a înregistrat după pornire.

channel_size() și channel_read() sunt metodele de bază; channel_write() transferă dus-întors un tampon (buffer) către cameră dacă backendul are o metodă write; has_channel() este modul sigur de a verifica dacă un nume este înregistrat înainte de a-l folosi. Numele canalului este căutat o singură dată pentru a obține ID-ul de canal pe care camera l-a atribuit în timpul register și folosit în fiecare pachet de atunci înainte.

Fiecare pereche channel_size() / channel_read() costă două drumuri dus-întors: un pachet pentru a cere dimensiunea, unul pentru a cere octeții. Peste USB-CDC ambele se termină în aproximativ o milisecundă combinat; peste UART același schimb durează mai mult, proporțional cu rata baud a liniei seriale. Codul de aplicație care citește într-o buclă strânsă ar trebui să apeleze channel_size() doar atunci când dimensiunea se poate schimba efectiv – pentru datele de dimensiune fixă, dimensiunea obținută din primul apel poate fi memorată în cache.

12.6.4. Independența între canale

Trei lucruri merită știute despre modul în care interacționează canalele:

  • Control de flux independent. Fiecare canal are propria stare de citire în așteptare, propriile date și propriile funcții de retroapelare size / read / write. O citire de lungă durată pe canalul stream nu blochează citirile pe canalul config al aplicației.

  • Secvențial per canal. În cadrul unui singur canal, pachetele sunt livrate în ordine. Nivelul de fiabilitate garantează acest lucru chiar și atunci când sunt implicate retransmisii.

  • Transport partajat, buget de retransmisie partajat. Toate canalele partajează aceeași legătură fizică, așa că un torent de trafic pe un canal le încetinește pe celelalte acaparând firul. Mecanismul CHANNEL_LOCK permite unui canal să rezerve firul pentru o citire atomică pe mai multe pachete; backendul optează pentru aceasta implementând funcțiile de retroapelare lock / unlock.

Un canal este suprafața minimă pe care un program gazdă și un program de cameră convin să coopereze. Numele, direcționalitatea (citire sau scriere sau ambele), metodele de retroapelare de pe partea camerei și apelurile de metodă corespunzătoare de pe partea gazdei reprezintă întregul contract.