
لذلك ، وصفت بنية نظام ملحقات البرامج المدارة.
يتضمن النموذج المبسط العملية الرئيسية ( bobaoskit.worker ) والبرامج النصية bobaoskit.sdk (باستخدام bobaoskit.accessory و bobaoskit.accessory ). من العملية الرئيسية يوجد طلب لملحق للتحكم في بعض الحقول. من الملحق ، بدوره ، هناك طلب إلى الشيء الرئيسي لتحديث الحالة.
خذ تتابع عادي كمثال.
باستخدام أمر وارد ، قد لا يغير المرحل في بعض الأحيان موضعه لأسباب متعددة (تجميد المعدات ، إلخ). وفقًا لذلك ، كم لن نرسل فرقًا ، لن يتغير الوضع. وفي حالة أخرى ، يمكن للمرحل أن يغير حالته عندما يقوده نظام تابع لجهة خارجية. في هذه الحالة ، ستتغير حالته ، يمكن للبرنامج النصي الملحق الاستجابة لحدث وارد حول تغيير الحالة وإرسال طلب إلى العملية الرئيسية.
الدافع
بعد أن قمت بتطبيق Apple HomeKit على العديد من الكائنات ، بدأت أبحث عن شيء مشابه لنظام Android ، لأنه لديّ فقط جهاز iPad يعمل من أجهزة iOS. المعيار الرئيسي هو القدرة على العمل على شبكة محلية ، دون الخدمات السحابية. أيضا ، ما كان مفقودا في HomeKit هو المعلومات المحدودة. على سبيل المثال ، يمكنك أن تأخذ ترموستات. يتم التحكم في كل التحكم في اختيار وضع التشغيل (إيقاف ، التدفئة والتبريد والسيارات) ودرجة الحرارة المحددة. أبسط هو أفضل ، ولكن ، في رأيي ، ليس دائما. لا توجد معلومات تشخيصية كافية. على سبيل المثال ، ما إذا كان مكيف الهواء ، المسخن ، ما هي معلمات التهوية تعمل. ربما لا يمكن أن يعمل مكيف الهواء بسبب خطأ داخلي. بالنظر إلى أنه يمكن النظر في هذه المعلومات ، فقد تقرر كتابة تنفيذها.
يمكن للمرء أن ينظر إلى خيارات مثل ioBroker و OpenHAB ومساعد منزلي.
لكن على node.js ، من هؤلاء المدرجين في القائمة ، ioBroker فقط (أثناء كتابة مقال ، لاحظت أن redis متورط أيضًا في العملية). وبحلول ذلك الوقت ، كان قد اكتشف كيفية تنظيم التواصل بين العمليات ، وكان من المثير للاهتمام التعامل مع redis ، والذي تم الاستماع إليه مؤخرًا.
يمكنك أيضًا الانتباه إلى المواصفات التالية:
→ الويب شيء API
الجهاز

تساعد Redis على التواصل بين العمليات ، وتعمل أيضًا كقاعدة بيانات للملحقات.
الوحدة النمطية bobaoskit.worker يحدث قائمة انتظار الطلب (أعلى redis باستخدام bee-queue ) ، تنفيذ الطلب ، يكتب / يقرأ من قاعدة البيانات.
في البرامج النصية للمستخدم ، يستمع كائن bobaoskit.accessory bee-queue منفصلة لهذا الملحق المحدد ، ويقوم بتنفيذ الإجراءات الموصوفة ، ويرسل الطلبات إلى قائمة انتظار العملية الرئيسية من خلال كائن bobaoskit.sdk .
البروتوكول
جميع الطلبات والرسائل المنشورة عبارة عن سلاسل بتنسيق JSON ، وتحتوي على حقول method payload . الحقول مطلوبة ، حتى إذا كانت payload = null .
طلبات bobaoskit.worker :
- الطريقة: ping، الحمولة:null.
- الطريقة: get general info، الحمولة:null
- طريقة: clear accessories، الحمولة:null،
- طريقة: add accessory،
 الحمولة:
 { id: "accessoryId", type: "switch/sensor/etc", name: "Accessory Display Name", control: [<array of control fields>], status: [<array of status fields>] } 
- الطريقة: remove accessory، الحمولة:accessoryId/[acc1id, acc2id, ...]
- الطريقة: get accessory info، الحمولة:null/accId/[acc1id, acc2id...]
 في حقلpayload، يمكنك إرسالidnull/idالملحق / الكتلة. إذاnullإرسال قيمةnull، فسيتم إرجاع المعلومات المتعلقة بجميع الملحقات الموجودة.
- الطريقة: get status value، الحمولة:{id: accessoryId, status: fieldId}
 في حقل البياناتpayload، يمكنك إرسال كائن من النموذج{id: accessoryId, status: fieldId}، (حيث يمكن أن يكون حقلstatusعبارة عن مجموعة من الحقول) ، أو يمكن أن تكون البيانات{id: accessoryId, status: fieldId}عبارة عن مجموعة من الكائنات من هذا النوع.
- الطريقة: update status value، الحمولة:{id: accessoryId, status: {field: fieldId, value: value}
 في حقلpayload، يمكنك إرسال كائن من النموذج{id: accessoryId, status: {field: fieldId, value: value}}، (حيث يمكن أن يكون حقلstatusعبارة عن صفيف{field: fieldId, value: value}) ، أو يمكن أن يكونpayloadعبارة عن صفيف من الكائنات بهذه نوع من.
- الطريقة: control accessory value، الحمولة:{id: accessoryId, control: {field: fieldId, value: value}}.
 في حقلpayload، يمكنك إرسال كائن من النموذج{id: accessoryId, control: {field: fieldId, value: value}}، (حيث يمكن أن يكون حقلcontrolصفيف{field: fieldId, value: value}) ، أو يمكن أن يكونpayloadعبارة عن صفيف من الكائنات بهذه نوع من.
استجابة لأي طلب ، إذا نجحت ، رسالة من النموذج:
{ method: "success", payload: <...> }
في حالة الفشل:
{ method: "error", payload: "Error description" }
يتم أيضًا نشر الرسائل في redis PUB/SUB (المعرفة في config.json ) في الحالات التالية: يتم مسح جميع الملحقات ( clear accessories ) ؛ إضافة ملحق ( add accessory ) ؛ تم إزالة الملحق ( remove accessory ) ؛ ملحق تحديث الحالة ( update status value ).
تحتوي رسائل البث أيضًا على حقلين: method payload .
عميل SDK
الوصف
يسمح لك عميل SDK ( bobaoskit.accessory ) باستدعاء الأساليب المذكورة أعلاه من البرامج النصية js .
داخل الوحدة النمطية كائنين منشئ. الأول ينشئ كائن Sdk للوصول إلى الأساليب المذكورة أعلاه ، والثاني يقوم بإنشاء ملحق - غلاف أعلى هذه الوظائف.
 const BobaosKit = require("bobaoskit.accessory"); //   sdk. //  , //     , //     sdk, const sdk = BobaosKit.Sdk({ redis: redisClient // optional job_channel: "bobaoskit_job", // optional. default: bobaoskit_job broadcast_channel: "bobaoskit_bcast" // optional. default: bobaoskit_bcast }); //   const dummySwitchAcc = BobaosKit.Accessory({ id: "dummySwitch", // required name: "Dummy Switch", // required type: "switch", // required control: ["state"], // requried. ,   . status: ["state"], // required.   . sdk: sdk, // optional. //   ,   sdk   //     redis: undefined, job_channel: "bobaoskit_job", broadcast_channel: "bobaoskit_bcast" }); 
كائن sdk يدعم أساليب Promise :
 sdk.ping(); sdk.getGeneralInfo(); sdk.clearAccessories(); sdk.addAccessory(payload); sdk.removeAccessory(payload); sdk.getAccessoryInfo(payload); sdk.getStatusValue(payload); sdk.updateStatusValue(payload); sdk.controlAccessoryValue(payload); 
كائن BobaosKit.Accessory({..}) عبارة عن مجمّع أعلى كائن BobaosKit.Sdk(...) .
بعد ذلك ، سأظهر كيف يتحول هذا:
 //     self.getAccessoryInfo = _ => { return _sdk.getAccessoryInfo(id); }; self.getStatusValue = payload => { return _sdk.getStatusValue({ id: id, status: payload }); }; self.updateStatusValue = payload => { return _sdk.updateStatusValue({ id: id, status: payload }); }; 
كلا الكائنات هي أيضا EventEmitter .
Sdk يدعو وظائف على الأحداث ready broadcasted event .
Accessory المكالمات وظائف في الأحداث ready ، error ، control accessory value .
مثال
 const BobaosKit = require("bobaoskit.accessory"); const Bobaos = require("bobaos.sub"); // init bobaos with default params const bobaos = Bobaos(); // init sdk with default params const accessorySdk = BobaosKit.Sdk(); const SwitchAccessory = params => { let { id, name, controlDatapoint, stateDatapoint } = params; // init accessory const swAcc = BobaosKit.Accessory({ id: id, name: name, type: "switch", control: ["state"], status: ["state"], sdk: accessorySdk }); //       state //     KNX  bobaos swAcc.on("control accessory value", async (payload, cb) => { const processOneAccessoryValue = async payload => { let { field, value } = payload; if (field === "state") { await bobaos.setValue({ id: controlDatapoint, value: value }); } }; if (Array.isArray(payload)) { await Promise.all(payload.map(processOneAccessoryValue)); return; } await processOneAccessoryValue(payload); }); const processOneBaosValue = async payload => { let { id, value } = payload; if (id === stateDatapoint) { await swAcc.updateStatusValue({ field: "state", value: value }); } }; //      KNX //   state  bobaos.on("datapoint value", payload => { if (Array.isArray(payload)) { return payload.forEach(processOneBaosValue); } return processOneBaosValue(payload); }); return swAcc; }; const switches = [ { id: "sw651", name: "", controlDatapoint: 651, stateDatapoint: 652 }, { id: "sw653", name: " 1", controlDatapoint: 653, stateDatapoint: 653 }, { id: "sw655", name: " 2", controlDatapoint: 655, stateDatapoint: 656 }, { id: "sw657", name: " 1", controlDatapoint: 657, stateDatapoint: 658 }, { id: "sw659", name: "", controlDatapoint: 659, stateDatapoint: 660 } ]; switches.forEach(SwitchAccessory); 
WebSocket API
يستمع bobaoskit.worker على منفذ WebSocket المحدد في ./config.json.
الطلبات الواردة هي سلاسل JSON التي يجب أن تحتوي على الحقول التالية: request_id ، method payload .
تقتصر واجهة برمجة التطبيقات على الطلبات التالية:
- الطريقة: ping، الحمولة:null
- الطريقة: get general info، الحمولة:null،
- الطريقة: get accessory info، الحمولة:null/accId/[acc1Id, ...]
- الطريقة: get status value، الحمولة:{id: accId, status: field1/[field1, ...]}/[{id: ...}...]
- الطريقة: control accessory value، الحمولة:{id: accId, control: {field: field1, value: value}/[{field: .. value: ..}]}/[{id: ...}, ...]
تقبل get status value ، control accessory value طرق control accessory value حقل payload ككائن واحد أو كصفيف. يمكن أن تكون حقول control/status داخل payload إما كائن واحد أو صفيف.
يتم أيضًا إرسال رسائل الأحداث التالية من الخادم إلى جميع العملاء:
- الطريقة: clear accessories، حمولة: فارغة
- الطريقة: remove accessory، الحمولة: معرف ملحق
- الطريقة: add accessory, payload: {id: ...}
- الطريقة: update status value, payload: {id: ...}
dnssd
يُعلن التطبيق عن منفذ WebSocket على الشبكة المحلية كخدمة _bobaoskit._tcp ، وذلك بفضل وحدة dnssd .
عرض
سيتم كتابة مقال منفصل حول كيفية كتابة التطبيق مع الفيديو وانطباعات flutter .
خاتمة
وبالتالي ، تم الحصول على نظام بسيط لإدارة ملحقات البرامج.
يمكن أن تتناقض الملحقات مع كائنات من العالم الحقيقي: الأزرار ، وأجهزة الاستشعار ، والمفاتيح ، والحرارة ، وأجهزة الراديو. نظرًا لعدم وجود توحيد قياسي ، يمكنك تنفيذ أي ملحقات تتناسب مع طراز control < == > update .
ما الذي يمكن عمله بشكل أفضل:
- يسمح البروتوكول الثنائي بإرسال بيانات أقل. JSON، من ناحية أخرى ، أسرع في التطور والفهم. يتطلب البروتوكول الثنائي أيضًا التقييس.
هذا كل شيء ، سأكون سعيدًا بأي ملاحظات.