NEWS
[Gelöst] Datenpunkte im JSON-Format und deren Visualisierung
-
Hallo,
nachdem ich nun sehr viel gesucht habe und nichts wirklich passendes gefunden habe, schreibe ich nun hier in der Hoffnung, dass ich die ganz simple, existierende Lösung übersehen habe:
Ich habe über zigbee2mqtt einige Geräte eingebunden. Die Geräte stellen ihre Daten als Datenpunkt im JSON-Format über den MQTT-Adapter zur Verfügung, soweit alles super.
Nun möchte ich natürlich die Daten dieser Geräte auf einer Visualisierung anzeigen und diese auch darüber ansteuern.
Leider habe ich bisher keinen einfachen Weg gefunden, wie ich diese Werte direkt anzeigen kann und erst recht nicht, wie ich entsprechende Steuerbefehle zurückschreiben kann.
Konkret:
Unter dem Datenpunkt mqtt.0.zigbee2mqtt.Lampe wird mir der Wert {"brightness":0,"color_mode":"color_temp","color_temp":450,"linkquality":47,"state":"OFF","update":{"state":"available"},"update_available":true} geliefert und wenn ich die Lampenhelligkeit einstellen möchte, muss ich z.B. an den Datenpunkt mqtt.0.zigbee2mqtt.Lampe.set den Wert {"brightness":50} schicken.Die einzige Möglichkeit, die ich bisher gefunden habe, ist, dass ich für jeden Wert, den ich auslesen möchte und für jeden Wert, den ich einstellen möchte, einen eigenen Datenpunkt anlege und die entsprechenden Daten bei Änderung über ein kleines Skript übertrage. Abgesehen davon, dass dies unheimlich viele kleine Skripte werden, die alle händisch angelegt werden müssen (in blockly zumindest, in JavaScript geht es womöglich einfacher), müssen diese Scripte natürlich auch die ganze Zeit reagieren, wenn sich Werte ändern, unabhängig davon, ob die Oberfläche zur Zeit irgendwo dargestellt wird.
Habe ich da irgend etwas übersehen? Gibt es eine Möglichkeit beim Eintragen von Datenpunkten in Vis direkt einen Wert aus dem JSON-Format zu extrahieren und evtl. die Möglichkeit, den eingestellten Wert in Vis über eine Formatanweisung in einen entsprechenden JSON-Wert zu übersetzen?
OpenHAB bietet diese Möglichkeiten, jedoch wollte ich gucken, ob mir ioBroker nicht besser gefällt und mehr Möglichkeiten bietet. Bisher macht ioBroker auch einen deutlich besseren Eindruck als OpenHAB, mit einer Lösung für dieses kleine Problem würde ich sofort wechseln.
-
Inzwischen habe ich ein "kleines" Skript geschrieben, welches entsprechende Datenpunkte aus den Zigbee-Geräten unter mqtt.0 erzeugt (falls sie nicht existieren). Die Datenpunkte liegen unter javascript.0.MQTT und sind per Default nur lesbar. Die entsprechenden Einstellungen können natürlich an den Elementen geändert werden. Wenn ein Datenpunkt auch schreibbar geschaltet wird, muss das Skript neu gestartet werden, damit die entsprechenden Subscriptions erstellt werden.
Es muss auch ein Datenpunkt 0_userdata.0.WriteZigbeeData angelegt werden (boolean), mit dem im Zweifelsfall die Aktualisierungen unterbrochen werden können.Das Skript versucht die Daten, die an einem entsprechend schreibbar geschalteten Datenpunkt gesetzt werden, auf mqtt zurück zu schreiben. Hierbei können natürlich Rückkopplungseffekte auftreten. Ich habe im Skript versucht diese weitestgehend abzufangen. Falls es schief geht, kann man 0_userdata.0.WriteZigbeeData auf false setzen um diese Rückkopplung zu unterbrechen.
Außerdem können im Skript am Anfang Kanäle für Geräte definiert werden, falls diese nicht automatisch erkannt wurden und es können auch Einstellungen für Datenpunkte vorgegeben werden.
Dabei habe ich auch eine Möglichkeit zur Parameterkonvertierung und zur bedingten Übertragung eingebaut. Ersteres, da ich Elemente habe welche in MQTT "ON" oder "OFF" als Wert haben, jedoch in ioBroker natürlich als boolean dargestellt werden sollen. Zweiteres, um Rückkopplungen bei z.B. einer Lampe zu vermeiden. Die Lampe unterstützt "color" und "color_temp" als Kanäle. Wird "color_temp" gesetzt, so ändert sich auch der Wert von "color", wodurch es hier eine Rückkopplung geben würde, da dementsprechend der geänderte Wert von "color" darauf folgend wieder an MQTT geschrieben werden würde.Hier das Skript, vielleicht hilft es ja irgend jemandem. Für Anregungen oder Änderungsvorschläge bin ich immer dankbar.
// Datenpunkt (boolean) über den das Kopieren der MQTT-Zustände auf die endsprechenden Datenpunkte aktiviert/deaktiviert werden kann. // Dieser Datenpunkt muss existieren const WriteZigbeeDataActivated = "0_userdata.0.WriteZigbeeData" // Datenpunkt (boolean) über den weitere Logs dieses Skripts aktiviert werden können. const LogDebugZigbeeDataActivated = "0_userdata.0.WriteZigbeeDebug" /* Eine Map von zigbee2mqtt-Werten eines Elements, mit den Default-Eigenschaften, die ein Element dieses Namens haben soll. Siehe auch DATA_POINTS */ const GENERAL_DATA_POINTS = {"battery": {"type":"number"}, "linkquality": {"type":"number"}, "humidity": {"type": "number"}, "temperature": {"type": "number"} }; /* DATA_POINTS * Eine Map von zigbee2mqtt-Elementen zu deren anzulegenden Datenpunkten. * Die Datenpunkte sind dabei wiederum eine Map mit entsprechenden Einträgen für den Namen des Datenpunktes zu seinen Eigenschaften. * Die Eigenschaften sind dabei Werte, die in das "common"-Feld des zu erzeugenden Datenpunktes geschrieben werden. * Dabei sind die Defaults: {"read": true, "write": false, "type": "mixed"} * Wird statt einer Map der Wert null angegeben, so werden die Defaults unter Berücksichtigung von GENERAL_DATA_POINTS verwendet. * * Mögliche weitere Felder, welche bei der Übersetzung zwischen MQTT-Datum und Datenpunkt verwendet werden, sind: * "trans": [[0,"OFF"],[1,"ON"]] * Eine Liste von Wertepaaren, mit jeweils Index 0: Wert des Datenpunktes, Index 1: Wert des MQTT-Datums * "condition_zigbee_get": ["color_mode","color_temp"] * Eine Liste mit zwei Werten, der erste Wert gibt einen anderes MQTT-Datum dieses Geräts an, welches, wenn es vorhanden ist, den zweiten Wert enthalten muss, damit * der dazugehörige Datenpunkt bei Veränderung auch gesetzt wird. * * Zigbee-Elemente, die hier nicht aufgeführt sind, werden beim Start dieses Skripts auch übertragen. Dabei werden sämtliche aktuell enthaltene MQTT-Daten einfach mit * den Defaults als Datenpunkte angelegt. */ const DATA_POINTS = {"Steckdose1": {"state": {"type": "boolean", "trans": [[0,"OFF"],[1,"ON"]]}, "linkquality": null}, "WZSubwoofer": {"state": {"type": "boolean", "trans": [[0,"OFF"],[1,"ON"]]}, "linkquality": null}, "SwitchIO1": {"action": null, "battery": null, "linkquality": null}, "SwitchLamp1": {"action": null, "battery": null, "linkquality": null}, "SwitchRollo1": {"action": null, "battery": null, "linkquality": null}, "SwitchRollo2": {"action": null, "battery": null, "linkquality": null}, "RolloWZLinks": {"position": {"write": true, "type": "number"}, "battery": null, "linkquality": null}, "RolloWZRechts": {"position": {"write": true, "type": "number"}, "battery": null, "linkquality": null}, "LichtPanelWZ": {"brightness": {"write": true, "type": "number"}, "color": {"write": true, "condition_zigbee_get": ["color_mode","xy"]}, "color_mode": {}, "color_temp": {"write": true, "type": "number", "condition_zigbee_get": ["color_mode","color_temp"]}, "state": {"type": "boolean", "trans": [[0,"OFF"],[1,"ON"]]}, "linkquality": null} }; // Basis der zu erzeugenden Datenpunkte var js_base = "javascript.0."; // Verzeichnis in der Basis, in dem die Datenpunkte erzeugt werden sollen var dest_base = "MQTT."; /********************** * Konfiguration Ende **********************/ // Aufsammeln der Zigbee-Datenpunkte. Falls es mehrere Zigbee-Instanzen gibt muss dies hier angepasst werden. var states = $('state[id=mqtt.0.zigbee2mqtt.*]'); function writeZigbeeData () { try { var obj = getObject (WriteZigbeeDataActivated); if (obj.type == "state" && obj.common.type == "boolean") { var state = getState (WriteZigbeeDataActivated); return state.val; } return false; } catch (err) { console.log ("Get zigbee write state failed: " + err.message); throw err; } } function debugLogActive () { try { var obj = getObject (LogDebugZigbeeDataActivated); if (obj.type == "state" && obj.common.type == "boolean") { var state = getState (LogDebugZigbeeDataActivated); return state.val; } } catch (err) {} return false; } function debugLog (msg) { if (debugLogActive ()) console.log (msg); } function getGeneralDataPoint (name) { try { let dp = GENERAL_DATA_POINTS[name]; if (dp != undefined) { if (dp["read"] == undefined) dp["read"] = true; if (dp["write"] == undefined) dp["write"] = false; if (dp["type"] == undefined) dp["type"] = "mixed"; return dp; } } catch (err) { } return {read: true, write: false, "type": "mixed"}; } function getDataPoints (dp_name, zigbee_state) { try { let dp = DATA_POINTS[dp_name]; if (dp != undefined) { for (v in dp) { if (dp[v] == null) { dp[v] = getGeneralDataPoint (v); } if (dp[v]["read"] == undefined) dp[v]["read"] = true; if (dp[v]["write"] == undefined) dp[v]["write"] = false; if (dp[v]["type"] == undefined) dp[v]["type"] = "mixed"; } return dp; } } catch (err) { } let dps = {}; for (var v in zigbee_state) { debugLog (" v[" + String (v) + "]: " + JSON.stringify (zigbee_state[v])); dps[v] = getGeneralDataPoint (v); } return dps; } function convertToDataPointType (datapoint, value) { if (!existsObject (datapoint)) return value; try { let t = getObject (datapoint).common.type; switch (t) { case "number": return Number (value); case "boolean": switch (value) { case "ON": case "true": case "True": case true: case 1: return true; } return false; } } catch (err) {} return JSON.stringify (value); } if (!writeZigbeeData ()) { console.log ("Set zigbee state deactivated"); } // Als erstes die Datenpunkte anlegen. for (var j = 0; j <= states.length; j++) { // Nicht definierte, .set-Datenpunkte, die Bridge und nicht existierende Datenpunkte ignorieren if (states[j] == undefined || states[j].startsWith ("mqtt.0.zigbee2mqtt.bridge") || states[j].endsWith (".set")) continue; if (!existsObject (states[j])) continue; // Fixe Codierung ab welchem Zeichen die Namen der Zigbee-Geräte zu finden sind - wäre besser dynamisch, ist mir aber gerade zu aufwendig. let zigbee_name = states[j].substr (19); debugLog (String (j) + ": " + JSON.stringify (zigbee_name)); let device_name = dest_base + zigbee_name; let state = JSON.parse (getState (states[j]).val); debugLog ("state of " + zigbee_name + ": " + JSON.stringify (state)); // Das Gerät selber auch als Datenpunkt anlegen if (!existsState (device_name)) { let obj = { read: true, write: true, desc: "Automatically created from " + states[j], }; createState (device_name, "", obj); console.log ("Create " + device_name); } let datapoints = getDataPoints (zigbee_name, state); for (var dp in datapoints) { debugLog (" v[" + String (dp) + "]: " + JSON.stringify (datapoints[dp])); var datapoint_name = device_name + "." + dp; if (!existsState (datapoint_name)) { let obj = datapoints[dp]; obj.desc = "Automatically created from " + states[j] + "[\"" + dp + "\"]"; createState (datapoint_name, "", obj); console.log ("Create " + datapoint_name); } } } for (let j = 0; j <= states.length; j++) { if (states[j] == undefined || states[j].startsWith ("mqtt.0.zigbee2mqtt.bridge") || states[j].endsWith (".set")) continue; if (!existsObject (states[j])) continue; // Fixe Codierung ab welchem Zeichen die Namen der Zigbee-Geräte zu finden sind - wäre besser dynamisch, ist mir aber gerade zu aufwendig. let zigbee_name = states[j].substr (19); let device_name = dest_base + zigbee_name; let state = JSON.parse (getState (states[j]).val); let read_dps = [] let datapoints = getDataPoints (zigbee_name, state); for (let dp in datapoints) { debugLog (" v[" + String (dp) + "]: " + JSON.stringify (state[dp])); let datapoint_name = device_name + "." + dp; if (!existsState (datapoint_name)) continue; let obj = getObject (js_base + datapoint_name); try { if (obj.common.write) { console.log ("Subscribe: " + js_base + datapoint_name); const zigbeeObject = states[j]; const control_name = dp; const dp_name = js_base + datapoint_name; on({id: js_base + datapoint_name, change: "ne"}, async function (obj2) { try { if (getState (WriteZigbeeDataActivated).val) { let value = obj2.state.val; let oldValue = obj2.oldState.val; // Nur übertragen, wenn es eine relevante Änderung gab - Überprüfung über Strings, damit die Werte entsprechend gerundet werden. if (JSON.stringify (value) != JSON.stringify (oldValue)) { let obj = getObject (dp_name); let v = getState(dp_name).val; try { let trans = obj.common.trans; //console.log ("find " + JSON.stringify (v) + " in " + JSON.stringify (trans)); for (let l in trans) if (trans[l][0] == v) { v = trans[l][1]; //console.log (" translated to " + JSON.stringify (v) + " (l=" + JSON.stringify (trans[l]) + ")"); break; } } catch (err) { //console.log ("error trans: " + err.message); } let v2 = JSON.stringify (v); //console.log ("Set zigbee state " + zigbeeObject + ".set: " + '{"' + control_name + '":' + v2 + '}' + " value: " + JSON.stringify (value) + " oldV: " + JSON.stringify (oldValue)); setState(zigbeeObject + ".set", ('{"' + control_name + '":' + v2 + '}')); } } else { console.log ("Set zigbee state deactivated"); } } catch (err) { console.log ("Set zigbee state failed: " + err.message); } }); } if (obj.common.read) read_dps.push (dp); } catch (err) { console.log ("Object " + datapoint_name + " err: " + err.message); } } if (read_dps.length > 0) { console.log ("Subscribe: " + states[j]); const zigbeeObject = states[j]; const dev_name = js_base + device_name; const control_names = read_dps; on({id: states[j], change: "ne"}, async function (obj) { let value = obj.state.val; let oldValue = obj.oldState.val; for (let v in control_names) { let datapoint_name = dev_name + "." + control_names[v]; let val = getState(zigbeeObject).val; let v2 = jsonataExpression((function () { try {return JSON.parse(val);} catch(e) {return {};}})(),control_names[v]); let dp_obj = getObject (datapoint_name); try { let trans = dp_obj.common.trans; for (let l in trans) if (trans[l][1] == v2) { v2 = trans[l][0]; break; } } catch (err) {} try { let cond = dp_obj.common.condition_zigbee_get; let vc = jsonataExpression((function () { try {return JSON.parse(val);} catch(e) {return {};}})(),cond[0]); if (vc != cond[1]) { debugLog ("Skip js state " + datapoint_name + ": " + JSON.stringify (v2) + " from: " + zigbeeObject + ": " + getState(zigbeeObject).val + " [" + control_names[v] + "]"); continue; } } catch (err) {} let v3 = convertToDataPointType (datapoint_name, v2) if (getState (datapoint_name).val != v3) { debugLog ("Set js state " + datapoint_name + ": " + JSON.stringify (v2) + " from: " + zigbeeObject + ": " + getState(zigbeeObject).val + " [" + control_names[v] + "]"); setState(datapoint_name, v3, true); } } }); } }
Edit: Bugfix im Script
-
https://forum.iobroker.net/topic/31521/test-widget-json-template?_=1638915795778
da könntest auch mal schauen
-
Okay, es geht auch deutlich einfacher wie hier von @hydrotec beschrieben. Einfach bei zigbee2mqtt die richtige Konfiguration einstellen.
Da ich inzwischen den Zigbee-Adapter ans Laufen bekommen habe, habe ich nun eine andere Lösung für dieses Problem gefunden.
Zwar ist dadurch die Migration von OpenHAB zu ioBroker nicht ganz so sanft verlaufen, da ich sämtliche Skripte von OpenHAB auf einen Schlag umstellen musste und nicht von OpenHAB und ioBroker gemeinsam auf die Zigbee-Geräte zugreifen konnte, aber dafür funktioniert jetzt auch alles problemlos.Dementsprechend stelle ich diesen Thread mal auf gelöst.