protocol — Canaux du protocole OpenMV#
Le module protocol expose le protocole hôte OpenMV à Python. Il permet d’initialiser et de configurer la pile de protocole côté micrologiciel, et permet au code utilisateur d’enregistrer des canaux logiques personnalisés adossés à un objet Python qui implémente l’interface de canal (read, write, size, poll, etc.). C’est ce avec quoi les outils compagnons de bureau communiquent lorsqu’ils diffusent des données d’image ou exposent des widgets interactifs à une caméra connectée.
Exemples#
Diffusez une image RGB565 vers un outil hôte à l’aide d’un backend personnalisé qui implémente l’interface de canal brute (backend.size(), backend.shape(), backend.poll(), backend.read())
import csi
import protocol
csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.HD)
img = csi0.snapshot()
img_mv = memoryview(img.bytearray())
frame_ready = True
class FrameChannel:
def size(self):
return len(img_mv)
def shape(self):
return (img.height(), img.width(), len(img_mv))
def poll(self):
return frame_ready
def read(self, offset, size):
global frame_ready
end = offset + size
chunk = img_mv[offset:end]
if end >= len(img_mv):
frame_ready = False
return chunk
protocol.register(name="frame", backend=FrameChannel())
while True:
if not frame_ready:
img = csi0.snapshot()
img_mv = memoryview(img.bytearray())
frame_ready = True
Le script côté hôte correspondant, utilisant le paquet Python openmv (pip install openmv) pour se connecter, envoyer le script embarqué sur la caméra et récupérer chaque trame
import cv2
import numpy as np
from openmv.camera import Camera
# The on-cam script above, stored as a string (or read from a file).
SCRIPT = open("frame_streamer_on_cam.py").read()
with Camera("/dev/ttyACM0", baudrate=921600) as cam:
cam.stop()
cam.exec(SCRIPT)
while True:
status = cam.read_status()
if not cam.has_channel("frame") or not status.get("frame"):
continue
h, w, size = cam._channel_shape(cam.get_channel(name="frame"))
if cam.channel_size("frame") < size:
continue
data = cam.channel_read("frame", size)
rgb565 = np.frombuffer(data, dtype="<u2").reshape(h, w)
# Unpack RGB565 to an HxWx3 uint8 RGB image.
r = ((rgb565 >> 11) & 0x1F) << 3
g = ((rgb565 >> 5) & 0x3F) << 2
b = ( rgb565 & 0x1F) << 3
frame = np.dstack([r, g, b]).astype(np.uint8)
# Display with OpenCV (cv2 expects BGR, not RGB).
cv2.imshow("OpenMV", cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))
if cv2.waitKey(1) == ord("q"):
break
cv2.destroyAllWindows()
Remplacez /dev/ttyACM0 par le port série de la caméra (par ex. COM3 sous Windows). Le constructeur openmv.camera.Camera accepte les mêmes paramètres de protocole que init (crc / seq / ack / events / max_payload / max_retry / timeout) lorsque la pile côté caméra a été reconfigurée en conséquence.
Fonctions#
- protocol.init(crc: bool = True, seq: bool = True, ack: bool = True, events: bool = True, max_payload: int = ..., rtx_retries: int = 3, rtx_timeout_ms: int = 500, lock_interval_ms: int = 10, poll_ms: int = 0) None#
Initialise (ou reconfigure) la pile du protocole et enregistre les canaux de données logiques par défaut (
stdin,stdout,streamet, s’il est compilé,profile). LèveRuntimeErrorsi l’initialisation échoue. Le micrologiciel démarre avec une pile de protocole USB par défaut déjà en cours d’exécution, de sorte que cet appel n’est nécessaire que pour changer de transport ou remplacer les paramètres de tramage par défaut. Un nouvel appel réinitialise la pile : le minuteur d’interrogation précédent est supprimé et les canaux sont enregistrés à nouveau.crcactive la validation CRC sur les trames du protocole.seqactive le suivi des numéros de séquence.ackactive les accusés de réception par trame.eventsactive les notifications d’événements de canal.max_payloadest la taille maximale de la charge utile en octets. S’il est omis, la valeur par défaut propre à chaque caméra ci-dessous est utilisée ; elle est dérivée de la taille du tampon de protocole de chaque carte selonbuffer - 10 (header) - 4 (CRC).Caméra
Taille du tampon
Charge utile max
OpenMV Cam M4 (
OPENMV2)512
498
OpenMV Cam M7 (
OPENMV3)512
498
OpenMV Cam H7 (
OPENMV4)512
498
OpenMV Cam H7 Plus (
OPENMV4P)4096
4082
OpenMV Pure Thermal (
OPENMVPT)4096
4082
OpenMV Cam RT1062 (
OPENMV_RT1060)4096
4082
OpenMV Cam N6 (
OPENMV_N6)8192
8178
OpenMV AE3 (
OPENMV_AE3)8192
8178
Arduino Portenta H7 (
ARDUINO_PORTENTA_H7)4096
4082
Arduino Giga (
ARDUINO_GIGA)4096
4082
Arduino Nicla Vision (
ARDUINO_NICLA_VISION)4096
4082
rtx_retriesest le nombre de tentatives de retransmission. Par défaut3.rtx_timeout_msest le délai d’expiration de retransmission en millisecondes (doublé après chaque expiration). Par défaut500.lock_interval_msest l’intervalle de verrouillage minimal en millisecondes. Par défaut10.poll_msest l’intervalle d’interrogation en millisecondes.0(la valeur par défaut) désactive l’interrogation par minuteur.
- protocol.is_active() bool#
Renvoie
Truesi un hôte est actuellement connecté et que la pile de protocole est active, sinonFalse.
- protocol.poll() int#
Exécute une itération de la tâche du protocole : vide le tampon de réception du transport, analyse les trames complètes, distribue les commandes et traite les événements de canal. Le micrologiciel s’en charge normalement pour vous depuis l’interruption USB et depuis le minuteur
poll_ms, de sorte que ceci n’est nécessaire que pour les transports personnalisés qui ne sont pilotés par aucun des deux – par exemple lorsquepoll_ms=0et que le transport est interrogé depuis la boucle principale du script.Renvoie
0en cas de succès et-1si aucun transport actif n’est enregistré ou si l’appel a été effectué depuis un contexte d’interruption.
- protocol.register(name: str, *, backend: object, flags: int = 0) ProtocolChannel#
Enregistre un objet Python
backendcomme nouveau canal logique et renvoie un descripteurProtocolChannel. Les méthodes disponibles de l’objetbackend(voir Interface du backend ci-dessous) déterminent les capacités du canal ;protocol.CHANNEL_FLAG_READ,protocol.CHANNEL_FLAG_WRITEetprotocol.CHANNEL_FLAG_LOCKsont ajoutés automatiquement àflagslorsque les méthodes correspondantes sont implémentées.nameest le nom du canal sous forme de chaîne. Tronqué à la taille du tampon de nom de canal du micrologiciel. Obligatoire.backendest l’objet Python qui implémente l’interface du backend. Obligatoire. Généralement passé par mot-clé (backend=...).flagscorrespond à des bits d’indicateur de canal supplémentaires (voir les constantesCHANNEL_FLAG_*). Facultatif ; vaut0par défaut.Lève
RuntimeErrorsi le canal ne peut pas être enregistré (par ex. aucun emplacement de canal libre).
Classes#
- class protocol.ProtocolChannel#
Descripteur renvoyé par
protocol.register. Les instances ne sont pas construites directement.
Interface du backend#
Un objet backend passé à protocol.register peut implémenter n’importe quel sous-ensemble des méthodes suivantes. Seules les méthodes présentes sur l’objet sont reliées à la couche de protocole C ; les méthodes manquantes laissent la capacité correspondante désactivée.
- class protocol.backend#
Objet backend de canal passé à
protocol.register. Les méthodes ci-dessous décrivent l’interface optionnelle qu’un backend Python peut implémenter.- init() object#
Appelée une seule fois lorsque le canal est initialisé. Renvoie toute valeur autre que
Noneen cas de succès ; une exception ou l’absence de valeur de retour est considérée comme une erreur.
- shape() tuple#
Renvoie un tuple d’au plus quatre entiers décrivant la forme des données (par ex. les dimensions d’une image). Jusqu’à quatre éléments sont consommés par la couche de protocole.
- flush() object#
Vide toutes les données en attente. Renvoie toute valeur autre que
Noneen cas de succès.
- read(offset: int, size: int) bytes#
Renvoie jusqu’à
sizeoctets à partir deoffsetsous forme d’objet de typebytesprenant en charge le protocole tampon.
- readp(offset: int, size: int) bytes#
Variante sans copie de
read. Renvoie un tampon dont la mémoire sous-jacente est lue directement par la couche de protocole ; le tampon doit rester valide pendant toute la durée du transfert.
- write(offset: int, data: bytearray) int#
Écrit
dataàoffset.dataest unbytearrayréférençant directement le tampon C. Renvoie le nombre d’octets écrits, ou0en cas de succès par défaut.
- class protocol.CBORChannel(on_read: Callable | None = None, on_write: Callable | None = None)#
Un backend Python de plus haut niveau (fourni par le paquet gelé
protocol) qui sérialise des champs nommés en CBOR à l’aide de clés entières compatibles SenML. Prend en charge les widgets d’affichage (label,text,depth,waveform) et les contrôles interactifs (toggle,pushbutton,slider,spinbox,select,radio,lineedit) avec des rappelson_read/on_write.on_readest un appelable optionnelon_read(channel)invoqué avant que le canal ne soit sérialisé pour l’hôte. Utilisez-le pour rafraîchir les valeurs des champs.on_writeest un appelable optionnelon_write(channel, name, value)invoqué lorsque l’hôte écrit une nouvelle valeur pour un champ nommé.- add(name: str, type: str, value: Any = None, unit: str | None = None, min: int | float | None = None, max: int | float | None = None, step: int | float | None = None, options: list | None = None, width: int | None = None, height: int | None = None, sample_rate: float | None = None, series: int = 1, typecode: str | None = None) None#
Ajoute un champ nommé au canal.
nameest le nom d’affichage ; il doit être unique au sein de ce canal.typeest le type de widget :"label"– affichage numérique en lecture seule avec uneunitfacultative."text"– affichage de chaîne en lecture seule."toggle"– interrupteur booléen."pushbutton"– action momentanée (déclenchement, calibration, …). L’hôte écritTrueau clic et n’affiche aucun état."slider"– contrôle numérique avecmin/max/step."spinbox"– saisie numérique précise par pas ; partage les champs du slider et la forme de mise à jour par tuple(min, max, value)."select"– liste déroulante de chaînesoptions."radio"–optionsaffichées sous forme de groupe de boutons radio."lineedit"– saisie de chaîne modifiable."depth"– affichage 2D de carte de profondeur dewidthxheight."waveform"– tracé 1D de série temporelle d’échantillons entrelacés.
valueest la valeur initiale. La valeur par défaut dépend detype:0pourlabel,""pourtext/lineedit/select/radio,Falsepourtoggle/pushbutton,minpourslider/spinbox.unitest la chaîne d’unité pourlabel/slider/spinbox/waveform(par ex."Cel","%RH").minest la valeur minimale (plage du slider/spinbox, ou plage d’affichage depth/waveform).maxest la valeur maximale (plage du slider/spinbox, ou plage d’affichage depth/waveform).stepest la taille du pas (slider/spinbox).optionsest la liste des chaînes d’options (select/radio), ou la liste des noms de chaque série (waveform).widthest la largeur en pixels (depth).heightest la hauteur en pixels (depth).sample_rateest la fréquence d’échantillonnage en échantillons par seconde (waveform). Envoyée à l’hôte sous forme de période d’échantillonnage.seriesest le nombre de séries entrelacées dans chaque bloc d’échantillons (waveform), par ex.3pour une trace d’accéléromètre XYZ.typecodeest le typecodearraydes échantillons de la waveform (par ex."H","h","f"). Vaut"H"par défaut (16 bits non signé).
- __getitem__(name: str) object#
Renvoie la valeur actuelle du champ nommé. Pour les champs
depthetwaveform, le tampon de données binaires est renvoyé, sinon la valeur scalaire.
- __setitem__(name: str, value: Any) None#
Définit la valeur du champ nommé. Pour les champs
slideretspinbox, un tuple(min, max, value)met à jour simultanément la plage et la valeur actuelle. Pour les champsdepth,valueest le tampon de données binaires. Pour les champswaveform,valueest un bloc de type bytes d’échantillons entrelacés dans letypecodedu champ ; le nombre d’échantillons par série est déduit de sa longueur et un horodatage monotone (en microsecondes, accumulé à partir detime.ticks_us()) y est attaché afin que l’hôte puisse placer le fragment sur son axe temporel.
- poll() bool#
Méthode de l’interface du backend. Renvoie
Truelorsque des données sérialisées sont disponibles pour l’hôte.
- size() int#
Méthode de l’interface du backend. Invoque
on_read(s’il est défini) et renvoie la taille du tampon sérialisé.
Constantes#
Bits d’indicateur de canal (combinés au niveau du bit ; passés à protocol.register via flags ou définis automatiquement en fonction des méthodes du backend).
- protocol.CHANNEL_FLAG_PHYSICAL: int#
Le canal représente un transport physique (par opposition à un canal de données logique).
Identifiants de canaux intégrés.