protocol --- قنوات بروتوكول OpenMV#
تُتيح الوحدة protocol بروتوكول مضيف OpenMV لـ Python. فهي تسمح بتهيئة وضبط حزمة البروتوكول من جانب البرنامج الثابت، وتتيح لشيفرة المستخدم تسجيل قنوات منطقية مخصصة مدعومة بكائن Python يطبّق واجهة القناة (read وwrite وsize وpoll وما إلى ذلك). هذا ما تتخاطب معه أدوات سطح المكتب المرافقة عندما تبث بيانات الصور أو تعرض عناصر واجهة تفاعلية لكاميرا متصلة.
أمثلة#
بث صورة RGB565 إلى أداة مضيف باستخدام خلفية مخصصة تطبّق واجهة القناة الخام (backend.size() وbackend.shape() وbackend.poll() وbackend.read()):
import csi
import protocol
csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.HD)
img = csi0.snapshot()
img_mv = memoryview(img.bytearray())
frame_ready = True
class FrameChannel:
def size(self):
return len(img_mv)
def shape(self):
return (img.height(), img.width(), len(img_mv))
def poll(self):
return frame_ready
def read(self, offset, size):
global frame_ready
end = offset + size
chunk = img_mv[offset:end]
if end >= len(img_mv):
frame_ready = False
return chunk
protocol.register(name="frame", backend=FrameChannel())
while True:
if not frame_ready:
img = csi0.snapshot()
img_mv = memoryview(img.bytearray())
frame_ready = True
البرنامج النصي المطابق من جانب المضيف، الذي يستخدم حزمة Python openmv (pip install openmv) للاتصال ودفع البرنامج النصي على الكاميرا وسحب كل إطار:
import cv2
import numpy as np
from openmv.camera import Camera
# The on-cam script above, stored as a string (or read from a file).
SCRIPT = open("frame_streamer_on_cam.py").read()
with Camera("/dev/ttyACM0", baudrate=921600) as cam:
cam.stop()
cam.exec(SCRIPT)
while True:
status = cam.read_status()
if not cam.has_channel("frame") or not status.get("frame"):
continue
h, w, size = cam._channel_shape(cam.get_channel(name="frame"))
if cam.channel_size("frame") < size:
continue
data = cam.channel_read("frame", size)
rgb565 = np.frombuffer(data, dtype="<u2").reshape(h, w)
# Unpack RGB565 to an HxWx3 uint8 RGB image.
r = ((rgb565 >> 11) & 0x1F) << 3
g = ((rgb565 >> 5) & 0x3F) << 2
b = ( rgb565 & 0x1F) << 3
frame = np.dstack([r, g, b]).astype(np.uint8)
# Display with OpenCV (cv2 expects BGR, not RGB).
cv2.imshow("OpenMV", cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))
if cv2.waitKey(1) == ord("q"):
break
cv2.destroyAllWindows()
استبدل /dev/ttyACM0 بمنفذ الكاميرا التسلسلي (مثل COM3 على Windows). يقبل مُنشئ openmv.camera.Camera نفس معاملات البروتوكول التي يقبلها init (crc / seq / ack / events / max_payload / max_retry / timeout) عند إعادة ضبط الحزمة من جانب الكاميرا لتطابقها.
الدوال#
- protocol.init(crc: bool = True, seq: bool = True, ack: bool = True, events: bool = True, max_payload: int = ..., rtx_retries: int = 3, rtx_timeout_ms: int = 500, lock_interval_ms: int = 10, poll_ms: int = 0) None#
تهيئة (أو إعادة ضبط) حزمة البروتوكول وتسجيل قنوات البيانات المنطقية الافتراضية (
stdinوstdoutوstream، وprofileإذا كانت مُضمَّنة في البناء). يرفعRuntimeErrorإذا فشلت التهيئة. يُقلع البرنامج الثابت وحزمة بروتوكول USB الافتراضية تعمل بالفعل، لذا لا يلزم استدعاء هذا إلا لتغيير وسيلة النقل أو تجاوز معاملات التأطير الافتراضية. استدعاؤه مرة أخرى يعيد تهيئة الحزمة: يُزال مؤقت الاستطلاع السابق وتُسجَّل القنوات من جديد.crcيُفعّل التحقق من CRC على أُطُر البروتوكول.seqيُفعّل تتبع أرقام التسلسل.ackيُفعّل الإقرارات لكل إطار.eventsيُفعّل إشعارات أحداث القناة.max_payloadهو الحد الأقصى لحجم الحمولة بالبايت. إذا حُذف، يُستخدم الافتراضي الخاص بكل كاميرا أدناه؛ وهو مشتق من حجم مخزن بروتوكول كل لوحة على النحوbuffer - 10 (header) - 4 (CRC).الكاميرا
حجم المخزن
أقصى حمولة
OpenMV Cam M4 (
OPENMV2)512
498
OpenMV Cam M7 (
OPENMV3)512
498
OpenMV Cam H7 (
OPENMV4)512
498
OpenMV Cam H7 Plus (
OPENMV4P)4096
4082
OpenMV Pure Thermal (
OPENMVPT)4096
4082
OpenMV Cam RT1062 (
OPENMV_RT1060)4096
4082
OpenMV Cam N6 (
OPENMV_N6)8192
8178
OpenMV AE3 (
OPENMV_AE3)8192
8178
Arduino Portenta H7 (
ARDUINO_PORTENTA_H7)4096
4082
Arduino Giga (
ARDUINO_GIGA)4096
4082
Arduino Nicla Vision (
ARDUINO_NICLA_VISION)4096
4082
rtx_retriesهو عدد محاولات إعادة الإرسال. الافتراضي3.rtx_timeout_msهو مهلة إعادة الإرسال بالمللي ثانية (تتضاعف بعد كل انتهاء مهلة). الافتراضي500.lock_interval_msهو الحد الأدنى لفترة القفل بالمللي ثانية. الافتراضي10.poll_msهو فترة الاستقصاء بالمللي ثانية. تُعطّل القيمة0(الافتراضية) الاستقصاء عبر المؤقت.
- protocol.is_active() bool#
يُرجع
Trueإذا كان هناك مضيف متصل حاليًا وحزمة البروتوكول نشطة، وإلا فيُرجعFalse.
- protocol.poll() int#
تشغيل دورة واحدة من مهمة البروتوكول: تفريغ مخزن الاستقبال لوسيلة النقل، وتحليل أي أُطر مكتملة، وتوزيع الأوامر، وخدمة أحداث القنوات. يقوم البرنامج الثابت بذلك عادةً نيابةً عنك من مقاطعة USB ومن مؤقت
poll_ms، لذا لا يلزم هذا إلا لوسائل النقل المخصصة التي لا يقودها أيٌّ منهما -- مثلًا عندما يكونpoll_ms=0وتُستطلع وسيلة النقل من الحلقة الرئيسية للسكربت.يُرجع
0عند النجاح و-1إذا لم تكن هناك وسيلة نقل نشطة مسجلة أو إذا جرى الاستدعاء من سياق مقاطعة.
- protocol.register(name: str, *, backend: object, flags: int = 0) ProtocolChannel#
تسجيل كائن Python
backendكقناة منطقية جديدة وإرجاع مقبضProtocolChannel. تحدد الدوال المتاحة لكائنbackend(انظر واجهة الخلفية أدناه) قدرات القناة؛ وتُضافprotocol.CHANNEL_FLAG_READوprotocol.CHANNEL_FLAG_WRITEوprotocol.CHANNEL_FLAG_LOCKإلىflagsتلقائيًا عند تطبيق الدوال المقابلة.nameهو اسم القناة كسلسلة نصية. يُقتطع إلى حجم مخزن اسم القناة في البرنامج الثابت. مطلوب.backendهو كائن Python الذي يطبّق واجهة الخلفية. مطلوب. يُمرَّر عادةً بالكلمة المفتاحية (backend=...).flagsهي بتات أعلام إضافية للقناة (انظر ثوابتCHANNEL_FLAG_*). اختيارية؛ القيمة الافتراضية0.تثير
RuntimeErrorإذا تعذّر تسجيل القناة (مثلًا عدم وجود فتحات قنوات شاغرة).
الأصناف#
- class protocol.ProtocolChannel#
المقبض الذي تُرجعه
protocol.register. لا تُنشأ النسخ مباشرة.
واجهة الخلفية#
قد يطبّق كائن الخلفية الممرَّر إلى protocol.register أي مجموعة جزئية من الدوال التالية. تُربط فقط الدوال الموجودة على الكائن بطبقة بروتوكول C؛ أما الدوال الغائبة فتترك القدرة المقابلة معطّلة.
- class protocol.backend#
كائن خلفية القناة الممرَّر إلى
protocol.register. تصف الدوال أدناه الواجهة الاختيارية التي قد يطبّقها كائن خلفية Python.- init() object#
يُستدعى مرة واحدة عند تهيئة القناة. يُرجع أي قيمة غير
Noneعند النجاح؛ ويُعامَل أي استثناء أو غياب قيمة الإرجاع على أنه خطأ.
- shape() tuple#
يُرجع صفًا (tuple) من أربعة أعداد صحيحة بحد أقصى تصف شكل البيانات (مثل أبعاد الصورة). تستهلك طبقة البروتوكول أربعة عناصر بحد أقصى.
- read(offset: int, size: int) bytes#
يُرجع ما يصل إلى
sizeبايت بدءًا منoffsetعلى هيئة كائن شبيه بـbytesيدعم بروتوكول المخزن المؤقت.
- readp(offset: int, size: int) bytes#
نسخة بلا نسخ (zero-copy) من
read. تُرجع مخزنًا مؤقتًا تُقرأ ذاكرته الأساسية مباشرة بواسطة طبقة البروتوكول؛ ويجب أن يبقى المخزن المؤقت صالحًا طوال مدة النقل.
- write(offset: int, data: bytearray) int#
كتابة
dataعندoffset.dataهوbytearrayيشير إلى مخزن C المؤقت مباشرة. يُرجع عدد البايتات المكتوبة، أو0عند النجاح الافتراضي.
- class protocol.CBORChannel(on_read: Callable | None = None, on_write: Callable | None = None)#
واجهة خلفية Python عالية المستوى (توفرها حزمة
protocolالمجمّدة) تُسلسل الحقول المسماة إلى CBOR باستخدام مفاتيح عددية صحيحة متوافقة مع SenML. تدعم عناصر العرض (labelوtextوdepthوwaveform) وعناصر التحكم التفاعلية (toggleوpushbuttonوsliderوspinboxوselectوradioوlineedit) مع ردود نداءon_read/on_write.on_readهو قابل للاستدعاء اختياريon_read(channel)يُستدعى قبل تسلسل القناة من أجل المضيف. استخدمه لتحديث قيم الحقول.on_writeهو قابل للاستدعاء اختياريon_write(channel, name, value)يُستدعى عندما يكتب المضيف قيمة جديدة لحقل مسمّى.- add(name: str, type: str, value: Any = None, unit: str | None = None, min: int | float | None = None, max: int | float | None = None, step: int | float | None = None, options: list | None = None, width: int | None = None, height: int | None = None, sample_rate: float | None = None, series: int = 1, typecode: str | None = None) None#
إضافة حقل مسمّى إلى القناة.
nameهو اسم العرض؛ ويجب أن يكون فريدًا ضمن هذه القناة.typeهو نوع عنصر الواجهة:"label"-- عرض رقمي للقراءة فقط معunitاختيارية."text"-- عرض سلسلة نصية للقراءة فقط."toggle"-- مفتاح منطقي."pushbutton"-- إجراء لحظي (تشغيل، معايرة، ...). يكتب المضيفTrueعند النقر ولا يعرض أي حالة."slider"-- عنصر تحكم رقمي معmin/max/step."spinbox"-- إدخال رقمي دقيق بخطوات؛ يشترك مع شريط التمرير في حقوله وفي صيغة التحديث بالصف(min, max, value)."select"-- قائمة منسدلة من سلاسلoptions."radio"-- تُعرضoptionsكمجموعة من أزرار الاختيار."lineedit"-- إدخال سلسلة نصية قابل للكتابة."depth"-- عرض خريطة عمق ثنائية الأبعاد بحجمwidthxheight."waveform"-- رسم بياني لسلسلة زمنية أحادية البعد من عينات متداخلة.
valueهي القيمة الابتدائية. يعتمد الافتراضي علىtype:0لـlabel، و""لـtext/lineedit/select/radio، وFalseلـtoggle/pushbutton، وminلـslider/spinbox.unitهي سلسلة الوحدة لـlabel/slider/spinbox/waveform(مثل"Cel"و"%RH").minهي القيمة الدنيا (نطاق slider/spinbox، أو نطاق عرض depth/waveform).maxهي القيمة القصوى (نطاق slider/spinbox، أو نطاق عرض depth/waveform).stepهو حجم الخطوة (slider/spinbox).optionsهي قائمة سلاسل الخيارات (select/radio)، أو قائمة أسماء كل سلسلة (waveform).widthهو العرض بالبكسل (depth).heightهو الارتفاع بالبكسل (depth).sample_rateهو معدل أخذ العينات بالعينات في الثانية (waveform). يُرسَل إلى المضيف على هيئة فترة العينة.seriesهو عدد السلاسل المتداخلة في كل كتلة عينات (waveform)، مثل3لأثر مقياس تسارع XYZ.typecodeهو رمز نوعarrayلعينات الشكل الموجي (مثل"H"و"h"و"f"). الافتراضي"H"(16 بت بدون إشارة).
- __getitem__(name: str) object#
يُرجع القيمة الحالية للحقل المسمّى. بالنسبة لحقول
depthوwaveformيُرجع مخزن البيانات الثنائية، وإلا فالقيمة العددية.
- __setitem__(name: str, value: Any) None#
يضبط قيمة الحقل المسمّى. بالنسبة لحقول
sliderوspinbox، يحدّث الصف(min, max, value)النطاق والقيمة الحالية في آن واحد. بالنسبة لحقولdepth،valueهي مخزن البيانات الثنائية. بالنسبة لحقولwaveform،valueهي كتلة شبيهة بالبايتات من عينات متداخلة بحسبtypecodeالحقل؛ يُشتق عدد العينات لكل سلسلة من طولها ويُرفَق طابع زمني رتيب (بالميكروثانية، متراكم منtime.ticks_us()) ليتمكن المضيف من وضع القطعة على محوره الزمني.
الثوابت#
بتات أعلام القناة (تُدمج بعملية ثنائية على البتات؛ وتُمرَّر إلى protocol.register عبر flags أو تُضبط تلقائيًا بناءً على دوال الخلفية).
معرّفات القنوات المدمجة.