12.9. Двостороннє передавання¶
Канали не є односторонніми. Бекенд, що реалізує write, дозволяє хосту надсилати байти до камери, і камера реагує. Це шаблон, що лежить в основі кожного справжнього інтерактивного інструменту: оператор повертає ручку в GUI хоста, хост записує нове значення в канал конфігурації, камера зчитує його наступного разу, коли робить знімок.
12.9.1. Канал конфігурації¶
Додаючи до скрипта потокової передачі на стороні камери, надайте другий канал для якості JPEG:
class ConfigChannel:
def __init__(self):
self.quality = 85
def size(self):
return 0
def read(self, offset, size):
# Not used for "host writes to cam" -- but the library
# still needs the method present.
return b''
def write(self, offset, data):
# data is a bytearray view into the protocol buffer.
# Copy out the contents before doing anything with it.
new_q = int(bytes(data))
if 1 <= new_q <= 100:
self.quality = new_q
return len(data)
config = ConfigChannel()
protocol.register(name='config', backend=config)
Цикл захоплення зчитує значення з config.quality щоразу, коли стискає кадр:
while True:
img = csi0.snapshot()
latest_jpeg = bytes(
img.compress(quality=config.quality).bytearray()
)
ch.send_event(0x01)
Тепер у хоста є ручка керування. Встановіть значення 50, і наступний кадр буде меншим (і гіршої якості); встановіть 95, і наступний кадр буде більшим (і чіткішим). Камера продовжує захоплення без перезапуску; хосту не потрібно надсилати новий скрипт.
12.9.2. Виклик запису з хоста¶
На стороні хоста channel_write() надсилає байти до іменованого каналу:
cam.channel_write('config', b'50')
SDK хоста кодує байти як один (або фрагментований) пакет CHANNEL_WRITE, рівень протоколу доставляє його камері, запускається write(offset=0, data=...) камери, і сторона камери підтверджує отримання. До моменту повернення виклику камера отримала та прийняла нове значення.
Запис є атомарним з точки зору камери – бібліотека протоколу гарантує, що write бекенду виконається до завершення, перш ніж будь-яка інша операція на цьому каналі продовжиться. Код застосунку може зчитувати config.quality всередині циклу захоплення, не турбуючись про те, що хост перезапише значення в середині знімка.
12.9.3. Розмір заглушки та читання на каналі лише для запису¶
Чистий канал лише для запису все одно потребує визначення size та read, навіть якщо вони є заглушками, що повертають 0 і b''. Бібліотека використовує наявність методів для визначення бітових прапорів можливостей каналу; бекенд без read не матиме встановленого CHANNEL_FLAG_READ, і хост відхилить спробу читання.
Байти, що повертаються з read на каналі лише для запису, корисні для іншої мети: відображення поточного значення, щоб хост, що щойно підключився, міг запитати камеру «яке поточне налаштування?», а не починати зі значення за замовчуванням. Щоб це працювало, обидва напрямки мають узгодити серіалізацію. Парсинг сирих байтів int(bytes(data)) у попередньому прикладі працює для одного цілочисельного поля, але не масштабується, коли з’явиться друга ручка. Перехід write до парсингу JSON і поєднання з read, що повертає відповідний JSON-дамп, перетворює канал на справжнє двостороннє сховище конфігурації:
import json
class ConfigChannel:
def __init__(self):
self.quality = 85
self._buf = b''
def size(self):
self._buf = json.dumps({'quality': self.quality}).encode()
return len(self._buf)
def read(self, offset, size):
return self._buf[offset:offset + size]
def write(self, offset, data):
new = json.loads(bytes(data))
if 'quality' in new:
self.quality = int(new['quality'])
return len(data)
Тепер хост записує cam.channel_write('config', b'{"quality": 50}') для встановлення значення та cam.channel_read('config') для зчитування поточного стану. Камера серіалізує свіжий JSON-дамп при кожному читанні, тому хост завжди бачить актуальні значення, а додавання ще однієї ручки (threshold, exposure, orientation) – це один рядок у словнику JSON з кожного боку.
12.9.4. Повний цикл¶
З каналом кадрів для передачі від камери до хоста, каналом конфігурації для керування від хоста до камери та невеликою кількістю клею застосунок стає інтерактивним інструментом:
Хост відкриває камеру, починає отримувати кадри та відображає їх у вікні.
Коли оператор пересуває повзунок, хост записує нове значення в канал
config.Цикл захоплення камери зчитує значення на наступному кадрі.
Нові кадри надходять через той самий канал
frame.
Ось і вся модель. Два канали, два зворотних виклики кожен, цикл захоплення на камері, цикл читання-запису на хості. Жодної видимої логіки формування кадрів, жодної видимої обробки помилок – бібліотека протоколу робить надійний рух байтів непомітним.
Все, що йде після цього, є кодом застосунку. Додавання третього каналу для гістограми, четвертого для телеметрії або п’ятого для тригерів датчика – це той самий рецепт класу бекенду та protocol.register, повторений. Як тільки проєкт камери досягає цього моменту, протокол перестає бути цікавою проблемою; власна логіка застосунку стає нею.