12.7. Funcții de retroapelare ale canalului

Obiectul backend transmis către protocol.register() este o clasă Python. Biblioteca de protocol nu întreabă clasa ce metode implementează; ea inspectează instanța și conectează metodele pe care le găsește. Această introspecție este cea care face ca interfața backend să fie flexibilă: cel mai mic backend util are două metode, cel mai elaborat are douăsprezece, iar aplicația optează pentru fiecare capabilitate, câte o metodă pe rând.

12.7.1. Regulile de introspecție

Când rulează protocol.register(), biblioteca parcurge o listă fixă de nume apelabile și leagă fiecare nume pe care îl găsește în instanța backend:

  • Adăugarea metodei read la clasă activează CHANNEL_FLAG_READ. Un apel al gazdei către channel_read() ajunge la backend doar dacă acest indicator este setat.

  • Adăugarea metodei write activează CHANNEL_FLAG_WRITE, permițând channel_write().

  • Adăugarea metodelor lock și unlock activează CHANNEL_FLAG_LOCK, permițând gazdei să blocheze canalul pentru o citire atomică pe mai multe pachete.

  • Adăugarea metodei poll permite gazdei să întrebe ieftin „este ceva gata?”, fără a forța o citire completă.

Metodele lipsă nu sunt erori – biblioteca de protocol lasă pur și simplu capabilitatea corespunzătoare dezactivată. Un backend cu doar size și read este perfect valid; este un canal de date doar pentru citire.

12.7.2. Un canal de senzor doar pentru citire

Un canal de senzor care publică o citire proaspătă de fiecare dată când gazda cere și refuză scrierile gazdei utilizează patru dintre funcțiile de retroapelare:

import protocol
import struct

class TempChannel:
    def __init__(self, read_sensor):
        self._read_sensor = read_sensor
        self._buf = b''
        self._fresh = False

    def poll(self):
        # Tell the host whether a reading is waiting.
        return self._fresh

    def size(self):
        # Sample fresh data on every host-side size query.
        value = self._read_sensor()
        self._buf = struct.pack('<f', value)
        self._fresh = True
        return len(self._buf)

    def read(self, offset, size):
        end = offset + size
        if end >= len(self._buf):
            self._fresh = False
        return self._buf[offset:end]

protocol.register(name='temp', backend=TempChannel(read_temperature))

Parcurgând ce face fiecare metodă:

  • poll returnează indicatorul de prospețime. Gazda îl apelează înainte de citire și omite citirea complet atunci când returnează False. Acest lucru economisește costul dus-întors pentru „încă nu există date noi.”

  • size regenerează tamponul (buffer) la cerere și raportează lungimea acestuia. Efectuarea eșantionării aici înseamnă că backendul nu are nevoie de o sarcină de fundal – un apel al gazdei declanșează fiecare măsurătoare.

  • read returnează o secțiune din tampon (buffer). Biblioteca de protocol îl poate apela de mai multe ori atunci când tamponul este mai mare decât sarcina utilă maximă negociată; argumentul offset parcurge fragmentele.

  • Absența metodei write înseamnă că scrierile gazdei sunt refuzate la nivelul de încadrare, înainte ca backendul să fie implicat.

12.7.3. Setul complet de funcții de retroapelare

Pentru referință, fiecare metodă pe care biblioteca o caută într-un backend:

Metodă

Returnează

Scop

init(self)

object

Inițializare opțională unică atunci când canalul se leagă pentru prima dată de o gazdă. Returnează orice valoare diferită de None în caz de succes.

poll(self)

bool

Returnează True când datele sunt disponibile.

lock(self)

bool

Achiziționează canalul pentru un transfer atomic pe mai multe pachete.

unlock(self)

bool

Eliberează un lock anterior.

size(self)

int

Numărul de octeți care pot fi citiți în prezent din canal.

shape(self)

tuple

Până la patru numere întregi care descriu structura datelor (de exemplu, înălțimea imaginii, lățimea, numărul de octeți). Folosit de gazdă pentru a despacheta tampoane tipizate.

read(self, offset, size)

bytes

Returnează până la size octeți începând de la offset. Apelat o dată per fragment atunci când sarcina utilă depășește maximul negociat.

readp(self, offset, size)

bytes

Variantă fără copiere a metodei read: memoria tamponului (buffer) trebuie să rămână validă pe durata transferului.

write(self, offset, data)

int

Gazda a scris data la offset. data este o vizualizare bytearray în tamponul de recepție al nivelului de protocol – copiază ceea ce vrei să păstrezi înainte de a reveni.

ioctl(self, cmd, length, arg)

int

Cod de operație definit de aplicație, în afara modelului de citire/scriere. O valoare returnată negativă este o eroare.

flush(self)

object

Eliberează orice date stocate în tampon (buffer). Apelat atunci când gazda dorește să reseteze canalul.

is_active(self)

bool

Semnificativ doar pe backenduri care reprezintă un transport fizic (canalele USB încorporate). Canalele de aplicație nu au nevoie de acest lucru.

Aceasta este întreaga interfață backend. Douăsprezece nume de metode, toate opționale, iar biblioteca de protocol decide ce poate face fiecare canal pe baza celor care sunt prezente.