12.2. Les quatre couches

La bibliothèque de protocole est construite comme une pile de quatre couches, chacune résolvant un seul problème et s’appuyant sur la couche inférieure. Le reste du chapitre parcourt la pile de bas en haut.

Une pile verticale de quatre couches étiquetées : le transport en bas (USB ou UART), le tramage au-dessus (en-tête de paquet avec CRC), la fiabilité encore au-dessus (numéros de séquence, ACK / NAK, retransmissions) et les canaux au sommet (flux logiques nommés).

12.2.1. Transport

Tout en bas se trouve le canal d’octets entre la caméra et l’hôte. La bibliothèque de protocole se moque de savoir lequel transporte les octets :

  • USB-CDC sur le port USB sur lequel la caméra est branchée. L’option par défaut et offrant la plus grande bande passante pour toutes les caméras.

  • UART sur une paire de broches GPIO de la caméra connectées à un adaptateur série côté hôte. Utile pour les déploiements sans tête où le port USB est occupé ou n’est pas physiquement accessible.

Le seul rôle du transport est « les octets entrent, les octets sortent, dans l’ordre ». Tout ce qui se trouve au-dessus de cette couche suppose que le transport livre les octets dans l’ordre où ils ont été écrits, mais admet que les octets eux-mêmes puissent être corrompus ou que la liaison puisse être entièrement perdue. Les rafales avec pertes (quelques octets manquants) et les coupures nettes (toute la liaison disparue pendant un moment, puis rétablie) sont toutes deux gérées à un niveau supérieur.

12.2.2. Tramage

La couche suivante impose une structure au flux d’octets. Chaque message devient un paquet – un en-tête de 10 octets suivi d’une charge utile, suivie d’un suffixe de 4 octets. L’en-tête contient :

  • Un mot de synchronisation de 2 octets (0xD5AA) qui permet à un récepteur de retrouver le début d’un paquet après une désynchronisation.

  • Un numéro de séquence de 1 octet utilisé par la couche de fiabilité.

  • Un identifiant de canal de 1 octet qui indique à quel flux logique appartient le paquet.

  • Un champ d’indicateurs de 1 octet pour les bits ACK / NAK / fragment / événement.

  • Un opcode de 1 octet qui distingue les commandes de protocole, les commandes système et les commandes de canal.

  • Une longueur de charge utile de 2 octets.

  • Un CRC de 2 octets calculé sur les huit octets d’en-tête précédents.

La charge utile suit, puis un CRC de 4 octets calculé sur la charge utile elle-même. Les deux CRC détectent la corruption indépendamment : un bit retourné dans l’en-tête invalide le CRC de l’en-tête, et le récepteur peut abandonner le paquet sans jamais avoir besoin de lire la charge utile.

12.2.3. Fiabilité

La couche de fiabilité transforme « des paquets qui pourraient arriver » en « des paquets qui sont arrivés ». Elle suit les numéros de séquence présents dans l’en-tête, demande à l’autre côté d’envoyer des accusés de réception pour chaque paquet qui en requiert un, et retransmet lorsqu’un accusé de réception n’arrive pas dans un délai imparti. Par défaut, le délai de retransmission commence à 500 ms et double à chaque nouvel essai, avec trois tentatives avant d’abandonner.

Chacun de ces comportements est configurable dans l’appel protocol.init() : l’ACK peut être désactivé pour les flux unidirectionnels, la validation CRC peut être ignorée sur des transports parfaitement propres, et les paramètres de retransmission peuvent être ajustés pour des liaisons lentes ou à forte latence.

12.2.4. Canaux

La couche supérieure est ce que voit le code applicatif. Un canal est un flux logique nommé identifié par un identifiant de canal compris entre 0 et 31. Jusqu’à 32 canaux peuvent coexister sur un même transport ; chacun est indépendant des autres, adressé par son identifiant dans l’en-tête de chaque paquet. La caméra démarre avec quatre canaux intégrés – stdin, stdout, stream et profile – et le code applicatif en enregistre d’autres par-dessus en appelant protocol.register() avec une classe Python.

Les quatre couches ne mélangent pas les préoccupations. Le tramage ne connaît rien des canaux ; la fiabilité ne connaît rien du contenu des paquets ; la couche de canaux ne sait pas comment les octets arrivent. Cette séparation explique pourquoi un changement de transport (de l’USB à l’UART, par exemple) ne se répercute pas sur le code des canaux, et c’est ce qui rend le reste du chapitre parcourable une couche à la fois.