protocol — Каналы протокола OpenMV#
Модуль protocol предоставляет протокол хоста OpenMV для Python. Он позволяет инициализировать и настраивать стек протокола на стороне прошивки, а также даёт пользовательскому коду регистрировать собственные логические каналы, реализованные объектом Python, который реализует интерфейс канала (read, write, size, poll и т. д.). Именно с этим взаимодействуют настольные сопутствующие инструменты, когда передают данные изображения в потоковом режиме или предоставляют интерактивные виджеты подключённой камере.
Примеры#
Передача изображения RGB565 в потоковом режиме на инструмент хоста с использованием собственного бэкенда, реализующего интерфейс необработанного канала (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
Соответствующий скрипт на стороне хоста, использующий пакет Python openmv (pip install openmv) для подключения, отправки скрипта на камеру и получения каждого кадра:
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()
Замените /dev/ttyACM0 последовательным портом камеры (например, COM3 в Windows). Конструктор openmv.camera.Camera принимает те же параметры протокола, что и init (crc / seq / ack / events / max_payload / max_retry / timeout), когда стек на стороне камеры был перенастроен соответствующим образом.
Функции#
- 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#
Инициализирует (или перенастраивает) стек протокола и регистрирует логические каналы данных по умолчанию (
stdin,stdout,streamи, если включён при сборке,profile). ВызываетRuntimeError, если инициализация не удалась. Прошивка загружается с уже запущенным USB-стеком протокола по умолчанию, поэтому вызывать эту функцию нужно только для смены транспорта или переопределения параметров фреймирования по умолчанию. Повторный вызов заново инициализирует стек: предыдущий таймер опроса удаляется, а каналы регистрируются заново.crcвключает проверку CRC для кадров протокола.seqвключает отслеживание порядковых номеров.ackвключает покадровые подтверждения.eventsвключает уведомления о событиях канала.max_payload— максимальный размер полезной нагрузки в байтах. Если не указано, используется приведённое ниже значение по умолчанию для каждой камеры; оно вычисляется из размера буфера протокола каждой платы какbuffer - 10 (header) - 4 (CRC).Камера
Размер буфера
Макс. полезная нагрузка
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— количество попыток повторной передачи. По умолчанию3.rtx_timeout_ms— тайм-аут повторной передачи в миллисекундах (удваивается после каждого тайм-аута). По умолчанию500.lock_interval_ms— минимальный интервал блокировки в миллисекундах. По умолчанию10.poll_ms— интервал опроса в миллисекундах.0(по умолчанию) отключает опрос по таймеру.
- protocol.is_active() bool#
Возвращает
True, если хост в данный момент подключён и стек протокола активен, иначеFalse.
- protocol.poll() int#
Выполняет одну итерацию задачи протокола: вычитывает приёмный буфер транспорта, разбирает все полные кадры, диспетчеризует команды и обслуживает события каналов. Обычно прошивка делает это за вас из USB-прерывания и по таймеру
poll_ms, поэтому это нужно только для собственных транспортов, которые не обслуживаются ни тем, ни другим – например, когдаpoll_ms=0и транспорт опрашивается из главного цикла скрипта.Возвращает
0в случае успеха и-1, если активный транспорт не зарегистрирован или вызов был сделан из контекста прерывания.
- protocol.register(name: str, *, backend: object, flags: int = 0) ProtocolChannel#
Регистрирует объект Python
backendкак новый логический канал и возвращает дескрипторProtocolChannel. Доступные методы объектаbackend(см. Интерфейс бэкенда ниже) определяют возможности канала; флагиprotocol.CHANNEL_FLAG_READ,protocol.CHANNEL_FLAG_WRITEиprotocol.CHANNEL_FLAG_LOCKдобавляются кflagsавтоматически, когда соответствующие методы реализованы.name— имя канала в виде строки. Усекается до размера буфера имени канала в прошивке. Обязательно.backend— объект Python, реализующий интерфейс бэкенда. Обязательно. Обычно передаётся по ключевому слову (backend=...).flags— дополнительные биты флагов канала (см. константыCHANNEL_FLAG_*). Необязательно; по умолчанию0.Вызывает
RuntimeError, если канал не удаётся зарегистрировать (например, нет свободных слотов каналов).
Классы#
- class protocol.ProtocolChannel#
Дескриптор, возвращаемый
protocol.register. Экземпляры не создаются напрямую.
Интерфейс бэкенда#
Объект бэкенда, передаваемый в protocol.register, может реализовывать любое подмножество следующих методов. К C-уровню протокола подключаются только присутствующие на объекте методы; отсутствующие методы оставляют соответствующую возможность отключённой.
- class protocol.backend#
Объект бэкенда канала, передаваемый в
protocol.register. Методы ниже описывают необязательный интерфейс, который может реализовывать бэкенд на Python.- init() object#
Вызывается один раз при инициализации канала. Возвращает любое значение, отличное от
None, в случае успеха; исключение или отсутствие возвращаемого значения считается ошибкой.
- shape() tuple#
Возвращает кортеж из не более чем четырёх целых чисел, описывающих форму данных (например, размеры изображения). Уровень протокола использует не более четырёх элементов.
- flush() object#
Сбрасывает все ожидающие данные. Возвращает любое значение, отличное от
None, в случае успеха.
- read(offset: int, size: int) bytes#
Возвращает до
sizeбайтов начиная со смещенияoffsetв видеbytes-подобного объекта, поддерживающего протокол буфера.
- readp(offset: int, size: int) bytes#
Вариант
readбез копирования. Возвращает буфер, базовая память которого читается напрямую уровнем протокола; буфер должен оставаться действительным в течение всей передачи.
- write(offset: int, data: bytearray) int#
Записывает
dataпо смещениюoffset.data— этоbytearray, ссылающийся непосредственно на C-буфер. Возвращает количество записанных байтов или0при успехе по умолчанию.
- class protocol.CBORChannel(on_read: Callable | None = None, on_write: Callable | None = None)#
Более высокоуровневый бэкенд на Python (предоставляемый замороженным пакетом
protocol), который сериализует именованные поля в CBOR с использованием SenML-совместимых целочисленных ключей. Поддерживает виджеты отображения (label,text,depth,waveform) и интерактивные элементы управления (toggle,pushbutton,slider,spinbox,select,radio,lineedit) с обратными вызовамиon_read/on_write.on_read— необязательный вызываемый объектon_read(channel), вызываемый перед сериализацией канала для хоста. Используйте его для обновления значений полей.on_write— необязательный вызываемый объектon_write(channel, name, value), вызываемый, когда хост записывает новое значение для именованного поля.- 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#
Добавляет именованное поле в канал.
name— отображаемое имя; должно быть уникальным в пределах этого канала.type– тип виджета:"label"– числовой индикатор только для чтения с необязательной единицей измеренияunit."text"– строковый индикатор только для чтения."toggle"– логический переключатель."pushbutton"– одномоментное действие (запуск, калибровка, …). Хост записываетTrueпри нажатии и не отображает состояние."slider"– числовой элемент управления сmin/max/step."spinbox"– точный пошаговый числовой ввод; использует те же поля, что и slider, и ту же форму обновления кортежем(min, max, value)."select"– выпадающий список строкoptions."radio"–options, отображаемые как группа радиокнопок."lineedit"– записываемое строковое поле ввода."depth"– отображение двумерной карты глубины размеромwidthxheight."waveform"– одномерный график временного ряда из чередующихся отсчётов.
value– начальное значение. Значение по умолчанию зависит отtype:0дляlabel,""дляtext/lineedit/select/radio,Falseдляtoggle/pushbutton,minдляslider/spinbox.unit– строка единицы измерения дляlabel/slider/spinbox/waveform(например,"Cel","%RH").min– минимальное значение (диапазон slider/spinbox или диапазон отображения depth/waveform).max– максимальное значение (диапазон slider/spinbox или диапазон отображения depth/waveform).step– размер шага (slider/spinbox).options– список строк вариантов (select/radio) либо список имён серий (waveform).width— ширина в пикселях (depth).height— высота в пикселях (depth).sample_rate– частота дискретизации в отсчётах в секунду (waveform). Передаётся хосту как период дискретизации.series– количество чередующихся серий в каждом блоке отсчётов (waveform), например3для трассы акселерометра XYZ.typecode– код типаarrayдля отсчётов waveform (например,"H","h","f"). По умолчанию"H"(беззнаковое 16-битное).
- __getitem__(name: str) object#
Возвращает текущее значение именованного поля. Для полей
depthиwaveformвозвращается буфер двоичных данных, иначе – скалярное значение.
- __setitem__(name: str, value: Any) None#
Устанавливает значение именованного поля. Для полей
sliderиspinboxкортеж(min, max, value)одновременно обновляет диапазон и текущее значение. Для полейdepthvalue– это буфер двоичных данных. Для полейwaveformvalue– это байтоподобный блок чередующихся отсчётов вtypecodeполя; количество отсчётов на серию вычисляется из его длины, и к нему прикрепляется монотонная метка времени (в микросекундах, накапливаемая изtime.ticks_us()), чтобы хост мог разместить фрагмент на своей оси времени.
- poll() bool#
Метод интерфейса бэкенда. Возвращает
True, когда для хоста доступны сериализованные данные.
- size() int#
Метод интерфейса бэкенда. Вызывает
on_read(если задан) и возвращает размер сериализованного буфера.
Константы#
Биты флагов канала (объединяются побитово; передаются в protocol.register через flags или устанавливаются автоматически на основе методов бэкенда).
- protocol.CHANNEL_FLAG_PHYSICAL: int#
Канал представляет физический транспорт (в отличие от логического канала данных).
Встроенные идентификаторы каналов.