12.8. วิดเจ็ตและเทเลเมทรีด้วย CBORChannel#

แบ็กเอนด์ในสองหน้าก่อนหน้าเคลื่อนย้ายไบต์ดิบ และแชนเนลไบต์ดิบต้องการโปรแกรมฝั่งโฮสต์ที่รู้วิธีถอดรหัส ส่วนใหญ่แล้วสิ่งที่กล้องต้องการเผยแพร่นั้นเรียบง่ายกว่านั้น: ค่าอ่านที่มีชื่อไม่กี่ค่า ตัวควบคุมที่มีชื่อไม่กี่ตัว รูปคลื่น แผนที่ความลึก สำหรับกรณีนั้น แพ็กเกจ protocol มาพร้อม protocol.CBORChannel แบ็กเอนด์สำเร็จรูปที่เก็บ ฟิลด์ ที่มีชื่อ ซีเรียไลซ์พวกมันเป็นเรคคอร์ด CBOR ในเลย์เอาต์ SenML และถอดรหัสการเขียนจากโฮสต์กลับเป็นค่าของฟิลด์ ผลตอบแทนของมันคือ OpenMV IDE เข้าใจมันอยู่แล้ว: ลงทะเบียน CBORChannel แล้ว มุมมอง Channels ของ IDE จะแสดงผลทุกฟิลด์เป็นวิดเจ็ตสด -- ป้ายกำกับ สวิตช์ แถบเลื่อน กราฟ แผนที่ -- โดยไม่ต้องมีโค้ดฝั่งโฮสต์เลย เรคคอร์ดเดียวกันคือ CBOR ธรรมดา โฮสต์แบบกำหนดเองจึงถอดรหัสด้วยไลบรารี CBOR ใด ๆ ได้เมื่อคุณโตเกิน IDE

The Channels view showing a thermal camera script's controls channel: FPA and AUX temperature readouts with their unit, Measurement and High Temp toggles, Temp Min and Temp Max sliders, and H-Mirror and V-Flip toggles

แชนเนลของตัวอย่าง 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 สลับแผงใต้เฟรมบัฟเฟอร์ไปที่ Channels แล้ววิดเจ็ตจะปรากฏ: label คือค่าแบบอ่านอย่างเดียว (พร้อม unit ข้าง ๆ), text คือบล็อกของ rich text แบบคงที่, toggle คือสวิตช์, slider และ spinbox กำหนดตัวเลขภายใน min / max / step (กล่องหมุนค่าสำหรับการป้อนที่แม่นยำ), radio และ select เลือกหนึ่งใน options, lineedit คือช่องข้อความอิสระ และ pushbutton คือการสั่งการชั่วขณะที่เรียก on_write ด้วย True สคริปต์อ่านค่าตัวควบคุมกลับด้วย ch["Threshold"] ได้ทุกเมื่อที่ต้องการ -- แชนเนลเก็บค่าล่าสุดไว้ -- และ on_write คือ hook สำหรับตัวที่ต้องการการตอบสนองทันที tuple (min, max, value) ที่กำหนดให้แถบเลื่อนหรือกล่องหมุนค่าจะเลื่อนช่วงของมันไปพร้อมกับค่า ซึ่งเป็นวิธีที่ตัวควบคุมติดตามเซ็นเซอร์ที่ขีดจำกัดขึ้นอยู่กับการตั้งค่าอื่น

คอลแบ็ก on_read(channel) ที่เลือกใส่ได้จะรันก่อนที่แชนเนลจะถูกซีเรียไลซ์ให้โฮสต์ -- ที่สำหรับสุ่มตัวอย่างเซ็นเซอร์ลงใน label เพื่อให้มีการอ่านค่าเฉพาะเมื่อมีคนกำลังดูอยู่:

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 บิต หนึ่งระยะทางต่อเซลล์ -- สิ่งที่เซ็นเซอร์ time-of-flight สร้างขึ้น 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 จับคู่แชนเนล depth แบบนี้กับแชนเนลค่าอ่านและตัวตรวจจับใบหน้าบนกล้องเดียวกัน การเรียก 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 แสดงรายการทุกคีย์ แต่สำหรับโปรเจกต์ส่วนใหญ่ มุมมอง Channels ของ IDE คือโฮสต์ และโค้ดเดียวที่มีคือสคริปต์ฝั่งกล้องข้างต้น