Node-RED · Preis und CO2
Strompreis und CO2 in Node-RED nutzen, ohne lange zu klicken
Für den Einstieg reicht ein importierbarer Summary-Flow. Wenn du später mehr willst, gibt es zusätzlich einen eigenen Price-Series-Pfad für eigene Fensterlogik, Charts und Spezialfälle.
- eigener Node-RED-Endpunkt mit kompakter Struktur für Flows
- Summary für schnellen Start, Price-Series für eigene Logik
- optional mit Bearer-Token und bis zu 120h in der Testphase
Der Node-RED-Flow bleibt ohne API-Key mit bis zu 48 Stunden nutzbar. Für Planungen über mehrere Tage kannst du Private Pro mit bis zu 120 Stunden 14 Tage kostenlos testen.
Wichtige Logik früh erklärt
Die Preislogik ist auch in Node-RED nicht einfach „nur Forecast“. Sie kombiniert bewusst echte Marktdaten mit Prognose, damit ein Flow nicht unnötig auf reine Schätzwerte reagiert.
mixed ist also eine Mischung aus echten Marktdaten und Forecast für den noch offenen Zeitraum.Node-RED kann JSON-APIs direkt verarbeiten. Du brauchst deshalb keinen Spezialnode, sondern nur einen kleinen Flow mit Inject, Function, HTTP Request und Debug.
Im Beispiel ist der HTTP-Request so konfiguriert, dass die Antwort direkt als geparstes Objekt zurückkommt. Zusätzlich gibt es einen catch-Pfad, damit Fehler nicht still verschwinden.
Empfohlener Einstieg: Summary
Für Node-RED gibt es einen eigenen Summary-Pfad mit aktuellen Werten, besten Fenstern und Meta-Feldern für Limits und API-Key-Status. Das ist der sinnvollste Startpunkt für erste Automationen.
https://api.energypriceforecast.eu/api/v1/node-red/summary?country=de&hours=48&window_hours=4msg.*.msg.api_key_state, msg.allowed_horizon_hours und mehr kommen direkt mit.Alternative für eigene Preislogik: node-red/prices
Für fortgeschrittene Flows gibt es zusätzlich einen zweiten Node-RED-Pfad nur für die Preisreihe. Der ist für eigene Fensterberechnung, Charts, Schwellwerte oder Kombinationen mit PV, Batterie und Hausstatus gedacht.
https://api.energypriceforecast.eu/api/v1/node-red/prices?country=de&hours=48&mode=mixed&resolution=15msummaryEinsteigerpfad mit aktuellem Preis, CO2 und schon berechneten Fenstern.pricesRohpreisreihe für eigene Flows, eigene Fensterlogik und Dashboards.15m-Modus wird Forecast nicht geglättet, sondern stündlich auf vier Viertelstunden wiederholt.Was die API genau liefert
Der Summary-Endpunkt liefert eine kompakte Automation-Sicht. Für Node-RED ist das praktisch, weil du die Antwort sofort für einfache Flows nutzen kannst, aber trotzdem genug Struktur für spätere Erweiterungen bekommst.
| Teil | Inhalt | Nutzen |
|---|---|---|
flat | Aktueller Preis, aktuelles CO2, Boolean-Felder für aktive beste Fenster und Restlaufzeiten. | Sofort für msg.* nutzbar. |
price | Aktueller Preis-Slot sowie best_window und next_full_window als Objekte. | Mehr Kontext für eigene Flow-Logik. |
co2 | Aktueller CO2-Slot sowie best_window und next_full_window. | Für CO2-orientierte Entscheidungen. |
source | Metadaten zu Day-Ahead und Forecast. | Wichtig für Einordnung und Debug. |
meta | Zugriffs- und Vertragsinfos wie API-Key-Status, erlaubter Horizont und Tageszähler. | Sauberes Monitoring pro Nutzer oder Flow. |
Wenn du statt der Summary eine echte Preisreihe für eigene Berechnungen brauchst, nutze /api/v1/node-red/prices. Die Summary bleibt bewusst kompakt. Die Preisreihe ist für fortgeschrittene eigene Flow-Logik gedacht.
Preisreihe für eigene Flows: node-red/prices
Der zusätzliche Preisreihen-Pfad liefert bewusst nur Preise, keine CO2-Felder und keine fertige Fensterlogik. Genau das ist der Punkt: In Node-RED kannst du selbst entscheiden, wie du die Slots auswertest oder mit anderen Signalen kombinierst.
| Parameter | Bedeutung | Typischer Einsatz |
|---|---|---|
mode=mixed | Day-Ahead zuerst, Forecast nur für noch nicht offiziell abgedeckte Zukunft. | Praxisnaher Standard. |
mode=forecast_only | Nur Forecast, ohne Day-Ahead-Mischung. | Vergleich oder bewusst forecast-zentrierte Logik. |
price_mode=base | Standardmodus mit Basis- bzw. Marktpreis. | Wenn dir der relative Verlauf für Automationen reicht. |
price_mode=retail | Annahmenbasierter Gesamtpreis mit Aufschlägen, Netzentgelten und MwSt. für unterstützte Märkte. | Wenn du näher an einem Haushalts-Endpreis automatisieren willst. |
plz=10115 | Für DE im Retail-Modus erforderlich, damit die Netzentgelt-Annahmen regional besser passen. | Pflicht für price_mode=retail in Deutschland. |
resolution=15m | Durchgehende Viertelstundenreihe. Forecast-Stunden werden auf vier Viertelstunden wiederholt. | Charts und einheitliche Slot-Logik. |
resolution=native | Day-Ahead bleibt 15-minütig, Forecast bleibt stündlich. | Wenn du die Ursprungsauflösung unverändert sehen willst. |
https://api.energypriceforecast.eu/api/v1/node-red/prices?country=de&hours=48&mode=mixed&resolution=15m
https://api.energypriceforecast.eu/api/v1/node-red/prices?country=de&hours=48&mode=mixed&resolution=15m&price_mode=retail&plz=10115
Wichtig: Im 15m-Modus werden Forecast-Werte nicht zwischen zwei Stunden geglättet. Die API erfindet also keine Zwischenpreise, sondern markiert sauber, dass diese Viertelstunden aus einem stündlichen Forecast-Block stammen.
Basispreis oder Gesamtpreis?
Die Node-RED-summary liefert weiterhin bewusst Basis- bzw. Marktpreise. Der Pfad prices liefert standardmäßig ebenfalls Basispreise, kann aber optional auch annahmenbasierte Retail-/Gesamtpreise ausgeben.
summaryBleibt kompakt und bewusst bei Basispreisen.prices mit price_mode=retailLiefert auf Wunsch eine 15-Minuten- oder native Preisreihe als annahmenbasierten Gesamtpreis.Zeitraum und Auflösung
Die Beispiele nutzen hours=48 und beim Summary window_hours=4. Das ist ein sinnvoller Einstieg, aber nicht die komplette Produktgrenze.
hours legst du den angefragten Horizont fest. Öffentlich belastbar kommunizieren wir für die Preisprognose aktuell maximal 120 Stunden.meta.allowed_horizon_hours.source.day_ahead_entries und source.forecast_entries zählen Slots, nicht Stunden. Deshalb können Werte wie 97 Day-Ahead und 0 Forecast plausibel sein, wenn der angefragte Zeitraum fast vollständig mit veröffentlichten Day-Ahead-Werten abgedeckt ist.
Wo der API-Key eingetragen wird
Im Beispiel-Flow wird der API-Key im Function-Node gesetzt. Wenn der String leer bleibt, läuft der Abruf ohne Key mit dem freien Horizont. Mit Key kannst du zum Beispiel hours=120 testen.
const API_KEY = "";
const API_URL = "https://api.energypriceforecast.eu/api/v1/node-red/summary?country=de&hours=120&window_hours=4";
Maßgeblich ist am Ende nicht der gewünschte Wert in der URL, sondern das, was der Server in meta.allowed_horizon_hours tatsächlich freigibt.
Schnellstart: Summary-Flow importieren
- In Node-RED oben rechts auf
Importklicken. - Den kompletten JSON-Block unten einfügen.
- Importieren und danach auf
Deployklicken. - Die Debug-Seitenleiste öffnen und den Inject-Knopf einmal manuell auslösen.
country=de in der URL und optional den API-Key im Function-Node.msg.current_price, msg.current_co2_g_kwh, aktive beste Fenster und die wichtigsten Meta-Felder.Import-Flow für die Summary
Den gesamten Block kopieren und in Node-RED importieren. Der Flow fängt HTTP- und Parse-Fehler zusätzlich über einen catch-Pfad ab.
[
{
"id": "epf_summary_tab",
"type": "tab",
"label": "EPF Summary",
"disabled": false,
"info": ""
},
{
"id": "epf_summary_inject",
"type": "inject",
"z": "epf_summary_tab",
"name": "Summary laden",
"props": [],
"repeat": "900",
"once": true,
"onceDelay": "1",
"topic": "",
"x": 130,
"y": 120,
"wires": [["epf_summary_prepare"]]
},
{
"id": "epf_summary_prepare",
"type": "function",
"z": "epf_summary_tab",
"name": "API-Key und URL setzen",
"func": "const API_KEY = \"\";\\nmsg.url = \"https://api.energypriceforecast.eu/api/v1/node-red/summary?country=de&hours=48&window_hours=4\";\\nmsg.headers = {};\\nif (API_KEY.trim()) {\\n msg.headers.Authorization = \"Bearer \" + API_KEY.trim();\\n}\\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 390,
"y": 120,
"wires": [["epf_summary_http"]]
},
{
"id": "epf_summary_http",
"type": "http request",
"z": "epf_summary_tab",
"name": "Summary abrufen",
"method": "GET",
"ret": "obj",
"paytoqs": "ignore",
"url": "",
"persist": false,
"x": 630,
"y": 120,
"wires": [["epf_summary_extract"]]
},
{
"id": "epf_summary_extract",
"type": "function",
"z": "epf_summary_tab",
"name": "Wichtige Felder setzen",
"func": "const data = msg.payload || {};\\nconst flat = data.flat || {};\\nconst meta = data.meta || {};\\nconst price = data.price || {};\\nconst co2 = data.co2 || {};\\nmsg.current_price = flat.current_price;\\nmsg.current_co2_g_kwh = flat.current_co2_g_kwh;\\nmsg.cheapest_window_start = flat.cheapest_window_start || null;\\nmsg.greenest_window_start = flat.greenest_window_start || null;\\nmsg.is_cheapest_window_now = flat.is_cheapest_window_now === true;\\nmsg.cheapest_window_remaining_minutes = flat.cheapest_window_remaining_minutes ?? null;\\nmsg.next_full_cheapest_window_start = price.next_full_window ? price.next_full_window.start : null;\\nmsg.next_full_greenest_window_start = co2.next_full_window ? co2.next_full_window.start : null;\\nmsg.api_key_state = meta.api_key_state || 'missing';\\nmsg.allowed_horizon_hours = meta.allowed_horizon_hours;\\nmsg.used_horizon_hours = meta.used_horizon_hours;\\nmsg.rate_limit_daily = meta.rate_limit_daily;\\nmsg.used_calls_today = meta.used_calls_today;\\nmsg.error = null;\\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 870,
"y": 120,
"wires": [["epf_summary_debug"]]
},
{
"id": "epf_summary_debug",
"type": "debug",
"z": "epf_summary_tab",
"name": "Summary anzeigen",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"x": 1110,
"y": 120,
"wires": []
},
{
"id": "epf_summary_catch",
"type": "catch",
"z": "epf_summary_tab",
"name": "Fehler fangen",
"scope": null,
"uncaught": false,
"x": 140,
"y": 220,
"wires": [["epf_summary_error"]]
},
{
"id": "epf_summary_error",
"type": "function",
"z": "epf_summary_tab",
"name": "Fehler lesbar machen",
"func": "const message = msg.error && msg.error.message ? msg.error.message : 'Unbekannter Fehler';\\nmsg.payload = { error: message, source: msg.error && msg.error.source ? msg.error.source : null };\\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 400,
"y": 220,
"wires": [["epf_summary_error_debug"]]
},
{
"id": "epf_summary_error_debug",
"type": "debug",
"z": "epf_summary_tab",
"name": "Summary-Fehler",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": true,
"complete": "payload",
"targetType": "msg",
"x": 660,
"y": 220,
"wires": []
}
]
Danach kannst du anstelle des Debug-Knotens später eigene Logik anschließen, zum Beispiel Dashboard, Benachrichtigungen oder Scheduler-Regeln.
Fortgeschritten: Price-Series-Flow importieren
Wenn du lieber selbst rechnest, ist der zweite Flow für die Preisreihe gedacht. Er holt die komplette Slotliste und legt sie in msg.price_entries ab.
[
{
"id": "epf_prices_tab",
"type": "tab",
"label": "EPF Prices",
"disabled": false,
"info": ""
},
{
"id": "epf_prices_inject",
"type": "inject",
"z": "epf_prices_tab",
"name": "Preisreihe laden",
"props": [],
"repeat": "900",
"once": false,
"onceDelay": "1",
"topic": "",
"x": 130,
"y": 120,
"wires": [["epf_prices_prepare"]]
},
{
"id": "epf_prices_prepare",
"type": "function",
"z": "epf_prices_tab",
"name": "Parameter setzen",
"func": "const API_KEY = \"\";\\nconst BASE_URL = \"https://api.energypriceforecast.eu/api/v1/node-red/prices?country=de&hours=48&mode=mixed&resolution=15m\";\\nconst USE_RETAIL = false;\\nconst PLZ = \"10115\";\\nmsg.url = USE_RETAIL ? BASE_URL + \"&price_mode=retail&plz=\" + encodeURIComponent(PLZ) : BASE_URL;\\nmsg.headers = {};\\nif (API_KEY.trim()) {\\n msg.headers.Authorization = \"Bearer \" + API_KEY.trim();\\n}\\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 370,
"y": 120,
"wires": [["epf_prices_http"]]
},
{
"id": "epf_prices_http",
"type": "http request",
"z": "epf_prices_tab",
"name": "Preisreihe abrufen",
"method": "GET",
"ret": "obj",
"paytoqs": "ignore",
"url": "",
"persist": false,
"x": 610,
"y": 120,
"wires": [["epf_prices_extract"]]
},
{
"id": "epf_prices_extract",
"type": "function",
"z": "epf_prices_tab",
"name": "Preisreihe weiterreichen",
"func": "const data = msg.payload || {};\\nmsg.price_entries = Array.isArray(data.entries) ? data.entries : [];\\nmsg.first_price_slot = msg.price_entries.length ? msg.price_entries[0] : null;\\nmsg.price_series_source = data.source || {};\\nmsg.api_key_state = data.meta && data.meta.api_key_state ? data.meta.api_key_state : 'missing';\\nmsg.allowed_horizon_hours = data.meta ? data.meta.allowed_horizon_hours : null;\\nmsg.series_request = data.request || {};\\nmsg.series_assumptions = data.assumptions || null;\\nmsg.error = null;\\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 860,
"y": 120,
"wires": [["epf_prices_debug"]]
},
{
"id": "epf_prices_debug",
"type": "debug",
"z": "epf_prices_tab",
"name": "Preisreihe anzeigen",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"x": 1110,
"y": 120,
"wires": []
}
]
Für Retail in Deutschland musst du im Function-Node USE_RETAIL = true setzen und eine passende PLZ eintragen.
Für Automationen: Anzeige und Laufentscheidung trennen
Für Dashboards reicht oft msg.cheapest_window_start. Für echte Steuerung ist aber meist wichtiger, ob das beste Fenster bereits läuft. Genau dafür setzt der Summary-Flow zusätzlich msg.is_cheapest_window_now und msg.cheapest_window_remaining_minutes.
if (msg.is_cheapest_window_now) {
// Verbraucher jetzt freigeben
} else if (msg.next_full_cheapest_window_start) {
// nächstes vollständiges Fenster für Planung verwenden
}
return msg;
Pragmatisch: Ein Boolean für „jetzt“ ist für Automationen meist wertvoller als nur ein Zeitstempel in der Zukunft.
Welche Message-Felder du danach hast
| Feld | Bedeutung | Typischer Einsatz |
|---|---|---|
msg.current_price | Aktueller Preis in EUR/kWh. | Einfache Preislogik oder Debug-Ausgabe. |
msg.current_co2_g_kwh | Aktuelle CO2-Intensität. | CO2-orientierte Steuerung. |
msg.is_cheapest_window_now | true, wenn das beste Preisfenster bereits läuft. | Direkte Laufentscheidung für Verbraucher. |
msg.cheapest_window_remaining_minutes | Restlaufzeit des aktuell besten Preisfensters. | Gerät nur so lange laufen lassen, wie das Fenster aktiv ist. |
msg.next_full_cheapest_window_start | Startzeit des nächsten vollständigen Preisfensters. | Planung statt Sofort-Trigger. |
msg.price_entries | Komplette Preisreihe aus dem Price-Series-Pfad. | Eigene Fensterlogik, Charts und Spezialfälle. |
msg.series_assumptions | Retail-Annahmen wie PLZ, Netzentgeltquelle und MwSt. | Debug und Plausibilitätsprüfung bei Gesamtpreis-Modus. |
msg.api_key_state | Status des verwendeten API-Keys. | Sauberes Monitoring pro Nutzer oder Flow. |
msg.allowed_horizon_hours | Serverseitig erlaubter Maximalhorizont. | Prüfen, ob ein Key mehr freischaltet. |
msg.used_calls_today | Heute bereits verbrauchte Requests. | Limits im Blick behalten. |
Wichtige Hinweise
Ist das ein offizieller Node-RED-Node?
Nein. Es ist bewusst ein normaler Flow mit Standardknoten. Das ist für den Einstieg leichter nachvollziehbar und besser an eigene Automationen anpassbar.
Wie oft sollte ich abrufen?
Standardmäßig empfehlen wir hier 30 Minuten. Für viele Setups reicht das, weil sich Day-Ahead nicht dauernd ändert. Gleichzeitig bleiben aktueller Slot und Fensterstatus trotzdem ausreichend frisch. Wenn du bewusst näher an Viertelstunden-Slots reagieren willst, kannst du den Abruf selbst auf 15 Minuten umstellen.
Gibt es schon eine Warnung für Extremstunden?
Ja, im Produkt gibt es bereits eine Warnung für mögliche Extremstunden, zum Beispiel in der App bzw. Web-App über das Tail-Beta- bzw. Extrempfad-Signal. Im Node-RED-Endpunkt selbst ist dieses Signal aktuell aber noch kein eigenes stabiles Feld.
Was tun, wenn im Debug statt JSON HTML ankommt?
Dann zeigt die URL sehr wahrscheinlich auf den falschen Pfad oder auf eine fehlerhafte Zwischenadresse. Für Node-RED solltest du direkt die API-URL von dieser Seite verwenden.
Kleine Testphase
Für die neue Node-RED-Anbindung läuft aktuell eine kleine Testphase. Gesucht sind vor allem echte Flows, bei denen schnell sichtbar wird, ob die importierbaren Beispiele verständlich sind und ob die Daten in realen Automationen sauber weiterverarbeitet werden können.
Wenn du Node-RED aktiv nutzt und das testen möchtest, schreib kurz mit deinem Setup und Markt an StrompreisVorhersage@proton.me. Die ersten Testeinbindungen werden bewusst noch manuell begleitet.
Verwandt: ioBroker, openHAB und Home Assistant.