protocol — Canales del protocolo OpenMV#

El módulo protocol expone el protocolo de host de OpenMV a Python. Permite inicializar y configurar la pila de protocolo del lado del firmware, y deja que el código de usuario registre canales lógicos personalizados respaldados por un objeto Python que implementa la interfaz del canal (read, write, size, poll, etc.). Esto es con lo que se comunican las herramientas complementarias de escritorio cuando transmiten datos de imagen o exponen widgets interactivos a una cámara conectada.

Ejemplos#

Transmite una imagen RGB565 a una herramienta de host usando un backend personalizado que implementa la interfaz del canal en bruto (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

El script del lado del host correspondiente, que usa el paquete Python openmv (pip install openmv) para conectarse, enviar el script de la cámara y recuperar cada fotograma:

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

Reemplace /dev/ttyACM0 con el puerto serie de la cámara (por ejemplo COM3 en Windows). El constructor openmv.camera.Camera acepta los mismos parámetros de protocolo que init (crc / seq / ack / events / max_payload / max_retry / timeout) cuando la pila del lado de la cámara se ha reconfigurado para que coincida.

Funciones#

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#

Inicializa (o reconfigura) la pila del protocolo y registra los canales lógicos de datos predeterminados (stdin, stdout, stream y, si está compilado, profile). Genera RuntimeError si la inicialización falla. El firmware arranca con una pila de protocolo USB predeterminada ya en ejecución, por lo que solo es necesario llamar a esta función para cambiar el transporte o sobrescribir los parámetros de entramado predeterminados. Llamarla de nuevo reinicializa la pila: se elimina el temporizador de sondeo anterior y los canales se registran desde cero.

crc habilita la validación CRC en las tramas del protocolo.

seq habilita el seguimiento de números de secuencia.

ack habilita los acuses de recibo por trama.

events habilita las notificaciones de eventos de canal.

max_payload es el tamaño máximo de la carga útil en bytes. Si se omite, se usa el valor predeterminado por cámara que se indica a continuación; se deriva del tamaño del búfer de protocolo de cada placa como buffer - 10 (header) - 4 (CRC).

Cámara

Tamaño del búfer

Carga útil máxima

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_retries es el número de intentos de retransmisión. Predeterminado 3.

rtx_timeout_ms es el tiempo de espera de retransmisión en milisegundos (se duplica tras cada tiempo de espera agotado). Predeterminado 500.

lock_interval_ms es el intervalo mínimo de bloqueo en milisegundos. Predeterminado 10.

poll_ms es el intervalo de sondeo en milisegundos. 0 (el valor predeterminado) deshabilita el sondeo por temporizador.

protocol.is_active() → bool#

Devuelve True si actualmente hay un host conectado y la pila de protocolo está activa; de lo contrario, False.

protocol.poll() → int#

Ejecuta una iteración de la tarea del protocolo: vacía el búfer de recepción del transporte, analiza las tramas completas, despacha los comandos y atiende los eventos de los canales. Normalmente el firmware lo hace por usted desde la interrupción USB y desde el temporizador poll_ms, por lo que solo es necesario para transportes personalizados que no son controlados por ninguno de los dos – por ejemplo, cuando poll_ms=0 y el transporte se sondea desde el bucle principal del script.

Devuelve 0 en caso de éxito y -1 si no hay ningún transporte activo registrado o si la llamada se hizo desde un contexto de interrupción.

protocol.register(name: str, *, backend: object, flags: int = 0) → ProtocolChannel#

Registra un objeto Python backend como un nuevo canal lógico y devuelve un manejador ProtocolChannel. Los métodos disponibles del objeto backend (consulte Interfaz del backend más abajo) determinan las capacidades del canal; protocol.CHANNEL_FLAG_READ, protocol.CHANNEL_FLAG_WRITE y protocol.CHANNEL_FLAG_LOCK se añaden a flags automáticamente cuando se implementan los métodos correspondientes.

name es el nombre del canal como cadena. Se trunca al tamaño del búfer de nombres de canal del firmware. Obligatorio.

backend es el objeto Python que implementa la interfaz del backend. Obligatorio. Normalmente se pasa por palabra clave (backend=...).

flags son bits de indicador de canal adicionales (consulte las constantes CHANNEL_FLAG_*). Opcional; el valor predeterminado es 0.

Lanza RuntimeError si el canal no se puede registrar (por ejemplo, no hay ranuras de canal libres).

Clases#

class protocol.ProtocolChannel#

Manejador devuelto por protocol.register. Las instancias no se construyen directamente.

send_event(event: int, wait_ack: bool = False) → None#

Envía una notificación de evento de canal al host.

event es el identificador del evento (entero).

wait_ack si es True se bloquea hasta que el host acuse recibo del evento.

Lanza RuntimeError si el envío del evento falla.

Interfaz del backend#

Un objeto backend pasado a protocol.register puede implementar cualquier subconjunto de los siguientes métodos. Solo los métodos presentes en el objeto se conectan a la capa de protocolo en C; los métodos ausentes dejan deshabilitada la capacidad correspondiente.

class protocol.backend#

Objeto backend de canal pasado a protocol.register. Los métodos siguientes describen la interfaz opcional que puede implementar un backend Python.

init() → object#

Se llama una vez cuando se inicializa el canal. Devuelve cualquier valor distinto de None en caso de éxito; una excepción o la ausencia de valor de retorno se tratan como un error.

poll() → bool#

Devuelve True si el canal tiene datos listos para que el host los lea.

lock() → bool#

Adquiere el canal para una transferencia. Devuelve True en caso de éxito.

unlock() → bool#

Libera el canal después de una transferencia. Devuelve True en caso de éxito.

size() → int#

Devuelve el número de bytes actualmente legibles del canal.

shape() → tuple#

Devuelve una tupla de hasta cuatro enteros que describen la forma de los datos (por ejemplo, las dimensiones de la imagen). La capa de protocolo consume hasta cuatro elementos.

flush() → object#

Vacía cualquier dato pendiente. Devuelve cualquier valor distinto de None en caso de éxito.

read(offset: int, size: int) → bytes#

Devuelve hasta size bytes a partir de offset como un objeto similar a bytes que admite el protocolo de búfer.

readp(offset: int, size: int) → bytes#

Variante de read sin copia (zero-copy). Devuelve un búfer cuya memoria subyacente lee directamente la capa de protocolo; el búfer debe permanecer válido durante toda la transferencia.

write(offset: int, data: bytearray) → int#

Escribe data en offset. data es un bytearray que referencia directamente el búfer de C. Devuelve el número de bytes escritos, o 0 en caso de éxito predeterminado.

ioctl(cmd: int, length: int, arg: bytearray | None) → int#

Gestiona un ioctl. arg es None si length es cero; de lo contrario, un bytearray que referencia el búfer de C. Devuelve 0 o None en caso de éxito, o un entero negativo en caso de error.

is_active() → bool#

Para canales de transporte, devuelve True si el transporte subyacente está actualmente conectado.

class protocol.CBORChannel(on_read: Callable | None = None, on_write: Callable | None = None)#

Un backend de Python de más alto nivel (proporcionado por el paquete congelado protocol) que serializa campos con nombre a CBOR usando claves enteras compatibles con SenML. Admite widgets de visualización (label, text, depth, waveform) y controles interactivos (toggle, pushbutton, slider, spinbox, select, radio, lineedit) con retrollamadas on_read/on_write.

on_read es un invocable opcional on_read(channel) que se invoca antes de serializar el canal para el host. Úselo para actualizar los valores de los campos.

on_write es un invocable opcional on_write(channel, name, value) que se invoca cuando el host escribe un nuevo valor para un campo con nombre.

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#

Añade un campo con nombre al canal.

name es el nombre para mostrar; debe ser único dentro de este canal.

type es el tipo de widget:

  • "label" – visualización numérica de solo lectura con una unit opcional.

  • "text" – visualización de cadena de solo lectura.

  • "toggle" – interruptor booleano.

  • "pushbutton" – acción momentánea (disparar, calibrar, …). El host escribe True al hacer clic y no muestra ningún estado.

  • "slider" – control numérico con min/max/step.

  • "spinbox" – entrada numérica precisa por pasos; comparte los campos del slider y la forma de actualización mediante la tupla (min, max, value).

  • "select" – lista desplegable de cadenas options.

  • "radio" – options mostradas como un grupo de botones de opción.

  • "lineedit" – entrada de cadena escribible.

  • "depth" – visualización 2D de mapa de profundidad de width x height.

  • "waveform" – gráfica 1D de series temporales de muestras intercaladas.

value es el valor inicial. El valor predeterminado depende de type: 0 para label, "" para text/lineedit/select/ radio, False para toggle/pushbutton, min para slider/spinbox.

unit es la cadena de unidad para label/slider/spinbox/ waveform (p. ej. "Cel", "%RH").

min es el valor mínimo (rango del slider/spinbox, o rango de visualización de depth/waveform).

max es el valor máximo (rango del slider/spinbox, o rango de visualización de depth/waveform).

step es el tamaño del paso (slider/spinbox).

options es la lista de cadenas de opciones (select/radio), o la lista de nombres de cada serie (waveform).

width es el ancho en píxeles (depth).

height es la altura en píxeles (depth).

sample_rate es la frecuencia de muestreo en muestras por segundo (waveform). Se envía al host como el período de muestreo.

series es el número de series intercaladas en cada bloque de muestras (waveform), p. ej. 3 para una traza de acelerómetro XYZ.

typecode es el código de tipo de array de las muestras de la waveform (p. ej. "H", "h", "f"). El valor predeterminado es "H" (16 bits sin signo).

__getitem__(name: str) → object#

Devuelve el valor actual del campo con nombre. Para los campos depth y waveform se devuelve el búfer de datos binarios; en caso contrario, el valor escalar.

__setitem__(name: str, value: Any) → None#

Establece el valor del campo con nombre. Para los campos slider y spinbox, una tupla (min, max, value) actualiza el rango y el valor actual simultáneamente. Para los campos depth, value es el búfer de datos binarios. Para los campos waveform, value es un bloque de tipo bytes de muestras intercaladas en el typecode del campo; el número de muestras por serie se deriva de su longitud y se adjunta una marca de tiempo monotónica (en microsegundos, acumulada a partir de time.ticks_us()) para que el host pueda ubicar el fragmento en su eje temporal.

poll() → bool#

Método de la interfaz del backend. Devuelve True cuando hay datos serializados disponibles para el host.

size() → int#

Método de la interfaz del backend. Invoca on_read (si está definido) y devuelve el tamaño del búfer serializado.

read(offset: int, size: int) → bytes#

Método de la interfaz del backend. Devuelve una porción del búfer serializado.

write(offset: int, data: bytearray) → int#

Método de la interfaz del backend. Decodifica una lista de actualización CBOR y aplica los valores a los campos con nombre coincidentes, invocando on_write para cada uno.

Constantes#

Bits de indicador de canal (combinados a nivel de bits; pasados a protocol.register mediante flags o establecidos automáticamente en función de los métodos del backend).

protocol.CHANNEL_FLAG_READ: int#

El canal admite lecturas.

protocol.CHANNEL_FLAG_WRITE: int#

El canal admite escrituras.

protocol.CHANNEL_FLAG_LOCK: int#

El canal implementa lock/unlock.

protocol.CHANNEL_FLAG_PHYSICAL: int#

El canal representa un transporte físico (en contraposición a un canal de datos lógico).

Identificadores de canal integrados.

protocol.CHANNEL_ID_TRANSPORT: int#

ID de canal reservado para el transporte activo.

protocol.CHANNEL_ID_STDIN: int#

ID de canal del canal integrado stdin.

protocol.CHANNEL_ID_STDOUT: int#

ID de canal del canal integrado stdout.

protocol.CHANNEL_ID_STREAM: int#

ID de canal del canal integrado stream.

protocol.CHANNEL_ID_PROFILE: int#

ID de canal del canal del perfilador integrado (solo presente cuando el firmware se compila con el perfilador habilitado).