12.8. 使用 CBORChannel 实现控件与遥测#
前两页的后端传输的是原始字节,而原始字节通道需要一个知道如何解码它们的主机程序。大多数时候摄像头想要发布的内容比这简单:几个命名读数、几个命名控件、一段波形、一幅深度图。针对这种情况,protocol 包附带了 protocol.CBORChannel,这是一个现成的后端,它保存命名的 字段,将它们序列化为 SenML 布局的 CBOR 记录,并将主机的写入解码回字段值。它的好处在于 OpenMV IDE 已经理解它:注册一个 CBORChannel,IDE 的 通道视图 就会把每个字段渲染为实时控件——标签、开关、滑块、图形、深度图——完全不需要主机代码。同样的记录就是普通的 CBOR,因此当你的需求超出 IDE 时,自定义主机可以用任何 CBOR 库解码它们。

IDE 所渲染的 controls_channel.py 示例的通道: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 是瞬时动作,会以 True 调用 on_write。脚本可以随时用 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)
File → Examples → 12-Protocol 下的 controls_channel.py 示例正是用这种形式在摄像头流式传输的同时公开 FLIR Lepton 的测量模式、温度范围和镜像 / 翻转设置。
12.8.2. 波形#
waveform 字段携带一块带采样率的采样数据。采样是以字段 typecode(默认为无符号 16 位)表示的 array 的原始字节;若要同时传多个信号——例如加速度计的三个轴——请将它们交错排列,并说明有多少个 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)解码;将控件写回则是一个由 {0: name, 2: value} 映射组成的 CBOR 数组,通过 channel_write() 发送。protocol.CBORChannel 参考列出了每一个键。不过对于大多数项目,IDE 的通道视图就是主机,唯一的代码就是上面的摄像头端脚本。