protocol — OpenMV Protocol Channels#
The protocol module exposes the OpenMV host protocol to Python. It allows the firmware-side protocol stack to be initialized and configured, and lets user code register custom logical channels backed by a Python object that implements the channel interface (read, write, size, poll, etc.). This is what desktop companion tools talk to when they stream image data or expose interactive widgets to a connected camera.
Examples#
Stream an RGB565 image to a host tool using a custom backend that implements the raw channel interface (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
The matching host-side script, using the openmv Python package (pip install openmv) to connect, push the on-camera script, and pull each frame:
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()
Replace /dev/ttyACM0 with the camera’s serial port (e.g. COM3 on Windows). The openmv.camera.Camera constructor accepts the same protocol parameters as init (crc / seq / ack / events / max_payload / max_retry / timeout) when the camera-side stack has been reconfigured to match.
Functions#
- 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#
Initialize (or reconfigure) the protocol stack and register the default logical data channels (
stdin,stdout,streamand, if compiled in,profile). RaisesRuntimeErrorif initialization fails. The firmware boots with a default USB protocol stack already running, so calling this is needed only to change the transport or override the default framing parameters. Calling it again re-initializes the stack: the previous polling timer is removed and the channels are registered afresh.crcenables CRC validation on protocol frames.seqenables sequence number tracking.ackenables per-frame acknowledgements.eventsenables channel event notifications.max_payloadis the maximum payload size in bytes. If omitted the per-camera default below is used; it is derived from each board’s protocol buffer size asbuffer - 10 (header) - 4 (CRC).Camera
Buffer size
Max payload
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_retriesis the number of retransmission attempts. Default3.rtx_timeout_msis the retransmission timeout in milliseconds (doubled after each timeout). Default500.lock_interval_msis the minimum lock interval in milliseconds. Default10.poll_msis the polling interval in milliseconds.0(the default) disables timer polling.
- protocol.is_active() bool#
Return
Trueif a host is currently connected and the protocol stack is active, otherwiseFalse.
- protocol.poll() int#
Run one iteration of the protocol task: drain the transport’s receive buffer, parse any complete frames, dispatch commands and service channel events. The firmware normally does this for you from the USB interrupt and from the
poll_mstimer, so this is only needed for custom transports that are not driven by either – for example whenpoll_ms=0and the transport is polled from the script’s main loop.Returns
0on success and-1if no active transport is registered or the call was made from an interrupt context.
- protocol.register(name: str, *, backend: object, flags: int = 0) ProtocolChannel#
Register a Python
backendobject as a new logical channel and return aProtocolChannelhandle. Thebackendobject’s available methods (see Backend Interface below) determine the channel’s capabilities;protocol.CHANNEL_FLAG_READ,protocol.CHANNEL_FLAG_WRITEandprotocol.CHANNEL_FLAG_LOCKare added toflagsautomatically when the corresponding methods are implemented.nameis the channel name as a string. Truncated to the firmware’s channel-name buffer size. Required.backendis the Python object implementing the backend interface. Required. Typically passed by keyword (backend=...).flagsis additional channel flag bits (see theCHANNEL_FLAG_*constants). Optional; defaults to0.Raises
RuntimeErrorif the channel cannot be registered (e.g. no free channel slots).
Classes#
- class protocol.ProtocolChannel#
Handle returned by
protocol.register. Instances are not constructed directly.
Backend Interface#
A backend object passed to protocol.register may implement any subset of the following methods. Only the methods present on the object are wired to the C protocol layer; missing methods leave the corresponding capability disabled.
- class protocol.backend#
Channel backend object passed to
protocol.register. The methods below describe the optional interface a Python backend may implement.- init() object#
Called once when the channel is initialized. Return any non-
Nonevalue on success; an exception or missing return is treated as an error.
- shape() tuple#
Return a tuple of up to four integers describing the data shape (e.g. image dimensions). Up to four elements are consumed by the protocol layer.
- read(offset: int, size: int) bytes#
Return up to
sizebytes starting atoffsetas abytes-like object that supports the buffer protocol.
- readp(offset: int, size: int) bytes#
Zero-copy variant of
read. Returns a buffer whose underlying memory is read directly by the protocol layer; the buffer must remain valid for the duration of the transfer.
- write(offset: int, data: bytearray) int#
Write
dataatoffset.datais abytearrayreferencing the C buffer directly. Return the number of bytes written, or0on default success.
- class protocol.CBORChannel(on_read: Callable | None = None, on_write: Callable | None = None)#
A higher-level Python backend (provided by the frozen
protocolpackage) that serializes named fields to CBOR using SenML-compatible integer keys. Supports display widgets (label,text,depth,waveform) and interactive controls (toggle,pushbutton,slider,spinbox,select,radio,lineedit) withon_read/on_writecallbacks.on_readis an optional callableon_read(channel)invoked before the channel is serialized for the host. Use it to refresh field values.on_writeis an optional callableon_write(channel, name, value)invoked when the host writes a new value for a named field.- 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#
Add a named field to the channel.
nameis the display name; must be unique within this channel.typeis the widget type:"label"– read-only numeric display with an optionalunit."text"– read-only string display."toggle"– boolean switch."pushbutton"– momentary action (trigger, calibrate, …). The host writesTrueon click and renders no state."slider"– numeric control withmin/max/step."spinbox"– precise stepped numeric entry; shares the slider fields and the(min, max, value)tuple update form."select"– drop-down ofoptionsstrings."radio"–optionsrendered as a group of radio buttons."lineedit"– writable string input."depth"– 2D depth-map display ofwidthxheight."waveform"– 1D time-series plot of interleaved samples.
valueis the initial value. The default depends ontype:0forlabel,""fortext/lineedit/select/radio,Falsefortoggle/pushbutton,minforslider/spinbox.unitis the unit string forlabel/slider/spinbox/waveform(e.g."Cel","%RH").minis the minimum value (slider/spinbox range, or depth/waveform display range).maxis the maximum value (slider/spinbox range, or depth/waveform display range).stepis the step size (slider/spinbox).optionsis the list of option strings (select/radio), or the list of per-series names (waveform).widthis the pixel width (depth).heightis the pixel height (depth).sample_rateis the sample rate in samples per second (waveform). Sent to the host as the sample period.seriesis the number of interleaved series in each sample block (waveform), e.g.3for an XYZ accelerometer trace.typecodeis thearraytypecode of the waveform samples (e.g."H","h","f"). Defaults to"H"(unsigned 16-bit).
- __getitem__(name: str) object#
Return the current value of the named field. For
depthandwaveformfields the binary data buffer is returned, otherwise the scalar value.
- __setitem__(name: str, value: Any) None#
Set the value of the named field. For
sliderandspinboxfields, a(min, max, value)tuple updates the range and current value simultaneously. Fordepthfields,valueis the binary data buffer. Forwaveformfields,valueis a bytes-like block of interleaved samples in the field’stypecode; the samples-per- series count is derived from its length and a monotonic timestamp (in microseconds, accumulated fromtime.ticks_us()) is attached so the host can place the chunk on its time axis.
- size() int#
Backend interface method. Invokes
on_read(if set) and returns the size of the serialized buffer.
Constants#
Channel flag bits (combined bitwise; passed to protocol.register via flags or set automatically based on the backend’s methods).
- protocol.CHANNEL_FLAG_PHYSICAL: int#
The channel represents a physical transport (as opposed to a logical data channel).
Built-in channel identifiers.