
Der Perfektion sind keine Grenzen gesetzt. Es scheint, dass alles gut funktioniert hat, kleinere Fehler usw. wurden behoben.
Jetzt erzähle ich Ihnen zum einen von den Problemen, auf die ich seit dem letzten Artikel gestoßen bin, und zum anderen von den Lösungen, die zum aktuellen Status des Projekts beigetragen haben.
→ Artikel zur Vorgängerversion
Bezeichnungen
bobaos - npm Modul für die Interaktion mit BAOS 83x über UART. Gibt Rohdaten zurück. Wird in allen anderen unten aufgeführten Modulen verwendet.
bdsd.sock - ein Skript zum Arbeiten mit KNX-Objekten. Speichert eine Liste von Datenpunkten und konvertiert Werte beim Senden / Empfangen. Von DPT1 nach true / false, von DPT9 nach float. Lauscht auch auf Unix Socket, um Anforderungen von anderen Prozessen zu empfangen.
bobaos.pub ist eine neue Version, die redis für die Interprozesskommunikation verwendet.
KNX / - Ein Kommunikationsobjekt des in ETS konfigurierten BAOS 83x-Moduls, dem die Gruppenadresse (a) entspricht (oder nicht). In aktuellen Eisenversionen beträgt die maximale Menge 1000.
Herausforderung
Die Hauptaufgabe ist dieselbe wie in der vorherigen Version. Es besteht nur eine Verbindung zur seriellen Schnittstelle. Es gibt viele Skripte, die mit KNX arbeiten. Darüber hinaus wollte ich die Interprozesskommunikation implementieren. Das heißt, Damit nicht nur ein bdsd.sock Prozess den Socket bdsd.sock , sondern jedes ausgeführte Skript Anforderungen senden und empfangen kann.
Idee
In meinem Kopf entstand die Idee, einen eigenen Nachrichtenbroker auf node.js über Unix-Sockets zu erstellen, mit dem Clients eine Verbindung herstellen, Themen abonnieren und Nachrichten gemäß dem in ihnen geschriebenen Code empfangen / senden können. Ich wusste, dass es bereits vorgefertigte Lösungen gab, von denen nur die Faulen in letzter Zeit noch nichts gehört hatten, aber die Idee, meine eigene Entscheidung zu treffen, war obsessiv.
Infolgedessen wird der Dienst gestartet.
Schrieb einen Logger, der Nachrichten an das Thema sendet. Abonnenten erhalten und können alles tun, was vorgeschrieben ist. Praktisch - Protokolle aus mehreren Quellen können in einer Konsolenausgabe angezeigt werden.
Ich habe das Paket bobaos.pub in npm geschrieben und veröffentlicht, das im Gegensatz zu bdsd.sock keine Socket-Datei mehr erstellt, sondern eine Verbindung zu einem Broker herstellt. Auf den ersten Blick funktioniert alles so, wie es sollte.
Das Problem
Dann habe ich ein Skript ausgeführt, das regelmäßig über Nacht Anforderungen an den KNX-Bus sendet. Als ich morgens aufwachte und die LEDs blinkten, die das Senden / Senden von Daten signalisierten, stellte ich fest, dass etwas nicht stimmte. Nachrichten erreichten nicht rechtzeitig. Ich fand heraus, dass der selbstgeschriebene Nachrichtenbroker fast alle verfügbaren 512 MB RAM (von BeagleBoard Black) beanspruchte. Weitere Arbeiten mit nodejs bestätigten, dass der Speicher die Schwachstelle von js-Skripten ist.
Lösung
Aus diesem Grund wurde beschlossen, von generischen Unix-Sockets auf Redis umzusteigen (er weiß übrigens auch, wie man mit ihnen arbeitet). Vielleicht hat es sich gelohnt, den Speicher zu sortieren und Lecks zu finden, aber ich wollte schneller laufen.
bobaos bedeutet UART-Kommunikation mit Nachrichtenumbruch in FT1.2, wir haben synchrone Kommunikation. Das heißt, <..--..> . Das Bobaos-Modul, das für die Kommunikation verantwortlich ist, speichert alle Anforderungen im Array, zieht sie nacheinander heraus, sendet sie an UART und lässt mit der eingehenden Antwort das für diese Anforderung verantwortliche Versprechen zu.
Sie können diesen Weg gehen: Der Dienst hört den redis PUB / SUB-Kanal ab, akzeptiert Anforderungen und sendet ihn an KNX. In diesem Fall liegt die Last in der Anforderungswarteschlange bobaos Modul js bobaos . Für die Implementierung müssen Sie ein einfaches Modul schreiben, das einen Kanal abonniert hat, und Nachrichten mithilfe der JSON.parse() -Methode konvertieren. Weiterhin kann dieses Modul in anderen Skripten verwendet werden.
Eine weitere Option, nach der ich redis : Verwenden Sie einen vorhandenen Task-Manager über redis . In der bee-queue werden verschiedene Optionen ausgewählt.
→ Unter der Haube
Es beschreibt, wie die bee-queue funktioniert. Wenn Sie diese Bibliothek für andere Programmiersprachen implementieren, können bobaos auf diese Weise Client-Bibliotheken für bobaos .
In der zweiten Version werden alle Anforderungen in redis Listen gespeichert, nacheinander herausgezogen und an die serielle Schnittstelle gesendet.
Weiter folgt das Umschreiben der vorherigen Version, aber ich speichere bereits alle Daten auf Datenpunkten in der redis Datenbank. Die einzige Unannehmlichkeit, die ich erlebe, ist, dass alle Anforderungen asynchron sind. Daher ist es bereits etwas schwieriger, ein Array von Werten zu erhalten, als nur auf das Array zuzugreifen.
Kleinere Optimierungen wurden vorgenommen.
Wenn es früher separate Methoden gab, getValue/getValues/readValue/readValues/setValue/setValues , akzeptieren getValue/readValue/setValues jetzt sowohl einen einzelnen Wert als auch ein Array.
Die Methode getValue([id1, id2, ...]) in der vorherigen Version hat für jeden Datenpunkt eine Anforderung an die serielle Schnittstelle gesendet. Es besteht jedoch die Möglichkeit, eine Anfrage für mehrere Werte zu senden. Einschränkungen - Die Antwortgröße muss gleich BufferSize , maximal 250 Byte. Es ist auch unmöglich, über die Anzahl der Objekte hinauszugehen, für aktuelle Versionen von BAOS 83x-Modulen - 1000.
Wertelängen sind bekannt, auch Header. Weiterhin ein ziemlich einfacher Algorithmus mit while- und wait-Zyklen =)
- Sortieren Sie das Array und löschen Sie gegebenenfalls doppelte Elemente. Wir erhalten ein idUniqArray.
- Wir starten den Zyklus i < idUniq.length, in dem wir Folgendes tun:
 a) Nehmen Siestart: idUniq[i], dafür betrachten wir die maximale Anzahl von Werten, die wir bekommen können. Wenn zum Beispiel alle Objekte vom Typ DPT1 / DPT5 (1 Byte) sind, können wir Werte in der Menge von 48 erhalten. Es gibt eine Bemerkung: Wenn wir zum Beispiel Objekte#[1, 2, 3, 10, 20]konfiguriert haben#[1, 2, 3, 10, 20], dann gibt die Antwort beim Abfragen vonGetDatapointValue.Req(1, 30)selbst für nicht vorhandene Datenpunkte[4, 5, 6, ..., 30]Null-GetDatapointValue.Req(1, 30)-Werte zurück.
 b) Die Zählung erfolgt in einem neuen Zyklusj < i + max(wobeimax50 ist oder, wenn nahe 1000, dann maximal1000 - id + 1, für 990 11, für 999 - 2), wenn wir uns beim Zählen treffen Elemente des Arrays aus der ursprünglichen Abfrage, weisen Sie danniVariableniden Indexi.
 c) Wenn im Zyklusjberechnete Länge die maximale Pufferlänge überschreitet, bilden wir das Abfragekartenelement{start: start, number: number}, legen es in einem separaten Array ab, erhöhen die Variablei(oder weisen den Index dem imidUniqgefundenenidUniq), unterbrechen Zyklusj, beide Zyklen werden neu gestartet.
Daher bilden wir mehrere Anfragen für bobaos . Wenn Sie beispielsweise eine getValue([1, 2, 3, 40, 48, 49, 50, 100, 998, 999, 1000]) Anforderung senden getValue([1, 2, 3, 40, 48, 49, 50, 100, 998, 999, 1000]) , können die Anforderungen für einen speziellen Fall wie folgt lauten:
 {start: 1, number: 48}, // 1, 2, 3, 40, 48 {start: 49, number: 48}, // 49, 50 {start: 100, number: 48}, // 100 {start: 998, number: 3} // 998, 999, 1000 
Es könnte anders gemacht werden:
 {start: 1, number: 48}, // 1, 2, 3, 40, 48 {start: 49, number: 2}, // 49, 50 {start: 100, number: 1}, // 100 {start: 998, number: 3} // 998, 999, 1000 
Es würde so viele Anfragen geben, weniger Daten. Ich habe jedoch bei der ersten Option angehalten, da die erhaltenen Werte in der redis Datenbank gespeichert sind und mit der Methode getStoredValue abgerufen werden getStoredValue , die ich häufiger zu verwenden versuche als mit getValue , die Daten über die serielle Schnittstelle sendet.
Für die ping/get sdk state/reset eine separate Warteschlange erstellt. Wenn also etwas mit der Kommunikation an der seriellen Schnittstelle nicht stimmt (der Frame-Zähler ist in die Irre gegangen usw.) und die Ausführung bei einer Aufgabe stoppt, können Sie eine reset in einer anderen Warteschlange senden und sdk entsprechend neu sdk .
Client-Seite - bobaos
Zur Steuerung von KNX-Datenpunkten in Benutzerskripten kann das Modul bobaos.sub verwendet werden.
Das folgende Beispiel behandelt alle Funktionen eines Moduls:
 const BobaosSub = require("bobaos.sub"); //     : // redis:   url // request_channel: "bobaos_req"  , // service_channel: "bobaos_service"  , // broadcast_channel: "bobaos_bcast"   let my = BobaosSub(); my.on("connect", _ => { console.log("connected to ipc, still not subscribed to channels"); }); my.on("ready", async _ => { try { console.log("hello, friend"); console.log("ping:", await my.ping()); console.log("get sdk state:", await my.getSdkState()); console.log("get value:", await my.getValue([1, 107, 106])); console.log("get stored value:", await my.getValue([1, 107, 106])); console.log("get server item:", await my.getServerItem([1, 2, 3])); console.log("set value:", await my.setValue({id: 103, value: 0})); console.log("read value:", await my.readValue([1, 103, 104, 105])); console.log("get programming mode:", await my.getProgrammingMode()); console.log("set programming mode:", await my.setProgrammingMode(true)); console.log("get parameter byte", await my.getParameterByte([1, 2, 3, 4])); console.log("reset", await my.reset()); } catch(e) { console.log("err", e.message); } }); my.on("datapoint value", payload => { //   ,  payload    ,    //     Array.isArray(payload) console.log("broadcasted datapoint value: ", payload); }); my.on("server item", payload => { //   ,  payload    ,    //     Array.isArray(payload) console.log("broadcasted server item: ", payload); }); my.on("sdk state", payload => { console.log("broadcasted sdk state: ", payload); }); 
Die Befehlszeilenschnittstelle wurde neu geschrieben. Wie ich es implementiert habe, folgender Artikel:
→ CLI auf NodeJS schreiben
Die Teams sind kürzer, klarer und funktionaler geworden.
 bobaos> progmode ? BAOS module in programming mode: false bobaos> progmode 1 BAOS module in programming mode: true bobaos> progmode false BAOS module in programming mode: false bobaos> description 1 2 3 #1: length = 2, dpt = dpt9, prio: low, flags: [C-WTU] #2: length = 1, dpt = dpt1, prio: low, flags: [C-WT-] #3: length = 1, dpt = dpt1, prio: low, flags: [C-WT-] bobaos> set 2: 0 20:27:06:239, id: 2, value: false, raw: [AA==] bobaos> set [2: 0, 3: false] 20:28:48:586, id: 2, value: false, raw: [AA==] 20:28:48:592, id: 3, value: false, raw: [AA==] 
Nachwort
Das Ergebnis war ein stabiles Arbeitssystem. Redis als Backend funktioniert stabil. Während der Entwicklung wurden viele Zapfen verpackt. Aber der Lernprozess ist so, dass er manchmal unvermeidlich ist. Aus meiner Erfahrung heraus nodejs ich fest, dass nodejs Prozesse von nodejs ziemlich viel RAM verbrauchen (20 MB zu Beginn) und es möglicherweise zu Undichtigkeiten kommen kann. Für die Heimautomation ist dies von entscheidender Bedeutung - da das Skript ständig funktionieren muss und im Laufe der Zeit immer mehr wächst, kann es ab einem bestimmten Punkt den gesamten Speicherplatz beanspruchen. Daher müssen Sie sorgfältig Skripte schreiben, die Funktionsweise des Garbage Collectors verstehen und alles unter Kontrolle halten.
Die aktualisierte Dokumentation finden Sie hier .
Im nächsten Artikel werde ich darüber sprechen, wie ich mit redis und bee-queue einen Service für Software-Zubehör erstellt habe.
UPD: bobaoskit - Zubehör, dnssd und WebSocket
Ich freue mich über jede Rückmeldung.