13.3.1.4. Власні канали¶
Канал — це іменований двонапрямлений байтовий потік між скриптом на стороні камери та хостом. Камера реєструє канал і надає зворотні виклики, що виробляють або споживають дані; хост зчитує з цього каналу і записує в нього за іменем. Той самий механізм, який пакет використовує внутрішньо для каналу stream, що передає кадри, каналу stdout, що передає вивід скрипту, і каналу stdin, що передає завантаження скрипту, відкритий для користувацьких скриптів, тому будь-які специфічні для застосунку дані, потрібні хосту, можуть використовувати те саме USB-з’єднання без необхідності створювати другий протокол.
Це найкорисніша функція пакета, яку стандартна документація висвітлює найменше, тому ця сторінка розглядає її від початку до кінця.
13.3.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.3.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.3.1.4.3. Що це дає застосунку¶
Власні канали — правильний інструмент, коли застосунок хоче використовувати наявне USB-з’єднання для даних, що не є кадрами чи виводом: лічильники телеметрії, налаштувальні параметри, що транслюються з UI хоста в реальному часі, керуючі команди в зворотному напрямку, результати вимірювань, обчислених камерою, що не підходять для «зображення» у форматі, який припускає канал трансляції. Рівень протоколу обробляє формування кадрів, фрагментацію, підтвердження і повторні спроби; скрипту потрібно лише реалізувати бекенд із чотирьох методів, а хосту — знати лише ім’я каналу та форму даних.
Прапор --channel NAME в CLI — швидкий спосіб перевірити власний канал з термінала без написання програми на стороні хоста: CLI опитує вказаний канал і виводить перші десять байтів кожного оновлення.
Обмеження розміру для одного виклику channel_read() або channel_write() — це узгоджений max_payload протоколу (за замовчуванням 4096 байт). Методи на стороні хоста автоматично розбивають більші записи на потрібну кількість пакетів, тому застосунок може передавати довільно великі буфери; фрагментація є невидимою.