9.18. MQTT، بايتًا تلو الآخر

في هذه المرحلة تمتلك الكاميرا كل ما تحتاجه للتواصل مع خدمة حقيقية على الإنترنت المفتوح: مقبس TCP، وطبقة TLS لتغليفه، وDNS لتسمية الطرف الآخر، وasyncio للسماح للبرنامج النصي ذاته بأداء أعمال أخرى أثناء بقاء الاتصال مفتوحًا. وMQTT هو أول بروتوكول على مستوى السلك يجمع كل هذه العناصر معًا في شيء يستخدمه منتج منشور فعليًا.

تتناول هذه الصفحة البروتوكول نفسه -- التنسيق على مستوى السلك، والأدوار التي يلعبها كل مشارك، والمفاضلات في تصميمه -- بصدق كافٍ بحيث يبدو عميل mqtt المضمّن تغليفًا بديهيًا لما هو معروف بالفعل بدلًا من قفزة في المجهول.

9.18.1. النشر/الاشتراك مقابل الطلب/الاستجابة

إن HTTP -- وهو البروتوكول الذي تلجأ إليه معظم مشاريع الكاميرا أولًا -- يعمل بأسلوب الطلب/الاستجابة. يطلب العميل موردًا محددًا من خادم محدد؛ ويجيب الخادم. كل تبادل هو واحد لواحد، ويعرف الطرفان عنوان كل منهما مسبقًا.

أما MQTT فيعمل بأسلوب النشر/الاشتراك. تتصل العملاء بطرف ثالث في المنتصف يُسمى الوسيط (broker). يرسل الناشر رسالة إلى موضوع (topic) مسمّى دون أن يعرف أو يهتم بمن يستمع. ويخبر المشترك الوسيط بالمواضيع التي يريدها فيستقبل بعد ذلك كل رسالة تُنشر إلى تلك المواضيع. والوسيط هو من ينفّذ التوزيع: نشر واحد على yard-cam/motion يصل إلى كل جهاز مشترك في yard-cam/motion، سواء كان عددها صفرًا أو واحدًا أو خمسين.

ثلاثة أمور تنتج عن هذا التغيير في النموذج:

  • الفصل. ليس على الناشرين أن يعرفوا بوجود المشتركين. يمكن للمشتركين أن يأتوا ويذهبوا دون أن يلاحظ الناشر. إضافة لوحة معلومات ثانية هي سطر برمجي واحد على لوحة المعلومات الجديدة؛ ولا تتغير الكاميرا.

  • التوزيع. يتولى الوسيط كل نسخة مكررة، لذا ترسل الكاميرا حزمة واحدة بغض النظر عن عدد الأجهزة التي تقرأها. هذه هي حالة الاستخدام التي بُني MQTT من أجلها.

  • عدم التناظر. أصبح الوسيط الآن قطعة بنية تحتية مطلوبة -- فبدونه لا يعمل البروتوكول. وبالنسبة للمشاريع المنزلية يكون هذا عادةً وسيطًا عامًا مجانيًا (test.mosquitto.org، broker.hivemq.com) أو وسيطًا صغيرًا تشغّله بنفسك.

One cam publishing to a yard-cam/motion topic on a broker while two browser dashboards and one cloud archiver each receive the same message.

9.18.2. المواضيع

المواضيع هي سلاسل نصية مفصولة بشرطات مائلة. والعُرف هو أن يكون الأعمّ على اليسار والأكثر تحديدًا على اليمين:

yard-cam/motion
yard-cam/temperature
workshop-cam/motion
workshop-cam/temperature/sensor-3

يعمل حرفان بدليان في الاشتراكات (لا في عمليات النشر):

  • يطابق + مستوى واحدًا. +/motion يشترك في موضوع الحركة لكل كاميرا؛ وyard-cam/+ يشترك في كل موضوع فرعي لـ yard-cam.

  • يطابق # مستوى لاحقًا واحدًا أو أكثر. yard-cam/# يشترك في yard-cam/motion وyard-cam/temperature وyard-cam/temperature/sensor-3 وأي شيء آخر تحت yard-cam/. ويجب أن يظهر في نهاية الاشتراك.

سلاسل المواضيع حساسة لحالة الأحرف. ووفقًا للمواصفة، فإن وجود $ في البداية يدل على مواضيع داخلية للوسيط ($SYS/...) لا ينبغي للناشرين الكتابة إليها.

9.18.3. تنسيق الحزمة

يعمل MQTT فوق TCP. تبدأ كل حزمة تحكم بـ ترويسة ثابتة من بايت واحد يتبعها حقل الطول المتبقي متغير الطول، ثم ترويسة متغيرة خاصة بنوع الحزمة، ثم الحمولة. ويغطي التنسيق الخارجي ذاته كل أمر -- CONNECT وPUBLISH وSUBSCRIBE وPUBACK وDISCONNECT والبقية -- ولهذا يمكن كتابة عميل MQTT في بضع مئات من الأسطر.

The byte layout of an MQTT PUBLISH packet showing the fixed-header type and flags byte, the variable-length Remaining Length field, the topic name, the optional packet identifier, and the payload bytes.

الترويسة الثابتة هي بايت واحد:

  • البتات 7..4 هي نوع حزمة التحكم. 0x3 هو PUBLISH (لذا يبدأ البايت الأول عادةً بـ 0x3?). و0x1 هو CONNECT، و0x2 CONNACK، و0x8 SUBSCRIBE، و0xC PINGREQ، و0xE DISCONNECT، وهكذا.

  • البتات 3..0 هي أعلام خاصة بنوع الحزمة. بالنسبة لـ PUBLISH تُرمّز الأعلام علم إعادة الإرسال DUP، ومستوى QoS (بتان)، وعلم RETAIN.

الطول المتبقي هو عدد صحيح متغير الطول من 1 إلى 4 بايتات يحصي كل بايت بعده. والبت الأعلى في كل بايت هو علامة استمرار -- 1 تعني "يتبع بايت طول آخر"، و0 تعني "هذا هو الأخير". الطول الأقل من 128 يتسع في بايت واحد؛ والحمولات الأكبر تستخدم المزيد. والحد الأقصى للطول المُرمّز هو 256 ميبي بايت.

بالنسبة لـ PUBLISH تكون الترويسة المتغيرة هي اسم الموضوع -- طول من بايتين، ثم بايتات UTF-8 -- متبوعًا بـ معرّف حزمة من بايتين لا يوجد إلا عندما يكون QoS بقيمة 1 أو 2. والبايتات المتبقية هي الحمولة، التي يعاملها البروتوكول كبايتات معتمة.

أصغر حزمة PUBLISH بمستوى QoS-0 للقيمة ok إلى a/b هي:

30 07 00 03 'a' '/' 'b' 'o' 'k'
  • 30 -- PUBLISH، كل الأعلام صفر.

  • 07 -- يتبعها 7 بايتات.

  • 00 03 -- طول الموضوع 3.

  • 'a' '/' 'b' -- الموضوع.

  • 'o' 'k' -- الحمولة.

تسعة بايتات على السلك وتصل الرسالة إلى كل مشترك في a/b على الوسيط.

9.18.4. مستويات QoS

تتحكم جودة الخدمة (Quality-of-Service) في مدى الجهد الذي يبذله الوسيط (والعميل) لضمان التسليم. المستويات الثلاثة:

QoS 0 -- مرة على الأكثر. أطلق وانسَ. تُرسل حزمة PUBLISH ولا تُؤكَّد أبدًا. إذا سلّم TCP، يعيد الوسيط توجيهها. وإذا انقطع الاتصال في منتصف الإرسال، ضاعت الرسالة. ومعظم القياسات الحسية عن بُعد تكون على ما يرام عند QoS 0 -- قراءة حرارة واحدة فائتة في تيار يصدر كل 30 ثانية لا تهم.

QoS 1 -- مرة على الأقل. يُضمّن الناشر معرّف حزمة وينتظر PUBACK. وإذا لم يصل PUBACK قبل انتهاء المهلة، يعيد الناشر الإرسال مع تعيين علم DUP. وقد ينتهي الأمر بالوسيط بتسليم الرسالة ذاتها مرتين إلى مشترك على المستوى ذاته؛ فعلى المشترك أن يكون مستعدًا للتعامل مع النسخ المكررة.

QoS 2 -- مرة واحدة بالضبط. مصافحة من أربع خطوات (PUBREC / PUBREL / PUBCOMP) تضمن وصول الرسالة مرة واحدة بالضبط، حتى عبر عمليات إعادة الاتصال. وهي مكلفة من حيث رحلات الذهاب والإياب وحالة الوسيط. وقليل من تطبيقات الكاميرا يحتاجها.

ينفّذ عميل mqtt المضمّن QoS 0 وQoS 1؛ أما QoS 2 فيثير استثناءً إن طلبته. وبالنسبة لكاميرا تبلّغ عن قراءات المستشعرات، فإن QoS 0 هو الإجابة الصحيحة في معظم الأحيان.

9.18.5. الرسائل المحتفظ بها والوصية الأخيرة

ميزتان جديرتان بالمعرفة لأنهما تغيّران ما يتذكره الوسيط عن موضوعك.

RETAIN. إذا كان علم RETAIN معيّنًا في حزمة PUBLISH، يخزّن الوسيط الرسالة ويعيد توجيهها إلى كل مشترك مستقبلي في لحظة اشتراكه. هكذا يتعامل MQTT مع "ما هي القيمة الحالية؟" -- ينشر مستشعرٌ أحدث قراءاته محتفَظًا بها، فتظل لوحة معلومات تشترك بعد عشر دقائق تتلقى أحدث قيمة بدلًا من انتظار النشر التالي. وإعادة النشر بالموضوع ذاته تستبدل القيمة المحتفظ بها؛ ونشر حمولة فارغة يمسحها.

الوصية الأخيرة. عندما يتصل العميل يمكنه أن يعطي الوسيط "وصية أخيرة": موضوعًا، وحمولة، وQoS، وعلم احتفاظ. وإذا انقطع ذلك العميل بشكل غير نظيف -- TCP RESET، أو انقطاع التيار، أو سقوط الشبكة دون حزمة DISCONNECT -- ينشر الوسيط الوصية نيابةً عن العميل. ويراها المشتركون على أنها إشعار الكاميرا بأنها أصبحت غير متصلة. والكاميرا نفسها لا ترسل الوصية أبدًا؛ بل يرسلها الوسيط، لأن الكاميرا بحلول ذلك الوقت قد اختفت.

9.18.6. الإبقاء على الاتصال وإعادة الاتصال

يحمل CONNECT فترة إبقاء على الاتصال (keepalive) بالثواني. إذا ظل العميل صامتًا تلك المدة، يعتبره الوسيط ميتًا. ولمنع ذلك، يرسل العميل دوريًا PINGREQ (بايت واحد: 0xC0) ويتلقى ردًّا PINGRESP (0xD0) -- وهو أصغر وأرخص نبض قلب يمكن للبروتوكول حمله. وتضبط معظم تطبيقات الكاميرا الإبقاء على الاتصال على 30 أو 60 ثانية.

إذا انقطع اتصال TCP، يلاحظ الطرفان ذلك ويعيدان الاتصال من الصفر. وتُفقد الاشتراكات التي أُجريت قبل الانقطاع ما لم يستخدم العميل جلسة دائمة عند الاتصال؛ وبالنسبة لتطبيقات الكاميرا البسيطة يكون نمط إعادة الاشتراك عند إعادة الاتصال أقصر وبالجودة ذاتها.

هذا كافٍ لقراءة مواصفة MQTT أو كتابة عميل يدويًا فوق socket.socket. والعميل المضمّن في mqtt يفعل ذلك بالضبط، إضافةً إلى API معقولة لشيفرة التطبيق.