12.8. Widgetek és telemetria a CBORChannel segítségével#

Az előző két oldal háttérrendszerei nyers bájtokat mozgatnak, és egy nyers bájtcsatornához olyan gazdagépprogram kell, amely tudja, hogyan dekódolja őket. Legtöbbször az, amit egy kamera közzé akar tenni, ennél egyszerűbb: néhány nevesített leolvasás, néhány nevesített vezérlő, egy hullámforma, egy mélységtérkép. Erre az esetre a protocol csomag a protocol.CBORChannel-t szállítja, egy kész háttérrendszert, amely nevesített mezőket tárol, CBOR rekordokként sorosítja őket SenML elrendezésben, és a gazdagép írásait visszadekódolja mezőértékekké. A hozadéka az, hogy az OpenMV IDE már érti: regisztrálj egy CBORChannel-t, és az IDE Csatornák nézete minden mezőt élő widgetként jelenít meg – címkék, kapcsolók, csúszkák, grafikonok, térképek – mindenféle gazdagépkód nélkül. Ugyanezek a rekordok sima CBOR-ok, így egy egyéni gazdagép bármely CBOR könyvtárral dekódolja őket, amikor kinövöd az IDE-t.

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

A controls_channel.py példa csatornája, ahogy az IDE megjeleníti: kiolvasások, kapcsolók és csúszkák egy FLIR Leptonhoz. Minden vezérlő visszaír a szkriptnek.#

12.8.1. Kiolvasások és vezérlők#

A CBORChannel tipizált mezők szótára. Az add mindegyiket egy névvel, egy widget type-pal és a típus által igényelt argumentumokkal deklarálja; a ch["name"]-hez való hozzárendelés frissít egy mezőt; az on_write visszahívás kapja meg a gazdagép változtatásait:

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)

Futtasd az IDE-ben, váltsd a képkocka-puffer alatti ablaktáblát a Csatornák nézetre, és megjelennek a widgetek: a label csak olvasható érték (mellette a unit-jával), a text statikus formázott szövegblokk, a toggle kapcsoló, a slider és a spinbox egy számot állít be a min / max / step keretein belül (a léptetőmező a pontos bevitelhez), a radio és a select az options közül választ egyet, a lineedit szabad szöveges mező, a pushbutton pedig pillanatnyi művelet, amely True értékkel hívja az on_write-ot. A szkript bármikor visszaolvashatja a vezérlőit a ch["Threshold"] segítségével – a csatorna a legutóbbi értéket tárolja –, az on_write pedig a horog azokhoz, amelyeknél azonnali reakció kell. Egy csúszkához vagy léptetőmezőhöz rendelt (min, max, value) rekord az értékével együtt a tartományát is mozgatja, így követ egy vezérlő egy olyan érzékelőt, amelynek határai egy másik beállítástól függnek.

Az opcionális on_read(channel) visszahívás közvetlenül azelőtt fut, hogy a csatorna a gazdagép számára sorosításra kerülne – ez a hely, ahol egy érzékelőt egy címkébe mintavételezhetsz, hogy a leolvasás csak akkor történjen meg, amikor valaki nézi:

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

A File → Examples → 12-Protocol alatti controls_channel.py példa pontosan ezt a formát használja egy FLIR Lepton mérési módjának, hőmérséklet-tartományának és tükrözési / átfordítási beállításainak elérhetővé tételére, miközben a kamera közvetít.

12.8.2. Hullámformák#

A waveform mező mintablokkot hordoz mintavételi sebességgel. A minták egy array nyers bájtjai a mező typecode-jában (alapértelmezés szerint előjel nélküli 16 bites); több jel egyszerre – egy gyorsulásmérő három tengelye – esetén fésüld őket össze, és add meg, hány series van, és hogyan nevezzük őket:

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)

Az IDE minden hullámformát görgetődő grafikonként ábrázol, adatsoronként egy görbével, minden adatdarabot időbélyeggel lát el, hogy a frissítések közötti hézagok hézagként látszódjanak, és spektrumnézetet, kioldókat, jelölőket, valamint CSV, WAV, NumPy vagy Edge Impulse fájlba rögzítést kínál. Cseréld a szintetikus szinuszt audio mintákra vagy egy IMU-illesztőprogram leolvasásaira, és a szkript adatgyűjtő eszközzé válik.

12.8.3. Mélységtérképek#

A depth mező width x height méretű, 32 bites lebegőpontos számokból álló rács, cellánként egy távolsággal – amit egy repülésiidő-érzékelő előállít. A min / max deklarálja a tartományt, amelyet a gazdagépnek színekre kell leképeznie; az IDE a színek tartományát automatikusan is beállíthatja az adatokból:

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)

A sensors_channel.py példa egy ilyen mélységcsatornát egy leolvasáscsatornával és egy arcérzékelővel párosít ugyanazon a kamerán. A send_event() hívás opcionális – az IDE magától lekérdezi a csatornákat –, de egy eseményvezérelt gazdagépnek jelzi, hogy új képkocka áll készen, anélkül hogy annak kérdeznie kellene.

12.8.4. Dekódolás egyéni gazdagépen#

A rekordok szabványos CBOR-ok: minden csatornaolvasás a SenML egész kulcsaival (0 név, 1 mértékegység, 2 numerikus érték, 3 karakterlánc érték, 8 adatérték, és negatív kulcsok a widget típusához, az opciókhoz, a tartományhoz és a dimenziókhoz) kulcsolt leképezések tömbjét adja vissza. Az IDE-t kinőtt gazdagép a channel_read() segítségével olvassa a csatornát, és bármely CBOR könyvtárral dekódolja (pip install cbor2); egy vezérlő visszaírása {0: name, 2: value} leképezések CBOR tömbje, amelyet a channel_write() küld el. A protocol.CBORChannel referencia minden kulcsot felsorol. A legtöbb projektben azonban az IDE Csatornák nézete a gazdagép, és az egyetlen kód a fenti kameraoldali szkript.