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.
- eigener ioBroker-Endpunkt mit kompakter Struktur für Skripte
- ein Skript, das Preis, CO2, aktive beste Fenster und Meta-Infos direkt in States schreibt
- optional mit API-Key, z. B. für 120h in der aktuellen Testphase
Wichtige Logik früh erklärt
Die Preislogik ist nicht „nur Forecast“. Sie arbeitet bewusst in zwei Schritten, damit echte Marktdaten bevorzugt werden.
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.
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=40_userdata.0.energypriceforecast ab.api_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=15msummaryEinsteigerpfad mit aktuellem Preis, CO2 und bereits berechneten Fenstern.pricesRohpreisreihe für eigene Skripte, Charts und eigene Automationslogik.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 |
|---|---|---|
flat | Aktueller Preis, aktuelles CO2, Boolean-Felder für aktive beste Fenster und Restlaufzeiten. | Ideal für einfache States. |
price | Aktueller Preis-Slot sowie best_window und next_full_window als Objekte. | Mehr Kontext für eigene Logik. |
co2 | Aktueller CO2-Slot sowie best_window und next_full_window. | Für CO2-orientierte Automationen. |
source | Metadaten zu Day-Ahead und Forecast. | Wichtig zum Debuggen und Einordnen. |
meta | Zugriffs- 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=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, Debugging oder bewusste Forecast-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 zum Gebiet passen. | Pflicht für price_mode=retail in Deutschland. |
resolution=15m | Durchgehende Viertelstundenreihe. Forecast-Stunden werden dafür einfach 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/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.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.
hours legst du den angefragten Horizont fest. Öffentlich belastbar kommunizieren wir für die Preisprognose aktuell maximal 120 Stunden.meta.allowed_horizon_hours.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
- Im ioBroker Admin den JavaScript-Adapter öffnen.
- Ein neues JavaScript-Skript anlegen.
- Den kompletten Block unten einfügen und speichern.
- Einmal manuell starten oder kurz warten. Danach sollten neue States unter
0_userdata.0.energypriceforecastsichtbar sein.
COUNTRY und optional API_KEY.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_price | Aktueller Preis in EUR/kWh. | Schwellwerte oder Visualisierung. |
0_userdata.0.energypriceforecast.current_co2_g_kwh | Aktuelle CO2-Intensität. | CO2-orientierte Automationen. |
0_userdata.0.energypriceforecast.cheapest_window_start | Startzeit des günstigsten zukünftigen Fensters. | Trigger für späteres Laden oder Heizen. |
0_userdata.0.energypriceforecast.greenest_window_start | Startzeit des CO2-ärmsten zukünftigen Fensters. | Ökologisch optimierte Steuerung. |
0_userdata.0.energypriceforecast.is_cheapest_window_now | true, wenn das beste Preisfenster bereits läuft. | Direkte Freigabe-Logik ohne eigene Zeitvergleiche. |
0_userdata.0.energypriceforecast.cheapest_window_remaining_minutes | Restlaufzeit des aktuell besten Preisfensters. | Gerät nur laufen lassen, solange das Fenster aktiv ist. |
0_userdata.0.energypriceforecast.next_full_cheapest_window_start | Startzeit des nächsten vollständigen Preisfensters in der Zukunft. | Planung statt Sofort-Entscheidung. |
0_userdata.0.energypriceforecast.api_key_state | Status des verwendeten API-Keys. | Fehlersuche und Prüfung des Key-Status. |
0_userdata.0.energypriceforecast.allowed_horizon_hours | Serverseitig erlaubter Maximalhorizont. | Prüfen, ob ein Key mehr freischaltet. |
0_userdata.0.energypriceforecast.used_calls_today | Heute bereits verbrauchte Requests. | Limits oder Monitoring im Blick behalten. |
0_userdata.0.energypriceforecast.last_error | Letzter 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.