13.3.1.4. القنوات المخصصة¶
القناة هي تدفق بايتات مُسمّى ثنائي الاتجاه بين برنامج نصي على جانب الكاميرا والمضيف. تُسجّل الكاميرا قناة وتوفّر دوال رد نداء تنتج البيانات أو تستهلكها؛ ويقرأ المضيف من تلك القناة ويكتب إليها بالاسم. الآلية نفسها التي تستخدمها الحزمة داخليًا لقناة stream التي تحمل الإطارات، وقناة stdout التي تحمل مخرجات البرنامج النصي، وقناة stdin التي تحمل رفع البرنامج النصي، مكشوفة للبرامج النصية للمستخدم، فأي بيانات خاصة بالتطبيق يحتاجها المضيف يمكنها أن تركب اتصال USB نفسه دون اختراع بروتوكول ثانٍ.
هذه هي أكثر ميزات الحزمة فائدة وأقلها تغطية في الوثائق القياسية، لذا تتناولها هذه الصفحة من البداية إلى النهاية.
13.3.1.4.1. النصفان¶
تحتاج القناة المخصصة إلى تعليمات برمجية متعاونة على الجانبين. البرنامج النصي على جانب الكاميرا يستورد protocol، ويُعرّف صنفًا بثلاث دوال (size() وread() وpoll()) بالإضافة إلى دالة write() اختيارية، ويستدعي protocol.register(name=..., backend=...) لنشر القناة تحت اسم مختار:
import protocol
import time
class TicksChannel:
def size(self):
return 10
def read(self, offset, size):
return f'{time.ticks_ms():010d}'
def poll(self):
return True
protocol.register(name='ticks', backend=TicksChannel())
تُرجع الدالة size() عدد البايتات المتاحة حاليًا في القناة. أما read() فهي المنتِج: بناءً على offset وsize اللذين يطلبهما المضيف، تُرجع البايتات (أو سلسلة نصية تُرمّزها طبقة البروتوكول). وتُرجع poll() القيمة True عندما يكون هناك شيء للقراءة -- تستخدم طبقة البروتوكول هذا لوضع علامة على القناة كجاهزة في read_status().
برنامج جانب المضيف يستخدم أربع دوال من openmv.Camera: has_channel() للتحقق من وجود القناة، وchannel_size() للسؤال عن مقدار البيانات المنتظرة، وchannel_read() لسحب البايتات، وchannel_write() لدفع البايتات. وتستطلع read_status() كل قناة دفعة واحدة:
from openmv import Camera
with Camera('/dev/ttyACM0') as cam:
cam.stop()
cam.exec(open('ticks_cam.py').read())
while True:
status = cam.read_status()
if status.get('ticks'):
data = cam.channel_read('ticks')
print(f"ticks: {data.decode()}")
تستطلع حلقة المضيف read_status()؛ وعندما تكون قناة ticks جاهزة تستدعي channel_read() بدون size لسحب كل ما هو متاح. وتُرجع TicksChannel.poll() في الكاميرا القيمة True في كل فحص، فتكون القناة دائمًا "جاهزة" ويحصل المضيف على قيمة نبضة جديدة في كل استطلاع.
13.3.1.4.2. قناة ثنائية الاتجاه¶
بالنسبة لمضيف يحتاج إلى دفع البيانات إلى الخلف، يضيف الصنف على جانب الكاميرا دالة write() تقبل البايتات الواردة:
import protocol
class CommandChannel:
def __init__(self):
self.last_command = b''
self.replied = False
def size(self):
return len(self.last_command)
def read(self, offset, size):
self.replied = True
return self.last_command
def write(self, offset, data):
self.last_command = b'echo: ' + bytes(data)
self.replied = False
def poll(self):
return not self.replied and len(self.last_command) > 0
protocol.register(name='echo', backend=CommandChannel())
يكتب المضيف إلى القناة باستخدام channel_write() ويقرأ الرد عبر نمط read_status() / channel_read() المعتاد:
with Camera('/dev/ttyACM0') as cam:
cam.stop()
cam.exec(open('echo_cam.py').read())
cam.channel_write('echo', b'hello')
while True:
if cam.read_status().get('echo'):
print(cam.channel_read('echo').decode())
break
13.3.1.4.3. ما الذي يكسبه هذا للتطبيق¶
القنوات المخصصة هي الأداة الصحيحة كلما أراد تطبيق أن يركب اتصال USB القائم لبيانات ليست إطارات وليست مطبوعات: عدّادات قياس عن بُعد، ومقابض إعداد تُبثّ مباشرة من واجهة مستخدم على المضيف، وأوامر تحكم تُرسل في الاتجاه الآخر، ونتائج قياس حسبته الكاميرا لا يناسبه تأطير "الصورة" الذي تفترضه قناة البثّ. تتولى طبقة البروتوكول التأطير والتجزئة والإقرار وإعادة المحاولة؛ ولا يحتاج البرنامج النصي إلا إلى تنفيذ الخلفية ذات الدوال الأربع، ولا يحتاج المضيف إلا إلى معرفة اسم القناة وشكل البيانات.
علَم --channel NAME في واجهة سطر الأوامر طريقة سريعة للتحقق من قناة مخصصة من الطرفية دون كتابة برنامج على جانب المضيف: تستطلع واجهة سطر الأوامر القناة المُسمّاة وتطبع أول عشرة بايتات من كل تحديث.
حد الحجم في استدعاء channel_read() أو channel_write() المنفرد هو قيمة max_payload المتفاوض عليها في البروتوكول -- 4096 بايت افتراضيًا. تقسم دوال جانب المضيف الكتابات الأكبر تلقائيًا إلى العدد الصحيح من الحزم، فيستطيع التطبيق تمرير مخازن مؤقتة بأي حجم كان؛ والتجزئة غير مرئية.