Omdat het te groot is om te posten in het Homey-forum, deel ik het hier 
Dit jaar heeft Power By The Hour me al meerdere malen in de steek gelaten en geen energieprijzen opgehaald. Er is geen mogelijkheid om dit te forceren, met als gevolg dat alle prijsgestuurde flows (warmtepomp, EV, etc) niet meer werken. Recent heeft Athom zelf ook het een en ander toegevoegd, maar dit is niet uitgebreid en flexibel genoeg. Daar komt nog bij dat de energieprijzen om 13:00 al beschikbaar zijn, en met name PbtH vaak pas na 15:00 de prijzen heeft, waardoor de auto nog 20 kWh heeft geladen terwijl de prijzen de dag erna flink lager waren. Dus... tijd om het zelf te doen. Of ja, met behulp van ChatGPT.
Allereerst heb ik een tekstuele variabele aangemaakt waar ik de JSON opsla. Een voor vandaag en een voor morgen. Met dit script haal je het ID van de variabele op. Die heb je later nodig.
De JSON bevat tegenwoordig kwartierwaarden, maar Zonneplan werkt met uurwaarden. Dit is letterlijk het gemiddelde van de betreffende vier uurwaarden. Ook wil ik de data van UTC tijden naar NL-tijd. Dus uur 0 = tijdvak 00:00-01:00.
Elke middag wordt om 13:05 dit script gedraaid, met nog enkele checks later op de dag of er data is.
Elke nacht om 0:00 wordt de JSON van morgen naar de JSON van vandaag gezet, en die van morgen leeggemaakt.
Oké, nu heeft Homey alle prijzen, opgeslagen in variabelen met ID's. Ik heb een advanced virtual device aangemaakt met de waarden die ik wil hebben. Die heb ik nodig zodat het laatste script alles erin op kan slaan. Maar eerst hebben we weer het ID nodig van het AVD, en de nummers van de waarden die je gebruikt. Het ID is het resultaat wat je in onderstaand script invoert in targetDeviceId. Ga naar https://tools.developer.homey.app/tools/devices en zoek naar je AVD.
/f/image/2817QaEY21cOW1vNJlE080G1.png?f=fotoalbum_large)
Hierin zie je je ID, en tevens de namen van de nummers die je gebruikt. Tot slot draai je onderstaand script elk uur:
En dit is het resultaat!
Dit jaar heeft Power By The Hour me al meerdere malen in de steek gelaten en geen energieprijzen opgehaald. Er is geen mogelijkheid om dit te forceren, met als gevolg dat alle prijsgestuurde flows (warmtepomp, EV, etc) niet meer werken. Recent heeft Athom zelf ook het een en ander toegevoegd, maar dit is niet uitgebreid en flexibel genoeg. Daar komt nog bij dat de energieprijzen om 13:00 al beschikbaar zijn, en met name PbtH vaak pas na 15:00 de prijzen heeft, waardoor de auto nog 20 kWh heeft geladen terwijl de prijzen de dag erna flink lager waren. Dus... tijd om het zelf te doen. Of ja, met behulp van ChatGPT.
Allereerst heb ik een tekstuele variabele aangemaakt waar ik de JSON opsla. Een voor vandaag en een voor morgen. Met dit script haal je het ID van de variabele op. Die heb je later nodig.
code:
1
2
3
| let variables = await Homey.logic.getVariables(); //use this to get the ID of any Logic Variable
console.log('Variables:', variables); //use this to get the ID of any Logic Variable
return variables; //use this to get the ID of any Logic Variable |
De JSON bevat tegenwoordig kwartierwaarden, maar Zonneplan werkt met uurwaarden. Dit is letterlijk het gemiddelde van de betreffende vier uurwaarden. Ook wil ik de data van UTC tijden naar NL-tijd. Dus uur 0 = tijdvak 00:00-01:00.
Elke middag wordt om 13:05 dit script gedraaid, met nog enkele checks later op de dag of er data is.
Elke nacht om 0:00 wordt de JSON van morgen naar de JSON van vandaag gezet, en die van morgen leeggemaakt.
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
| // ===== Config =====
const DELIVERY_AREA = "NL";
const CURRENCY = "EUR";
const BTW_FACTOR = 1.21;
const OPSLAG = 0.14278;
// ==================
// Datum van morgen in lokale tijd
let now = new Date();
now.setDate(now.getDate() + 1);
now.setHours(0, 0, 0, 0);
let yyyy = now.getFullYear();
let mm = String(now.getMonth() + 1).padStart(2, '0');
let dd = String(now.getDate()).padStart(2, '0');
let tomorrowStr = `${yyyy}-${mm}-${dd}`;
// API ophalen
let url = `https://dataportal-api.nordpoolgroup.com/api/DayAheadPrices?date=${tomorrowStr}&market=DayAhead&deliveryArea=${DELIVERY_AREA}¤cy=${CURRENCY}`;
let response = await fetch(url);
if (!response.ok) throw new Error(`HTTP-fout ${response.status}`);
// Controle op lege of ongeldige JSON
let text = await response.text();
if (!text || text.trim().length === 0) {
throw new Error("Lege respons van Nordpool – prijzen nog niet beschikbaar");
}
let data;
try {
data = JSON.parse(text);
} catch {
throw new Error("Kon JSON niet parsen – waarschijnlijk onvolledige data");
}
if (!data.multiAreaEntries) {
throw new Error("Ongeldige API-structuur – prijzen nog niet gepubliceerd");
}
// === Controle of data wel echt voor morgen is ===
let datesInData = data.multiAreaEntries.map(e => e.deliveryStart.split("T")[0]);
let uniqueDates = [...new Set(datesInData)];
if (!uniqueDates.includes(tomorrowStr)) {
// Data bevat niet de verwachte datum
await Homey.logic.updateVariable({
id: "8eaf420a-2749-41ec-8be3-d1ea789ef2df",
variable: { value: "error" }
});
return "error";
}
// Filter alle entries voor NL
let entries = data.multiAreaEntries.filter(e => e.entryPerArea[DELIVERY_AREA] !== undefined);
// Groeperen per lokale uur
let hourlyPrices = Array.from({ length: 24 }, () => []);
entries.forEach(e => {
let start = new Date(e.deliveryStart);
let localHour = start.toLocaleString("nl-NL", {
hour: "2-digit",
hour12: false,
timeZone: "Europe/Amsterdam"
});
localHour = parseInt(localHour, 10);
if (localHour >= 0 && localHour < 24) {
hourlyPrices[localHour].push(e.entryPerArea[DELIVERY_AREA] / 1000); // €/MWh → €/kWh
}
});
// Gemiddelde per uur berekenen en all-in prijs
let hourData = hourlyPrices.map((hourArray, index) => {
if (hourArray.length === 0) return { hour: index, price: null };
let avg = hourArray.reduce((sum, p) => sum + p, 0) / hourArray.length;
return { hour: index, price: Number(((avg * BTW_FACTOR) + OPSLAG).toFixed(4)) };
});
// Datum toevoegen aan elk uur
let hourDataWithDate = hourData.map(h => ({
date: tomorrowStr,
hour: h.hour,
price: h.price
}));
// Homey variabele bijwerken
await Homey.logic.updateVariable({
id: "8eaf420a-2749-41ec-8be3-d1ea789ef2df",
variable: { value: JSON.stringify(hourDataWithDate) }
});
return hourDataWithDate; |
Oké, nu heeft Homey alle prijzen, opgeslagen in variabelen met ID's. Ik heb een advanced virtual device aangemaakt met de waarden die ik wil hebben. Die heb ik nodig zodat het laatste script alles erin op kan slaan. Maar eerst hebben we weer het ID nodig van het AVD, en de nummers van de waarden die je gebruikt. Het ID is het resultaat wat je in onderstaand script invoert in targetDeviceId. Ga naar https://tools.developer.homey.app/tools/devices en zoek naar je AVD.
/f/image/2817QaEY21cOW1vNJlE080G1.png?f=fotoalbum_large)
Hierin zie je je ID, en tevens de namen van de nummers die je gebruikt. Tot slot draai je onderstaand script elk uur:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
| /**
* stroomprijzen_fixed.js
* Zelfde functionaliteit als eerder, maar fixed: gebruik lokaal uur voor huidige match
*/
const vandaagId = 'ebeb6976-a47a-4086-bd40-25d9df2a2306';
const morgenId = '8eaf420a-2749-41ec-8be3-d1ea789ef2df';
const targetDeviceId = '465e6417-b002-4246-bc7f-0760faeb2b93';
const TZ = 'Europe/Amsterdam';
const capabilities = {
vandaag: {
actuelePrijs: 'measure_devicecapabilities_number-custom_12.number1',
laagstePrijs: 'measure_devicecapabilities_number-custom_11.number2',
laagsteUur: 'measure_devicecapabilities_number-custom_61.number3',
gemiddelde: 'measure_devicecapabilities_number-custom_12.number4',
plus1: 'measure_devicecapabilities_number-custom_12.number8',
plus2: 'measure_devicecapabilities_number-custom_12.number9',
plus3: 'measure_devicecapabilities_number-custom_12.number10',
plus4: 'measure_devicecapabilities_number-custom_12.number11',
plus5: 'measure_devicecapabilities_number-custom_12.number12',
plus6: 'measure_devicecapabilities_number-custom_12.number13',
plus7: 'measure_devicecapabilities_number-custom_12.number14',
plus8: 'measure_devicecapabilities_number-custom_12.number15',
gem8: 'measure_devicecapabilities_number-custom_12.number16',
laag8: 'measure_devicecapabilities_number-custom_11.number17',
laag24: 'measure_devicecapabilities_number-custom_11.number18',
gem24: 'measure_devicecapabilities_number-custom_12.number19',
laagToekomst: 'measure_devicecapabilities_number-custom_11.number20',
gemToekomst: 'measure_devicecapabilities_number-custom_12.number21',
},
morgen: {
laagstePrijs: 'measure_devicecapabilities_number-custom_11.number5',
laagsteUur: 'measure_devicecapabilities_number-custom_61.number6',
gemiddelde: 'measure_devicecapabilities_number-custom_12.number7',
},
};
// HELPERS
function getDateHourInTZ(date = new Date(), timeZone = TZ) {
const fmt = new Intl.DateTimeFormat('en-CA', {
timeZone,
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', hour12: false,
});
const parts = fmt.formatToParts(date);
const get = t => parts.find(p => p.type === t)?.value;
return { localDate: `${get('year')}-${get('month')}-${get('day')}`, localHour: Number(get('hour')) };
}
function round3(v) { return Math.round(Number(v) * 1000) / 1000; }
function safeStats(values) {
const nums = (values || []).map(Number).filter(v => Number.isFinite(v));
if (!nums.length) return { min: 999, avg: 999 };
const min = Math.min(...nums);
const avg = nums.reduce((a, b) => a + b, 0) / nums.length;
return { min, avg };
}
function parsePrices(raw) {
if (!Array.isArray(raw)) return [];
return raw
.map(p => ({ date: String(p.date), hour: Number(p.hour), price: Number(p.price) }))
.filter(p => Number.isFinite(p.hour) && Number.isFinite(p.price));
}
function sortPrices(prices) {
return prices.slice().sort((a, b) => {
if (a.date !== b.date) return a.date < b.date ? -1 : 1;
return a.hour - b.hour;
});
}
// === main ===
const now = new Date();
const { localDate, localHour } = getDateHourInTZ(now, TZ);
// Load vandaag
let vandaagVar;
try {
vandaagVar = await Homey.logic.getVariable({ id: vandaagId });
} catch (e) {
return `❌ ERROR: Kon Logic-variabele vandaag niet ophalen: ${e.message || e}`;
}
if (!vandaagVar?.value) return `❌ ERROR: Logic-variabele vandaag heeft geen waarde`;
let vandaagRaw;
try {
vandaagRaw = typeof vandaagVar.value === 'string' ? JSON.parse(vandaagVar.value) : vandaagVar.value;
} catch (e) {
return `❌ ERROR: Kon JSON van vandaag niet parsen: ${e.message || e}`;
}
let vandaagPrices = parsePrices(vandaagRaw);
if (!vandaagPrices.length) return `❌ ERROR: Vandaag-JSON bevat geen bruikbare entries`;
// Load morgen (optioneel)
let morgenPrices = [];
try {
const morgenVar = await Homey.logic.getVariable({ id: morgenId });
if (morgenVar?.value) {
const morgenRaw = typeof morgenVar.value === 'string' ? JSON.parse(morgenVar.value) : morgenVar.value;
morgenPrices = parsePrices(morgenRaw);
}
} catch (e) {
console.log(`[WARN] Morgenprijzen konden niet geladen worden: ${e.message || e}`);
}
// Combineer en sorteer
let combinedPrices = sortPrices([...vandaagPrices, ...morgenPrices]);
// Bepaal index voor 'nu' — gebruik LOKAAL uur en prefer today's date when possible
let idxNow = combinedPrices.findIndex(p => p.date === localDate && p.hour === localHour);
if (idxNow === -1) {
// probeer volgend uur op dezelfde datum
idxNow = combinedPrices.findIndex(p => p.date === localDate && p.hour >= localHour);
}
if (idxNow === -1) {
// fallback: eerste toekomstige entry (eerste met datum >= vandaag)
idxNow = combinedPrices.findIndex(p => {
return p.date > localDate || (p.date === localDate && p.hour >= localHour);
});
}
if (idxNow === -1) {
// laatste fallback: eerste entry
idxNow = 0;
console.log(`[WARN] Kon geen passende 'nu' entry vinden; fallback naar index 0`);
}
// Huidige en +1..+8 prijzen
const getPriceAt = (i) => combinedPrices[idxNow + i]?.price ?? 999;
const currentPrice = getPriceAt(0);
const prijsPlus = Array.from({ length: 8 }, (_, i) => getPriceAt(i + 1));
// Statistieken komende N uren (neem alleen bestaande entries)
function statsNext(n) {
const slice = combinedPrices.slice(idxNow, idxNow + n).map(p => p.price);
return safeStats(slice);
}
const stat8 = statsNext(8);
const stat24 = statsNext(24);
const statFuture = safeStats(combinedPrices.slice(idxNow).map(p => p.price));
// Vandaag statistieken (gewoon op vandaagPrices)
const statVandaag = safeStats(vandaagPrices.map(p => p.price));
const recLaagsteVandaag = vandaagPrices.find(p => p.price === statVandaag.min);
const laagsteUurVandaagLocal = (recLaagsteVandaag?.hour ?? 999);
// Bereid capability updates
const updates = [
{ id: capabilities.vandaag.actuelePrijs, value: round3(currentPrice), label: 'Actuele prijs' },
{ id: capabilities.vandaag.plus1, value: round3(prijsPlus[0]), label: '+1u' },
{ id: capabilities.vandaag.plus2, value: round3(prijsPlus[1]), label: '+2u' },
{ id: capabilities.vandaag.plus3, value: round3(prijsPlus[2]), label: '+3u' },
{ id: capabilities.vandaag.plus4, value: round3(prijsPlus[3]), label: '+4u' },
{ id: capabilities.vandaag.plus5, value: round3(prijsPlus[4]), label: '+5u' },
{ id: capabilities.vandaag.plus6, value: round3(prijsPlus[5]), label: '+6u' },
{ id: capabilities.vandaag.plus7, value: round3(prijsPlus[6]), label: '+7u' },
{ id: capabilities.vandaag.plus8, value: round3(prijsPlus[7]), label: '+8u' },
{ id: capabilities.vandaag.gem8, value: round3(stat8.avg), label: 'Gemiddelde komende 8u' },
{ id: capabilities.vandaag.laag8, value: round3(stat8.min), label: 'Laagste komende 8u' },
{ id: capabilities.vandaag.gem24, value: round3(stat24.avg), label: 'Gemiddelde komende 24u' },
{ id: capabilities.vandaag.laag24, value: round3(stat24.min), label: 'Laagste komende 24u' },
{ id: capabilities.vandaag.gemToekomst, value: round3(statFuture.avg), label: 'Gemiddelde toekomst' },
{ id: capabilities.vandaag.laagToekomst, value: round3(statFuture.min), label: 'Laagste toekomst' },
{ id: capabilities.vandaag.laagstePrijs, value: round3(statVandaag.min), label: 'Laagste prijs vandaag' },
{ id: capabilities.vandaag.laagsteUur, value: round3(laagsteUurVandaagLocal), label: 'Laagste uur vandaag (lokaal)' },
{ id: capabilities.vandaag.gemiddelde, value: round3(statVandaag.avg), label: 'Gemiddelde vandaag' },
// Morgen (zoals voorheen)
{ id: capabilities.morgen.laagstePrijs, value: round3(safeStats(morgenPrices.map(p => p.price)).min), label: 'Laagste prijs morgen' },
{ id: capabilities.morgen.laagsteUur, value: round3((morgenPrices.find(p => p.price === safeStats(morgenPrices.map(p => p.price)).min)?.hour) ?? 999), label: 'Laagste uur morgen (lokaal)' },
{ id: capabilities.morgen.gemiddelde, value: round3(safeStats(morgenPrices.map(p => p.price)).avg), label: 'Gemiddelde morgen' },
];
// Zet capabilities
for (const cap of updates) {
try {
await Homey.devices.setCapabilityValue({ deviceId: targetDeviceId, capabilityId: cap.id, value: cap.value });
console.log(`✅ ${cap.label}: ${cap.value} (${cap.id})`);
} catch (e) {
console.log(`❌ Fout bij ${cap.label} (${cap.id}): ${e.message || e}`);
}
} |
En dit is het resultaat!
Daikin Altherma 3 LT 8 kW + 14,2 kWp PV