12.8. Widget dan telemetri dengan CBORChannel#

Backend di dua halaman sebelumnya memindahkan byte mentah, dan kanal byte mentah memerlukan program host yang tahu cara mendekodenya. Sering kali, apa yang ingin dipublikasikan sebuah kamera lebih sederhana dari itu: beberapa pembacaan bernama, beberapa kontrol bernama, sebuah bentuk gelombang, sebuah peta kedalaman. Untuk kasus tersebut paket protocol menyertakan protocol.CBORChannel, backend siap pakai yang menyimpan bidang bernama, menserialisasikannya sebagai record CBOR dalam tata letak SenML, dan mendekode penulisan dari host kembali menjadi nilai bidang. Keuntungannya adalah OpenMV IDE sudah memahaminya: daftarkan sebuah CBORChannel dan tampilan Kanal IDE merender setiap bidang sebagai widget langsung -- label, sakelar, slider, grafik, peta -- tanpa kode host sama sekali. Record yang sama adalah CBOR biasa, sehingga host kustom dapat mendekodenya dengan pustaka CBOR apa pun saat Anda sudah melampaui 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

Kanal contoh controls_channel.py sebagaimana dirender IDE: pembacaan, toggle, dan slider untuk FLIR Lepton. Setiap kontrol menulis kembali ke skrip.#

12.8.1. Pembacaan dan kontrol#

CBORChannel adalah kamus bidang bertipe. add mendeklarasikan masing-masing dengan nama, type widget, dan argumen yang dibutuhkan tipe tersebut; menetapkan nilai ke ch["name"] memperbarui sebuah bidang; callback on_write menerima perubahan dari host:

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)

Jalankan di IDE, alihkan panel di bawah buffer bingkai ke Kanal, dan widget-widget muncul: label adalah nilai hanya-baca (dengan unit di sampingnya), text adalah blok rich text statis, toggle adalah sakelar, slider dan spinbox menetapkan angka dalam min / max / step (spin box untuk entri yang presisi), radio dan select memilih salah satu dari options, lineedit adalah kolom teks bebas, dan pushbutton adalah aksi sesaat yang memanggil on_write dengan True. Skrip membaca kembali kontrolnya dengan ch["Threshold"] kapan pun diinginkan -- kanal menyimpan nilai terbaru -- dan on_write adalah kait untuk kontrol yang memerlukan reaksi segera. Tuple (min, max, value) yang ditetapkan ke slider atau spin box menggeser rentangnya bersama nilainya, yaitu cara sebuah kontrol mengikuti sensor yang batasnya bergantung pada pengaturan lain.

Callback on_read(channel) opsional berjalan tepat sebelum kanal diserialisasikan untuk host -- tempat untuk mencuplik sensor ke dalam label sehingga pembacaan hanya diambil saat seseorang sedang melihat:

def on_read(ch):
    ch["FPA Temp"] = round(csi0.ioctl(csi.IOCTL_LEPTON_GET_FPA_TEMP), 1)

Contoh controls_channel.py di bawah File → Contoh → 12-Protocol menggunakan bentuk yang persis seperti ini untuk mengekspos mode pengukuran, rentang suhu, dan pengaturan mirror / flip FLIR Lepton saat kamera melakukan streaming.

12.8.2. Bentuk gelombang#

Bidang waveform membawa blok sampel dengan laju sampel. Sampelnya adalah byte mentah dari sebuah array dalam typecode bidang tersebut (16-bit tak bertanda secara default); untuk beberapa sinyal sekaligus -- tiga sumbu akselerometer -- sisipkan (interleave) sinyal-sinyal tersebut dan sebutkan berapa banyak series yang ada dan apa namanya:

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 memplot setiap bentuk gelombang sebagai grafik bergulir dengan satu jejak per seri, memberi stempel waktu pada setiap potongan sehingga celah antar pembaruan tampak sebagai celah, dan menawarkan tampilan spektrum, pemicu, penanda, serta perekaman ke file CSV, WAV, NumPy, atau Edge Impulse. Ganti sinus sintetis dengan sampel audio atau pembacaan driver IMU, dan skrip tersebut menjadi alat pengumpulan data.

12.8.3. Peta kedalaman#

Bidang depth adalah kisi width x height berisi float 32-bit, satu jarak per sel -- apa yang dihasilkan sensor time-of-flight. min / max mendeklarasikan rentang yang harus dipetakan host ke warna; IDE juga dapat menentukan rentang warna secara otomatis dari data:

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)

Contoh sensors_channel.py memasangkan kanal depth seperti ini dengan kanal pembacaan dan detektor wajah pada kamera yang sama. Panggilan send_event() bersifat opsional -- IDE melakukan polling kanal dengan sendirinya -- tetapi panggilan itu memberi tahu host yang digerakkan peristiwa bahwa bingkai baru sudah siap tanpa host harus bertanya.

12.8.4. Pendekodean pada host kustom#

Record-nya adalah CBOR standar: setiap pembacaan kanal mengembalikan array peta yang dikunci dengan kunci integer SenML (0 nama, 1 satuan, 2 nilai numerik, 3 nilai string, 8 nilai data, dan kunci negatif untuk tipe widget, opsi, rentang, dan dimensi). Host yang telah melampaui IDE membaca kanal dengan channel_read() dan mendekodenya dengan pustaka CBOR apa pun (pip install cbor2); menulis kontrol kembali adalah array CBOR berisi peta {0: name, 2: value} yang dikirim dengan channel_write(). Referensi protocol.CBORChannel mencantumkan setiap kunci. Namun, untuk kebanyakan proyek, tampilan Kanal IDE adalah host-nya, dan satu-satunya kode adalah skrip sisi kamera di atas.