13.4.1.4. Власні канали#
Канал — це іменований двонапрямлений байтовий потік між скриптом на стороні камери та хостом. Камера реєструє канал і надає зворотні виклики, що виробляють або споживають дані; хост зчитує з цього каналу і записує в нього за іменем. Той самий механізм, який пакет використовує внутрішньо для каналу stream, що передає кадри, каналу stdout, що передає вивід скрипту, і каналу stdin, що передає завантаження скрипту, відкритий для користувацьких скриптів, тому будь-які специфічні для застосунку дані, потрібні хосту, можуть використовувати те саме USB-з’єднання без необхідності створювати другий протокол.
Це найкорисніша функція пакета, яку стандартна документація висвітлює найменше, тому ця сторінка розглядає її від початку до кінця.
13.4.1.4.1. Дві половини#
Власний канал потребує взаємодіючого коду з обох сторін. Скрипт на стороні камери імпортує protocol, визначає клас із трьома методами (size(), read(), poll()) та необов’язковим write(), і викликає protocol.register(name=..., backend=...) для публікації каналу під обраним ім’ям:
import protocol
import time
class TicksChannel:
def size(self):
return 10
def read(self, offset, size):
return f'{time.ticks_ms():010d}'
def poll(self):
return True
protocol.register(name='ticks', backend=TicksChannel())
Метод size() повертає кількість байтів, наявних у каналі на даний момент. read() є виробником: отримавши offset та size, запитані хостом, він повертає байти (або рядок, який кодує рівень протоколу). poll() повертає True, коли є що зчитати — рівень протоколу використовує це для позначення каналу як готового в read_status().
Програма на стороні хоста використовує чотири методи openmv.Camera: has_channel() для перевірки існування каналу, channel_size() для запиту обсягу очікуючих даних, channel_read() для витягання байтів і channel_write() для надсилання байтів. read_status() опитує всі канали одночасно:
from openmv import Camera
with Camera('/dev/ttyACM0') as cam:
cam.stop()
cam.exec(open('ticks_cam.py').read())
while True:
status = cam.read_status()
if status.get('ticks'):
data = cam.channel_read('ticks')
print(f"ticks: {data.decode()}")
Цикл хоста опитує read_status(); коли канал ticks готовий, викликається channel_read() без size, щоб витягнути все доступне. TicksChannel.poll() камери повертає True при кожній перевірці, тому канал завжди «готовий» і хост отримує свіже значення tick при кожному опитуванні.
13.4.1.4.2. Двонапрямлений канал#
Для хоста, якому потрібно надсилати дані назад, клас на стороні камери додає метод write(), що приймає вхідні байти:
import protocol
class CommandChannel:
def __init__(self):
self.last_command = b''
self.replied = False
def size(self):
return len(self.last_command)
def read(self, offset, size):
self.replied = True
return self.last_command
def write(self, offset, data):
self.last_command = b'echo: ' + bytes(data)
self.replied = False
def poll(self):
return not self.replied and len(self.last_command) > 0
protocol.register(name='echo', backend=CommandChannel())
Хост записує в канал через channel_write() і зчитує відповідь через стандартний шаблон read_status() / channel_read()
with Camera('/dev/ttyACM0') as cam:
cam.stop()
cam.exec(open('echo_cam.py').read())
cam.channel_write('echo', b'hello')
while True:
if cam.read_status().get('echo'):
print(cam.channel_read('echo').decode())
break
13.4.1.4.3. Що це дає застосунку#
Власні канали — правильний інструмент, коли застосунок хоче використовувати наявне USB-з’єднання для даних, що не є кадрами чи виводом: лічильники телеметрії, налаштувальні параметри, що транслюються з UI хоста в реальному часі, керуючі команди в зворотному напрямку, результати вимірювань, обчислених камерою, що не підходять для «зображення» у форматі, який припускає канал трансляції. Рівень протоколу обробляє формування кадрів, фрагментацію, підтвердження і повторні спроби; скрипту потрібно лише реалізувати бекенд із чотирьох методів, а хосту — знати лише ім’я каналу та форму даних.
Прапор --channel NAME в CLI — швидкий спосіб перевірити власний канал з термінала без написання програми на стороні хоста: CLI опитує вказаний канал і виводить перші десять байтів кожного оновлення.
Обмеження розміру для одного виклику channel_read() або channel_write() — це узгоджений max_payload протоколу (за замовчуванням 4096 байт). Методи на стороні хоста автоматично розбивають більші записи на потрібну кількість пакетів, тому застосунок може передавати довільно великі буфери; фрагментація є невидимою.