Sprache

ioBroker · Preis und CO2

Strompreis und CO2 in ioBroker einbinden, ohne eigenen Adapter zu bauen

Wenn du in ioBroker den JavaScript-Adapter hast, reicht hier ein einziges Skript. Es holt die Daten standardmäßig alle 30 Minuten ab und schreibt einfache States unter 0_userdata.0.

Wichtige Logik früh erklärt

Die Preislogik ist nicht „nur Forecast“. Sie arbeitet bewusst in zwei Schritten, damit echte Marktdaten bevorzugt werden.

Schritt 1Sobald offizieller Day-Ahead für einen Slot vorliegt, wird dieser Preis bevorzugt verwendet.
Schritt 2Nur für zukünftige Slots außerhalb der bereits verfügbaren Day-Ahead-Abdeckung wird mit Prognosewerten ergänzt.
Wichtige FolgeDie API kombiniert also echte Day-Ahead-Preise mit Forecast für den noch offenen Zeitraum.
Der pragmatische Weg

Für den Einstieg ist ein kleines JavaScript-Skript in ioBroker sinnvoller als ein eigener Spezialadapter. Du siehst sofort Datenpunkte, kannst sie visualisieren und später in eigene Logik weiterverwenden.

Voraussetzung

Installiert sein muss der JavaScript-Adapter in ioBroker. Das Beispiel nutzt das im Adapter verfügbare axios sowie die üblichen Funktionen createState und setState.

Empfohlener Endpunkt

Für ioBroker gibt es einen eigenen Summary-Pfad. Er liefert die kompakten Felder, die für erste Automationen meist reichen, plus Meta-Felder für Limits und Key-Status.

https://api.energypriceforecast.eu/api/v1/iobroker/summary?country=de&hours=48&window_hours=4
Direkt nutzbarPreis, CO2 und Fenster kommen als einfaches JSON.
Eigene StatesDas Skript legt die Werte unter 0_userdata.0.energypriceforecast ab.
Meta inklusiveapi_key_state, erlaubter Horizont und Tageszähler landen ebenfalls in States.

Alternative für eigene Preislogik

Wenn du nicht mit der kompakten Summary arbeiten willst, gibt es zusätzlich einen zweiten ioBroker-Pfad nur für die Preisreihe. Der ist eher für fortgeschrittene Nutzer gedacht, die eigene Fensterlogik, Charts oder Preisregeln in JavaScript bauen wollen.

https://api.energypriceforecast.eu/api/v1/iobroker/prices?country=de&hours=48&resolution=15m
summaryEinsteigerpfad mit aktuellem Preis, CO2 und bereits berechneten Fenstern.
pricesRohpreisreihe für eigene Skripte, Charts und eigene Automationslogik.
Wichtige EhrlichkeitDay-Ahead bleibt nativ 15-minütig. Forecast wird im 15m-Modus nicht geglättet, sondern stündlich auf vier Viertelstunden wiederholt.

Was die API genau liefert

Der Endpunkt liefert eine allgemeine Automation-Summary. Für ioBroker ist das gut, weil du nicht auf einen festen Spezialadapter festgelegt bist und dieselben Daten auch in eigenen Skripten oder Visualisierungen weiterverwenden kannst.

Teil Inhalt Nutzen
flatAktueller Preis, aktuelles CO2, Boolean-Felder für aktive beste Fenster und Restlaufzeiten.Ideal für einfache States.
priceAktueller Preis-Slot sowie best_window und next_full_window als Objekte.Mehr Kontext für eigene Logik.
co2Aktueller CO2-Slot sowie best_window und next_full_window.Für CO2-orientierte Automationen.
sourceMetadaten zu Day-Ahead und Forecast.Wichtig zum Debuggen und Einordnen.
metaZugriffs- und Vertragsinfos wie erlaubter Horizont, API-Key-Status und Tagesverbrauch.Hilft beim Tracken pro Nutzer oder Integration.

Wenn du statt der Summary eine echte Preisreihe für eigene Berechnungen brauchst, nutze den separaten Pfad /api/v1/iobroker/prices. Die Summary bleibt bewusst beim kompakten Basispreis. Der Preisreihen-Pfad liefert standardmäßig ebenfalls Basispreise, kann jetzt aber optional auch annahmenbasierte Retail-/Gesamtpreise liefern.

Preisreihe für eigene Skripte: iobroker/prices

Der zusätzliche Preisreihen-Pfad liefert bewusst nur Preise, keine CO2-Felder und keine fertige Fensterlogik. Genau das ist der Sinn: Du kannst in ioBroker selbst entscheiden, wie du günstige Zeitfenster, Schwellwerte oder Visualisierungen bauen willst.

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, Debugging oder bewusste Forecast-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 zum Gebiet passen.Pflicht für price_mode=retail in Deutschland.
resolution=15mDurchgehende Viertelstundenreihe. Forecast-Stunden werden dafür einfach 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/iobroker/prices?country=de&hours=48&mode=mixed&resolution=15m
https://api.energypriceforecast.eu/api/v1/iobroker/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 ioBroker-summary liefert weiterhin bewusst Basis- bzw. Marktpreise. Der Pfad prices liefert standardmäßig ebenfalls Basispreise, kann aber jetzt optional auch annahmenbasierte Retail-/Gesamtpreise ausgeben.

summaryBleibt kompakt und 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. Unterstützt werden aktuell DE, NL, DK1, DK2, AT und NO1 bis NO5.

Zeitraum und Auflösung

Das Beispiel nutzt hours=48 und window_hours=4. Das ist ein sinnvoller Einstieg, aber nicht die einzige mögliche Einstellung.

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, aber mehr als 120 Stunden Preisprognose sollten aktuell nicht als verlässliche öffentliche Zusage verstanden werden. Maßgeblich ist immer meta.allowed_horizon_hours.
AuflösungDay-Ahead kann in Viertelstunden kommen, Forecast und CO2 meist stündlich.

Wichtig: source.price.day_ahead_entries und source.price.forecast_entries zählen Slots, nicht Stunden. Deshalb können Werte wie 97 Day-Ahead und 0 Forecast plausibel sein, wenn der angefragte Zeitraum gerade fast vollständig durch veröffentlichte Day-Ahead-Slots abgedeckt wird.

Schnellstart für Copy-Paste

  1. Im ioBroker Admin den JavaScript-Adapter öffnen.
  2. Ein neues JavaScript-Skript anlegen.
  3. Den kompletten Block unten einfügen und speichern.
  4. Einmal manuell starten oder kurz warten. Danach sollten neue States unter 0_userdata.0.energypriceforecast sichtbar sein.
Für Einsteiger gebautDas Skript legt die benötigten States selbst an. Du musst nicht zuerst Datenpunkte per Hand vorbereiten.
Was du ändern musstFast immer nur COUNTRY und optional API_KEY.
Was du danach siehstAktueller Preis, aktuelles CO2, aktive beste Fenster, nächste vollständige Zukunftsfenster und die wichtigsten API-Meta-Felder.

Fertiges Skript für den JavaScript-Adapter

Das Skript holt die Daten sofort beim Start und danach standardmäßig alle 30 Minuten neu. Das ist ein sinnvoller Standard für viele Setups: Day-Ahead ändert sich nicht ständig, aber aktueller Slot, laufende Fenster und der Forecast-Kontext bewegen sich trotzdem weiter. Wenn du keinen API-Key hast, bleibt API_KEY einfach leer.

const axios = require("axios");

const COUNTRY = "de";
const API_KEY = "";
const API_URL = "https://api.energypriceforecast.eu/api/v1/iobroker/summary?country=" + COUNTRY + "&hours=48&window_hours=4";
const ROOT = "0_userdata.0.energypriceforecast";
const POLL_MS = 30 * 60 * 1000;

const STATE_DEFINITIONS = [
  [".current_price", 0, { name: "Aktueller Preis", type: "number", role: "value", unit: "EUR/kWh", read: true, write: false }],
  [".current_co2_g_kwh", 0, { name: "Aktuelle CO2-Intensität", type: "number", role: "value", unit: "gCO2/kWh", read: true, write: false }],
  [".cheapest_window_start", "", { name: "Günstigstes Fenster Start", type: "string", role: "text", read: true, write: false }],
  [".greenest_window_start", "", { name: "Grünstes Fenster Start", type: "string", role: "text", read: true, write: false }],
  [".is_cheapest_window_now", false, { name: "Bestes Preisfenster läuft jetzt", type: "boolean", role: "indicator", read: true, write: false }],
  [".cheapest_window_remaining_minutes", 0, { name: "Bestes Preisfenster Restlaufzeit", type: "number", role: "value.interval", unit: "min", read: true, write: false }],
  [".next_full_cheapest_window_start", "", { name: "Nächstes vollständiges Preisfenster", type: "string", role: "text", read: true, write: false }],
  [".is_greenest_window_now", false, { name: "Bestes CO2-Fenster läuft jetzt", type: "boolean", role: "indicator", read: true, write: false }],
  [".api_key_state", "missing", { name: "API-Key-Status", type: "string", role: "text", read: true, write: false }],
  [".allowed_horizon_hours", 0, { name: "Erlaubter Horizont", type: "number", role: "value", unit: "h", read: true, write: false }],
  [".used_horizon_hours", 0, { name: "Verwendeter Horizont", type: "number", role: "value", unit: "h", read: true, write: false }],
  [".rate_limit_daily", 0, { name: "Tageslimit", type: "number", role: "value", read: true, write: false }],
  [".used_calls_today", 0, { name: "Verbrauchte Calls heute", type: "number", role: "value", read: true, write: false }],
  [".last_update", "", { name: "Letztes Update", type: "string", role: "text", read: true, write: false }],
  [".last_error", "", { name: "Letzter Fehler", type: "string", role: "text", read: true, write: false }]
];

STATE_DEFINITIONS.forEach(([suffix, initialValue, definition]) => {
  createState(ROOT + suffix, initialValue, definition);
});

function setTextState(suffix, value) {
  setState(ROOT + suffix, value == null ? "" : String(value), true);
}

function setNumberState(suffix, value, digits) {
  if (!Number.isFinite(value)) {
    return;
  }

  const normalized = typeof digits === "number" ? Number(value.toFixed(digits)) : Number(value);
  setState(ROOT + suffix, normalized, true);
}

function setBooleanState(suffix, value) {
  if (typeof value !== "boolean") {
    return;
  }

  setState(ROOT + suffix, value, true);
}

async function updateEnergyPriceForecast() {
  const headers = {};

  if (API_KEY.trim()) {
    headers.Authorization = "Bearer " + API_KEY.trim();
  }

  try {
    const response = await axios.get(API_URL, {
      timeout: 10000,
      headers
    });

    const data = response.data || {};
    const flat = data.flat || {};
    const meta = data.meta || {};

    setNumberState(".current_price", flat.current_price, 4);
    setNumberState(".current_co2_g_kwh", flat.current_co2_g_kwh, 1);
    setTextState(".cheapest_window_start", flat.cheapest_window_start);
    setTextState(".greenest_window_start", flat.greenest_window_start);
    setBooleanState(".is_cheapest_window_now", flat.is_cheapest_window_now);
    setNumberState(".cheapest_window_remaining_minutes", flat.cheapest_window_remaining_minutes);
    setTextState(".next_full_cheapest_window_start", data.price && data.price.next_full_window ? data.price.next_full_window.start : null);
    setBooleanState(".is_greenest_window_now", flat.is_greenest_window_now);
    setTextState(".api_key_state", meta.api_key_state || (API_KEY.trim() ? "unknown" : "missing"));
    setNumberState(".allowed_horizon_hours", meta.allowed_horizon_hours);
    setNumberState(".used_horizon_hours", meta.used_horizon_hours);
    setNumberState(".rate_limit_daily", meta.rate_limit_daily);
    setNumberState(".used_calls_today", meta.used_calls_today);
    setTextState(".last_update", data.generated_at);
    setTextState(".last_error", "");
  } catch (error) {
    const statusCode = error && error.response ? error.response.status : null;
    const errorData = error && error.response ? error.response.data || {} : {};
    const errorMeta = errorData.meta || {};

    setTextState(".api_key_state", errorMeta.api_key_state || (API_KEY.trim() ? "error" : "missing"));

    if (statusCode === 401) {
      setTextState(".last_error", "401: API-Key fehlt, ist ungültig oder wurde deaktiviert.");
    } else if (statusCode === 403) {
      setTextState(".last_error", "403: Zugriff für Route, Markt oder Vertragszustand nicht erlaubt.");
    } else if (statusCode === 429) {
      setTextState(".last_error", "429: Tageslimit erreicht.");
    } else {
      setTextState(".last_error", "Abruf fehlgeschlagen.");
    }

    console.error("StrompreisVorhersage Fehler:", statusCode, error && error.message ? error.message : error);
  }
}

updateEnergyPriceForecast();
setInterval(updateEnergyPriceForecast, POLL_MS);

Wenn du lieber einen anderen Markt willst, ändere nur COUNTRY. Für Dänemark immer dk1 oder dk2 verwenden. Wenn du bewusst häufiger pollen willst, ändere POLL_MS selbst. Der Standardwert ist absichtlich zurückhaltend gewählt.

Wo der API-Key eingetragen wird

Der Key kommt direkt in die Variable API_KEY. Wenn du einen Test-Key bekommen hast, kannst du gleichzeitig den angefragten Horizont in der URL höher setzen, zum Beispiel auf 120 Stunden.

const API_KEY = "DEIN_TEST_KEY";
const API_URL = "https://api.energypriceforecast.eu/api/v1/iobroker/summary?country=de&hours=120&window_hours=4";

Wichtig: Maßgeblich ist am Ende nicht nur die URL, sondern meta.allowed_horizon_hours. Daran siehst du, was dein aktueller Zugriff serverseitig wirklich darf.

Für Automationen: jetzt laufen oder später planen

Ein häufiger Fehler ist, nur auf cheapest_window_start zu schauen. Das reicht für Anzeige, ist aber für echte Steuerung oft zu grob. Mit is_cheapest_window_now erkennst du direkt, ob das beste Preisfenster bereits aktiv ist. Mit next_full_cheapest_window_start planst du das nächste vollständige Zukunftsfenster.

on({ id: ROOT + ".is_cheapest_window_now", change: "any" }, obj => {
  if (obj.state.val === true) {
    log("Bestes Preisfenster läuft jetzt.");
    // Wallbox freigeben oder Auto-Laden starten
  }
});

Pragmatisch gedacht: Für Wallbox, Wärmepumpe oder Speicher ist ein Boolean für "jetzt" robuster als nur eine einzelne Startzeit in die Zukunft zu vergleichen.

Welche States das Skript anlegt

State Bedeutung Typischer Einsatz
0_userdata.0.energypriceforecast.current_priceAktueller Preis in EUR/kWh.Schwellwerte oder Visualisierung.
0_userdata.0.energypriceforecast.current_co2_g_kwhAktuelle CO2-Intensität.CO2-orientierte Automationen.
0_userdata.0.energypriceforecast.cheapest_window_startStartzeit des günstigsten zukünftigen Fensters.Trigger für späteres Laden oder Heizen.
0_userdata.0.energypriceforecast.greenest_window_startStartzeit des CO2-ärmsten zukünftigen Fensters.Ökologisch optimierte Steuerung.
0_userdata.0.energypriceforecast.is_cheapest_window_nowtrue, wenn das beste Preisfenster bereits läuft.Direkte Freigabe-Logik ohne eigene Zeitvergleiche.
0_userdata.0.energypriceforecast.cheapest_window_remaining_minutesRestlaufzeit des aktuell besten Preisfensters.Gerät nur laufen lassen, solange das Fenster aktiv ist.
0_userdata.0.energypriceforecast.next_full_cheapest_window_startStartzeit des nächsten vollständigen Preisfensters in der Zukunft.Planung statt Sofort-Entscheidung.
0_userdata.0.energypriceforecast.api_key_stateStatus des verwendeten API-Keys.Fehlersuche und Prüfung des Key-Status.
0_userdata.0.energypriceforecast.allowed_horizon_hoursServerseitig erlaubter Maximalhorizont.Prüfen, ob ein Key mehr freischaltet.
0_userdata.0.energypriceforecast.used_calls_todayHeute bereits verbrauchte Requests.Limits oder Monitoring im Blick behalten.
0_userdata.0.energypriceforecast.last_errorLetzter abrufbezogener Fehlertext.Fehlersuche ohne direkt ins Adapter-Log zu springen.

Fehlerfälle und Limits

Was bedeutet 401?

Der API-Key fehlt, ist falsch oder wurde deaktiviert. Genau dafür schreibt das Skript den State api_key_state und den Fehlertext mit.

Was bedeutet 403?

Der Request ist technisch korrekt, aber für diesen Zugriff nicht erlaubt. Das betrifft eher Route, Markt oder Vertragszustand als einen simplen Tippfehler.

Was bedeutet 429?

Das Tageslimit ist erreicht. Dafür sind rate_limit_daily und used_calls_today gedacht.

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-/Extrempfad-Signal. Im ioBroker-Endpunkt selbst ist dieses Signal aktuell aber noch kein eigenes stabiles Feld.

Kleine Testphase

Für die neue ioBroker-Anbindung läuft aktuell eine kleine Testphase. Gesucht sind vor allem echte Setups, nicht nur theoretisches Feedback. Wichtig ist dabei: Ist die Einrichtung verständlich, wirken die Werte plausibel und sind Fehlermeldungen ausreichend klar?

Wenn du das in einem echten ioBroker-System testen möchtest, schreib kurz mit deinem Setup, Markt und deinem groben Ziel an StrompreisVorhersage@proton.me. Für die ersten Tester kann die API-Anbindung manuell begleitet werden.

Verwandt: openHAB, Node-RED und Home Assistant.