bobaoskit - Zubehör, dnssd und WebSocket


Daher habe ich die Struktur eines Systems aus verwaltetem Softwarezubehör beschrieben.


Das vereinfachte Modell umfasst den Hauptprozess ( bobaoskit.worker ) und Zubehörskripte (unter Verwendung der bobaoskit.accessory bobaoskit.sdk und bobaoskit.accessory ). Im Hauptprozess wird ein Zubehörteil zur Steuerung einiger Felder angefordert. Vom Zubehör gibt es wiederum eine Aufforderung an die Hauptsache, den Status zu aktualisieren.


Nehmen Sie als Beispiel ein gewöhnliches Relais.


Bei einem eingehenden Befehl kann es vorkommen, dass das Relais aus verschiedenen Gründen (Einfrieren der Geräte usw.) seine Position manchmal nicht ändert. Dementsprechend ändert sich der Status nicht, wie viel wir keine Teams schicken werden. In einer anderen Situation kann das Relais seinen Status ändern, wenn es von einem System eines Drittanbieters angewiesen wird. In diesem Fall ändert sich der Status. Das Zubehörskript kann auf ein eingehendes Ereignis bezüglich der Statusänderung reagieren und eine Anforderung an den Hauptprozess senden.


Motivation


Nachdem ich Apple HomeKit auf mehreren Objekten implementiert hatte, suchte ich nach etwas ähnlichem wie Android, weil Ich selbst habe nur ein funktionierendes iPad von iOS-Geräten. Das Hauptkriterium war die Fähigkeit, in einem lokalen Netzwerk ohne Cloud-Dienste zu arbeiten. Was in HomeKit fehlte, waren auch die begrenzten Informationen. Zum Beispiel können Sie einen Thermostat nehmen. Die gesamte Steuerung ist auf die Wahl der Betriebsart (Aus, Heizen, Kühlen und Automatisch) und der eingestellten Temperatur reduziert. Einfacher ist besser, aber meiner Meinung nach nicht immer. Nicht genügend Diagnoseinformationen. Zum Beispiel, ob die Klimaanlage, der Konvektor, welche Lüftungsparameter funktionieren. Möglicherweise kann die Klimaanlage aufgrund eines internen Fehlers nicht funktionieren. Da diese Informationen berücksichtigt werden können, wurde beschlossen, ihre Implementierung zu schreiben.


Man könnte sich Optionen wie ioBroker, OpenHAB, Home-Assistant ansehen.
Auf node.js von den aufgelisteten jedoch nur ioBroker (beim Schreiben des Artikels wurde festgestellt, dass redis ebenfalls in den Prozess involviert ist). Zu diesem Zeitpunkt hatte er herausgefunden, wie man die Kommunikation zwischen Prozessen organisiert, und es war interessant, mit Redis umzugehen, was in letzter Zeit gehört wurde.


Sie können auch die folgende Spezifikation beachten:


Web Thing API


Gerät



Redis unterstützt die Interprozesskommunikation und fungiert auch als Datenbank für Zubehör.


Das Modul bobaoskit.worker eine Anforderungswarteschlange durch (zusätzlich zu redis mit der bee-queue ), führt die Anforderung aus und schreibt / liest aus der Datenbank.


In Benutzerskripten bobaoskit.accessory Objekt bobaoskit.accessory auf eine separate bee-queue für dieses bestimmte Zubehör, führt die vorgeschriebenen Aktionen aus und sendet Anforderungen über das Objekt bobaoskit.sdk an die Hauptprozesswarteschlange.


Protokoll


Alle Anforderungen und veröffentlichten Nachrichten sind Zeichenfolgen im JSON Format und enthalten die Felder method und payload . Felder sind erforderlich, auch wenn payload = null .


Anfragen an bobaoskit.worker :


  • Methode: ping , Nutzlast: null .
  • Methode: get general info , Nutzlast: null
  • Methode: clear accessories , Nutzlast: null ,
  • Methode: add accessory ,
    Nutzlast:

 { id: "accessoryId", type: "switch/sensor/etc", name: "Accessory Display Name", control: [<array of control fields>], status: [<array of status fields>] } 

  • Methode: remove accessory , Nutzlast: accessoryId/[acc1id, acc2id, ...]
  • Methode: null/accId/[acc1id, acc2id...] , Nutzlast: null/accId/[acc1id, acc2id...]
    Im Feld payload können Sie die null / id Zubehör- / Massen- id senden. Wenn null gesendet wird, werden Informationen zu allen vorhandenen Zubehörteilen zurückgegeben.
  • Methode: {id: accessoryId, status: fieldId} , Nutzlast: {id: accessoryId, status: fieldId}
    Im payload können Sie ein Objekt der Form {id: accessoryId, status: fieldId} (wobei das status ein Array von Feldern sein kann) oder die payload ein Array von Objekten dieser Art senden.
  • Methode: update status value , Nutzlast: {id: accessoryId, status: {field: fieldId, value: value}
    Im payload können Sie ein Objekt der Form {id: accessoryId, status: {field: fieldId, value: value}} (wobei das status ein Array sein kann {field: fieldId, value: value} ) oder payload ein Array von Objekten dieser Art {field: fieldId, value: value} Art von.
  • Methode: control accessory value , Nutzlast: {id: accessoryId, control: {field: fieldId, value: value}} .
    Im payload können Sie ein Objekt der Form {id: accessoryId, control: {field: fieldId, value: value}} (wobei das control ein Array sein kann {field: fieldId, value: value} ) oder payload ein Array von Objekten dieser Art {field: fieldId, value: value} Art von.

Als Antwort auf eine Anfrage, falls erfolgreich, eine Nachricht des Formulars:


{ method: "success", payload: <...> }


Im Fehlerfall:


{ method: "error", payload: "Error description" }


In den folgenden Fällen werden Nachrichten auch im redis PUB/SUB Kanal (definiert in config.json ) veröffentlicht: Alle Zubehörteile werden gelöscht ( clear accessories ); Zubehör hinzugefügt ( add accessory ); Zubehör entfernt ( remove accessory ); Aktualisierter Status des Zubehörs ( update status value ).


Broadcast-Nachrichten enthalten außerdem zwei Felder: method und payload .


Client SDK


Beschreibung


Mit dem Client-SDK ( bobaoskit.accessory ) können Sie die oben genannten Methoden aus js Skripten js .


Innerhalb des Moduls befinden sich zwei Konstruktorobjekte. Das erste erstellt ein Sdk Objekt, um auf die oben genannten Methoden zuzugreifen, und das zweite erstellt ein Zubehör - einen Wrapper über diesen Funktionen.


 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" }); 

Das sdk-Objekt unterstützt Promise Methoden:


 sdk.ping(); sdk.getGeneralInfo(); sdk.clearAccessories(); sdk.addAccessory(payload); sdk.removeAccessory(payload); sdk.getAccessoryInfo(payload); sdk.getStatusValue(payload); sdk.updateStatusValue(payload); sdk.controlAccessoryValue(payload); 

Das BobaosKit.Accessory({..}) Objekt BobaosKit.Accessory({..}) ist ein Wrapper über dem BobaosKit.Sdk(...) Objekt BobaosKit.Sdk(...) .


Als nächstes werde ich zeigen, wie sich das ändert:


 //     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 }); }; 

Beide Objekte sind auch EventEmitter .
Sdk ruft Funktionen für die ready und broadcasted event Ereignisse auf.
Accessory ruft Funktionen bei Ereignissen ready , error , control accessory value .


Beispiel


 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 den in ./config.json definierten WebSocket-Port.


Eingehende Anforderungen sind JSON Zeichenfolgen, die die folgenden Felder enthalten müssen: request_id , method und payload .


Die API ist auf folgende Anforderungen beschränkt:


  • Methode: ping , Nutzlast: null
  • Methode: get general info , Nutzlast: null ,
  • Methode: null/accId/[acc1Id, ...] Nutzlast: null/accId/[acc1Id, ...]
  • Methode: get status value {id: accId, status: field1/[field1, ...]}/[{id: ...}...] , Nutzlast: {id: accId, status: field1/[field1, ...]}/[{id: ...}...]
  • Methode: control accessory value , Nutzlast: {id: accId, control: {field: field1, value: value}/[{field: .. value: ..}]}/[{id: ...}, ...]

Die Methoden zum Abrufen des get status value und zum control accessory value akzeptieren das payload als einzelnes Objekt oder als Array. Die control/status innerhalb der payload können auch entweder ein einzelnes Objekt oder ein Array sein.


Die folgenden Ereignismeldungen vom Server werden ebenfalls an alle Clients gesendet:


  • Methode: clear accessories , Nutzlast: null
  • Methode: remove accessory , Nutzlast: Zubehör-ID
  • Methode: add accessory, payload : {id: ...}
  • Methode: update status value, payload : {id: ...}

dnssd


Die Anwendung _bobaoskit._tcp den WebSocket-Port im lokalen Netzwerk dank des Moduls npm dnssd als _bobaoskit._tcp Dienst an.


Demo



Es wird ein separater Artikel darüber geschrieben, wie die Anwendung mit Video und Eindrücken von flutter .


Nachwort


So wurde ein einfaches System zur Verwaltung von Softwarezubehör erhalten.
Zubehör kann mit Objekten aus der realen Welt verglichen werden: Tasten, Sensoren, Schalter, Thermostate, Radios. Da es keine Standardisierung gibt, können Sie jedes Zubehör implementieren, das in das control < == > update der control < == > update passt.


Was könnte besser gemacht werden:


  1. Ein binäres Protokoll würde das Senden weniger Daten ermöglichen. JSON hingegen ist schneller zu entwickeln und zu verstehen. Das Binärprotokoll erfordert auch eine Standardisierung.

Das ist alles, ich freue mich über jedes Feedback.

Source: https://habr.com/ru/post/de437846/


All Articles