NEWS
Ostrom Api auslesen
-
Hallo,
da ich nirgends was passendes gefunden habe bin ich gerade dabei, mir ein Skript zum Auslesen der Verbrauchsdaten von meinem dynamischen Tarif meines Stromanbieters Ostrom abzufragen. Das ganze ist noch eine sehr frühe Version und ich arbeite noch dran, vermutlich werde ich das skript hier auch noch aktualisieren.
Was aktuell geht:
- Monatliche Verbräche abfragen des aktuellen Jahres
- Tägliche Verbrauchsdaten des aktuellen Monats
nur eine kleiner Schritt den ich noch gehen möchte: Stündliche Verbrauchsdaten des aktuellen Tages (bzw des gestrigen, der heutige geht noch nicht)
Bin gespannt ob das hier noch jemanden interessiert, kann dann auch gern bei der Einrichtung helfen, auch wenn es mein erstes Skript mit Javaskript ist (ja ich hatte Hilfe von CHatGPT um mir ein Grundgerüst hinzustellen), verwende sonst eher Blockly oder außerhalb von iobroker Python.
Bin auch gespannt auf Verbesserungsvorschlägeconst axios = require('axios'); const clientId = getState('javascript.0.ostrom.client_id').val; const clientSecret = getState('javascript.0.ostrom.client_secret').val; const contractNumber = getState('javascript.0.ostrom.contract_number').val; async function getToken() { try { const authHeader = 'Basic ' + Buffer.from(`${clientId}:${clientSecret}`).toString('base64'); const response = await axios.post( 'https://auth.production.ostrom-api.io/oauth2/token', 'grant_type=client_credentials', { headers: { 'Authorization': authHeader, 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json' } } ); return response.data.access_token; } catch (err) { console.error('❌ Fehler bei getToken: ' + (err.response ? err.response.status + ' ' + JSON.stringify(err.response.data) : err.message)); return null; } } async function getConsumption(token) { try { const response = await axios.get(`https://api.ostrom.de/contracts/${contractNumber}/consumption`, { headers: { Authorization: `Bearer ${token}` } }); return response.data; } catch (err) { console.error('❌ Fehler bei getConsumption:'); return []; } } async function updateConsumption_monthly() { try { const token = await getToken(); if (!token) { console.error('❌ Zugriffstoken konnte nicht abgerufen werden'); return; } const contractId = contractNumber const startDate = new Date(Date.UTC(new Date().getFullYear(), 0, 1)).toISOString(); const endDate = new Date(Date.UTC(new Date().getFullYear(), new Date().getMonth() + 1, 0)).toISOString(); const response = await axios.get( `https://production.ostrom-api.io/contracts/${contractId}/energy-consumption`, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' }, params: { startDate, endDate, resolution: 'MONTH' } } ); const consumptionData = response.data.data; if (!consumptionData || consumptionData.length === 0) { console.warn('⚠️ Keine Verbrauchsdaten verfügbar'); return; } for (const item of consumptionData) { const date = new Date(item.date); const year = date.getFullYear(); const month = (date.getMonth() + 1).toString().padStart(2, '0'); const key = `javascript.0.ostrom.consumption_monthly.${year}_${month}`; const value = item.kWh || 0; await createStateAsync(key, value, { type: 'number', read: true, write: false }); await setStateAsync(key, value, true); } console.log('✅ monthly Verbrauchsdaten erfolgreich aktualisiert'); } catch (err) { console.error('❌ Fehler bei updateConsumption:' +(err.response ? err.response.status + ' ' + JSON.stringify(err.response.data) : err.message)); } } async function updateConsumption_daily() { try { const token = await getToken(); if (!token) { console.error('❌ Zugriffstoken konnte nicht abgerufen werden'); return; } const contractId = contractNumber const jetzt = new Date(); //const startDate = new Date(Date.UTC(jetzt.getUTCFullYear(), jetzt.getUTCMonth(), jetzt.getUTCDate())); //const endDate = new Date(Date.UTC(jetzt.getUTCFullYear(), jetzt.getUTCMonth(), jetzt.getUTCDate()+1)); const startDate = new Date(Date.UTC(new Date().getFullYear(), new Date().getMonth(), 1)).toISOString(); const endDate = new Date(Date.UTC(new Date().getFullYear(), new Date().getMonth(), jetzt.getUTCDate())).toISOString(); const response = await axios.get( `https://production.ostrom-api.io/contracts/${contractId}/energy-consumption`, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' }, params: { startDate, endDate, resolution: 'DAY' } } ); const consumptionData = response.data.data; if (!consumptionData || consumptionData.length === 0) { console.warn('⚠️ Keine Verbrauchsdaten verfügbar'); return; } for (const item of consumptionData) { const date = new Date(item.date); const year = date.getFullYear(); const month = (date.getMonth() + 1).toString().padStart(2, '0'); const day = date.getDate().toString().padStart(2, '0'); const key = `javascript.0.ostrom.consumption_daily.${year}.${month}.${day}`; const value = item.kWh || 0; await createStateAsync(key, value, { type: 'number', read: true, write: false }); await setStateAsync(key, value, true); } console.log('✅ daily Verbrauchsdaten erfolgreich aktualisiert'); } catch (err) { console.error('❌ Fehler bei updateConsumption:' +(err.response ? err.response.status + ' ' + JSON.stringify(err.response.data) : err.message)); } } // Hilfsfunktion für async createState function createStateAsync(id, value, options) { return new Promise(resolve => { if (!existsState(id)) { createState(id, value, options, () => resolve()); } else { resolve(); } }); } updateConsumption_monthly(); updateConsumption_daily(); // Manueller Trigger on({ id: 'javascript.0.ostrom.update', val: true, ack: false }, async () => { await updateConsumption_monthly(); await updateConsumption_daily(); // Reset Trigger setState('javascript.0.ostrom.update', false, true); }); //Monthly täglich updaten setInterval(updateConsumption_monthly, 24 * 60 * 60 * 1000); setInterval(updateConsumption_daily, 24 * 60 * 60 * 1000); -
ich habe weiter ein anderes:
const axios = require('axios'); // ======= OSTROM PROD ======= const CLIENT_ID = 'DEINE_ID'; const CLIENT_SECRET = 'DEIN_SECRET'; const ZIP = '59759'; // ======= INFLUXDB 2.x ======= const INFLUX_URL = 'http://192.168.178.103:8086'; const INFLUX_ORG = 'my_org'; const INFLUX_BUCKET = 'iobroker'; const INFLUX_TOKEN = 'DEIN_TOKEN'; // ======= IO BROKER ======= const BASE_DP = '0_userdata.0.ostrom'; const AUTH_URL = 'https://auth.production.ostrom-api.io/oauth2/token'; const API_URL = 'https://production.ostrom-api.io'; let isRunning = false; createStates(); // API nur selten abrufen schedule('30 14 * * *', fetchPrices); // Status stündlich aktualisieren schedule('0 * * * *', updateCurrentFromCache); // beim Start nur Status aus Cache aktualisieren setTimeout(updateCurrentFromCache, 10000); // manueller API-Abruf on({ id: `${BASE_DP}.control.runNow`, val: true }, async () => { await setStateAsync(`${BASE_DP}.control.runNow`, false, true); await fetchPrices(); }); async function fetchPrices() { if (isRunning) return; const lastFetch = getState(`${BASE_DP}.meta.lastFetch`)?.val || 0; const now = Date.now(); if (now - lastFetch < 120000) { log('Ostrom: Letzte API-Abfrage < 2 Minuten – überspringe', 'warn'); return; } isRunning = true; try { const token = await getAccessToken(); const prices = await getPrices(token); const sorted = prices .slice() .sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); await setStateAsync(`${BASE_DP}.todayTomorrow.json`, JSON.stringify(sorted), true); await updateCurrentFromPrices(sorted); await updateForecast(sorted); await writeToInflux(sorted); await setStateAsync(`${BASE_DP}.meta.lastFetch`, now, true); await setStateAsync(`${BASE_DP}.meta.lastFetchReadable`, new Date(now).toLocaleString('de-DE'), true); log(`Ostrom OK: ${sorted.length} Preise`, 'info'); } catch (err) { if (err.response?.status === 429) { log('Ostrom 429: Rate Limit – später erneut versuchen', 'warn'); } else { log(`Ostrom Fehler: ${err.message}`, 'error'); } if (err.response) { log(`Status: ${err.response.status}`, 'error'); log(JSON.stringify(err.response.data), 'error'); } } finally { isRunning = false; } } async function updateCurrentFromCache() { const json = getState(`${BASE_DP}.todayTomorrow.json`)?.val; if (!json) { log('Ostrom: Kein Preis-Cache vorhanden', 'warn'); return; } try { const prices = JSON.parse(json); await updateCurrentFromPrices(prices); log('Ostrom current.isFree stündlich aktualisiert', 'info'); } catch (err) { log(`Ostrom Cache Fehler: ${err.message}`, 'error'); } } async function updateCurrentFromPrices(prices) { const current = findCurrentPrice(prices); if (!current) return; const grossKwhPrice = Number(current.grossKwhPrice ?? 0); const grossKwhTaxAndLevies = Number(current.grossKwhTaxAndLevies ?? 0); // echter variabler Bruttopreis ohne monatliche Kosten const effectiveGrossKwhPrice = grossKwhPrice + grossKwhTaxAndLevies; await setStateAsync(`${BASE_DP}.current.grossKwhPrice`, round(grossKwhPrice), true); await setStateAsync(`${BASE_DP}.current.grossKwhTaxAndLevies`, round(grossKwhTaxAndLevies), true); await setStateAsync(`${BASE_DP}.current.effectiveGrossKwhPrice`, round(effectiveGrossKwhPrice), true); await setStateAsync(`${BASE_DP}.current.date`, current.date, true); await setStateAsync(`${BASE_DP}.current.dateReadable`, new Date(current.date).toLocaleString('de-DE'), true); await setStateAsync(`${BASE_DP}.current.hour`, new Date(current.date).getHours(), true); await setStateAsync(`${BASE_DP}.current.isFree`, effectiveGrossKwhPrice <= 0, true); } async function updateForecast(prices) { const effectivePrices = prices.map(p => { const gross = Number(p.grossKwhPrice ?? 0); const tax = Number(p.grossKwhTaxAndLevies ?? 0); return { date: p.date, value: gross + tax }; }); const values = effectivePrices.map(p => p.value); const min = Math.min(...values); const max = Math.max(...values); const avg = values.reduce((a, b) => a + b, 0) / values.length; const cheapest = effectivePrices.find(p => p.value === min); const expensive = effectivePrices.find(p => p.value === max); const nextFree = effectivePrices.find(p => new Date(p.date).getTime() >= Date.now() && p.value <= 0); await setStateAsync(`${BASE_DP}.forecast.minEffectiveGrossKwhPrice`, round(min), true); await setStateAsync(`${BASE_DP}.forecast.maxEffectiveGrossKwhPrice`, round(max), true); await setStateAsync(`${BASE_DP}.forecast.avgEffectiveGrossKwhPrice`, round(avg), true); await setStateAsync(`${BASE_DP}.forecast.cheapestDate`, cheapest?.date || '', true); await setStateAsync(`${BASE_DP}.forecast.cheapestDateReadable`, cheapest ? new Date(cheapest.date).toLocaleString('de-DE') : '', true); await setStateAsync(`${BASE_DP}.forecast.mostExpensiveDate`, expensive?.date || '', true); await setStateAsync(`${BASE_DP}.forecast.mostExpensiveDateReadable`, expensive ? new Date(expensive.date).toLocaleString('de-DE') : '', true); await setStateAsync(`${BASE_DP}.forecast.nextFreeDate`, nextFree?.date || '', true); await setStateAsync(`${BASE_DP}.forecast.nextFreeDateReadable`, nextFree ? new Date(nextFree.date).toLocaleString('de-DE') : '', true); await setStateAsync(`${BASE_DP}.forecast.hasFreeHour`, !!nextFree, true); } async function getAccessToken() { const auth = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64'); const res = await axios.post( AUTH_URL, 'grant_type=client_credentials', { headers: { Authorization: `Basic ${auth}`, 'Content-Type': 'application/x-www-form-urlencoded' }, timeout: 15000 } ); return res.data.access_token; } async function getPrices(token) { const now = new Date(); const start = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0); const end = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 2, 0, 0, 0); const res = await axios.get(`${API_URL}/spot-prices`, { params: { startDate: start.toISOString(), endDate: end.toISOString(), resolution: 'HOUR', zip: ZIP }, headers: { Authorization: `Bearer ${token}` }, timeout: 15000 }); return res.data.data || []; } async function writeToInflux(prices) { if (!prices.length) return; const lines = []; for (const p of prices) { const ts = BigInt(new Date(p.date).getTime()) * 1000000n; const grossKwhPrice = Number(p.grossKwhPrice ?? 0); const grossKwhTaxAndLevies = Number(p.grossKwhTaxAndLevies ?? 0); const effectiveGrossKwhPrice = grossKwhPrice + grossKwhTaxAndLevies; lines.push( `ostrom_prices,source=production,zip=${escapeTag(ZIP)} ` + `grossKwhPrice=${grossKwhPrice},` + `grossKwhTaxAndLevies=${grossKwhTaxAndLevies},` + `effectiveGrossKwhPrice=${effectiveGrossKwhPrice} ` + `${ts.toString()}` ); } const url = `${INFLUX_URL}/api/v2/write` + `?org=${encodeURIComponent(INFLUX_ORG)}` + `&bucket=${encodeURIComponent(INFLUX_BUCKET)}` + `&precision=ns`; await axios.post(url, lines.join('\n'), { headers: { Authorization: `Token ${INFLUX_TOKEN}`, 'Content-Type': 'text/plain' }, timeout: 15000 }); } function findCurrentPrice(prices) { const now = new Date(); return prices.find(p => { const start = new Date(p.date); const end = new Date(start.getTime() + 3600000); return now >= start && now < end; }); } async function createStates() { await ensure(`${BASE_DP}.control.runNow`, 'boolean', true, true, 'button'); await ensure(`${BASE_DP}.meta.lastFetch`, 'number', 0, true, 'value'); await ensure(`${BASE_DP}.meta.lastFetchReadable`, 'string', '', true, 'text'); await ensure(`${BASE_DP}.todayTomorrow.json`, 'string', '', true, 'json'); await ensure(`${BASE_DP}.current.grossKwhPrice`, 'number', 0, true, 'value', 'ct/kWh'); await ensure(`${BASE_DP}.current.grossKwhTaxAndLevies`, 'number', 0, true, 'value', 'ct/kWh'); await ensure(`${BASE_DP}.current.effectiveGrossKwhPrice`, 'number', 0, true, 'value', 'ct/kWh'); await ensure(`${BASE_DP}.current.date`, 'string', '', true, 'text'); await ensure(`${BASE_DP}.current.dateReadable`, 'string', '', true, 'text'); await ensure(`${BASE_DP}.current.hour`, 'number', 0, true, 'value', 'h'); await ensure(`${BASE_DP}.current.isFree`, 'boolean', false, true, 'indicator'); await ensure(`${BASE_DP}.forecast.minEffectiveGrossKwhPrice`, 'number', 0, true, 'value', 'ct/kWh'); await ensure(`${BASE_DP}.forecast.maxEffectiveGrossKwhPrice`, 'number', 0, true, 'value', 'ct/kWh'); await ensure(`${BASE_DP}.forecast.avgEffectiveGrossKwhPrice`, 'number', 0, true, 'value', 'ct/kWh'); await ensure(`${BASE_DP}.forecast.cheapestDate`, 'string', '', true, 'text'); await ensure(`${BASE_DP}.forecast.cheapestDateReadable`, 'string', '', true, 'text'); await ensure(`${BASE_DP}.forecast.mostExpensiveDate`, 'string', '', true, 'text'); await ensure(`${BASE_DP}.forecast.mostExpensiveDateReadable`, 'string', '', true, 'text'); await ensure(`${BASE_DP}.forecast.nextFreeDate`, 'string', '', true, 'text'); await ensure(`${BASE_DP}.forecast.nextFreeDateReadable`, 'string', '', true, 'text'); await ensure(`${BASE_DP}.forecast.hasFreeHour`, 'boolean', false, true, 'indicator'); } async function ensure(id, type, def, write, role, unit = '') { const exists = await existsStateAsync(id); if (!exists) { await createStateAsync(id, def, { type, role, read: true, write, unit }); } } function round(value) { return Math.round(value * 1000) / 1000; } function escapeTag(value) { return String(value) .replace(/ /g, '\\ ') .replace(/,/g, '\\,') .replace(/=/g, '\\='); }
Hey! Du scheinst an dieser Unterhaltung interessiert zu sein, hast aber noch kein Konto.
Hast du es satt, bei jedem Besuch durch die gleichen Beiträge zu scrollen? Wenn du dich für ein Konto anmeldest, kommst du immer genau dorthin zurück, wo du zuvor warst, und kannst dich über neue Antworten benachrichtigen lassen (entweder per E-Mail oder Push-Benachrichtigung). Du kannst auch Lesezeichen speichern und Beiträge positiv bewerten, um anderen Community-Mitgliedern deine Wertschätzung zu zeigen.
Mit deinem Input könnte dieser Beitrag noch besser werden 💗
Registrieren Anmelden
