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 байт). Методи на стороні хоста автоматично розбивають більші записи на потрібну кількість пакетів, тому застосунок може передавати довільно великі буфери; фрагментація є невидимою.