12.8. Widget và đo lường từ xa với CBORChannel#
Các backend ở hai trang trước di chuyển các byte thô, và một kênh byte thô cần một chương trình máy chủ biết cách giải mã chúng. Phần lớn thời gian, thứ mà camera muốn công bố đơn giản hơn thế: vài giá trị đọc có tên, vài điều khiển có tên, một dạng sóng, một bản đồ độ sâu. Cho trường hợp đó, gói protocol cung cấp protocol.CBORChannel, một backend làm sẵn giữ các trường có tên, tuần tự hóa chúng thành các bản ghi CBOR theo bố cục SenML, và giải mã các lần ghi của máy chủ trở lại thành giá trị trường. Lợi ích của nó là OpenMV IDE đã hiểu nó: đăng ký một CBORChannel và chế độ xem Kênh của IDE hiển thị mọi trường thành widget trực tiếp -- nhãn, công tắc, thanh trượt, đồ thị, bản đồ -- mà không cần mã máy chủ nào. Cùng các bản ghi đó là CBOR thuần, nên một máy chủ tùy chỉnh giải mã chúng bằng bất kỳ thư viện CBOR nào khi bạn vượt quá khả năng của IDE.

Kênh của ví dụ controls_channel.py như IDE hiển thị nó: giá trị đọc, công tắc và thanh trượt cho FLIR Lepton. Mọi điều khiển đều ghi ngược về tập lệnh.#
12.8.1. Giá trị đọc và điều khiển#
Một CBORChannel là một từ điển các trường có kiểu. add khai báo mỗi trường với một tên, một type widget và các đối số mà kiểu đó cần; gán vào ch["name"] cập nhật một trường; callback on_write nhận các thay đổi của máy chủ:
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)
Chạy nó trong IDE, chuyển khung bên dưới bộ đệm khung hình sang Kênh, và các widget xuất hiện: một label là giá trị chỉ đọc (với unit của nó bên cạnh), text là khối văn bản phong phú tĩnh, một toggle là công tắc, slider và spinbox đặt một số trong min / max / step (ô số để nhập chính xác), radio và select chọn một trong options, lineedit là trường văn bản tự do, và pushbutton là hành động tức thời gọi on_write với True. Tập lệnh đọc lại các điều khiển của nó bằng ch["Threshold"] bất cứ khi nào muốn -- kênh giữ giá trị mới nhất -- và on_write là móc nối cho những điều khiển cần phản ứng ngay lập tức. Một bộ (min, max, value) gán cho thanh trượt hoặc ô số dịch chuyển dải của nó cùng với giá trị, đó là cách một điều khiển theo dõi một cảm biến có giới hạn phụ thuộc vào cài đặt khác.
Callback tùy chọn on_read(channel) chạy ngay trước khi kênh được tuần tự hóa cho máy chủ -- nơi để lấy mẫu một cảm biến vào một nhãn sao cho chỉ đọc giá trị khi có ai đó đang xem:
def on_read(ch):
ch["FPA Temp"] = round(csi0.ioctl(csi.IOCTL_LEPTON_GET_FPA_TEMP), 1)
Ví dụ controls_channel.py trong File → Examples → 12-Protocol dùng đúng hình dạng này để phơi bày chế độ đo, dải nhiệt độ và cài đặt lật gương / lật của FLIR Lepton trong khi camera truyền phát.
12.8.2. Dạng sóng#
Một trường waveform mang một khối mẫu với tốc độ lấy mẫu. Các mẫu là các byte thô của một array theo typecode của trường (mặc định là 16-bit không dấu); với nhiều tín hiệu cùng lúc -- ba trục của gia tốc kế -- hãy xen kẽ chúng và cho biết có bao nhiêu series và gọi chúng là gì:
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 vẽ mỗi dạng sóng thành đồ thị cuộn với một vết cho mỗi chuỗi, gắn dấu thời gian cho mỗi khối để khoảng trống giữa các lần cập nhật hiện ra là khoảng trống, và cung cấp chế độ xem phổ, kích hoạt, điểm đánh dấu và ghi ra tệp CSV, WAV, NumPy hoặc Edge Impulse. Thay sóng sin tổng hợp bằng các mẫu audio hoặc số đọc của trình điều khiển IMU và tập lệnh trở thành công cụ thu thập dữ liệu.
12.8.3. Bản đồ độ sâu#
Một trường depth là một lưới width x height các số thực 32-bit, mỗi ô một khoảng cách -- thứ mà cảm biến thời gian bay tạo ra. min / max khai báo dải mà máy chủ nên ánh xạ sang màu; IDE cũng có thể tự động xác định dải màu từ dữ liệu:
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)
Ví dụ sensors_channel.py ghép một kênh độ sâu như thế này với một kênh giá trị đọc và một bộ phát hiện khuôn mặt trên cùng camera. Lời gọi send_event() là tùy chọn -- IDE tự thăm dò các kênh -- nhưng nó cho một máy chủ hướng sự kiện biết rằng một khung hình mới đã sẵn sàng mà máy chủ không phải hỏi.
12.8.4. Giải mã trên máy chủ tùy chỉnh#
Các bản ghi là CBOR chuẩn: mỗi lần đọc kênh trả về một mảng các map có khóa là các khóa số nguyên của SenML (0 tên, 1 đơn vị, 2 giá trị số, 3 giá trị chuỗi, 8 giá trị dữ liệu, và các khóa âm cho kiểu widget, tùy chọn, dải và kích thước). Một máy chủ đã vượt quá khả năng của IDE đọc kênh bằng channel_read() và giải mã nó bằng bất kỳ thư viện CBOR nào (pip install cbor2); ghi ngược một điều khiển là một mảng CBOR các map {0: name, 2: value} được gửi bằng channel_write(). Tài liệu tham khảo protocol.CBORChannel liệt kê mọi khóa. Tuy nhiên, với hầu hết các dự án, chế độ xem Kênh của IDE là máy chủ, và mã duy nhất là tập lệnh phía camera ở trên.