Virtuele thuisbatterij simuleren in Homey — met Claude als bouwer
Meet met je eigen P1-data of een thuisbatterij zich terugverdient, zonder een batterij te kopen.
Wat dit is
Een Advanced Flow die elke minuut je P1-meterstand uitleest en doet alsof je een thuisbatterij hebt. Hij simuleert laden bij teruglevering, ontladen bij afname, houdt de laadtoestand bij en telt op wat je bespaard zou hebben. Na een paar weken heb je een cijfermatig antwoord op “is een thuisbatterij iets voor mij” — gebaseerd op jouw werkelijke verbruikspatroon, niet op een rekentool met aannames.
Waarom nu relevant: met het einde van de saldering per 1-1-2027 verschuift de businesscase van batterijen aanzienlijk. Een simulatie die maanden meeloopt geeft je een veel harder antwoord dan een schatting.
Belangrijk vooraf: dit is een simulatie. Er wordt niets aangestuurd, er verandert niets aan je installatie. De flow schrijft alleen naar logische variabelen.
Wat je nodig hebt
- Homey Pro (getest op firmware 13.x)
- Een P1-meter in Homey met een
measure_powercapability (bijv. HomeWizard) - Claude met de Homey MCP-koppeling (
https://mcp.athom.com, via OAuth) - Optioneel: een dynamisch energiecontract voor realistische prijsberekening
Hoe het werkt
Elke minuut:
P1 uitlezen
│
├── P1 > 0 (je neemt af van het net)
│ └── batterij zou ontladen
│ ├── gevraagd vermogen > max ontlaadvermogen? → begrens op max
│ └── genoeg energie in de batterij? → anders lever wat er is
│
└── P1 < 0 (je levert terug)
└── batterij zou laden
├── teruglevering > max laadvermogen? → begrens op max
└── past het nog in de batterij? → anders vul tot vol
Daarna, ongeacht welke tak:
Evirtual += Pvirtual / 60 (cumulatieve verschoven energie)
ROI += Prijs × Pvirtual/60/1000 (cumulatieve besparing in €)
Acht eindpunten, één gedeelde afsluiting. In totaal 27 kaarten.
Ontwerpkeuze: in veel bestaande schema’s staat de ROI-berekening acht keer herhaald, één keer per tak. Dat is wiskundig identiek aan één gedeelde berekening aan het eind, omdat elke tak Pvirtual al correct heeft gezet. Eén berekening in plaats van acht scheelt kaarten én foutkansen.
Stap 1 — Variabelen aanmaken
Maak deze aan via de knop Variabelen in de flow-editor, allemaal type Nummer.
| Naam | Waarde | Betekenis |
|---|---|---|
Batt2-E |
0 | Huidige energie-inhoud (Wh) |
Batt2-Emax |
10000 | Capaciteit (Wh) |
Batt2-Pmax-charge |
7680 | Max laadvermogen (W) |
Batt2-Pmax-discharge |
7680 | Max ontlaadvermogen (W) |
Batt2-Pin |
0 | Werkvariabele: P1 van deze minuut |
Batt2-Pvirtual |
0 | Werkvariabele: vermogen deze minuut |
Batt2-Evirtual |
0 | Cumulatieve verschoven energie (Wh) |
Batt2-Prijs |
0.25 | €/kWh |
Batt2-ROI |
0 | Cumulatieve besparing (€) |
De waarden hierboven zijn voor 2× Enphase IQ Battery 5P (10 kWh / 7,68 kW). Vervang ze door de specs van de batterij die jij overweegt — dat is het enige dat je hoeft aan te passen om een ander model te simuleren.
Stap 2 — Laat Claude de flow bouwen
Gebruik de prompt onderaan dit bericht. Die bevat alle geverifieerde JSON-formaten, zodat Claude niet opnieuw hoeft uit te zoeken wat wij al hebben getest.
Stap 3 — Verifiëren vóór je 'm laat lopen
Zet Batt2-E tijdelijk op bijvoorbeeld 5000, schakel de flow in en wacht een paar minuten.
Bij afname van het net (P1 positief) hoort Batt2-E te dalen met ongeveer P1 / 60 Wh per minuut. Bij 900 W afname dus zo’n 15 Wh per minuut. Zie je dat, dan werkt de ontlaadtak.
De laadtak zie je pas als je zon hebt en daadwerkelijk terugleveert.
Zet daarna Batt2-E terug op 0 en laat 'm lopen.
Stap 4 — Resultaten bekijken
Alle variabelen verschijnen automatisch in Inzichten onder het kopje Logic, met historie en grafieken.
| Kijk naar | Wat het je vertelt |
|---|---|
Batt2-ROI |
Cumulatieve besparing in €. Extrapoleer naar een jaar en zet af tegen de aanschafprijs |
Batt2-E |
Vult hij overdag en leegt hij 's avonds? Dan is de capaciteit goed gekozen. Blijft hij vol staan → te groot. Loopt hij continu leeg → te klein |
Batt2-Evirtual |
Totaal verschoven volume in Wh |
Reken op minimaal een week voor een trend, en liefst een volle maand met wisselend weer.
De technische lessen — dit is waar we uren aan kwijt waren
Het bouwen van Advanced Flows via de Homey API loopt vast op tokenformaten die nergens gedocumenteerd staan. Het vervelende is dat een fout formaat geen foutmelding geeft — de kaart wordt geaccepteerd, broken blijft false, en de actie doet gewoon stilletjes niets. Dit zijn de formaten die wij empirisch hebben geverifieerd.
Variabele-token
[[homey:manager:logic|<variabele-UUID>]]
Let op: het volledige interne UUID, niet de variabelenaam. [[Batt2-E]] werkt niet. Het UUID haal je op met de MCP-tool search_flow_card_autocomplete_argument (argumentId variable).
Apparaat-token
[[homey:device:<device-UUID>|<capability>]]
Pijpteken tussen device en capability, geen dubbele punt. Bijvoorbeeld: [[homey:device:79e04a09-...|measure_power]]
Berekening
Kaart homey:manager:logic:variable_set_number_math:
json
{
"type": "action",
"id": "homey:manager:logic:variable_set_number_math",
"args": {
"variable": { "id": "<UUID>", "name": "Batt2-E" },
"value": "{{ [[homey:manager:logic|<UUID>]] - 100 }}"
}
}
Het token moet binnen de accolades staan. Dit was de valkuil die ons het meeste tijd kostte: een token buiten de accolades wordt geaccepteerd, maar levert onzin op.
Het variable-argument moet het volledige object {id, name} zijn, niet alleen de naam.
Vertakking op variabelen — zonder droptoken
De standaard vergelijkingskaarten (logic:lt, logic:gt) gebruiken een droptoken-veld dat via de API onbetrouwbaar is. Gebruik in plaats daarvan:
json
{
"type": "condition",
"id": "homey:manager:logic:equation",
"args": {
"value1": "{{ [[homey:manager:logic|<UUID-A>]] }}",
"operator": "lte",
"value2": "{{ [[homey:manager:logic|<UUID-B>]] }}"
},
"outputTrue": ["<kaart-id>"],
"outputFalse": ["<kaart-id>"]
}
Beide waardevelden zijn gewoon tekst en accepteren dezelfde tokensyntax. Operators: eq, neq, lt, gt, lte, gte. Hiermee kun je variabelen onderling vergelijken én rekenen in de vergelijking zelf ({{ [[token]] / 60 }}).
Dit omzeilt de droptoken-beperking volledig en is de sleutel tot complexe flows via de API.
Vertraging
json
{ "type": "delay", "args": { "delay": { "number": "10", "multiplier": 60 } } }
multiplier 60 = minuten, 1 = seconden. number is een string.
Trigger elke minuut
json
{ "id": "homey:manager:cron:every_nth", "args": { "n": 1, "type": "minute" } }
Overige API-eigenaardigheden
- Kaartsleutels in de JSON moeten geldige UUID’s zijn
- Er is geen
delete_flowtool — opruimen doe je handmatig in de app start_flowwerkt alleen op flows met een handmatigestart-trigger, niet op cron-triggerssearch_flow_card_autocomplete_argumentwerkt wél voor gewone autocomplete-argumenten, maar geeftNot Implementedvoor droptoken
Methodische les — hoe je dit soort dingen uitzoekt
Omdat fouten stil zijn, werkt seriële trial-and-error slecht. Wat wél werkte:
- Zet elke testvariabele eerst op een ‘vuile’ niet-nulwaarde (bijv. 999). Anders kun je “de actie deed niets” niet onderscheiden van “de actie zette hem op 0”.
- Test meerdere hypotheses tegelijk, elk op een eigen doelvariabele. In één run zie je meteen welke werkt.
- Neem altijd een controletest mee zonder de verdachte constructie — bijvoorbeeld
{{ 3 * 2 + 6 }}zonder token. Werkt die niet, dan ligt het niet aan je tokenformaat maar aan iets fundamentelers. - Bouw één kaart handmatig in de app en lees de JSON terug. Het juiste formaat staat er dan letterlijk in. Dit gaf ons uiteindelijk de doorbraak.
Beperkingen — wees hier eerlijk over
Momentopname, geen gemiddelde. De flow leest P1 één keer per minuut. Een korte piek precies op het meetmoment vertekent die minuut. Over duizenden metingen middelt dat grotendeels uit, maar het is minder nauwkeurig dan een echt minuutgemiddelde. Wie het preciezer wil: bouw een accumulator die elke 10 seconden optelt en per minuut deelt.
Rendement niet gemodelleerd. Een echte batterij heeft round-trip verlies (bij LFP zo’n 90–96%). Deze simulatie gaat uit van 100%. De werkelijke opbrengst ligt dus iets lager dan wat je meet.
Geen degradatie, geen DoD-beperking. De simulatie gebruikt de volledige capaciteit en houdt geen rekening met veroudering.
Prijs is een momentopname. Als je de prijskoppeling niet werkend krijgt (zie hieronder), rekent hij met een vaste prijs. Dat onderschat juist de arbitragewaarde die bij een dynamisch contract het interessantst is.
Saldering vertekent de uitkomst. Zolang saldering geldt, wordt teruglevering al vol verrekend. De gemeten ROI is daarmee vooral een indicatie voor de situatie na 2027.
Nog openstaand: prijskoppeling aan een dynamisch contract
Het idee: een aparte flow met als trigger “Stroomprijs verandert” van je energieleverancier-app, die het prijs-token wegschrijft naar Batt2-Prijs. De hoofdsimulatie leest die variabele.
Wij hebben het tokenformaat voor app-triggertokens nog niet geverifieerd. Device- en variabele-tokens wél (zie hierboven), maar een trigger-token uit een app werkt mogelijk anders. Bouw die ene kaart voor de zekerheid handmatig in de app met de token-picker.
Zonder werkende koppeling zet je Batt2-Prijs gewoon handmatig op een realistisch gemiddelde. De simulatie werkt dan prima, alleen minder scherp.
De prompt
Plak dit in Claude, met de Homey MCP-koppeling actief.
Ik wil in Homey een virtuele thuisbatterij-simulatie bouwen via de Homey MCP.
Doel: elke minuut mijn P1-meting uitlezen en simuleren wat een thuisbatterij
zou hebben gedaan, zodat ik na een paar weken kan zien of aanschaf rendeert.
Er wordt niets aangestuurd — alleen logische variabelen bijwerken.
=== BATTERIJSPECS (pas aan naar wens) ===
Capaciteit: 10000 Wh
Max laadvermogen: 7680 W
Max ontlaadvermogen: 7680 W
=== VARIABELEN (ik heb ze al aangemaakt, type Nummer) ===
Batt2-E (0), Batt2-Emax (10000), Batt2-Pmax-charge (7680),
Batt2-Pmax-discharge (7680), Batt2-Pin (0), Batt2-Pvirtual (0),
Batt2-Evirtual (0), Batt2-Prijs (0.25), Batt2-ROI (0)
Haal hun UUID's op met search_flow_card_autocomplete_argument
(cardId: homey:manager:logic:variable_set_number_math, argumentId: variable).
Zoek ook mijn P1-device en de bijbehorende measure_power capability op.
=== GEVERIFIEERDE JSON-FORMATEN — gebruik deze, ga niet zelf experimenteren ===
Variabele-token: [[homey:manager:logic|<variabele-UUID>]]
(volledig UUID, NIET de naam)
Apparaat-token: [[homey:device:<device-UUID>|<capability>]]
(pijpteken, geen dubbele punt)
Berekening:
{"type":"action","id":"homey:manager:logic:variable_set_number_math",
"args":{"variable":{"id":"<UUID>","name":"<naam>"},
"value":"{{ [[homey:manager:logic|<UUID>]] - 100 }}"}}
→ token MOET binnen de accolades staan
→ variable MOET het volledige object {id, name} zijn
Vaste waarde:
{"type":"action","id":"homey:manager:logic:variable_set_number",
"args":{"variable":{"id":"<UUID>","name":"<naam>"},"value":0}}
Vertakking (gebruik deze, NIET logic:lt of logic:gt met droptoken —
die zijn via de API onbetrouwbaar):
{"type":"condition","id":"homey:manager:logic:equation",
"args":{"value1":"{{ [[token]] }}","operator":"lte","value2":"{{ [[token]] }}"},
"outputTrue":["<id>"],"outputFalse":["<id>"]}
→ operators: eq, neq, lt, gt, lte, gte
→ beide velden zijn tekst en mogen rekenen: "{{ [[token]] / 60 }}"
Trigger elke minuut:
{"type":"trigger","id":"homey:manager:cron:every_nth",
"args":{"n":1,"type":"minute"}}
Kaartsleutels in de JSON moeten geldige UUID's zijn.
=== TE BOUWEN FLOW ===
Naam: "Batt2 Simulatie (virtuele batterij)", enabled: false
Trigger elke minuut
→ Batt2-Pin = P1 measure_power
→ conditie: Batt2-Pin > 0 ?
ONTLADEN (waar):
conditie: Pin > Pmax-discharge ?
waar: conditie: Pmax-discharge/60 <= E ?
waar: Pvirtual = Pmax-discharge ; E = E - Pmax-discharge/60
onwaar: Pvirtual = E * 60 ; E = 0
onwaar: conditie: Pin/60 <= E ?
waar: Pvirtual = Pin ; E = E - Pin/60
onwaar: Pvirtual = E*60 ; E = 0
LADEN (onwaar):
conditie: -1*Pin <= Pmax-charge ?
waar: conditie: -1*Pin/60 <= (Emax - E) ?
waar: Pvirtual = Pin ; E = E - Pin/60
onwaar: Pvirtual = -1*(Emax-E)*60 ; E = Emax
onwaar: conditie: Pmax-charge/60 <= (Emax - E) ?
waar: Pvirtual = -1*Pmax-charge ; E = E + Pmax-charge/60
onwaar: Pvirtual = -1*(Emax-E)*60 ; E = Emax
Alle acht eindpunten komen samen op:
→ Batt2-Evirtual = Batt2-Evirtual + Batt2-Pvirtual/60
→ Batt2-ROI = Batt2-ROI + Batt2-Prijs * Batt2-Pvirtual/60/1000
BELANGRIJK: zet in elke tak eerst Pvirtual, dán pas E.
Sommige Pvirtual-formules gebruiken de oude waarde van E.
=== DAARNA ===
Bouw ook een losse flow "Batt2 Reset" met een handmatige start-trigger die
E, Pvirtual, Evirtual, Pin en ROI op 0 zet. Handig bij het opnieuw beginnen.
Laat de simulatie uitgeschakeld — ik zet 'm zelf aan na controle.
Waarschuw me als iets niet geverifieerd is in plaats van te gokken.
Tot slot
Als je dit gebruikt: de simulatie is zo goed als je aannames. Kijk vooral naar het patroon van Batt2-E over de dag — dat vertelt je meer over de juiste batterijgrootte dan het ROI-getal alleen. En vergeet niet dat de uitkomst onder saldering anders is dan erna.
Vragen of verbeteringen: reageer gerust. De tokenformaten hierboven zijn empirisch getest, maar Homey-firmware verandert.
