12.7. Fonctions de rappel de canal

L’objet backend transmis à protocol.register() est une classe Python. La bibliothèque de protocole ne demande pas à la classe quelles méthodes elle implémente ; elle inspecte l’instance et connecte celles qu’elle trouve. Cette introspection est ce qui rend l’interface du backend flexible : le plus petit backend utile compte deux méthodes, le plus élaboré en compte douze, et l’application active chaque capacité une méthode à la fois.

12.7.1. Les règles d’introspection

Lorsque protocol.register() s’exécute, la bibliothèque parcourt une liste fixe de noms appelables et lie chacun de ceux qu’elle trouve sur l’instance du backend :

  • Ajouter read à la classe active CHANNEL_FLAG_READ. Un appel hôte à channel_read() n’atteint le backend que si cet indicateur est défini.

  • Ajouter write active CHANNEL_FLAG_WRITE, ce qui rend possible channel_write().

  • Ajouter lock et unlock active CHANNEL_FLAG_LOCK, permettant à l’hôte de verrouiller le canal pour une lecture atomique sur plusieurs paquets.

  • Ajouter poll permet à l’hôte de demander à moindre coût « quelque chose est-il prêt ? », sans forcer une lecture complète.

Les méthodes manquantes ne sont pas des erreurs – la bibliothèque de protocole laisse simplement la capacité correspondante désactivée. Un backend doté uniquement de size et read est parfaitement valide ; c’est un canal de données en lecture seule.

12.7.2. Un canal de capteur en lecture seule

Un canal de capteur qui publie une nouvelle lecture chaque fois que l’hôte la demande, en refusant les écritures de l’hôte, met en œuvre quatre des fonctions de rappel

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

Passons en revue ce que fait chaque méthode :

  • poll renvoie l’indicateur de fraîcheur. L’hôte l’appelle avant de lire et saute entièrement la lecture lorsqu’il renvoie False. Cela évite le coût de l’aller-retour pour « pas encore de nouvelles données ».

  • size régénère le tampon à la demande et indique sa longueur. Réaliser l’échantillonnage ici signifie que le backend n’a pas besoin d’une tâche en arrière-plan – un appel hôte pilote chaque mesure.

  • read renvoie une portion du tampon. La bibliothèque de protocole peut l’appeler plusieurs fois lorsque le tampon est plus grand que la charge utile maximale négociée ; l’argument offset parcourt les fragments.

  • L’absence de write signifie que les écritures de l’hôte sont refusées au niveau de la couche de tramage, avant même que le backend ne soit impliqué.

12.7.3. L’ensemble complet des fonctions de rappel

Pour référence, voici toutes les méthodes que la bibliothèque recherche sur un backend :

Méthode

Renvoie

Objet

init(self)

objet

Initialisation facultative en une seule fois lorsque le canal se lie pour la première fois à un hôte. Renvoie toute valeur non None en cas de succès.

poll(self)

bool

Renvoie True lorsque des données sont disponibles.

lock(self)

bool

Acquiert le canal pour un transfert atomique sur plusieurs paquets.

unlock(self)

bool

Libère un lock antérieur.

size(self)

int

Nombre d’octets actuellement lisibles depuis le canal.

shape(self)

tuple

Jusqu’à quatre entiers décrivant la structure des données (par exemple hauteur d’image, largeur, nombre d’octets). Utilisé par l’hôte pour décompresser les tampons typés.

read(self, offset, size)

bytes

Renvoie jusqu’à size octets à partir de offset. Appelé une fois par fragment lorsque la charge utile dépasse le maximum négocié.

readp(self, offset, size)

bytes

Variante sans copie de read : la mémoire du tampon doit rester valide pendant toute la durée du transfert.

write(self, offset, data)

int

L’hôte a écrit data à l’emplacement offset. data est une vue bytearray sur le tampon de réception de la couche de protocole – copiez ce que vous souhaitez conserver avant de renvoyer.

ioctl(self, cmd, length, arg)

int

Opcode défini par l’application en dehors du modèle lecture/écriture. Une valeur de retour négative indique une erreur.

flush(self)

objet

Supprime toutes les données mises en tampon. Appelé lorsque l’hôte souhaite réinitialiser le canal.

is_active(self)

bool

N’a de sens que sur les backends qui représentent un transport physique (les canaux USB intégrés). Les canaux d’application n’en ont pas besoin.

C’est là toute l’interface du backend. Douze noms de méthodes, toutes facultatives, et la bibliothèque de protocole décide de ce que chaque canal peut faire en fonction de celles qui sont présentes.