12.6. Именованные каналы¶
Идентификатор канала в заголовке каждого пакета позволяет до 32 независимым потокам совместно использовать один физический транспорт. Уровень каналов превращает эти числовые идентификаторы в именованные, видимые приложению конечные точки, к которым код хоста может обращаться по строке.
12.6.1. Четыре встроенных канала¶
Камера регистрирует четыре канала при загрузке, до запуска какого-либо кода приложения:
stdin– байты скрипта, которые хост отправляет камере на выполнение. IDE использует этот канал для отправки редактируемого скрипта;exec()в SDK хоста – это эквивалентный вызов из программы на Python.stdout– байты от вызововprint()на камере и трассировки неперехваченных исключений. Последовательная консоль IDE читает этот канал.stream– канал предпросмотра в реальном времени. IDE извлекает из него кадры JPEG; любой скрипт хоста может делать то же самое с помощьюread_frame().profile– события профилировщика, присутствуют только в том случае, если камера была собрана с включённым профилированием. Большинство релизных сборок его не содержат.
Коду приложения редко нужно обращаться к встроенным каналам; интересная работа происходит на каналах, которые приложение регистрирует само.
12.6.2. Регистрация канала¶
Скрипт на стороне камеры регистрирует новый канал, вызывая protocol.register() с именем и объектом бэкенда на Python:
import json
import protocol
import time
trigger_count = 0
class StatusChannel:
def size(self):
# Refresh the snapshot on every host query.
self._buf = json.dumps({
'uptime_s': time.ticks_ms() // 1000,
'triggers': trigger_count,
}).encode()
return len(self._buf)
def read(self, offset, size):
return self._buf[offset:offset + size]
protocol.register(name='status', backend=StatusChannel())
Методы объекта бэкенда определяют, что может делать канал. Бэкенд только с size и read – это канал данных, доступный только для чтения; добавьте write, и он становится двунаправленным; добавьте poll, и хост сможет спросить, готовы ли новые данные, прежде чем платить за чтение. Выборка данных внутри size – простейший шаблон, когда полезная нагрузка достаточно мала, чтобы поместиться в один фрагмент: буфер генерируется по запросу, никогда не кешируется и не подвержен состоянию гонки. Более крупные полезные нагрузки – кадры изображений, трассировки датчиков – требуют шаблона с фиксацией, который удерживает буфер, пока хост не завершит своё многофрагментное чтение; это рассматривается на примере канала кадров.
Небольшой объём учёта выполняется автоматически:
Библиотека назначает следующий свободный идентификатор канала (от 0 до 31).
Флаги возможностей выводятся из присутствующих методов:
CHANNEL_FLAG_READ, если определёнread,CHANNEL_FLAG_WRITE, если определёнwrite,CHANNEL_FLAG_LOCK, если определеныlock/unlock.Любому подключённому хосту отправляется пакет события
CHANNEL_REGISTERED, чтобы его список каналов обновился.
Возвращаемое значение – дескриптор protocol.ProtocolChannel, который приложение может сохранить. Метод дескриптора send_event() – это хук на стороне камеры для того, чтобы сообщить хосту «на этом канале что-то произошло, но читаемые данные не изменились»: сработал триггер, была нажата кнопка, пройдена контрольная отметка по числу выборок.
12.6.3. Чтение каналов с хоста¶
SDK хоста поставляется как пакет openmv на PyPI (pip install openmv) и построен на pyserial для транспорта. Его класс openmv.camera.Camera предоставляет именованные каналы камеры через высокоуровневые методы:
from openmv.camera import Camera
with Camera('/dev/ttyACM0', baudrate=921600) as cam:
cam.update_channels()
if cam.has_channel('status'):
size = cam.channel_size('status')
data = cam.channel_read('status', size)
Предупреждение
Пакет openmv требует CPython 3.12 или новее. В более ранних интерпретаторах отсутствуют возможности, от которых зависит SDK; установите сборку 3.12+ перед pip install openmv.
Несколько моментов, на которые стоит обратить внимание при настройке:
Строка последовательного порта – здесь
/dev/ttyACM0– имеет видCOM3в Windows,/dev/cu.usbmodemXXXXв macOS и/dev/ttyACM*в Linux. Фактический номер зависит от того, как порт камеры был перечислен.Скорость передачи – это магическое значение протокола
921600, которое USB-CDC-стек камеры распознаёт как «этот клиент говорит на протоколе, а не с REPL». Любая другая скорость приводит к откату на обычную последовательную линию.Контекстный менеджер
with Camera(...) as cam:открывает транспорт, выполняетPROTO_SYNC, обменивается возможностями и при выходе корректно закрывает порт. Явный вызовupdate_channels()после входа обновляет локальный список каналов любыми каналами, которые приложение зарегистрировало после загрузки.
channel_size() и channel_read() – основные рабочие методы; channel_write() передаёт буфер на камеру и обратно, если бэкенд имеет метод write; has_channel() – безопасный способ проверить, что имя зарегистрировано, прежде чем использовать его. Имя канала один раз преобразуется в идентификатор канала, назначенный камерой во время register, и используется в каждом пакете с этого момента.
Каждая пара channel_size() / channel_read() стоит двух обращений: один пакет для запроса размера, один для запроса байтов. По USB-CDC оба завершаются примерно за миллисекунду в сумме; по UART тот же обмен занимает дольше пропорционально скорости передачи последовательной линии. Код приложения, который читает в плотном цикле, должен вызывать channel_size() только тогда, когда размер действительно может измениться – для данных фиксированного размера размер из первого вызова можно кешировать.
12.6.4. Независимость между каналами¶
Три вещи стоит знать о том, как взаимодействуют каналы:
Независимое управление потоком. У каждого канала есть собственное состояние ожидающего чтения, собственные данные и собственные функции обратного вызова
size/read/write. Длительное чтение на каналеstreamне блокирует чтение на канале приложенияconfig.Последовательность в пределах канала. В пределах одного канала пакеты доставляются по порядку. Уровень надёжности гарантирует это даже при наличии повторных передач.
Общий транспорт, общий бюджет повторных передач. Все каналы используют одну физическую линию, поэтому поток трафика на одном канале замедляет остальные, занимая провод. Механизм
CHANNEL_LOCKпозволяет одному каналу зарезервировать провод для атомарного многопакетного чтения; бэкенд подключает это, реализуя функции обратного вызоваlock/unlock.
Канал – это минимальная область, на которой программа хоста и программа камеры договариваются о взаимодействии. Имя, направленность (чтение, запись или оба), методы обратного вызова на стороне камеры и соответствующие вызовы методов на стороне хоста составляют весь контракт.