
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:
- 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.