12.8. Віджети та телеметрія з CBORChannel#
Бекенди на попередніх двох сторінках переміщують сирі байти, а каналу сирих байтів потрібна хост-програма, яка вміє їх декодувати. Здебільшого те, що камера хоче публікувати, простіше: кілька іменованих показників, кілька іменованих елементів керування, осцилограма, карта глибини. Для цього випадку пакет protocol постачає protocol.CBORChannel – готовий бекенд, що зберігає іменовані поля, серіалізує їх як записи CBOR у форматі SenML і декодує записи хоста назад у значення полів. Його перевага в тому, що OpenMV IDE вже його розуміє: зареєструйте CBORChannel, і подання «Канали» IDE відобразить кожне поле як живий віджет – мітки, перемикачі, повзунки, графіки, карти – без жодного хост-коду. Ті самі записи – це звичайний CBOR, тож власний хост декодує їх будь-якою бібліотекою CBOR, коли ви переростете IDE.

Канал прикладу controls_channel.py, як його відображає IDE: показники, перемикачі та повзунки для FLIR Lepton. Кожен елемент керування записує значення назад скрипту.#
12.8.1. Показники та елементи керування#
CBORChannel – це словник типізованих полів. add оголошує кожне з ім’ям, type віджета та аргументами, які потрібні цьому типу; присвоєння ch["name"] оновлює поле; зворотний виклик on_write отримує зміни від хоста:
import time
import protocol
from protocol import CBORChannel
def on_write(ch, name, value):
# Called for every control the host changes.
print(name, "=", value)
if name == "Reset":
ch["Count"] = 0
ch = CBORChannel(on_write=on_write)
ch.add("Status", type="label", value="starting")
ch.add("Count", type="label", value=0)
ch.add("Enable", type="toggle", value=True)
ch.add("Threshold", type="slider", min=0, max=100, step=1,
value=50, unit="%")
ch.add("Gap", type="spinbox", min=0.0, max=15.0, step=0.1,
value=1.5, unit="mm")
ch.add("Mode", type="radio", options=["Idle", "Track", "Record"],
value="Idle")
ch.add("Quality", type="select", options=["Low", "Medium", "High"],
value="Medium")
ch.add("Name", type="lineedit", value="openmv-cam")
ch.add("Reset", type="pushbutton")
protocol.register(name="controls", backend=ch)
count = 0
while True:
if ch["Enable"]:
count += 1
ch["Count"] = count
ch["Status"] = "threshold %d%%" % ch["Threshold"]
time.sleep_ms(100)
Запустіть його в IDE, перемкніть панель під кадровим буфером на Канали, і з’являться віджети: label – це значення лише для читання (з unit поруч), text – блок статичного форматованого тексту, toggle – перемикач, slider і spinbox задають число в межах min / max / step (поле-лічильник для точного вводу), radio і select вибирають один із options, lineedit – поле вільного тексту, а pushbutton – миттєва дія, що викликає on_write з True. Скрипт може будь-коли зчитати свої елементи керування через ch["Threshold"] – канал зберігає останнє значення – а on_write є гачком для тих, що потребують негайної реакції. Кортеж (min, max, value), присвоєний повзунку або полю-лічильнику, переміщує його діапазон разом зі значенням – так елемент керування стежить за сенсором, межі якого залежать від іншого налаштування.
Необов’язковий зворотний виклик on_read(channel) виконується безпосередньо перед серіалізацією каналу для хоста – саме тут варто зчитувати сенсор у мітку, щоб показник знімався лише тоді, коли хтось дивиться:
def on_read(ch):
ch["FPA Temp"] = round(csi0.ioctl(csi.IOCTL_LEPTON_GET_FPA_TEMP), 1)
Приклад controls_channel.py у File → Examples → 12-Protocol використовує саме таку форму, щоб відкрити режим вимірювання, діапазон температур і налаштування дзеркалення / перевертання FLIR Lepton під час потокової передачі камери.
12.8.2. Осцилограми#
Поле waveform несе блок вибірок із частотою дискретизації. Вибірки – це сирі байти array у typecode поля (за замовчуванням беззнакові 16-бітні); для кількох сигналів одночасно – трьох осей акселерометра – чергуйте їх і вкажіть, скільки є series та як їх називати:
import math
import time
import protocol
from array import array
from protocol import CBORChannel
RATE = 1000 # samples per second
CHUNK = 100 # samples per update (100 ms)
ch = CBORChannel()
ch.add("Mic", type="waveform", sample_rate=RATE,
min=0, max=65535) # uint16
ch.add("IMU", type="waveform", sample_rate=RATE, series=3,
typecode="f", min=-2.0, max=2.0,
options=["X", "Y", "Z"], unit="g") # float32 x 3
protocol.register(name="signals", backend=ch)
t = 0
while True:
mic = array("H", (int(32768 + 20000 * math.sin(2 * math.pi * 50 * (t + i) / RATE))
for i in range(CHUNK)))
imu = array("f")
for i in range(CHUNK):
phase = 2 * math.pi * 2 * (t + i) / RATE
imu.extend((math.sin(phase), math.cos(phase), 1.0))
ch["Mic"] = bytes(mic) # the raw sample bytes
ch["IMU"] = bytes(imu)
t += CHUNK
time.sleep_ms(100)
IDE малює кожну осцилограму як графік, що прокручується, з одним трасуванням на серію, позначає кожен фрагмент часовою міткою, тож прогалини між оновленнями показуються як прогалини, і пропонує подання спектра, тригери, маркери та запис у файли CSV, WAV, NumPy або Edge Impulse. Замініть синтетичну синусоїду на вибірки audio або показники драйвера IMU – і скрипт стає інструментом збору даних.
12.8.3. Карти глибини#
Поле depth – це сітка width x height 32-бітних чисел з рухомою комою, по одній відстані на комірку – те, що створює сенсор часу прольоту. min / max оголошують діапазон, який хост має відобразити в кольори; IDE також може підібрати діапазон кольорів автоматично за даними:
import struct
import time
import tof
import protocol
from protocol import CBORChannel
tof.init()
ch = CBORChannel()
ch.add("depth", type="depth", width=tof.width(), height=tof.height(),
min=0, max=1000)
reg = protocol.register(name="ToF", backend=ch)
while True:
try:
d, dmin, dmax = tof.read_depth(vflip=True, hmirror=True)
except RuntimeError:
continue
ch["depth"] = struct.pack("<%df" % len(d), *d)
reg.send_event(0xFFFF)
time.sleep_ms(50)
Приклад sensors_channel.py поєднує такий канал глибини з каналом показників і детектором облич на тій самій камері. Виклик send_event() необов’язковий – IDE опитує канали самостійно – але він повідомляє подієво-орієнтованому хосту, що новий кадр готовий, без потреби запитувати.
12.8.4. Декодування на власному хості#
Записи – це стандартний CBOR: кожне читання каналу повертає масив словників із цілочисельними ключами SenML (0 ім’я, 1 одиниця, 2 числове значення, 3 рядкове значення, 8 значення даних та від’ємні ключі для типу віджета, варіантів, діапазону та розмірів). Хост, що переріс IDE, читає канал через channel_read() і декодує його будь-якою бібліотекою CBOR (pip install cbor2); запис елемента керування назад – це масив CBOR зі словників {0: name, 2: value}, надісланий через channel_write(). Довідка protocol.CBORChannel перелічує кожен ключ. Однак для більшості проєктів хостом є подання «Канали» IDE, а єдиний код – це наведений вище скрипт на боці камери.