Voor de geïnteresseerden: ik heb een “Slim Laden”-setup uitgewerkt in Homey voor mijn Smappee laadpaal. Tips/advies om dit te optimaliseren zijn absoluut welkom. ![]()
Wat doet deze setup?
Deze flows + HomeyScript zorgen ervoor dat de auto (max. 2 uur op voorhand) begint te laden wanneer er binnen de komende 2 uur een goedkoper variabel tarief zit (onder een ingestelde prijsdrempel), op voorwaarde dat:
-
Het globale verbruik onder 6 kW blijft (capaciteitstarief/piek wat temperen).
-
Er niet geladen wordt tussen 17u en 20u (piekperiode).
-
De laadkabel effectief aangesloten is.
-
De laadpaal in Smappee op “normaal laden” staat (dus niet op “smart” vanuit Smappee zelf).
Daarnaast stuur ik telkens een statusbericht naar Telegram met een korte evaluatie: tijd OK/blocked, prijs nu, goedkoopste binnen 2u, vermogen, oplaadmodus, en uiteindelijk “Mag laden: JA/NEE”.
Overzicht flows (conceptueel)
1) Script uitvoeren (regelmatig + bij laad-events)
Triggers:
-
Elke 5 minuten
-
Opladen gestart
-
Opladen gestopt
En dan enkel als “Kabel is aangesloten” → HomeyScript uitvoeren (SlimLadenSmappeeV2)
Doel: script berekent of laden mag, en zet:
-
boolean variabele:
MagSlimLaden -
tekst variabele:
LaadStatusTekst
2) Actie afdwingen op basis van MagSlimLaden (failsafe)
Trigger:
- Elke 5 minuten
Dan:
-
ALS
MagSlimLaden = ja→ Standaard laden starten -
ANDERS → Stop laden
Doel: zelfs als er ergens een state mismatch is, duwt dit elke 5 minuten de laadpaal terug naar de juiste stand.
3) Telegram + directe actie bij wijziging MagSlimLaden
Trigger:
MagSlimLaden is veranderd
Dan:
-
ALS
MagSlimLaden = ja: start laden + Telegram “Slim Laden is gestart” +LaadStatusTekst -
ALS
MagSlimLaden = nee: stop laden + Telegram “Slim Laden is gestopt” +LaadStatusTekst
4) Handmatig aan/uit (optioneel)
Met een flow/schakelaar (“SlimLadenFlow Aan/Uit”) zet ik:
-
bij Aangezet:
MagSlimLaden = ja, Telegram “Slim Laden aangezet”, script runnen -
bij Uitgezet:
MagSlimLaden = nee, Telegram “Slim Laden uitgezet”, slimme sturing uit
HomeyScript (SlimLadenSmappeeV2)
// =====================
// CONFIGURATIE
// =====================
const prijsLimiet = 0.10; // Max prijs €/kWh
const maxVermogenWatt = 6000; // Max capaciteitstarief (Watt)
const laadpaalNaam = "Smappee Laadpaal"; // Naam van je laadpaal apparaat
const energyDongleNaam = "Homey Energy Dongle"; // Naam van je P1 of dongle
const belpexNaam = "Energy Belpex pricing"; // Naam van Belpex app/device
const boolVarNaam = "MagSlimLaden"; // Boolean variabele
const infoVarNaam = "LaadStatusTekst"; // Tekst variabele
// Tijdvenster waarin laden niet mag
const uitsluitStartUur = 17;
const uitsluitEindUur = 20;
// =====================
// HELPERS: Variabelen zetten (Tommi-fix)
// =====================
async function setBooleanVariable(name, value) {
const vars = await Homey.logic.getVariables();
let v = Object.values(vars).find(v => v.name === name);
if (!v) {
console.log(`ℹ️ Boolean '${name}' bestaat nog niet. Wordt aangemaakt...`);
v = await Homey.logic.createVariable({ name, type: "boolean", value: false });
}
await Homey.logic.updateVariable({
id: v.id,
variable: { value: Boolean(value) } // ✅ Tommi-fix
});
console.log(`✅ Boolean '${name}' bijgewerkt naar: ${value}`);
}
async function setStringVariable(name, tekst) {
const vars = await Homey.logic.getVariables();
let v = Object.values(vars).find(v => v.name === name);
if (!v) {
console.log(`ℹ️ Tekstvariabele '${name}' bestaat nog niet. Wordt aangemaakt...`);
v = await Homey.logic.createVariable({ name, type: "string", value: "" });
}
await Homey.logic.updateVariable({
id: v.id,
variable: { value: String(tekst) } // ✅ Tommi-fix
});
console.log(`✅ Tekstvariabele '${name}' bijgewerkt naar:\n${tekst}`);
}
// =====================
// HELPERS: Brussels tijd
// =====================
function formatTime(date) {
return date.toLocaleTimeString("nl-BE", {
hour: "2-digit",
minute: "2-digit",
timeZone: "Europe/Brussels"
});
}
function formatDateTime(date) {
return date.toLocaleString("nl-BE", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
timeZone: "Europe/Brussels"
});
}
function getBrusselsHour(date) {
return Number(new Intl.DateTimeFormat("nl-BE", {
hour: "2-digit",
hour12: false,
timeZone: "Europe/Brussels"
}).format(date));
}
// =====================
// HELPERS: Belpex 2u-vooruit berekenen
// =====================
function readCapabilityValue(capObj, key) {
return capObj[key]?.value;
}
// Probeert eerst per-uur capabilities (h1/h2 of h+1/h+2), anders fallback.
function getNext2hPrices(capObj) {
const candidates = [
["meter_price_h1", "meter_price_h2"],
["meter_price_h+1", "meter_price_h+2"],
];
for (const [k1, k2] of candidates) {
const p1 = readCapabilityValue(capObj, k1);
const p2 = readCapabilityValue(capObj, k2);
const arr = [];
if (typeof p1 === "number") arr.push({ offset: 1, price: p1 });
if (typeof p2 === "number") arr.push({ offset: 2, price: p2 });
if (arr.length) return arr;
}
return [];
}
function fallbackFrom8hLowest(capObj, currentHour) {
const lowest8 = readCapabilityValue(capObj, "meter_price_next_8h_lowest");
const hour8 = readCapabilityValue(capObj, "hour_next_8h_lowest");
if (typeof lowest8 !== "number" || typeof hour8 !== "number") return null;
const delta = (hour8 - currentHour + 24) % 24;
if (delta >= 1 && delta <= 2) {
return {
offset: delta,
price: lowest8,
targetHour: (currentHour + delta) % 24,
source: "8h_fallback"
};
}
return null;
}
// =====================
// MAIN SCRIPT
// =====================
async function main() {
console.log("🧪 SlimLadenSmappeeV2: Brussels Time + PrijsLimiet");
const now = new Date();
const uur = getBrusselsHour(now);
const tijdString = formatTime(now);
const datumTijd = formatDateTime(now);
const tijdOK = !(uur >= uitsluitStartUur && uur < uitsluitEindUur);
const devices = await Homey.devices.getDevices();
const laadpaal = Object.values(devices).find(d => d.name === laadpaalNaam);
const dongle = Object.values(devices).find(d => d.name === energyDongleNaam);
const belpex = Object.values(devices).find(d => d.name === belpexNaam);
if (!laadpaal) return console.log("❌ Laadpaal niet gevonden.");
if (!dongle) return console.log("❌ Energy dongle niet gevonden.");
if (!belpex) return console.log("❌ Belpex-apparaat niet gevonden.");
// Huidige prijs
const prijsNu = belpex.capabilitiesObj["meter_price_h0"]?.value;
if (typeof prijsNu !== "number") return console.log("❌ Geen actuele Belpex-prijs gevonden.");
// Goedkoopste binnen 2 uur (per-hour caps), anders fallback op 8h-lowest als die toevallig binnen 2h valt
const nextArr = getNext2hPrices(belpex.capabilitiesObj);
let laagste2u = null;
if (nextArr.length) {
const minEntry = nextArr.reduce((min, e) => (min && min.price <= e.price) ? min : e, null);
laagste2u = {
price: minEntry.price,
offset: minEntry.offset,
targetHour: (uur + minEntry.offset) % 24,
source: "per_hour_caps"
};
} else {
laagste2u = fallbackFrom8hLowest(belpex.capabilitiesObj, uur);
}
const uurLaagste2u = laagste2u?.targetHour ?? null;
const prijsLaagste2u = laagste2u?.price ?? null;
const prijsZaktBinnen2uOnderLimiet =
(typeof prijsLaagste2u === "number") && (prijsLaagste2u < prijsLimiet);
// Vermogen (P1/dongle)
let huidigVermogen = null;
if (dongle.capabilitiesObj.meter_power) {
huidigVermogen = dongle.capabilitiesObj.meter_power.value;
} else if (dongle.capabilitiesObj.measure_power) {
huidigVermogen = dongle.capabilitiesObj.measure_power.value;
}
if (typeof huidigVermogen !== "number") return console.log("❌ Geen geldig vermogen gemeten.");
const vermogenOK = huidigVermogen < maxVermogenWatt;
// Laadpaal mode check (moet NIET "smart" zijn)
const chargingMode = laadpaal.capabilitiesObj["charging_mode"]?.value ?? null;
const opladenOK = chargingMode !== "smart";
// Logica:
// - Nu laden als prijs nu OK is
// - OF als er binnen 2u een prijs onder limiet zit (=> max. 2u op voorhand starten)
const prijsOK = prijsNu <= prijsLimiet;
const magLaden = tijdOK && vermogenOK && opladenOK && (prijsOK || prijsZaktBinnen2uOnderLimiet);
const goedkoopsteTekst = (typeof prijsLaagste2u === "number" && uurLaagste2u !== null)
? `€${prijsLaagste2u.toFixed(3)} om ${uurLaagste2u}u (${laagste2u?.source ?? "?"})`
: "n.v.t.";
const tekst =
`🔋 Slim Laden Evaluatie (${datumTijd})
🕒 Tijd: ${tijdString} → ${tijdOK ? "✅ OK" : "❌ Blok (17–20u)"}
💶 Prijs nu: €${prijsNu.toFixed(3)} → ${prijsOK ? "✅ OK" : "❌ Te hoog"}
📉 Goedkoopste binnen 2u: ${goedkoopsteTekst} → ${prijsZaktBinnen2uOnderLimiet ? "✅ onder limiet" : "❌ niet onder limiet"}
⚡ Vermogen: ${huidigVermogen.toFixed(0)} W → ${vermogenOK ? "✅ OK" : "❌ Te hoog"}
🔌 Oplaadmodus: ${chargingMode ?? "Onbekend"} → ${opladenOK ? "✅ OK" : "❌ Niet toegestaan (smart)"}
🚗 Mag laden: ${magLaden ? "✅ JA" : "❌ NEE"}`;
console.log(tekst);
await setBooleanVariable(boolVarNaam, magLaden);
await setStringVariable(infoVarNaam, tekst);
}
await main();
Telegram output (voorbeeld)
In Telegram krijg ik dan meldingen in deze stijl:
-
“Slim Laden is gestart” + de evaluatie (tijd/prijs/vermogen/mode)
-
“Slim Laden is gestopt” + idem
Ideeën voor optimalisatie (waar ik zelf nog aan denk)
-
Niet enkel “onder prijsLimiet”, maar ook écht het goedkoopste uur targetten (starten op T-2u van dat uur, maar pas effectief laden vanaf een bepaalde grens).
-
Extra check: minimum laadtijd of hysterese (om veel start/stop te vermijden bij schommelingen rond 6 kW).
-
Dynamische vermogensgrens: bv. ‘s nachts iets hoger, overdag lager.
-
EV SoC / gewenste vertrektijd meenemen (als je die data hebt via auto-integratie).
-
Integrate van info afkomstig uit zonnepanelen & opbrengst.


