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.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. لا تُنشأ النسخ مباشرة.

send_event(event: int, wait_ack: bool = False) None

إرسال إشعار حدث قناة إلى المضيف.

event هو معرّف الحدث (عدد صحيح).

wait_ack إذا كانت True يحجب التنفيذ حتى يُقرّ المضيف بالحدث.

تثير RuntimeError إذا فشل إرسال الحدث.

واجهة الخلفية

قد يطبّق كائن الخلفية الممرَّر إلى protocol.register أي مجموعة جزئية من الدوال التالية. تُربط فقط الدوال الموجودة على الكائن بطبقة بروتوكول C؛ أما الدوال الغائبة فتترك القدرة المقابلة معطّلة.

class protocol.backend

كائن خلفية القناة الممرَّر إلى protocol.register. تصف الدوال أدناه الواجهة الاختيارية التي قد يطبّقها كائن خلفية Python.

init() object

يُستدعى مرة واحدة عند تهيئة القناة. يُرجع أي قيمة غير None عند النجاح؛ ويُعامَل أي استثناء أو غياب قيمة الإرجاع على أنه خطأ.

poll() bool

يُرجع True إذا كانت لدى القناة بيانات جاهزة للقراءة من قبل المضيف.

lock() bool

اكتساب القناة من أجل عملية نقل. يُرجع True عند النجاح.

unlock() bool

تحرير القناة بعد عملية نقل. يُرجع True عند النجاح.

size() int

يُرجع عدد البايتات القابلة للقراءة حاليًا من القناة.

shape() tuple

يُرجع صفًا (tuple) من أربعة أعداد صحيحة بحد أقصى تصف شكل البيانات (مثل أبعاد الصورة). تستهلك طبقة البروتوكول أربعة عناصر بحد أقصى.

flush() object

تفريغ أي بيانات معلّقة. يُرجع أي قيمة غير None عند النجاح.

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 عند النجاح الافتراضي.

ioctl(cmd: int, length: int, arg: bytearray | None) int

معالجة استدعاء ioctl. تكون arg مساوية لـ None إذا كانت length صفرًا، وإلا فهي bytearray يشير إلى مخزن C المؤقت. يُرجع 0 أو None عند النجاح، أو عددًا صحيحًا سالبًا عند الخطأ.

is_active() bool

بالنسبة لقنوات النقل، يُرجع True إذا كانت وسيلة النقل الأساسية متصلة حاليًا.

class protocol.CBORChannel(on_read: Callable | None = None, on_write: Callable | None = None)

خلفية Python عالية المستوى (توفرها حزمة protocol المجمّدة) تقوم بتسلسل الحقول المسمّاة إلى CBOR باستخدام مفاتيح أعداد صحيحة متوافقة مع SenML. تدعم عناصر العرض (label وdepth) وعناصر التحكم التفاعلية (toggle وslider وselect) مع دوال رد النداء 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) None

إضافة حقل مسمّى إلى القناة.

name هو اسم العرض؛ ويجب أن يكون فريدًا ضمن هذه القناة.

type هو نوع عنصر الواجهة: "label" أو "toggle" أو "slider" أو "select" أو "depth".

value هي القيمة الأولية. وتعتمد القيمة الافتراضية على type.

unit هي سلسلة الوحدة لـ label/slider (مثل "Cel" و"%RH").

min هي القيمة الدنيا (مجال المنزلق أو مجال العمق).

max هي القيمة القصوى (مجال المنزلق أو مجال العمق).

step هو حجم الخطوة (المنزلق).

options هي قائمة سلاسل الخيارات (select).

width هو العرض بالبكسل (depth).

height هو الارتفاع بالبكسل (depth).

__getitem__(name: str) object

يُرجع القيمة الحالية للحقل المسمّى. بالنسبة لحقول depth يُرجع مخزن البيانات الثنائية، وإلا فالقيمة العددية.

__setitem__(name: str, value: Any) None

تعيين قيمة الحقل المسمّى. بالنسبة لحقول slider، يحدّث الصف (min, max, value) المجال والقيمة الحالية في آنٍ واحد. وبالنسبة لحقول depth، تكون value هي مخزن البيانات الثنائية.

poll() bool

دالة واجهة الخلفية. تُرجع True عند توفر بيانات مُسلسلة للمضيف.

size() int

دالة واجهة الخلفية. تستدعي on_read (إن كانت مضبوطة) وتُرجع حجم المخزن المُسلسل.

read(offset: int, size: int) bytes

دالة واجهة الخلفية. تُرجع شريحة من المخزن المُسلسل.

write(offset: int, data: bytearray) int

دالة واجهة الخلفية. تفك ترميز قائمة تحديثات CBOR وتطبّق القيم على الحقول المسمّاة المطابقة، مستدعيةً on_write لكل منها.

الثوابت

بتات أعلام القناة (تُدمج بعملية ثنائية على البتات؛ وتُمرَّر إلى protocol.register عبر flags أو تُضبط تلقائيًا بناءً على دوال الخلفية).

protocol.CHANNEL_FLAG_READ: int

تدعم القناة القراءة.

protocol.CHANNEL_FLAG_WRITE: int

تدعم القناة الكتابة.

protocol.CHANNEL_FLAG_LOCK: int

تطبّق القناة lock/unlock.

protocol.CHANNEL_FLAG_PHYSICAL: int

تمثّل القناة وسيلة نقل فيزيائية (في مقابل قناة بيانات منطقية).

معرّفات القنوات المدمجة.

protocol.CHANNEL_ID_TRANSPORT: int

معرّف قناة محجوز لوسيلة النقل النشطة.

protocol.CHANNEL_ID_STDIN: int

معرّف قناة stdin المدمجة.

protocol.CHANNEL_ID_STDOUT: int

معرّف قناة stdout المدمجة.

protocol.CHANNEL_ID_STREAM: int

معرّف قناة stream المدمجة.

protocol.CHANNEL_ID_PROFILE: int

معرّف قناة المُحلِّل (profiler) المدمجة (موجودة فقط عندما يُبنى البرنامج الثابت مع تمكين المُحلِّل).