Stromkosten mit Tibber simulieren
Dynamische Stromtarife sind eine starke Idee: Man übermittelt regelmäßig den eigenen Zählerstand und bekommt dann die Energie zum Börsenkurs (EPEX). Dieser lässt sich für die Zukunft ermitteln und somit kann man seine Verbräuche an den Preis anpassen. Damit verhält man sich automatisch “netzdienlich” und spart unterm Strich auch noch - Win/Win, oder? Ich möchte einmal errechnen, ob ich trotz günstigem Stromtarif mit fixem Preis noch etwas sparen könnte. Dafür importiere ich die Börsenpreise des aktuellen Jahres in meine InfluxDB und kann dann meine Verbräuche dagegen legen.
Video
Daten in InfluxDB schreiben
Über den ioBroker kann ich die Daten relativ einfach mit einem kurzen JavaScript in die InfluxDB importieren:
// v0.1
const axios = require('axios').default;
const influxDbInstance = 'influxdb.0';
const token = 'cIpi....';
const measurement = 'energy-stats';
const startDate = new Date('2023-01-01');
const interval = 7;
const endDate = new Date('2023-10-12');
async function start() {
const influxDbInstanceConfig = await getObjectAsync(`system.adapter.${influxDbInstance}`);
const protocol = influxDbInstanceConfig.native.protocol;
const host = influxDbInstanceConfig.native.host;
const port = influxDbInstanceConfig.native.port;
const org = influxDbInstanceConfig.native.organization;
const bucket = influxDbInstanceConfig.native.dbname;
while (startDate.getTime() < endDate.getTime()) {
const endInterval = new Date(startDate.getTime() - 1);
endInterval.setUTCDate(endInterval.getUTCDate() + interval);
try {
console.log(`Start from ${startDate.toISOString()} to ${endInterval.toISOString()}`);
const result = await axios.get(`https://api.energy-charts.info/price_spot_market?bzn=DE-LU&start=${startDate.toISOString()}&end=${endInterval.toISOString()}`);
const timeStamps = result.data['xAxisValues (Unix timestamp)'];
const values = result.data['Day Ahead Auction (DE-LU) (EUR/MWh)'];
const data = [];
for (let i = 0; i < timeStamps.length; i++) {
if (!isNaN(values[i])) {
// console.log(`Found ${new Date(timeStamps[i] * 1000).toISOString()}: ${values[i] / 1000} €/kWh`);
data.push(`${measurement} priceInEpex=${(values[i] / 1000).toFixed(4)} ${timeStamps[i]}000000000`);
}
}
await axios.post(`${protocol}://${host}:${port}/api/v2/write?bucket=${bucket}&org=${org}`, data.join('\n'), {
headers: {
'Content-Type': 'text/plain',
'Authorization': `Token ${token}`
}
});
} catch (err) {
console.error(err);
}
startDate.setUTCDate(startDate.getUTCDate() + interval);
}
console.log('Finished');
}
start();
Dashboard
from(bucket: "smarthome")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r._measurement == "energy-stats")
|> filter(fn: (r) => r._field == "importedWh" or r._field == "priceIn" or r._field == "priceInEpex")
|> aggregateWindow(every: 1h, fn: last, createEmpty: true, timeSrc: "_start")
|> pivot(rowKey: ["_time"], columnKey: ["_field"], valueColumn: "_value")
|> difference(columns: ["importedWh"])
|> map(fn: (r) => ({r with _priceInTotal: r.importedWh / 1000.0 * r.priceIn}))
|> map(fn: (r) => ({r with _priceInTotalEpex: (r.importedWh / 1000.0 * r.priceInEpex * 1.19) + (r.importedWh / 1000.0 * 0.1751)}))
|> drop(columns: ["importedWh", "priceIn", "priceInEpex"])
Task
Das errechnen der Daten auf den einzelnen Zeilen und Millionen Einträgen dauert ggf. extrem lange. Also werden auch diese Werte mit einem Task heruntergebrochen. Wie genau das geht, habe ich in einem anderen Beitrag schon ausführlich erklärt.
Einmalig Daten errechnen:
import "date"
fullHourTime = date.truncate(t: now(), unit: 1h)
from(bucket: "smarthome")
|> range(start: 2023-01-01T00:00:00.000Z, stop: fullHourTime)
|> filter(fn: (r) => r._field == "importedWh" or r._field == "priceInEpex")
|> aggregateWindow(every: 1h, fn: last, createEmpty: true, timeSrc: "_start")
|> pivot(rowKey: ["_time"], columnKey: ["_field"], valueColumn: "_value")
|> filter(fn: (r) => r.importedWh > 0)
|> difference(columns: ["importedWh"])
|> map(fn: (r) => ({r with _value: r.importedWh / 1000.0 * r.priceInEpex}))
|> to(
bucket: "smarthome-history",
fieldFn: (r) => ({ "importedEuroEpex": r._value })
)
Task für regelmäßige Ausführung (jeden Tag 1d
):
import "date"
fullHourTime = date.truncate(t: now(), unit: 1h)
startTime = date.sub(from: fullHourTime, d: 1d)
from(bucket: "smarthome")
|> range(start: startTime, stop: fullHourTime)
|> filter(fn: (r) => r._measurement == "energy-stats")
|> filter(fn: (r) => r._field == "importedWh" or r._field == "priceInEpex")
|> aggregateWindow(every: 1h, fn: last, createEmpty: true, timeSrc: "_start")
|> pivot(rowKey: ["_time"], columnKey: ["_field"], valueColumn: "_value")
|> filter(fn: (r) => r.importedWh > 0)
|> difference(columns: ["importedWh"])
|> map(fn: (r) => ({r with _value: r.importedWh / 1000.0 * r.priceInEpex}))
|> to(
bucket: "smarthome-history",
fieldFn: (r) => ({ "importedEuroEpex": r._value })
)
Damit kann dann recht einfach eine Tabelle erstellt werden:
import "timezone"
import "date"
option location = timezone.location(name: "Europe/Berlin")
from(bucket: "smarthome-history")
|> range(start: date.truncate(t: today(), unit: 1y))
|> filter(fn: (r) => r._measurement == "energy-stats")
|> filter(fn: (r) => r._field == "importedWh" or r._field == "importedEuro" or r._field == "importedEuroEpex")
|> aggregateWindow(every: 1mo, fn: sum, createEmpty: true, timeSrc: "_start")
|> pivot(rowKey: ["_time"], columnKey: ["_field"], valueColumn: "_value")
|> map(fn: (r) => ({r with importedEuroEpex: (r.importedEuroEpex * 1.19) + (r.importedWh / 1000.0 * 0.1751)}))
|> sort(columns: ["_time"])