NEWS
[Vorlage] Script: JSON-->Datenpunkte mit sync
-
Hallo zusammen,
ich beschäftige mich seit einiger Zeit damit, Daten aus meiner ENPHASE Photovoltaik Anlage zu extrahieren und damit dann die üblichen "smarten" Sachen zu machen wie Visualisierung, Statistik, Überschussladen etc.
Nach einigem Experimentieren bin ich als einzig wirklich zielführende Lösung bei einem Plugin für Homebridge gelandet, das aber leider nicht über den HAM Adapter funktioniert (es könnte etwas damit zu tun haben, dass das Plugin eine "Child Bridge" startet, aber das ist eine andere Diskussion) sondern nur über eine eigenständige Homebridge Instanz in einer eigenen VM und diese liefert dann über MQTT richtig viele Daten, verteilt auf 12 Datenpunkte mit JSON Struktur und jeder dieser JSON-Sätze hat zwischen 4 und etwa 50 Attribute. Das ist schön, aber etwas unhandlich für die Weiterverarbeitung. Zudem habe ich keine Doku für die Struktur gefunden und muss öfter mal andere Datenpunkte versuchsweise auswerten, um die Richtigen zu finden.
Lange Rede, kurzer Sinn: Aus dieser Aufgabe ist ein einfaches JavaScript enstanden, das das Extrahieren solcher Daten und Abbilden auf eigene Datenpunkte vereinfacht, in dem man die zu extrahierenden Daten in eine Array von Objekten einträgt, Quell- und Zielpfad definiert und das Script startet.
Danach werden die Zieldatenpunkte angelegt, wenn sie nicht schon existieren und über einen "$elektor" ein Trigger erzeugt, der dann dafür sorgt, dass die eigenen Datenpunkte immer aktualisiert werden, wenn neue JSON Daten geliefert worden sind. Da das schnell recht viel Ressourcen ziehen kann bei den Datenmengen habe ich das noch etwas optimiert, um nur dann die ganze Liste der Datenpunkte auf mögliche Aktualisierungen abzuklappern, wenn mindestens einer der relevanten JSON Datenpunkte aktualisiert wurde. Ich prüfe im Script nicht, welcher der Datenpunkte innerhalb des JSON geändert wurde, sondern aktualisiere/ändere immer alle Datenpunkte, die aus diesem JSON "gefüttert" werden.
Vielleicht kann das ja auch für Andere nützlich sein, ich wollte gerne der Community mal etwas zurück geben auf diesem Weg. Details zu ENPHASE demnächst in einem eigenen Thread.
'use strict'; const mqttPath = 'mqtt.0.envoy.Envoy-S.'; // Quellpfad mit den JSON Daten, muss nicht aus MQTT kommen const userPath = '0_userdata.0.envoy.'; // Zielpfad für die Datenpunkte, die einzelne Attribute der JSON Struktur repräsentieren /* Beispiel: Wenn "mqtt.0.envoy.Envoy-S.Production" enthält { "wattHoursToday": 2765, "wattHoursSevenDays": 25747, "wattsNow": 1601 } und wenn man diese Werte unter 0_userdata.0.envoy.Production.wattHoursSevenDays usw. in eigene Datenpunkte schreiben möchte, dann könnte das Array mit den Objekten wie folgt aussehen: const mapArr = [ { topic: 'Production', attr: 'wattHoursSevenDays', dpId: 'Production.wattHoursSevenDays', dpType: 'number', dpUnit: 'Wh', dpDef:0, dpFactor:0 }, { topic: 'Production', attr: 'wattHoursLifetime', dpId: 'Production.wattHoursLifetime', dpType: 'number', dpUnit: 'Wh', dpDef:0, dpFactor:0 }, { topic: 'Production', attr: 'wattsNow', dpId: 'Production.wattsNow', dpType: 'number', dpUnit: 'W', dpDef:0, dpFactor:0 }, ]; Dabei haben die Attribute die folgende Bedeutung: topic: Der letzte Teil des Namens des Datenpunktes, auf den getriggert werden soll, also der Teil nach dem, was in mqttPath steht. Bitte beachten, dass dies wirklich ein Datenpunkt sein muss, kein Folder/Channel etc. attr: Das JSON Attribut, das gelesen werden soll dpID: Der letzte Teil des Namens des Datenpunktes, in den geschrieben werden soll, also der Teil nach dem, was in userPath steht dpType: Typ des zu schreibenden Datenpunktes, also z.B. 'number', 'string', 'boolean' dpUnit: Einheit des zu schreibenden Datenpunktes, also z.B. 'Wh', 'V' dpDef: Der Default Wert des zu schreibenden Datenpunktes, mit dem der Datenpunkt initialisiert wird dpFactor: Für Datenpunkte vom Typ Number ein Faktor, mit dem Quellwert multipliziert wird. Damit kann man z.B. von Watt in Kilowatt umrechnen oder auch das Vorzeichen umdrehen. ACHTUNG: Für alle anderen Typen von Datenpunkten MUSS hier eine 0 stehen! */ // Hier die JSON Daten eintragen, die unter "<userPath>.<dpId>" gespeichert werden sollen und die Beispieldaten rauswerfen const mapArr = [ { topic: 'Production', attr: 'wattHoursSevenDays', dpId: 'Production.wattHoursSevenDays', dpType: 'number', dpUnit: 'Wh', dpDef:0, dpFactor:0 }, { topic: 'Production', attr: 'wattHoursLifetime', dpId: 'Production.wattHoursLifetime', dpType: 'number', dpUnit: 'Wh', dpDef:0, dpFactor:0 }, { topic: 'Production', attr: 'wattsNow', dpId: 'Production.wattsNow', dpType: 'number', dpUnit: 'W', dpDef:0, dpFactor:0 }, ]; // Ab hier nichts mehr ändern! let uniqueListOfTopics = new Set(); // Datenpunkte anlegen, wenn noch nicht vorhanden for (const [i,e] of mapArr.entries()){ createState(`${userPath+e.dpId}`, { name: `${e.dpId}`, type: `${e.dpType}`, unit: `${e.dpUnit}`, def: `${e.dpDef}`}); uniqueListOfTopics.add(e.topic); // log(`${userPath+userPrefix+e.dpId}`); } $(`state[id=${mqttPath}*]`).on(async function (obj) { const value = obj.state.val; const topic = obj.id.split('.').pop(); // log(topic); if (uniqueListOfTopics.has(topic)) { for (const [i,e] of mapArr.entries()){ if(e.topic === topic){ // log(topic); const valToRead= getAttr((function () { try {return JSON.parse(value);} catch(e) {return {};} })(), e.attr); // log(valToRead); setState(`${userPath+e.dpId}`, e.dpFactor? valToRead * e.dpFactor : valToRead , false); } } } });
-
um das wirklich multifunktional zu machen, könnten man
-
den Wert mapArr.attr als JSONata-Ausdruck definieren, so das der Quellwert aus einer beliebig tief verschachtelten JSON-Struktur kommen kann. so kann es nur Werte aus nur einer Ebene des Objektes lesen.
-
Mit der folgenden Bibliothek die Differenz zwischen dem letzten JSON und dem aktuellen ziehen und nur die Werte überhaupt behandeln, die sich seit dem letzten mal geändert haben.
https://www.npmjs.com/package/deep-object-diff
-
-
@oliverio Gute Ideen, das behalte ich mal im Hinterkopf...