12.9. Двунаправленный поток¶
Каналы не односторонние. Бэкенд, реализующий write, позволяет хосту отправлять байты в камеру, и камера реагирует. Это шаблон, лежащий в основе любого настоящего интерактивного инструмента: оператор поворачивает ручку в графическом интерфейсе хоста, хост записывает новое значение в канал конфигурации, а камера считывает его при следующем захвате.
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 на канале только для записи¶
Чистому каналу записи всё равно нужны определённые 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», повторённый снова. Как только проект камеры достигает этой точки, протокол перестаёт быть интересной проблемой; ею становится собственная логика приложения.