13.3.1.4. Canaux personnalisés¶
Un canal est un flux d’octets nommé et bidirectionnel entre un script côté caméra et l’hôte. La caméra enregistre un canal et fournit des fonctions de rappel qui produisent ou consomment des données ; l’hôte lit et écrit sur ce canal par son nom. Le même mécanisme que le paquet utilise en interne pour le canal stream qui transporte les trames, le canal stdout qui transporte la sortie du script et le canal stdin qui transporte le téléversement du script est exposé aux scripts utilisateur, de sorte que toute donnée propre à l’application dont l’hôte a besoin peut emprunter la même connexion USB sans inventer un second protocole.
Il s’agit de la fonctionnalité la plus utile du paquet et de celle que la documentation standard traite le moins bien ; cette page la présente donc de bout en bout.
13.3.1.4.1. Les deux moitiés¶
Un canal personnalisé nécessite du code coopérant des deux côtés. Le script côté caméra importe protocol, définit une classe avec trois méthodes (size(), read(), poll()) plus une méthode write() facultative, et appelle protocol.register(name=..., backend=...) pour publier le canal sous un nom choisi:
import protocol
import time
class TicksChannel:
def size(self):
return 10
def read(self, offset, size):
return f'{time.ticks_ms():010d}'
def poll(self):
return True
protocol.register(name='ticks', backend=TicksChannel())
La méthode size() renvoie le nombre d’octets actuellement disponibles sur le canal. read() est le producteur : étant donné un offset et une size demandés par l’hôte, elle renvoie les octets (ou une chaîne que la couche protocole encode). poll() renvoie True lorsqu’il y a quelque chose à lire : la couche protocole s’en sert pour marquer le canal comme prêt dans read_status().
Le programme côté hôte utilise quatre méthodes de openmv.Camera : has_channel() pour vérifier l’existence du canal, channel_size() pour demander la quantité de données en attente, channel_read() pour extraire des octets, et channel_write() pour insérer des octets. read_status() interroge tous les canaux à la fois:
from openmv import Camera
with Camera('/dev/ttyACM0') as cam:
cam.stop()
cam.exec(open('ticks_cam.py').read())
while True:
status = cam.read_status()
if status.get('ticks'):
data = cam.channel_read('ticks')
print(f"ticks: {data.decode()}")
La boucle de l’hôte interroge read_status() ; lorsque le canal ticks est prêt, elle appelle channel_read() sans size pour extraire tout ce qui est disponible. La méthode TicksChannel.poll() de la caméra renvoie True à chaque vérification, de sorte que le canal est toujours « prêt » et que l’hôte obtient une nouvelle valeur de tick à chaque interrogation.
13.3.1.4.2. Un canal bidirectionnel¶
Pour un hôte qui doit renvoyer des données, la classe côté caméra ajoute une méthode write() qui accepte les octets entrants:
import protocol
class CommandChannel:
def __init__(self):
self.last_command = b''
self.replied = False
def size(self):
return len(self.last_command)
def read(self, offset, size):
self.replied = True
return self.last_command
def write(self, offset, data):
self.last_command = b'echo: ' + bytes(data)
self.replied = False
def poll(self):
return not self.replied and len(self.last_command) > 0
protocol.register(name='echo', backend=CommandChannel())
L’hôte écrit sur le canal avec channel_write() et relit la réponse selon le schéma habituel read_status() / channel_read():
with Camera('/dev/ttyACM0') as cam:
cam.stop()
cam.exec(open('echo_cam.py').read())
cam.channel_write('echo', b'hello')
while True:
if cam.read_status().get('echo'):
print(cam.channel_read('echo').decode())
break
13.3.1.4.3. Ce que cela apporte à l’application¶
Les canaux personnalisés sont l’outil adapté chaque fois qu’une application souhaite emprunter la connexion USB existante pour des données qui ne sont ni des trames ni des sorties imprimées : compteurs de télémétrie, boutons de configuration diffusés en direct depuis une interface sur l’hôte, commandes de contrôle envoyées dans l’autre sens, résultats d’une mesure calculée par la caméra qui ne correspond pas au cadre « image » que suppose le canal stream. La couche protocole gère le tramage, la fragmentation, l’accusé de réception et les tentatives ; le script n’a qu’à implémenter le backend à quatre méthodes, et l’hôte n’a qu’à connaître le nom du canal et la forme des données.
L’indicateur --channel NAME de l’interface en ligne de commande est un moyen rapide de vérifier un canal personnalisé depuis le terminal sans écrire de programme côté hôte : l’interface interroge le canal nommé et affiche les dix premiers octets de chaque mise à jour.
La limite de taille d’un seul appel à channel_read() ou channel_write() est le max_payload négocié du protocole, soit 4096 octets par défaut. Les méthodes côté hôte fractionnent automatiquement les écritures plus volumineuses en le bon nombre de paquets, de sorte que l’application peut transmettre des tampons de taille arbitraire ; la fragmentation est invisible.