12.8. عناصر الواجهة والقياس عن بُعد باستخدام CBORChannel#
تنقل الواجهات الخلفية في الصفحتين السابقتين بايتات خامًا، وتحتاج قناة البايتات الخام إلى برنامج مضيف يعرف كيف يفكّ ترميزها. وفي معظم الأحيان يكون ما تريد الكاميرا نشره أبسط من ذلك: بضع قراءات مسمّاة، وبضعة عناصر تحكم مسمّاة، وشكل موجي، وخريطة عمق. لهذه الحالة تشحن حزمة protocol الصنف protocol.CBORChannel، وهو واجهة خلفية جاهزة تحمل حقولًا مسمّاة، وتسلسلها كسجلات CBOR بتخطيط SenML، وتفكّ ترميز كتابات المضيف إلى قيم الحقول. وثمرته أن OpenMV IDE يفهمه مسبقًا: سجّل CBORChannel فيعرض عرض Channels في الـ IDE كل حقل كعنصر واجهة حي -- تسميات ومفاتيح وأشرطة تمرير ورسوم بيانية وخرائط -- دون أي شيفرة مضيف على الإطلاق. والسجلات نفسها هي CBOR عادي، لذا يفكّ مضيف مخصّص ترميزها بأي مكتبة CBOR عندما تتجاوز حاجتك الـ IDE.

قناة المثال 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، فتظهر عناصر الواجهة: التسمية قيمة للقراءة فقط (مع unit بجانبها)، وtext كتلة من نص منسّق ثابت، وtoggle مفتاح، وslider وspinbox يضبطان رقمًا ضمن min / max / step (مربع الزيادة والنقصان للإدخال الدقيق)، وradio وselect يختاران أحد options، وlineedit حقل نص حر، وpushbutton إجراء لحظي يستدعي on_write بـ True. ويقرأ البرنامج النصي عناصر تحكمه مجددًا بـ ch["Threshold"] متى شاء -- فالقناة تحتفظ بآخر قيمة -- وon_write هو الخطّاف لتلك التي تحتاج إلى رد فعل فوري. وإسناد صفّ (min, max, value) إلى شريط تمرير أو مربع زيادة ونقصان يحرّك نطاقه مع قيمته، وهكذا يتتبع عنصر تحكم مستشعرًا تعتمد حدوده على إعداد آخر.
يعمل رد النداء الاختياري on_read(channel) قبل تسلسل القناة للمضيف مباشرةً -- وهو المكان لأخذ عينة من مستشعر إلى تسمية بحيث لا تُؤخذ القراءة إلا عندما ينظر أحد:
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 بت، مسافة واحدة لكل خلية -- وهو ما ينتجه مستشعر زمن الطيران. يعلن 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 قناة عمق كهذه بقناة قراءات وكاشف وجوه على الكاميرا نفسها. واستدعاء 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 هو المضيف لمعظم المشاريع، والشيفرة الوحيدة هي البرنامج النصي على جانب الكاميرا أعلاه.