Geräteprofil · Verschiedenes
Dynamische Strompreise
Dieses Profil bringt den Börsenstrompreis nach nomos: als Kachel in der App und als Wert, mit dem Automationen rechnen können — Waschmaschine, Wärmepumpe oder Wallbox laufen dann, wenn der Strom günstig ist.
Das Profil holt die Preise nicht selbst. Es ist die nomos-Hälfte der Verbindung; die andere Hälfte ist ein Node-RED-Flow, der die Preise abruft und auf MQTT veröffentlicht. Zwei fertige Flows stehen weiter unten zum Einfügen bereit.
Voraussetzungen
- Ein MQTT-Broker, in nomos unter Skills → MQTT eingetragen.
- Node-RED mit Zugriff auf denselben Broker. Die Flows brauchen nur Standardknoten — es ist nichts nachzuinstallieren.
-
Eine Preisquelle:
Quelle Konto nötig Was sie liefert aWATTar (Deutschland, Österreich) nein den reinen Börsenpreis je Stunde Tibber ja, mit persönlichem Token den Endpreis inklusive Steuern und Abgaben
Einrichtung in nomos
- Skills → Add-ons: das Profil importieren
- Gerät anlegen und ein MQTT-Topic vergeben, zum Beispiel
strompreis
Mehr ist in nomos nicht zu tun. Das Gerät bleibt leer, bis der Flow läuft.
Den Flow einfügen
In Node-RED: Menü rechts oben (☰) → Import → den JSON-Text einfügen → Import → Deploy.
Danach sind zwei Dinge anzupassen:
- Im Knoten
mqtt outden Broker eintragen: Adresse, bei Bedarf Benutzer und Passwort. - Lautet das MQTT-Topic des Geräts nicht
strompreis, im Funktionsknoten Preise aufbereiten die Zeileconst TOPIC = "strompreis";ändern — und im Broker-Knoten die drei Lebenszeichen-Topics (Birth, Close, Will) ebenso.
Flow für aWATTar
JSON zum Einfügen
[{"id":"nomos_price_awattar","type":"tab","label":"Strompreis (aWATTar)","disabled":false,"info":""},{"id":"nomos_price_awattar_broker","type":"mqtt-broker","name":"nomos Broker","broker":"localhost","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"birthTopic":"tele/strompreis/LWT","birthQos":"0","birthRetain":"true","birthPayload":"Online","birthMsg":{},"closeTopic":"tele/strompreis/LWT","closeQos":"0","closeRetain":"true","closePayload":"Offline","closeMsg":{},"willTopic":"tele/strompreis/LWT","willQos":"0","willRetain":"true","willPayload":"Offline","willMsg":{},"userProps":"","sessionExpiry":""},{"id":"nomos_price_awattar_tick","type":"inject","z":"nomos_price_awattar","name":"alle 15 Minuten","props":[{"p":"payload"}],"repeat":"900","crontab":"","once":true,"onceDelay":"5","topic":"","payload":"","payloadType":"date","x":160,"y":100,"wires":[["nomos_price_awattar_range"]]},{"id":"nomos_price_awattar_range","type":"function","z":"nomos_price_awattar","name":"Zeitraum","func":"// Zeitraum: ab heute 00:00 fuer 48 Stunden. Ohne Zeitraum liefert die Schnittstelle\n// nur die restlichen Stunden - dann waeren Tagesbestwert und Tagesmittel falsch.\nconst midnight = new Date();\nmidnight.setHours(0, 0, 0, 0);\nmsg.payload = {\n start: midnight.getTime(),\n end: midnight.getTime() + 48 * 3600 * 1000\n};\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":370,"y":100,"wires":[["nomos_price_awattar_req"]]},{"id":"nomos_price_awattar_req","type":"http request","z":"nomos_price_awattar","name":"aWATTar","method":"GET","ret":"obj","paytoqs":"query","url":"https://api.awattar.de/v1/marketdata","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":560,"y":100,"wires":[["nomos_price_awattar_fn"]]},{"id":"nomos_price_awattar_fn","type":"function","z":"nomos_price_awattar","name":"Preise aufbereiten","func":"// aWATTar liefert Marktpreise in Eur/MWh je Stunde; nomos bekommt ct/kWh.\n// TOPIC muss mit dem MQTT-Topic des Geraets in nomos uebereinstimmen.\nconst TOPIC = \"strompreis\";\n\nconst data = (msg.payload && msg.payload.data) || [];\nif (!data.length) { node.warn(\"keine Preisdaten\"); return null; }\n\nconst ct = (eurPerMWh) => Math.round(eurPerMWh / 10 * 100) / 100;\nconst now = Date.now();\nconst midnight = new Date(); midnight.setHours(0, 0, 0, 0);\nconst dayStart = midnight.getTime();\nconst dayEnd = dayStart + 24 * 3600 * 1000;\n\nconst today = data.filter((e) => e.start_timestamp >= dayStart && e.start_timestamp < dayEnd);\nconst current = data.find((e) => now >= e.start_timestamp && now < e.end_timestamp);\nif (!current || !today.length) { node.warn(\"keine Preise fuer jetzt\"); return null; }\nconst next = data.find((e) => e.start_timestamp >= current.end_timestamp);\n\nconst prices = today.map((e) => e.marketprice);\nconst min = Math.min.apply(null, prices);\nconst max = Math.max.apply(null, prices);\nconst avg = prices.reduce((a, b) => a + b, 0) / prices.length;\nconst cheapest = today.find((e) => e.marketprice === min);\n\n// Eine Einstufung, die der von Tibber entspricht, damit beide Flows dasselbe liefern.\nlet level = \"NORMAL\";\nconst p = current.marketprice;\nif (max - min < 1) level = \"NORMAL\"; // ein flacher Tag ist weder billig noch teuer\nelse if (p <= min + (avg - min) * 0.3) level = \"VERY_CHEAP\";\nelse if (p < avg) level = \"CHEAP\";\nelse if (p >= max - (max - avg) * 0.3) level = \"VERY_EXPENSIVE\";\nelse level = \"EXPENSIVE\";\n\nconst hhmm = (t) => new Date(t).toLocaleTimeString(\"de-DE\", { hour: \"2-digit\", minute: \"2-digit\" });\n\nconst values = [\n [\"PRICE\", ct(p)],\n [\"ENERGY\", ct(p)],\n [\"NEXT\", next ? ct(next.marketprice) : ct(p)],\n [\"MIN\", ct(min)],\n [\"MAX\", ct(max)],\n [\"AVG\", Math.round(ct(avg) * 100) / 100],\n [\"CHEAPEST\", hhmm(cheapest.start_timestamp)],\n [\"IS_CHEAP\", p < avg ? 1 : 0],\n [\"LEVEL\", level],\n [\"UPDATED\", hhmm(now)]\n];\n\nreturn [values.map(([key, value]) => ({ topic: \"stat/\" + TOPIC + \"/\" + key, payload: value, retain: true }))];\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":760,"y":100,"wires":[["nomos_price_awattar_out"]]},{"id":"nomos_price_awattar_out","type":"mqtt out","z":"nomos_price_awattar","name":"an nomos","topic":"","qos":"0","retain":"true","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"nomos_price_awattar_broker","x":980,"y":100,"wires":[]}]
Für Österreich im Knoten http request die Adresse auf https://api.awattar.at/v1/marketdata ändern — es ist ein eigener Markt mit eigenen Preisen.
Der Flow fragt alle 15 Minuten den Zeitraum ab heute 00:00 für 48 Stunden ab. Das ist Absicht: ohne Zeitraum liefert die Schnittstelle nur die noch kommenden Stunden, und Tagesmittel wie günstigste Stunde wären dann nur die des Tagesrests.
Flow für Tibber
JSON zum Einfügen
[{"id":"nomos_price_tibber","type":"tab","label":"Strompreis (Tibber)","disabled":false,"info":""},{"id":"nomos_price_tibber_broker","type":"mqtt-broker","name":"nomos Broker","broker":"localhost","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"birthTopic":"tele/strompreis/LWT","birthQos":"0","birthRetain":"true","birthPayload":"Online","birthMsg":{},"closeTopic":"tele/strompreis/LWT","closeQos":"0","closeRetain":"true","closePayload":"Offline","closeMsg":{},"willTopic":"tele/strompreis/LWT","willQos":"0","willRetain":"true","willPayload":"Offline","willMsg":{},"userProps":"","sessionExpiry":""},{"id":"nomos_price_tibber_tick","type":"inject","z":"nomos_price_tibber","name":"alle 15 Minuten","props":[{"p":"payload"}],"repeat":"900","crontab":"","once":true,"onceDelay":"5","topic":"","payload":"","payloadType":"date","x":160,"y":100,"wires":[["nomos_price_tibber_q"]]},{"id":"nomos_price_tibber_q","type":"change","z":"nomos_price_tibber","name":"Abfrage","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"query\":\"{viewer{homes{currentSubscription{priceInfo{current{total energy tax startsAt level} today{total energy tax startsAt level}}}}}}\"}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":360,"y":100,"wires":[["nomos_price_tibber_req"]]},{"id":"nomos_price_tibber_req","type":"http request","z":"nomos_price_tibber","name":"Tibber","method":"POST","ret":"obj","paytoqs":"ignore","url":"https://api.tibber.com/v1-beta/gql","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[{"keyType":"other","keyValue":"Authorization","valueType":"other","valueValue":"Bearer IHR-TIBBER-TOKEN"},{"keyType":"other","keyValue":"Content-Type","valueType":"other","valueValue":"application/json"}],"x":380,"y":100,"wires":[["nomos_price_tibber_fn"]]},{"id":"nomos_price_tibber_fn","type":"function","z":"nomos_price_tibber","name":"Preise aufbereiten","func":"// Tibber liefert Preise in EUR/kWh inklusive Steuern und Abgaben; nomos bekommt ct/kWh.\n// TOPIC muss mit dem MQTT-Topic des Geraets in nomos uebereinstimmen.\nconst TOPIC = \"strompreis\";\n\nconst home = msg.payload && msg.payload.data && msg.payload.data.viewer\n && msg.payload.data.viewer.homes && msg.payload.data.viewer.homes[0];\nconst info = home && home.currentSubscription && home.currentSubscription.priceInfo;\nif (!info || !info.current || !info.today || !info.today.length) {\n node.warn(\"keine Preisdaten von Tibber\"); return null;\n}\n\nconst ct = (eurPerKWh) => Math.round(eurPerKWh * 100 * 100) / 100;\nconst now = Date.now();\nconst cur = info.current;\nconst today = info.today;\nconst prices = today.map((e) => e.total);\nconst min = Math.min.apply(null, prices);\nconst max = Math.max.apply(null, prices);\nconst avg = prices.reduce((a, b) => a + b, 0) / prices.length;\nconst cheapest = today.find((e) => e.total === min);\nconst next = today.find((e) => new Date(e.startsAt).getTime() > new Date(cur.startsAt).getTime());\n\nconst hhmm = (iso) => new Date(iso).toLocaleTimeString(\"de-DE\", { hour: \"2-digit\", minute: \"2-digit\" });\n\nconst values = [\n [\"PRICE\", ct(cur.total)],\n [\"ENERGY\", ct(cur.energy)],\n [\"NEXT\", next ? ct(next.total) : ct(cur.total)],\n [\"MIN\", ct(min)],\n [\"MAX\", ct(max)],\n [\"AVG\", Math.round(ct(avg) * 100) / 100],\n [\"CHEAPEST\", hhmm(cheapest.startsAt)],\n [\"IS_CHEAP\", cur.total < avg ? 1 : 0],\n [\"LEVEL\", cur.level || \"NORMAL\"],\n [\"UPDATED\", hhmm(new Date(now).toISOString())]\n];\n\nreturn [values.map(([key, value]) => ({ topic: \"stat/\" + TOPIC + \"/\" + key, payload: value, retain: true }))];\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":600,"y":100,"wires":[["nomos_price_tibber_out"]]},{"id":"nomos_price_tibber_out","type":"mqtt out","z":"nomos_price_tibber","name":"an nomos","topic":"","qos":"0","retain":"true","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"nomos_price_tibber_broker","x":830,"y":100,"wires":[]}]
Im Knoten http request steht im Kopf Authorization der Platzhalter Bearer IHR-TIBBER-TOKEN. Nur den Platzhalter ersetzen — das Wort Bearer und das Leerzeichen danach bleiben stehen. Das Token wird im Konto des Anbieters erzeugt.
Was der Flow veröffentlicht
<Topic> ist das MQTT-Topic des Geräts in nomos. Alle Werte werden retained veröffentlicht, damit nach einem Neustart sofort wieder ein Preis dasteht.
| Topic | Inhalt |
|---|---|
stat/<Topic>/PRICE |
Preis der laufenden Stunde in ct/kWh |
stat/<Topic>/LEVEL |
Einstufung: VERY_CHEAP, CHEAP, NORMAL, EXPENSIVE, VERY_EXPENSIVE |
stat/<Topic>/IS_CHEAP |
1, solange der Preis unter dem Tagesmittel liegt, sonst 0 |
stat/<Topic>/MIN |
günstigster Preis des Tages |
stat/<Topic>/MAX |
teuerster Preis des Tages |
stat/<Topic>/AVG |
Tagesmittel |
stat/<Topic>/CHEAPEST |
Beginn der günstigsten Stunde, z. B. 03:00 |
stat/<Topic>/NEXT |
Preis der nächsten Stunde |
stat/<Topic>/ENERGY |
Energieanteil des Preises (bei aWATTar derselbe Wert wie PRICE) |
stat/<Topic>/UPDATED |
Uhrzeit der letzten Abfrage |
tele/<Topic>/LWT |
Online bzw. Offline — das Lebenszeichen, das der Broker selbst setzt |
Datenbereich
- Aktueller Preis, Preisstufe und Jetzt günstig — die drei Werte, um die es im Alltag geht
- Günstigster, teuerster und mittlerer Preis des Tages sowie die günstigste Stunde
- Ausgeblendet: Preis der Folgestunde, Energieanteil und Zeitpunkt der letzten Abfrage
- Erreichbar (
reachable): zeigt an, ob der Flow läuft
In Automationen verwenden
- Jetzt günstig ist ein Ja/Nein-Wert — der einfachste Auslöser: wird er wahr, darf der Verbraucher starten.
- Preisstufe ist Text und wird auf Gleichheit verglichen, etwa auf
VERY_CHEAPfür die wirklich billigen Stunden. - Aktueller Preis ist eine Zahl in ct/kWh und erlaubt eine eigene Schwelle, unabhängig vom Tagesverlauf.
Hinweise
- Der Preis ist ein Börsenpreis, keine Rechnung. Netzgebühren, Abgaben und der Aufschlag des Lieferanten fehlen bei aWATTar; wer den eigenen Endpreis sehen will, ergänzt Faktor und Aufschlag im Funktionsknoten. Tibber liefert den Endpreis bereits.
- Die Preise gelten je Stunde. Die Werte des Folgetags erscheinen am frühen Nachmittag; bis dahin beziehen sich Tagesmittel und günstigste Stunde auf den laufenden Tag.
- Negative Preise kommen vor und werden unverändert durchgereicht — an windigen Feiertagen ist das der Normalfall, kein Fehler.
- Die günstigste Stunde kann vorbei sein.
CHEAPESTnennt die günstigste Stunde des ganzen Tages, auch wenn sie am Vormittag lag. Für „jetzt starten oder warten?” ist Jetzt günstig der passende Wert. - Jetzt günstig vergleicht mit dem Tagesmittel, nicht mit dem eigenen Tarif. An einem teuren Tag ist auch die günstigste Stunde teuer.
- Ein Topic je Gerät. Zwei Tarife bedeuten zwei Geräte mit verschiedenen Topics und zwei Flows.
- Jede andere Quelle geht genauso. Wer die Topics aus der Tabelle einhält, kann jeden Anbieter anbinden — das Profil fragt nicht, woher die Zahlen kommen.
- Nicht enthalten: Prognose über mehrere Tage, Ladeplanung und das Verschieben von Verbrauchern. Das Profil liefert die Zahlen; was damit geschaltet wird, entscheiden die Automationen in nomos.