NEU! UPGRADE IP 10 verfügbar!
Timberwolf VISU jetzt mit Graphic V Upgrade
Optimierte Darstellung von VISU Editor und VISU Client - sowie viele weitere Verbesserungen
Infos im Wiki: https://elabnet.atlassian.net/l/cp/8HzePCm3

Insider & Leistungsmerkmale FÜR ALLE freigeschaltet
Damit kann nun jeder das Upgrade vornehmen und VISU & IFTTT testen. Alle Info hier: viewtopic.php?f=8&t=5074

NEU! Ausführliches Video Tutorial zur IP 10
Jetzt werden alle Fragen beantwortet. Das Video: https://youtu.be/_El-zaC2Rrs

[Erfahrungsbericht] [V4.0 IP8.1] NodeRed und Tibber-Preisanalyse

Alles rund um Node Red im Allgemeinen und den entsprechenden Docker-Container für den Timberwolf Server im Speziellen.
Forumsregeln
  • Denke bitte an aussagekräftige Titel und gebe dort auch die [Firmware] an. Wenn ETS oder CometVisu beteiligt sind, dann auch deren Version
  • Bitte mache vollständige Angaben zu Deinem Server, dessen ID und dem Online-Status in Deiner Signatur. Hilfreich ist oft auch die Beschreibung der angeschlossener Hardware sowie die verwendeten Protokolle
  • Beschreibe Dein Projekt und Dein Problem bitte vollständig. Achte bitte darauf, dass auf Screenshots die Statusleiste sichtbar ist
  • Bitte sei stets freundlich und wohlwollend, bleibe beim Thema und unterschreibe mit deinem Vornamen. Bitte lese alle Regeln, die Du hier findest: https://wiki.timberwolf.io/Forenregeln
Antworten

Ersteller
gbglace
Reactions:
Beiträge: 3596
Registriert: So Aug 12, 2018 10:20 am
Hat sich bedankt: 1255 Mal
Danksagung erhalten: 1662 Mal

[V4.0 IP8.1] NodeRed und Tibber-Preisanalyse

#1

Beitrag von gbglace »

Hallo zusammen,

ich beschäftige mich aktuell etwas mit NodeRed und der Auswertung meiner Tibber-Daten.

Da ich meine PV-Speicher im Winter noch mehr Ladenzyklen bescheren möchte, brauche ich hier mehr Informationen zum Preisniveau der aktuellen Stunde und wann denn demnächst wieder günstiger aus der Batterie entnommen werden kann als direkt aus der "Steckdose".

Ich will den Thread hier mal dazu verwenden meine Umsetzungen zu beschreiben und nehme gerne weitere Anregungen entgegen.
Es darf sich auch gern jemand mit profundem JavaScript Kenntnissen gern melden für einen CodeReview. Denn die Hilfe von MS-Copilot bringt zwar was man funktionell will, aber das es guter Code ist bedeutet es noch lange nicht.

Die Anbindung der Tibberpreise für 24h Heute und den 24h Morgen mache ich jeweils einmal am Tag. Um 0:00 Uhr wird die API für heute abgefragt und kurz nach 13:00 Uhr wird die API für die Preise Morgen abgefragt. Dafür gibt es im NR auch passende fertige Tibber-Nodes.
Ich speichere diese JSONs direkt jeweils in einer Contextvariable.

Damit diese Context-Variablen auch einen Restart und Reboot des TWS-Containers überleben, muss man in der settings.js (man erreicht sie über einen SSH Zugriff auf dem angelegten data volume für den Container) noch die Option setzen, dass die Contextvariablen auch auf dem Filesystem gesichert werden und nicht nur im RAM. Die Sicherung erfolgt dann im 30-Sekunden Takt das ist wohl irgendwo fest hinterlegt.

Code: Alles auswählen

/** Context Storage
      * The following property can be used to enable context storage. The configuration
      * provided here will enable file-based context that flushes to disk every 30 seconds.
      * Refer to the documentation for further options: https://nodered.org/docs/api/context/
      */
     contextStorage: {
         default: {
             module:"localfilesystem"
         },
     },
Die Passage war bei mir in dem Settings file bereits enthalten aber per // auskommentiert.
Die Kommentierungen entfernt und den NR-Container restartet und schon werden Context-Variablen auch im Filesystem persistiert.

Hinter dem Datenabzug habe ich nun jeweils einen Function-Node gesetzt.
Mit dem nächtlichen Abzug der Tagespreise werden direkt auch Statistiken berechnet. Die Statistiken berechne ich für alle drei Preisbestandteile getrennt (Gesamtpreis, Strom, Steuer).

Ich berechne hier über 30-Tage (Der Parameter kann eingestellt werden) Minimum, Maximum, Durchschnitt, Standardabweichung, für die letzten 7-Tage auch noch Minimum und Maximum.

Dazu bilde ich quasi aus den tgl. einlaufenden JSON ein großes Sammel-JSON was dann bis zu 30*24 Array Elemente aus diesen Preisen erhält. Das JSON wird dann quasi rollierend erweitert, hinten 24 rein vorne 24 raus. Das JSON wird ebenfalls in einer Context-Variable gespeichert dabei entsorge ich aber die Spalten Währung und Level.

Im direkten Anschluss erfolgt dann über zwei for each Schleifen die Berechnung der Statistiken.

Es ist ein etwas umständlicher Weg, aber die Berechnung dauert nicht so lange und ich wollte das alles nicht erst irgendwie in eine Datenbank umgewandelt speichern und dann per SQL wieder auslesen, auch wenn mit dem SQL die Berechnungen ebenfalls einfach möglich wären. Ich habe aber keinen Rechner wo ich da eine passende Datenbank drauf habe und will das auch nicht irgendwo einrichten.

Bei der stündlichen Abfrage des aktuellen IST-Preises habe ich ja eh die Daten im TWS in der Influx für bunte Bilder im Grafana und der Visu.
Datenspeicherung mit Zukunftszeitstempeln in der TWS-Influx-Instanz sind ja nicht möglich, daher gehe ich halt Wege ohne dies zu benötigen, da ich auch keine zweite Influx-Instanz aufsetzen möchte. Die Charts würden da zwar sicher schöner aussehen mit Forecast Informationen für den Tag. Aber derzeit tut es das auch so.

An die Abfrage der Zukunftsdaten habe ich ebenfalls eine function gebaut die für kommenden 24h plus der restlichen zukünftigen Stunden des laufenden Tages, die gleichen Statistiken berechnet (Minimum, Maximum, Durchschnitt, Standardabweichung).

Nächster Schritt wird sein, diese Statistiken für die Zukünftigen Daten stündlich neu zu berechnen und jeweils die vergangene Stunde aus der Berechnung wegzulassen.

Im Anschluss ergeben sich dann schon recht einfache Möglichkeiten in einfachen Wertevergleichen ob es sich lohnt in der laufenden Stunde den Akku zum Beladen oder Entladen für Eigenverbrauch frei zuschalten.

Je nach Größe der vorhanden PV Anlagenkapazität und Ertragspotentiale (Jahreszeiten) ergeben sich da ja recht unterschiedliche Schaltoptionen.
Bei mir geht es bei aktuelle PV-Kapazität eher darum ein wenig über die untertägigen oder teilweise mehrtägigen Preisschwankung die Stromrechnung zu reduzieren.
letztlich hatte ich den Akku in 3h komplett gefüllt dann einige Stunden abgeschalten, da noch Sonne kam und ab dem Abend wieder eingeschaltet und dann den Folgetag mit erheblich höheren Strompreisen komplett aus dem Akku bedient.

Ja das sind alles nur einstellige EUR die ich da derzeit sparen kann, aber es geht hier erstmal um das Funktionsprinzip.

Die Statistikergebnisse will ich dann an den TWS schicken und in seinen Logiken die Schwellwertabgleiche erledigen. Ich will mich ja nicht nur in Java-Script weiterbilden, sondern auch mit der TWS Logik.
Zuletzt geändert von Parsley am Fr Mär 15, 2024 11:19 am, insgesamt 1-mal geändert.
Grüße
Göran

#1 Timberwolf 2600 Velvet Red TWS #225 / VPN aktiv / Reboot OK
#2 Timberwolf 2600 Organic Silver TWS #438 / VPN aktiv / Reboot OK
#3 PBM 3 Kanäle, #4 Modbus-Extension

cheater
Reactions:
Beiträge: 613
Registriert: Sa Aug 11, 2018 11:16 pm
Hat sich bedankt: 382 Mal
Danksagung erhalten: 274 Mal

#2

Beitrag von cheater »

Servus Göran,
klingt interessant, vielleicht kannst du auch ein zwei Screenshots zu deinen Nodes beisteuern, das könnte hilfreich sein.

Frage: Die Tibber API sendet doch auch das Preisniveau (high, low, etc.). Das ist dir wahrscheinlich nicht präzise genug?
Grüße, Dominic

Timberwolf 2400 #126, VPN offen, Reboot nach Absprache

Ersteller
gbglace
Reactions:
Beiträge: 3596
Registriert: So Aug 12, 2018 10:20 am
Hat sich bedankt: 1255 Mal
Danksagung erhalten: 1662 Mal

#3

Beitrag von gbglace »

ja das ist immer nur auf den laufenden Tag bezogen, mehr oder weniger die Aussage über oder unter Durchschnitt. Selbst zum Tageswechsel passt das dann nicht mehr. Ich brauche dann für die Schwellwertabgleiche die effektiven Zahlen, da ich dann auch Wandlungsverluste mit einkalkulieren kann.
Und ich will das halt auch gleitend auswerten, daher die Zukunftsdaten kürzen auch wenn es zur Mittagszeit dann eben einen Sprung gibt.
Aber das werde ich ja jetzt erst auch beobachten können wie dann diese Statistischen Werte zu den 12h bis 36h springen.


Habe mal das JSON von den Nodes angefügt.
Vor dem Import auf diese Passagen achten, da habe ich jetzt meine Credentials rausgenommen und diese drei <Platzhalter> reingetippt. Die oberen beiden kommen öfters vor:

"apiEndpointRef": "<eigenen API Schlüssel>",
"homeId": "<eigene Home IDl>",
"broker": "<IP-vom MQTT Broker>",

Code: Alles auswählen

[
    {
        "id": "0199f53dd7c7426c",
        "type": "tibber-data",
        "z": "63be2c37d4803da7",
        "name": "Tibber Preise aktuell",
        "active": true,
        "apiEndpointRef": "<eigenen API Schlüssel>",
        "queryName": "getCurrentEnergyPrice",
        "homeId": "<eigene Home IDl>",
        "energyResolution": "HOURLY",
        "lastCount": "1",
        "x": 500,
        "y": 300,
        "wires": [
            [
                "485f0e5775c3a5c4"
            ]
        ]
    },
    {
        "id": "488453cad4316436",
        "type": "inject",
        "z": "63be2c37d4803da7",
        "name": "Trigger_1h",
        "props": [],
        "repeat": "",
        "crontab": "0 0-23 * * *",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "x": 150,
        "y": 300,
        "wires": [
            [
                "0199f53dd7c7426c"
            ]
        ]
    },
    {
        "id": "846b5af3b5607e70",
        "type": "inject",
        "z": "63be2c37d4803da7",
        "name": "Trigger_manuell",
        "props": [],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "x": 120,
        "y": 540,
        "wires": [
            [
                "960e68fdde64a645"
            ]
        ]
    },
    {
        "id": "485f0e5775c3a5c4",
        "type": "mqtt out",
        "z": "63be2c37d4803da7",
        "name": "Tibber Preise aktuell an MQTT",
        "topic": "TWS/Energy/Tibber/Data/Prices/current",
        "qos": "0",
        "retain": "true",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "cf93ee01b593785b",
        "x": 910,
        "y": 300,
        "wires": []
    },
    {
        "id": "d44d647f7a216e09",
        "type": "tibber-data",
        "z": "63be2c37d4803da7",
        "name": "Tibber Preise Tag heute",
        "active": true,
        "apiEndpointRef": "<eigenen API Schlüssel>",
        "queryName": "getTodaysEnergyPrices",
        "homeId": "<eigene Home IDl>",
        "energyResolution": "HOURLY",
        "lastCount": "1",
        "x": 510,
        "y": 380,
        "wires": [
            [
                "15dc1e9cd9242a01",
                "d8f6e5c7.9e0b18"
            ]
        ]
    },
    {
        "id": "960e68fdde64a645",
        "type": "tibber-data",
        "z": "63be2c37d4803da7",
        "name": "Tibber Preise Tag morgen",
        "active": true,
        "apiEndpointRef": "<eigenen API Schlüssel>",
        "queryName": "getTomorrowsEnergyPrices",
        "homeId": "<eigene Home IDl>",
        "energyResolution": "HOURLY",
        "lastCount": "1",
        "x": 510,
        "y": 500,
        "wires": [
            [
                "e38274687517647a",
                "857280b350e5baa4"
            ]
        ]
    },
    {
        "id": "2a6784e82ed17f50",
        "type": "inject",
        "z": "63be2c37d4803da7",
        "name": "Trigger_00:05",
        "props": [],
        "repeat": "",
        "crontab": "05 00 * * *",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "x": 160,
        "y": 380,
        "wires": [
            [
                "d44d647f7a216e09"
            ]
        ]
    },
    {
        "id": "f6204cf9f363e04a",
        "type": "inject",
        "z": "63be2c37d4803da7",
        "name": "Trigger_13:05",
        "props": [],
        "repeat": "",
        "crontab": "05 13 * * *",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "x": 160,
        "y": 500,
        "wires": [
            [
                "960e68fdde64a645"
            ]
        ]
    },
    {
        "id": "15dc1e9cd9242a01",
        "type": "mqtt out",
        "z": "63be2c37d4803da7",
        "name": "Tibber Preise Tag heute an MQTT",
        "topic": "TWS/Energy/Tibber/Data/Prices/today",
        "qos": "0",
        "retain": "true",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "cf93ee01b593785b",
        "x": 920,
        "y": 380,
        "wires": []
    },
    {
        "id": "e38274687517647a",
        "type": "mqtt out",
        "z": "63be2c37d4803da7",
        "name": "Tibber Preise Tag morgen an MQTT",
        "topic": "TWS/Energy/Tibber/Data/Prices/tomorrow",
        "qos": "0",
        "retain": "true",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "cf93ee01b593785b",
        "x": 920,
        "y": 500,
        "wires": []
    },
    {
        "id": "b5ba8a452957fe01",
        "type": "tibber-feed",
        "z": "63be2c37d4803da7",
        "name": "Tibber-Live-Daten",
        "active": true,
        "apiEndpointRef": "<eigenen API Schlüssel>",
        "homeId": "<eigene Home IDl>",
        "timestamp": true,
        "power": "1",
        "lastMeterConsumption": "1",
        "accumulatedConsumption": "1",
        "accumulatedProduction": "1",
        "accumulatedConsumptionLastHour": "1",
        "accumulatedProductionLastHour": "1",
        "accumulatedCost": "1",
        "accumulatedReward": "1",
        "currency": false,
        "minPower": false,
        "averagePower": false,
        "maxPower": false,
        "powerProduction": true,
        "minPowerProduction": false,
        "maxPowerProduction": false,
        "lastMeterProduction": true,
        "powerFactor": false,
        "voltagePhase1": false,
        "voltagePhase2": false,
        "voltagePhase3": false,
        "currentL1": false,
        "currentL2": false,
        "currentL3": false,
        "signalStrength": false,
        "x": 150,
        "y": 180,
        "wires": [
            [
                "efb1a4d5bcaa0586",
                "1c5ed9c1949b4460"
            ]
        ]
    },
    {
        "id": "af816b0ac417c267",
        "type": "mqtt out",
        "z": "63be2c37d4803da7",
        "name": "Tibber Bezugsdaten an MQTT",
        "topic": "TWS/Energy/Tibber/Data/Bezug",
        "qos": "0",
        "retain": "true",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "cf93ee01b593785b",
        "x": 910,
        "y": 140,
        "wires": []
    },
    {
        "id": "df6c860c5af4ac77",
        "type": "mqtt out",
        "z": "63be2c37d4803da7",
        "name": "Tibber Einspeisedaten an MQTT",
        "topic": "TWS/Energy/Tibber/Data/Einspeisung",
        "qos": "0",
        "retain": "true",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "cf93ee01b593785b",
        "x": 910,
        "y": 220,
        "wires": []
    },
    {
        "id": "efb1a4d5bcaa0586",
        "type": "function",
        "z": "63be2c37d4803da7",
        "name": "Extract Bezugsdaten (filter / sbc / trigger 15 min)",
        "func": "// Example Function node combining RBE, Change, and Trigger logic\nvar previousConsumption = null;\nvar previousPower = null;\n\n// Check if consumption has changed (RBE logic)\nif (msg.payload.accumulatedConsumptionLastHour !== previousConsumption || msg.payload.power !== previousPower) {\n    // Remove unnecessary properties (Change logic)\n    delete msg.payload.powerProduction;\n    delete msg.payload.lastMeterProduction;\n    delete msg.payload.accumulatedProduction;\n    delete msg.payload.accumulatedProductionLastHour;\n    delete msg.payload.accumulatedReward;\n\n    // Set a timer (Trigger logic)\n    setTimeout(function () {\n        // Your desired action here\n        // For example, send the modified message to the next node\n        node.send(msg);\n    }, 900000); // 15 minutes in milliseconds\n\n    // Update previous consumption value\n    previousConsumption = msg.payload.accumulatedConsumptionLastHour;\n    previousPower = msg.payload.power;\n}\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 500,
        "y": 140,
        "wires": [
            [
                "af816b0ac417c267"
            ]
        ]
    },
    {
        "id": "1c5ed9c1949b4460",
        "type": "function",
        "z": "63be2c37d4803da7",
        "name": "Extract Einspeisedaten (filter / sbc / trigger 15 min)",
        "func": "// Example Function node combining RBE, Change, and Trigger logic\nvar previousProduction = null;\nvar previousPower = null;\n\n// Check if consumption has changed (RBE logic)\nif (msg.payload.accumulatedProductionLastHour !== previousProduction || msg.payload.powerProduction !== previousPower) {\n    // Remove unnecessary properties (Change logic)\n    delete msg.payload.power;\n    delete msg.payload.lastMeterConsumption;\n    delete msg.payload.accumulatedConsumption;\n    delete msg.payload.accumulatedConsumptionLastHour;\n    delete msg.payload.accumulatedCost;\n\n    // Set a timer (Trigger logic)\n    setTimeout(function () {\n        // Your desired action here\n        // For example, send the modified message to the next node\n        node.send(msg);\n    }, 900000); // 15 minutes in milliseconds\n\n    // Update previous consumption value\n    previousProduction = msg.payload.accumulatedProductionLastHour;\n    previousPower = msg.payload.powerProduction;\n}\n\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 510,
        "y": 220,
        "wires": [
            [
                "df6c860c5af4ac77"
            ]
        ]
    },
    {
        "id": "0290922ebc4f755b",
        "type": "comment",
        "z": "63be2c37d4803da7",
        "name": "Daten abrufen und per MQTT an TWS Broker senden",
        "info": "",
        "x": 230,
        "y": 80,
        "wires": []
    },
    {
        "id": "d8f6e5c7.9e0b18",
        "type": "function",
        "z": "63be2c37d4803da7",
        "name": "Calculate Price Statistics Hist (30 days, 7 days)",
        "func": "let lastarraytoday      = flow.get(\"tibber_prices_today\") || [];\nlet lastarraytomorrow   = flow.get(\"tibber_prices_tomorrow\") || [];\nlet all_up_today        = flow.get(\"tibber_prices_30_today\") || [];\nlet history_days        = flow.get(\"tibber_prices_max_days_hist_for_statistics\") || 30;\nlet min_tax_30          = flow.get(\"tibber_prices_30_today\")\n\nif (msg.payload[23].tax){                                                      // if a valid msg including 24 prices is recieved\n    if (lastarraytoday.length == 0 || lastarraytoday[0].startsAt !== msg.payload[0].startsAt) {              // if a new date for todays prices recieved \n    \n        let newElements = msg.payload;\n        let cnt_new = newElements.length;\n        let newRelevant = [];\n        \n        // select only specific objects from new today price message\n        for (var i = 0; i < cnt_new; i++) {\n            var newObj = {\n                total: newElements[i].total,\n                energy: newElements[i].energy,\n                tax: newElements[i].tax,\n                startsAt: newElements[i].startsAt\n            };\n            newRelevant.push(newObj);                                       // write object in array\n        }\n        \n        // append new prices (array) to the history array\n        //all_up_today == all_up_today.concat(newRelevant);\n        all_up_today.push.apply(all_up_today,newRelevant);\n        \n        // remove oldest data if maximum days of historic data are allready included\n        if (all_up_today.length > history_days * 24) {\n            let cnt_cut = all_up_today.length - (history_days * 24);\n           // all_up_today.splice(0, cnt_cut);\n        }\n        \n        // update today prices in flow variables\n        flow.set(\"tibber_prices_30_today\",all_up_today);\n        flow.set(\"tibber_prices_today\",msg.payload);\n        \n        // clean tomorrow prices in flow variables if today = lasttomorrow\n        if (msg.payload[0].startsAt === lastarraytomorrow[0].startsAt){\n            flow.set(\"tibber_prices_tomorrow\",[]);\n        }\n\n        // calculate statistics\n        let cnt_days = all_up_today.length / 24;\n        let cnt_all = all_up_today.length;\n        let sum_total = 0;\n        let sum_energy = 0;\n        let sum_tax = 0;\n        let min_total_30 = Number.POSITIVE_INFINITY;\n        let min_energy_30 = Number.POSITIVE_INFINITY;\n        let min_tax_30 = Number.POSITIVE_INFINITY;\n        let min_total_7 = Number.POSITIVE_INFINITY;\n        let min_energy_7 = Number.POSITIVE_INFINITY;\n        let min_tax_7 = Number.POSITIVE_INFINITY;\n        let max_total_30 = Number.NEGATIVE_INFINITY;\n        let max_energy_30 = Number.NEGATIVE_INFINITY;\n        let max_tax_30 = Number.NEGATIVE_INFINITY;\n        let max_total_7 = Number.NEGATIVE_INFINITY;\n        let max_energy_7 = Number.NEGATIVE_INFINITY;\n        let max_tax_7 = Number.NEGATIVE_INFINITY;\n        let avg_total = 0;\n        let avg_energy = 0;\n        let avg_tax = 0;\n        let variance_total = 0;\n        let variance_energy = 0;\n        let variance_tax = 0;\n        let stdDev_total = 0;\n        let stdDev_energy = 0;\n        let stdDev_tax = 0;\n\n        // calculate sum, min, max over all dates and min, max over last 7 days\n        for (var j = 0; j < cnt_all; j++) {\n            \n            var total = all_up_today[j].total\n            var energy = all_up_today[j].energy\n            var tax = all_up_today[j].tax\n            \n            sum_total += total;\n            sum_energy += energy;\n            sum_tax += tax;\n            \n            min_total_30 = Math.min(min_total_30,total);\n            min_energy_30 = Math.min(min_energy_30,energy);\n            min_tax_30 = Math.min(min_tax_30,tax);\n            \n            max_total_30 = Math.max(max_total_30,total);\n            max_energy_30 = Math.max(max_energy_30,energy);\n            max_tax_30 = Math.max(max_tax_30,tax);\n            \n            // calculate min max over last 7 days\n            if (j+1 >= (cnt_all-(7*24))){\n                min_total_7 = Math.min(min_total_7,total);\n                min_energy_7 = Math.min(min_energy_7,energy);\n                min_tax_7 = Math.min(min_tax_7,tax);\n                \n                max_total_7 = Math.max(max_total_7,total);\n                max_energy_7 = Math.max(max_energy_7,energy);\n                max_tax_7 = Math.max(max_tax_7,tax);\n            }\n        }\n\n        // calculate average\n        avg_total = sum_total / cnt_all;\n        avg_energy = sum_energy / cnt_all;\n        avg_tax = sum_tax / cnt_all;\n        \n        // calculate variance\n       for (var k = 0; k < cnt_all; k++) {\n            variance_total  += Math.pow(all_up_today[k].total   - avg_total, 2);\n            variance_energy += Math.pow(all_up_today[k].energy  - avg_energy, 2);\n            variance_tax    += Math.pow(all_up_today[k].tax     - avg_tax, 2);\n        }\n        \n        // calculate standard Deviation\n        stdDev_total    += Math.sqrt(variance_total  / cnt_all);\n        stdDev_energy   += Math.sqrt(variance_energy / cnt_all);\n        stdDev_tax      += Math.sqrt(variance_tax    / cnt_all);\n        \n        // round results to 4 digits\n        avg_total = Math.round(avg_total*10000)/10000\n        avg_energy = Math.round(avg_energy*10000)/10000\n        avg_tax = Math.round(avg_tax*10000)/10000\n        stdDev_total = Math.round(stdDev_total*10000)/10000\n        stdDev_energy = Math.round(stdDev_energy*10000)/10000\n        stdDev_tax = Math.round(stdDev_tax*10000)/10000\n        \n        // generate output object\n        let result = {\n            cnt_days: cnt_days,\n            cnt_all: cnt_all,\n            avg_total: Math.round(avg_total*10000)/10000,\n            avg_energy: Math.round(avg_energy*10000)/10000,\n            avg_tax: Math.round(avg_tax*10000)/10000,\n            stdDev_total: Math.round(stdDev_total*10000)/10000,\n            stdDev_energy: Math.round(stdDev_energy*10000)/10000,\n            stdDev_tax: Math.round(stdDev_tax*10000)/10000,\n            min_total_30: min_total_30,\n            min_energy_30: min_energy_30,\n            min_tax_30: min_tax_30,\n            min_total_7: min_total_7,\n            min_energy_7: min_energy_7,\n            min_tax_7: min_tax_7,\n            max_total_30: max_total_30,\n            max_energy_30: max_energy_30,\n            max_tax_30: max_tax_30,\n            max_total_7: max_total_7,\n            max_energy_7: max_energy_7,\n            max_tax_7: max_tax_7,\n            first_hour_in: all_up_today[0].startsAt,\n            last_hour_in: all_up_today[cnt_all-1].startsAt\n        };\n        \n        \n        // output\n        flow.set(\"tibber_prices_stats_values_hist\",result);\n    }            \n}\n\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 960,
        "y": 440,
        "wires": [
            []
        ]
    },
    {
        "id": "c85004213315b336",
        "type": "inject",
        "z": "63be2c37d4803da7",
        "name": "30 Tage Historie für Statistik",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "30",
        "payloadType": "num",
        "x": 180,
        "y": 640,
        "wires": [
            [
                "e2af434de8fe232d"
            ]
        ]
    },
    {
        "id": "e2af434de8fe232d",
        "type": "change",
        "z": "63be2c37d4803da7",
        "name": "set flow.tibber_prices_max_days_hist_for_statistics",
        "rules": [
            {
                "t": "set",
                "p": "tibber_prices_max_days_hist_for_statistics",
                "pt": "flow",
                "to": "payload",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 530,
        "y": 640,
        "wires": [
            []
        ]
    },
    {
        "id": "12e618ace15dcdf8",
        "type": "function",
        "z": "63be2c37d4803da7",
        "name": "Calculate Price Statistics future",
        "func": "let lastarraytoday      = flow.get(\"tibber_prices_today\") || [];\nlet lastarraytomorrow   = flow.get(\"tibber_prices_tomorrow\") || [];\nlet all_up_today        = flow.get(\"tibber_prices_30_today\") || [];\nlet array_element_now   = msg.array_element_now;\n\nif (msg.payload[23].tax){                                                                                    // if a valid msg including 24 prices is recieved\n    //if (lastarraytomorrow.length == 0 || lastarraytomorrow[0].startsAt !== msg.payload[0].startsAt) {        // if a new date for todays prices recieved \n    if (1 == 1) {\n        // put current today data and new tomorrow data together\n        let data_all = [];\n        data_all.push.apply(data_all,lastarraytoday);\n        data_all.push.apply(data_all,msg.payload);\n        \n        let cnt_all = data_all.length;\n        let data_relevant = [];\n        \n        // select only specific objects from new today price message\n        for (var i = 0; i < cnt_all; i++) {\n            // only data from current hour and future\n            if (i >= array_element_now){                    \n                var newObj = {\n                    total: data_all[i].total,\n                    energy: data_all[i].energy,\n                    tax: data_all[i].tax,\n                    startsAt: data_all[i].startsAt\n                };\n                data_relevant.push(newObj);                                       // write object in array\n            }\n        }\n\n         // update tomorrow prices in flow variables\n        flow.set(\"tibber_prices_now_and_future\",data_relevant);\n        flow.set(\"tibber_prices_tomorrow\",msg.payload);\n        \n        // calculate statistics\n        let cnt_rel = data_relevant.length;\n        let sum_total = 0;\n        let sum_energy = 0;\n        let sum_tax = 0;\n        let min_total = Number.POSITIVE_INFINITY;\n        let min_energy = Number.POSITIVE_INFINITY;\n        let min_tax = Number.POSITIVE_INFINITY;\n        let max_total = Number.NEGATIVE_INFINITY;\n        let max_energy = Number.NEGATIVE_INFINITY;\n        let max_tax = Number.NEGATIVE_INFINITY;\n        let avg_total = 0;\n        let avg_energy = 0;\n        let avg_tax = 0;\n        let variance_total = 0;\n        let variance_energy = 0;\n        let variance_tax = 0;\n        let stdDev_total = 0;\n        let stdDev_energy = 0;\n        let stdDev_tax = 0;\n\n        // calculate sum, min, max over all dates and min, max over last 7 days\n        for (var j = 0; j < cnt_rel; j++) {\n        \n            var total = data_relevant[j].total\n            var energy = data_relevant[j].energy\n            var tax = data_relevant[j].tax\n            \n            sum_total += total;\n            sum_energy += energy;\n            sum_tax += tax;\n            \n            min_total = Math.min(min_total,total);\n            min_energy = Math.min(min_energy,energy);\n            min_tax = Math.min(min_tax,tax);\n            \n            max_total = Math.max(max_total,total);\n            max_energy = Math.max(max_energy,energy);\n            max_tax = Math.max(max_tax,tax);\n        }\n\n        // calculate average\n        avg_total = sum_total / cnt_rel;\n        avg_energy = sum_energy / cnt_rel;\n        avg_tax = sum_tax / cnt_rel;\n        \n        // calculate variance\n       for (var k = 0; k < cnt_rel; k++) {\n            variance_total  += Math.pow(data_relevant[k].total   - avg_total, 2);\n            variance_energy += Math.pow(data_relevant[k].energy  - avg_energy, 2);\n            variance_tax    += Math.pow(data_relevant[k].tax     - avg_tax, 2);\n        }\n        \n        // calculate standard Deviation\n        stdDev_total    += Math.sqrt(variance_total  / cnt_rel);\n        stdDev_energy   += Math.sqrt(variance_energy / cnt_rel);\n        stdDev_tax      += Math.sqrt(variance_tax    / cnt_rel);\n        \n        // round results to 4 digits\n        avg_total = Math.round(avg_total*10000)/10000\n        avg_energy = Math.round(avg_energy*10000)/10000\n        avg_tax = Math.round(avg_tax*10000)/10000\n        stdDev_total = Math.round(stdDev_total*10000)/10000\n        stdDev_energy = Math.round(stdDev_energy*10000)/10000\n        stdDev_tax = Math.round(stdDev_tax*10000)/10000\n        \n        // generate output object\n        let result = {\n            cnt_all: cnt_rel,\n            avg_total: Math.round(avg_total*10000)/10000,\n            avg_energy: Math.round(avg_energy*10000)/10000,\n            avg_tax: Math.round(avg_tax*10000)/10000,\n            stdDev_total: Math.round(stdDev_total*10000)/10000,\n            stdDev_energy: Math.round(stdDev_energy*10000)/10000,\n            stdDev_tax: Math.round(stdDev_tax*10000)/10000,\n            min_total_future: min_total,\n            min_energy_future: min_energy,\n            min_tax_future: min_tax,\n            max_total_future: max_total,\n            max_energy_future: max_energy,\n            max_tax_future: max_tax,\n            first_hour_in: data_relevant[0].startsAt,\n            last_hour_in: data_relevant[cnt_rel-1].startsAt\n        };\n        \n        \n        // output\n        flow.set(\"tibber_prices_stats_values_future\",result);\n        msg.stats == result;\n        return msg.stats\n    }            \n}\n\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1210,
        "y": 560,
        "wires": [
            []
        ]
    },
    {
        "id": "857280b350e5baa4",
        "type": "moment",
        "z": "63be2c37d4803da7",
        "name": "Array_Element_from_timestamp",
        "topic": "",
        "input": "",
        "inputType": "date",
        "inTz": "Europe/Berlin",
        "adjAmount": 0,
        "adjType": "days",
        "adjDir": "add",
        "format": "H",
        "locale": "de_DE",
        "output": "array_element_now",
        "outputType": "msg",
        "outTz": "Europe/Berlin",
        "x": 910,
        "y": 560,
        "wires": [
            [
                "12e618ace15dcdf8"
            ]
        ]
    },
    {
        "id": "8b720635f64f403e",
        "type": "inject",
        "z": "63be2c37d4803da7",
        "name": "Trigger_manuell",
        "props": [],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "x": 120,
        "y": 340,
        "wires": [
            [
                "d44d647f7a216e09"
            ]
        ]
    },
    {
        "id": "<eigenen API Schlüssel>",
        "type": "tibber-api-endpoint",
        "queryUrl": "https://api.tibber.com/v1-beta/gql",
        "feedConnectionTimeout": "30",
        "feedTimeout": "60",
        "queryRequestTimeout": "30",
        "name": "Tibber Nhg"
    },
    {
        "id": "cf93ee01b593785b",
        "type": "mqtt-broker",
        "name": "MQTT-Broker on TWS Container",
        "broker": "<IP-vom MQTT Broker>",
        "port": "1883",
        "clientid": "TWS_NR",
        "autoConnect": true,
        "usetls": false,
        "protocolVersion": "4",
        "keepalive": "60",
        "cleansession": true,
        "birthTopic": "",
        "birthQos": "0",
        "birthPayload": "",
        "birthMsg": {},
        "closeTopic": "",
        "closeQos": "0",
        "closePayload": "",
        "closeMsg": {},
        "willTopic": "",
        "willQos": "0",
        "willPayload": "",
        "willMsg": {},
        "userProps": "",
        "sessionExpiry": ""
    }
]
Ausschauen tut es gerade so:
Bild

PS: nicht wundern ich tippe hier und da meine Code in feinstem Denglisch. Bin halt kein studierter Programmierer.
Zuletzt geändert von gbglace am Do Mär 14, 2024 9:06 pm, insgesamt 1-mal geändert.
Grüße
Göran

#1 Timberwolf 2600 Velvet Red TWS #225 / VPN aktiv / Reboot OK
#2 Timberwolf 2600 Organic Silver TWS #438 / VPN aktiv / Reboot OK
#3 PBM 3 Kanäle, #4 Modbus-Extension
Antworten

Zurück zu „Docker Container: Node Red“