bobaos.pub - KNX TP / UART, Raspberry Pi und Redis


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 =)


  1. Sortieren Sie das Array und löschen Sie gegebenenfalls doppelte Elemente. Wir erhalten ein idUniq Array.
  2. Wir starten den Zyklus i < idUniq.length , in dem wir Folgendes tun:
    a) Nehmen Sie start: 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 von GetDatapointValue.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 Zyklus j < i + max (wobei max 50 ist oder, wenn nahe 1000, dann maximal 1000 - 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 dann i Variablen i den Index i .
    c) Wenn im Zyklus j berechnete 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 Variable i (oder weisen den Index dem im idUniq gefundenen idUniq ), unterbrechen Zyklus j , 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); }); 

bobaos.tool


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.

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


All Articles