12.7. Channel callbacks

Het backend-object dat aan protocol.register() wordt doorgegeven, is een Python-klasse. De protocolbibliotheek vraagt de klasse niet welke methoden ze implementeert; ze inspecteert de instantie en koppelt de methoden die ze aantreft. Die introspectie is wat de backend-interface flexibel maakt: de kleinste bruikbare backend bestaat uit twee methoden, de meest uitgebreide uit twaalf, en de applicatie kiest per methode één voor één voor elke mogelijkheid.

12.7.1. De introspectieregels

Wanneer protocol.register() wordt uitgevoerd, loopt de bibliotheek een vaste lijst met aanroepbare namen af en bindt elke naam die ze op de backend-instantie aantreft:

  • Het toevoegen van read aan de klasse zet CHANNEL_FLAG_READ aan. Een host-aanroep van channel_read() bereikt de backend alleen als deze vlag is ingesteld.

  • Het toevoegen van write zet CHANNEL_FLAG_WRITE aan en maakt channel_write() mogelijk.

  • Het toevoegen van lock en unlock zet CHANNEL_FLAG_LOCK aan, waardoor de host het channel kan vergrendelen voor een atomaire lees-actie over meerdere pakketten.

  • Het toevoegen van poll laat de host goedkoop vragen “is er iets klaar?”, zonder een volledige lees-actie af te dwingen.

Ontbrekende methoden zijn geen fouten – de protocolbibliotheek laat de bijbehorende mogelijkheid simpelweg uitgeschakeld. Een backend met alleen size en read is volkomen geldig; het is een alleen-lezen datachannel.

12.7.2. Een alleen-lezen sensor-channel

Een sensor-channel dat elke keer dat de host erom vraagt een verse meting publiceert en host-schrijfacties weigert, gebruikt vier van de callbacks:

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

Wat elke methode doet, stap voor stap:

  • poll retourneert de versheidsvlag. De host roept deze aan vóór het lezen en slaat de lees-actie helemaal over wanneer ze False retourneert. Dat bespaart de heen-en-weerkosten voor “nog geen nieuwe data.”

  • size genereert de buffer op verzoek opnieuw en rapporteert de lengte ervan. Door hier de bemonstering te doen, heeft de backend geen achtergrondtaak nodig – elke host-aanroep stuurt elke meting aan.

  • read retourneert een deel van de buffer. De protocolbibliotheek kan deze meer dan eens aanroepen wanneer de buffer groter is dan de onderhandelde maximale payload; het argument offset loopt door de fragmenten heen.

  • Geen write betekent dat host-schrijfacties worden geweigerd in de framinglaag, voordat de backend erbij betrokken wordt.

12.7.3. De volledige set callbacks

Ter referentie, elke methode waar de bibliotheek naar zoekt op een backend:

Methode

Retourneert

Doel

init(self)

object

Optionele eenmalige initialisatie wanneer het channel voor het eerst aan een host wordt gebonden. Retourneer bij succes een willekeurige waarde die niet None is.

poll(self)

bool

Retourneer True wanneer er data beschikbaar is.

lock(self)

bool

Reserveer het channel voor een atomaire overdracht over meerdere pakketten.

unlock(self)

bool

Geef een eerdere lock vrij.

size(self)

int

Aantal bytes dat momenteel uit het channel kan worden gelezen.

shape(self)

tuple

Tot vier gehele getallen die de datastructuur beschrijven (bijv. hoogte van de afbeelding, breedte, aantal bytes). Wordt door de host gebruikt om getypeerde buffers uit te pakken.

read(self, offset, size)

bytes

Retourneer tot size bytes vanaf offset. Wordt eenmaal per fragment aangeroepen wanneer de payload de onderhandelde maximumwaarde overschrijdt.

readp(self, offset, size)

bytes

Zero-copy-variant van read: het geheugen van de buffer moet geldig blijven voor de duur van de overdracht.

write(self, offset, data)

int

De host heeft data geschreven op offset. data is een bytearray-view in de ontvangstbuffer van de protocollaag – kopieer wat je wilt bewaren naar buiten voordat je terugkeert.

ioctl(self, cmd, length, arg)

int

Door de applicatie gedefinieerde opcode buiten het lees-/schrijfmodel. Een negatieve retourwaarde is een fout.

flush(self)

object

Verwijder alle gebufferde data. Wordt aangeroepen wanneer de host het channel wil resetten.

is_active(self)

bool

Alleen zinvol op backends die een fysiek transport vertegenwoordigen (de ingebouwde USB-channels). Applicatie-channels hebben dit niet nodig.

Dat is de volledige backend-interface. Twaalf methodenamen, allemaal optioneel, en de protocolbibliotheek bepaalt op basis van welke aanwezig zijn wat elk channel kan doen.