Sprache

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.

Kostenlos starten, optional länger planen

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.

Schritt 1Sobald offizieller Day-Ahead verfügbar ist, wird dieser Preis bevorzugt verwendet.
Schritt 2Forecast wird nur für den Teil der Zukunft genutzt, für den noch keine offiziellen Day-Ahead-Werte vorliegen.
Wichtige FolgeDer Pfad mixed ist also eine Mischung aus echten Marktdaten und Forecast für den noch offenen Zeitraum.
Warum dieser Weg sinnvoll ist

Node-RED kann JSON-APIs direkt verarbeiten. Du brauchst deshalb keinen Spezialnode, sondern nur einen kleinen Flow mit Inject, Function, HTTP Request und Debug.

Für Einsteiger wichtig

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=4
Sofort sichtbarDer Beispiel-Flow schreibt die wichtigsten Werte direkt in msg.*.
Einfach weiterverwendbarSpäter kannst du dieselben Felder in Switch-, Dashboard- oder Scheduler-Logik einhängen.
Meta inklusivemsg.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=15m
summaryEinsteigerpfad mit aktuellem Preis, CO2 und schon berechneten Fenstern.
pricesRohpreisreihe für eigene Flows, eigene Fensterlogik und Dashboards.
Wichtige EhrlichkeitIm 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
flatAktueller Preis, aktuelles CO2, Boolean-Felder für aktive beste Fenster und Restlaufzeiten.Sofort für msg.* nutzbar.
priceAktueller Preis-Slot sowie best_window und next_full_window als Objekte.Mehr Kontext für eigene Flow-Logik.
co2Aktueller CO2-Slot sowie best_window und next_full_window.Für CO2-orientierte Entscheidungen.
sourceMetadaten zu Day-Ahead und Forecast.Wichtig für Einordnung und Debug.
metaZugriffs- 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=mixedDay-Ahead zuerst, Forecast nur für noch nicht offiziell abgedeckte Zukunft.Praxisnaher Standard.
mode=forecast_onlyNur Forecast, ohne Day-Ahead-Mischung.Vergleich oder bewusst forecast-zentrierte Logik.
price_mode=baseStandardmodus mit Basis- bzw. Marktpreis.Wenn dir der relative Verlauf für Automationen reicht.
price_mode=retailAnnahmenbasierter Gesamtpreis mit Aufschlägen, Netzentgelten und MwSt. für unterstützte Märkte.Wenn du näher an einem Haushalts-Endpreis automatisieren willst.
plz=10115Für DE im Retail-Modus erforderlich, damit die Netzentgelt-Annahmen regional besser passen.Pflicht für price_mode=retail in Deutschland.
resolution=15mDurchgehende Viertelstundenreihe. Forecast-Stunden werden auf vier Viertelstunden wiederholt.Charts und einheitliche Slot-Logik.
resolution=nativeDay-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.
Wichtige EhrlichkeitRetail ist kein exakter Rechnungsbetrag, sondern ein modellierter Endpreis mit Annahmen. Für DE braucht der Endpunkt im Retail-Modus eine PLZ.

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.

ZeitraumMit hours legst du den angefragten Horizont fest. Öffentlich belastbar kommunizieren wir für die Preisprognose aktuell maximal 120 Stunden.
Tatsächlich erlaubtOhne API-Key sind aktuell 48 Stunden frei. Mit Key kann mehr erlaubt sein. Maßgeblich ist immer meta.allowed_horizon_hours.
AuflösungDay-Ahead kann in Viertelstunden kommen, Forecast und CO2 meist stündlich.

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

  1. In Node-RED oben rechts auf Import klicken.
  2. Den kompletten JSON-Block unten einfügen.
  3. Importieren und danach auf Deploy klicken.
  4. Die Debug-Seitenleiste öffnen und den Inject-Knopf einmal manuell auslösen.
Für den Start genugDer Flow zeigt bewusst erst einmal die wichtigsten Felder an, statt direkt eine komplexe Steuerlogik zu verstecken.
Was du ändern musstNormalerweise nur country=de in der URL und optional den API-Key im Function-Node.
Was du danach siehstmsg.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_priceAktueller Preis in EUR/kWh.Einfache Preislogik oder Debug-Ausgabe.
msg.current_co2_g_kwhAktuelle CO2-Intensität.CO2-orientierte Steuerung.
msg.is_cheapest_window_nowtrue, wenn das beste Preisfenster bereits läuft.Direkte Laufentscheidung für Verbraucher.
msg.cheapest_window_remaining_minutesRestlaufzeit des aktuell besten Preisfensters.Gerät nur so lange laufen lassen, wie das Fenster aktiv ist.
msg.next_full_cheapest_window_startStartzeit des nächsten vollständigen Preisfensters.Planung statt Sofort-Trigger.
msg.price_entriesKomplette Preisreihe aus dem Price-Series-Pfad.Eigene Fensterlogik, Charts und Spezialfälle.
msg.series_assumptionsRetail-Annahmen wie PLZ, Netzentgeltquelle und MwSt.Debug und Plausibilitätsprüfung bei Gesamtpreis-Modus.
msg.api_key_stateStatus des verwendeten API-Keys.Sauberes Monitoring pro Nutzer oder Flow.
msg.allowed_horizon_hoursServerseitig erlaubter Maximalhorizont.Prüfen, ob ein Key mehr freischaltet.
msg.used_calls_todayHeute 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.