12.9. التدفق ثنائي الاتجاه¶
القنوات ليست أحادية الاتجاه. فالخلفية التي تنفّذ write تتيح للمضيف دفع بايتات إلى الكاميرا، فتتفاعل الكاميرا. وهذا هو النمط الكامن خلف كل أداة تفاعلية حقيقية: يدير المشغّل مقبضاً في الواجهة الرسومية للمضيف، فيكتب المضيف القيمة الجديدة إلى قناة تهيئة، وتقرؤها الكاميرا في المرة التالية التي تلتقط فيها.
12.9.1. قناة تهيئة¶
بالإضافة إلى البرنامج النصي على جانب كاميرا البث، اعرض قناة ثانية لجودة JPEG:
class ConfigChannel:
def __init__(self):
self.quality = 85
def size(self):
return 0
def read(self, offset, size):
# Not used for "host writes to cam" -- but the library
# still needs the method present.
return b''
def write(self, offset, data):
# data is a bytearray view into the protocol buffer.
# Copy out the contents before doing anything with it.
new_q = int(bytes(data))
if 1 <= new_q <= 100:
self.quality = new_q
return len(data)
config = ConfigChannel()
protocol.register(name='config', backend=config)
تقرأ حلقة الالتقاط من config.quality كلما ضغطت إطاراً:
while True:
img = csi0.snapshot()
latest_jpeg = bytes(
img.compress(quality=config.quality).bytearray()
)
ch.send_event(0x01)
أصبح لدى المضيف الآن مقبض. اضبطه على 50 فيكون الإطار التالي أصغر (وأقبح)؛ واضبطه على 95 فيكون الإطار التالي أكبر (وأوضح). تواصل الكاميرا الالتقاط دون إعادة تشغيل؛ ولا يضطر المضيف إلى دفع برنامج نصي جديد.
12.9.2. استدعاء الكتابة من المضيف¶
على جانب المضيف، يرسل channel_write() بايتات إلى قناة مسمّاة:
cam.channel_write('config', b'50')
تُرمّز SDK المضيف البايتات على هيئة حزمة CHANNEL_WRITE واحدة (أو مجزَّأة)، وتسلّمها طبقة البروتوكول إلى الكاميرا، فتُنفَّذ دالة الكاميرا write(offset=0, data=...)، ويقرّ جانب الكاميرا بالاستلام. وبحلول عودة الاستدعاء تكون الكاميرا قد تلقّت القيمة الجديدة وقبلتها.
الكتابة ذرّية من وجهة نظر الكاميرا -- إذ تضمن مكتبة البروتوكول أن دالة write الخاصة بالخلفية تكتمل بالكامل قبل أن تمضي أي عملية أخرى على تلك القناة. ويمكن لشيفرة التطبيق قراءة config.quality من داخل حلقة الالتقاط دون قلق من أن يدوسها المضيف في منتصف اللقطة.
12.9.3. حجم زائف والقراءة على قناة للكتابة فقط¶
ما تزال قناة الكتابة الصِّرفة بحاجة إلى تعريف size و read، حتى لو كانتا زائفتين تُرجعان 0 و b''. فالمكتبة تستخدم وجود الدوال لاشتقاق أعلام قدرة القناة؛ والخلفية التي تفتقر إلى read لن تحصل على تعيين CHANNEL_FLAG_READ وسيرفض المضيف أي محاولة قراءة.
غير أن البايتات المُرجَعة من read على قناة للكتابة فقط مفيدة لغرض آخر: إعادة صدى القيمة الحالية بحيث يستطيع مضيف اتصل للتو أن يسأل الكاميرا "ما الإعداد الحالي؟" بدلاً من الانطلاق من قيمة افتراضية. ولتحقيق ذلك يجب أن يتفق الاتجاهان على تسلسل تمثيلي. إن تحليل البايتات الخام int(bytes(data)) في المثال السابق يصلح لحقل عدد صحيح واحد لكنه لن يتوسّع بمجرد وجود مقبض ثانٍ يُضبط. وتبديل write ليحلّل JSON واقترانها بدالة read تُرجع تفريغ JSON المطابق يحوّل القناة إلى مخزن تهيئة حقيقي ذي ذهاب وإياب:
import json
class ConfigChannel:
def __init__(self):
self.quality = 85
self._buf = b''
def size(self):
self._buf = json.dumps({'quality': self.quality}).encode()
return len(self._buf)
def read(self, offset, size):
return self._buf[offset:offset + size]
def write(self, offset, data):
new = json.loads(bytes(data))
if 'quality' in new:
self.quality = int(new['quality'])
return len(data)
يكتب المضيف الآن cam.channel_write('config', b'{"quality": 50}') لضبط قيمة، و cam.channel_read('config') لقراءة الحالة الراهنة عائدةً. وتُسلسِل الكاميرا تفريغ JSON جديداً عند كل قراءة بحيث يرى المضيف دوماً أحدث القيم، وإضافة مقبض آخر (threshold أو exposure أو orientation) هي سطر واحد في قاموس JSON على كل جانب.
12.9.4. حلقة كاملة¶
بوجود قناة إطار لبيانات الكاميرا ← المضيف، وقناة تهيئة للتحكم المضيف ← الكاميرا، وقدر يسير من الشيفرة الرابطة، يصبح التطبيق أداة تفاعلية:
يفتح المضيف الكاميرا، ويبدأ بسحب الإطارات، ويعرضها في نافذة.
عندما يسحب المشغّل شريط تمرير، يكتب المضيف القيمة الجديدة على
config.تلتقط حلقة الالتقاط في الكاميرا القيمة في الإطار التالي.
تتدفق الإطارات الجديدة عبر القناة
frameنفسها.
هذا هو النموذج بأكمله. قناتان، ودالتا رد نداء لكل منهما، وحلقة التقاط على الكاميرا، وحلقة قراءة وكتابة على المضيف. لا منطق تأطير ظاهر، ولا معالجة أخطاء ظاهرة -- إذ تجعل مكتبة البروتوكول نقل البايتات الموثوق يختفي.
كل ما يأتي بعد هذه النقطة هو شيفرة تطبيق. فإضافة قناة ثالثة لمدرج تكراري، أو رابعة للقياس عن بُعد، أو خامسة لمشغّلات المستشعر هي وصفة "صنف الخلفية و protocol.register" نفسها، مكرَّرة. وبمجرد أن يبلغ مشروع كاميرا هذه النقطة يكفّ البروتوكول عن كونه المشكلة المثيرة للاهتمام؛ ويصبح منطق التطبيق نفسه كذلك.