12.8. Widgetit ja telemetria CBORChannelilla#

Kahden edellisen sivun taustaosat siirtävät raakatavuja, ja raakatavukanava tarvitsee isäntäohjelman, joka osaa dekoodata ne. Useimmiten se, mitä kamera haluaa julkaista, on tätä yksinkertaisempaa: muutama nimetty lukema, muutama nimetty säädin, aaltomuoto, syvyyskartta. Tätä tapausta varten protocol-paketti sisältää luokan protocol.CBORChannel, valmiin taustaosan, joka säilyttää nimettyjä kenttiä, sarjallistaa ne CBOR-tietueiksi SenML-asettelussa ja dekoodaa isännän kirjoitukset takaisin kenttäarvoiksi. Sen etu on, että OpenMV IDE ymmärtää sen jo valmiiksi: rekisteröi CBORChannel, niin IDE:n Kanavat-näkymä piirtää jokaisen kentän reaaliaikaisena widgettinä – tunnisteina, kytkiminä, liukusäätiminä, kuvaajina, karttoina – ilman lainkaan isäntäkoodia. Samat tietueet ovat tavallista CBOR:ia, joten mukautettu isäntä dekoodaa ne millä tahansa CBOR-kirjastolla, kun kasvat ulos IDE:stä.

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-esimerkin kanava IDE:n piirtämänä: lukemat, kytkimet ja liukusäätimet FLIR Leptonille. Jokainen säädin kirjoittaa takaisin skriptille.#

12.8.1. Lukemat ja säätimet#

CBORChannel on sanakirja tyypitettyjä kenttiä. add ilmoittaa kunkin nimellä, widgetin type-tyypillä ja tyypin tarvitsemilla argumenteilla; sijoitus kohteeseen ch["name"] päivittää kentän; on_write-takaisinkutsu vastaanottaa isännän muutokset:

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)

Suorita se IDE:ssä, vaihda kehyspuskurin alapuolinen ruutu Kanavat-näkymään, niin widgetit ilmestyvät: label on vain luettava arvo (unit sen vieressä), text on lohko staattista muotoiltua tekstiä, toggle on kytkin, slider ja spinbox asettavat luvun min / max / step -rajoissa (askelluskenttä tarkkaa syöttöä varten), radio ja select valitsevat yhden options-vaihtoehdoista, lineedit on vapaa tekstikenttä, ja pushbutton on hetkellinen toiminto, joka kutsuu on_write-takaisinkutsua arvolla True. Skripti lukee säätimensä takaisin lausekkeella ch["Threshold"] milloin tahansa – kanava säilyttää viimeisimmän arvon – ja on_write on koukku niille, jotka tarvitsevat välittömän reaktion. Liukusäätimeen tai askelluskenttään sijoitettu (min, max, value)-monikko siirtää sen aluetta arvon mukana, millä säädin seuraa anturia, jonka rajat riippuvat toisesta asetuksesta.

Valinnainen on_read(channel)-takaisinkutsu ajetaan juuri ennen kuin kanava sarjallistetaan isännälle – paikka, jossa anturi näytteistetään tunnisteeseen, jotta lukema otetaan vain, kun joku katsoo:

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

controls_channel.py-esimerkki kohdassa File → Examples → 12-Protocol käyttää täsmälleen tätä muotoa paljastaakseen FLIR Leptonin mittaustilan, lämpötila-alueen ja peilaus- / kääntöasetukset kameran suoratoistaessa.

12.8.2. Aaltomuodot#

waveform-kenttä kuljettaa lohkon näytteitä näytteenottotaajuuksineen. Näytteet ovat array-taulukon raakatavut kentän typecode-tyyppikoodissa (oletuksena etumerkitön 16-bittinen); useaa signaalia varten kerralla – kiihtyvyysanturin kolme akselia – lomita ne ja kerro, kuinka monta series-sarjaa on ja miksi niitä kutsutaan:

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 piirtää jokaisen aaltomuodon vierivänä kuvaajana, jossa on yksi jälki sarjaa kohti, aikaleimaa jokaisen lohkon, jotta päivitysten väliset aukot näkyvät aukkoina, ja tarjoaa spektrinäkymän, liipaisut, merkit ja tallennuksen CSV-, WAV-, NumPy- tai Edge Impulse -tiedostoihin. Korvaa synteettinen siniaalto audio-näytteillä tai IMU-ajurin lukemilla, niin skripti on datankeruutyökalu.

12.8.3. Syvyyskartat#

depth-kenttä on width x height -ruudukko 32-bittisiä liukulukuja, yksi etäisyys solua kohti – sitä, mitä lentoaika-anturi tuottaa. min / max ilmoittavat alueen, jonka isännän tulisi kuvata väreiksi; IDE voi myös määrittää värialueen automaattisesti datasta:

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-esimerkki yhdistää tällaisen syvyyskanavan lukemakanavaan ja kasvontunnistimeen samassa kamerassa. send_event()-kutsu on valinnainen – IDE kyselee kanavia itse – mutta se kertoo tapahtumapohjaiselle isännälle, että uusi kehys on valmis, ilman että sen tarvitsee kysyä.

12.8.4. Dekoodaus mukautetussa isännässä#

Tietueet ovat standardia CBOR:ia: jokainen kanavan luku palauttaa taulukon karttoja, joiden avaimina ovat SenML:n kokonaislukuavaimet (0 nimi, 1 yksikkö, 2 numeerinen arvo, 3 merkkijonoarvo, 8 data-arvo sekä negatiiviset avaimet widgetin tyypille, vaihtoehdoille, alueelle ja mitoille). Isäntä, joka on kasvanut ulos IDE:stä, lukee kanavan metodilla channel_read() ja dekoodaa sen millä tahansa CBOR-kirjastolla (pip install cbor2); säätimen kirjoittaminen takaisin on CBOR-taulukko {0: name, 2: value}-karttoja, joka lähetetään metodilla channel_write(). protocol.CBORChannel-viite luettelee jokaisen avaimen. Useimmissa projekteissa IDE:n Kanavat-näkymä on kuitenkin isäntä, ja ainoa koodi on yllä oleva kameran puolen skripti.