Zelfbouw · Energie & domotica

Het huis dat over zichzelf leert nadenken

Hoe een logserver van tienduizenden regels code, gebouwd met een AI als coauteur, een Homey-installatie omvormt van "doet wat je hem vertelt" naar een systeem dat zijn eigen gedrag meet, verklaart, en voorstelt te verbeteren — inclusief een digitale tweeling van het huis, gecontroleerde zelf-experimenten, en een batterij die weet wanneer stroom geld kóst.

Levend overzicht van het project Homey_Log — bijgewerkt bij elke noemenswaardige uitbreiding Laatst bijgewerkt: 23 juli 2026
379
commits sinds 12 juni
33k
regels servercode
43
databasetabellen
80
API-routemodules
Kerncijfers van de codebase op het moment van de laatste update — het hele systeem draait naast de bestaande Homey-installatie, niet erin.

Homey handelt, maar onthoudt niets

Een Homey-installatie kan verrassend veel: flows die op prijssignalen reageren, een laadpaal die met de zon meebeweegt, een thuisbatterij die 's nachts vult op het goedkoopste kwartier. Wat een Homey niet doet, is onthouden. Er is geen plek waar "wat er gisteren gebeurde" samenkomt met "wat het kostte" en "of het eigenlijk wel klopte". Elke flow is een geïsoleerd reflexje; niemand — mens noch systeem — ziet het geheel.

Homey_Log is de laag die dat gat vult: een Node.js-server met een eigen SQLite-database die naast de Homey draait, alles vastlegt wat er gebeurt, er een 3D-tweeling van het huis bovenop legt, en er vervolgens een AI-analist op loslaat die dagelijks — en wekelijks dieper — door de cijfers heen gaat. Niet om zelf de thermostaat te bedienen, maar om te zeggen: "dit patroon klopt niet, hier verlies je geld, wil je dat ik dit script aanpas?" — en om onder strikte voorwaarden zelf een kleine, omkeerbare proef te draaien.

Architectuur in vogelvlucht

Homey_Log is een Express-server (Node.js) met better-sqlite3 als opslag, draaiend onder PM2 op een eigen host. Homey zelf blijft de bron van waarheid voor apparaten, flows en logica-variabelen — Homey_Log praat ermee via twee kanalen die bewust gescheiden zijn:

  • Push, vanuit HomeyScript. Scripts die toch al op het Homey-platform draaien (laadplanning, prijsberekening, vaatwasser-timing) sturen na elke run een logregel naar POST /api/log. Dit is de primaire databron: geen polling, geen vertraging, en de tekst van de logregel is precies wat het script zelf besliste — dus herleidbaar tot de brontaal.
  • Poll, vanuit de server. Voor alles waar Homey_Log zelf actief iets moet ophalen of moet kunnen bedienen, praat de server via de lokale Homey Web API met een Personal Access Token. Slimme meter-data (P1) wordt bijvoorbeeld rechtstreeks lokaal gepolld (routes/hw-p1.js) in plaats van via Homey's cloud-pad — sneller en onafhankelijk van Homey's eigen belasting.
Homeyflows · scripts · logica-variabelen · apparaten
│ HomeyScript-push (/api/log) + lokale Web-API-poll (P1, apparaatstaat)
Homey_LogExpress + SQLite — tientallen tabellen: logboek, kwartier/dag/maand-energie, apparaatstaat, regels
│ dagelijkse & wekelijkse review — zelfde tabellen, read-only SQL-tool
AI-analyselaagschema-gids + huisprofiel + regels + geleerde lessen → LLM (Anthropic / OpenAI / OpenRouter / lokaal)
│ voorstel (observatie / actie) — nooit automatisch, tenzij expliciet goedgekeurd
Voorstellen- en Experimenten-tabMarc keurt goed of wijst af
│ bij goedkeuring: geschreven via de Web API, altijd op een witte lijst, altijd omkeerbaar
terug naar Homeylogica-variabele of flow aan/uit — nooit een vrij commando
De lus sluit zich altijd terug bij een mens — behalve het abort-pad van een experiment, dat per ontwerp áltijd automatisch mag ingrijpen.
01

Alles vastleggen — het logboek van het huis

De basis is een simpele logs-tabel: tijdstip, bron, niveau, boodschap, een JSON-databundel en een run_id om herhaalde runs van hetzelfde script te groeperen. Daarnaast een resem specifieke tabellen die niet toevallig zijn, maar de sporen zijn van maandenlang "wat blijkt niet te kloppen": energy_quarterly (kwartierdata sinds 2026, de fijnste korrel), energy_daily en energy_monthly voor oudere jaren waar geen kwartierdata bestaat, device_events/device_state voor losse apparaatsignalen, variable_changes voor logica-variabelen, en entity_history die wijzigingen in flows en scripts zelf detecteert — zodat de AI-laag later kan zeggen "dit gedrag veranderde op dezelfde dag dat je die flow aanpaste".

Grondwaarheid boven afgeleide tabel

Een terugkerend patroon in dit project: een afgeleide tabel lijkt actueel, maar staat stil. De laadstatus van de auto's staat bijvoorbeeld in variable_changes, maar die tabel kan uren achterlopen op de rauwe HomeyScript-logregels van hetzelfde uur. Het principe daarachter: bij twijfel niet de nette afgeleide tabel gebruiken, maar de rauwe logregel die het script zelf wegschreef. Dat staat letterlijk als methode-item in de interne schema-gids van de AI.

Om te voorkomen dat de logtabel dichtslibt met herhaalde, dikke waarschuwingen (een terugkerende bug kan honderden keren per dag dezelfde payload loggen) is elk dataveld hard afgekapt op 400 tekens per rij — klein detail, maar het voorkomt een reëel "prompt too long"-probleem wanneer de AI-laag zo'n logboek leest.

Logische vs. fysieke apparaten

Als een kapotte slimme stekker vervangen wordt, geeft Homey het nieuwe exemplaar een gloednieuw apparaat-ID — waardoor alle rapportage bij nul zou beginnen, alsof het een ander apparaat is. Voor het huishouden is het echter gewoon "de stekker van de tv". Daarom zit er een dunne laag tussen: elk fysiek Homey-apparaat hangt aan een logisch apparaat (logical_devices + device_links), en instellingen als "meetellen in verbruik" en "hoort altijd aan te staan" horen bij dat logische niveau. De ruwe meetdata blijft onaangeraakt fysiek opgeslagen; de logische laag is puur een leesbril erbovenop. 👤 Marc markeert bij een vervanging in Instellingen het oude apparaat als "vervangen door…" (bewust een handmatige bevestiging, nooit automatisch raden), 🤖 waarna geschiedenis en instellingen naadloos doorlopen over de wissel heen — en tegelijk zichtbaar blijft wanneer het ene fysieke apparaat overging in het andere, met de volledige meetreeks als één doorlopend apparaat. Alle lezers gaan door deze laag: Usage-tab en drill-down, het effect-gasloos-model, de aanwezigheidsgeschiedenis, het sluipverbruik én het 3D-huis — dat laatste zó dat een verouderde apparaat-verwijzing in het 3D-model gewoon blijft werken (status én bediening worden stil naar de vervanger geleid). De apparatentabel zelf staat in Instellingen → Apparaten.

Eén haperende meter mag de rest niet meeslepen

Het kwartier-meetscript op Homey bemonstert drie dingen in één run: de laadpaal, de warmtepomp en het accuniveau van de auto. Die drie deel-metingen zijn ontkoppeld: elk vangt zijn eigen fouten op, zodat een onleesbare bron alleen voor zichzélf een leeg veld oplevert terwijl de rest gewoon doorloopt. De laatst bekende meterstand wordt bij zo'n fout bewust niet overschreven, zodat de gemiste energie bij herstel als inhaal-som binnenkomt in plaats van te verdampen — het dagtotaal blijft daardoor altijd kloppen. De repo bewaart ook een kopie van dit script (scripts/homey/Sample_EV_Charge.js), zodat wijzigingen eraan als nette diff te beoordelen zijn.

Omdat Homey elke 15 minuten de héle buffer opnieuw opstuurt, houdt de ingest binnen één aanlevering per kwartier alleen de eerste, reguliere meting aan. Zo kan een extra (bijvoorbeeld handmatige) testrun midden in een kwartier geen tweede meting voor datzelfde kwartier laten na-echoën als een etmaal lang herhaalde "verdacht: waarde overschreven"-waarschuwing.

02

De digitale tweeling: het huis in 3D, live

Boven op de logdata staat een volledig 3D-model van het huis — beide verdiepingen, kamers, meubels, ramen, dakkapellen, tot en met de wc-ramen en de nis onder de trap — vastgelegd in een house-model.json van duizenden regels en gerenderd door een eigen clientscript. Dit is geen decoratie: het is een live-laag boven op de Homey Web API. Apparaten kleuren mee met hun werkelijke staat (een lamp die aan staat gloeit, een kamer die te warm is krijgt een andere temperatuurkleur), en je kunt er direct in bedienen — klikken schakelt een apparaat aan of uit, rechtsklikken opent een paneel voor dimmen, kleur, wit-tint of een cameraframe.

3D-model van het huis, dakaanzicht met zonnepanelen, in de Huis-tab van de desktop-app
De Huis-tab: dakaanzicht met de zonnepanelen zichtbaar. De verdiepingsknoppen linksboven (Dak/Zolder/ Begane grond/Eerste verdieping) en de Live/Comfort-toggle schakelen lagen aan/uit; klikken op een kamer zoomt erin.
Eerste verdieping geïsoleerd (dak, zolder en begane grond uitgeschakeld): kamers in eigen pastelkleur, met meubels
Met dak, zolder en begane grond uitgeschakeld blijft alleen de eerste verdieping over — hier komen de kamer-kleuren en meubels pas echt tot hun recht.

Voor lezen en schrijven gebruikt de server bewust twee gescheiden Personal Access Tokens (routes/homey-client.js): een read-only token voor de constante status-poll, en een apart schrijf-token dat alleen wordt aangesproken op expliciete klik-acties, met een harde whitelist van welke capabilities (aan/uit, dim, kleur, kleurtemperatuur) vanuit de tweeling mogen worden aangestuurd. Niets buiten die lijst is bereikbaar vanuit de 3D-weergave.

Waarom dit meer is dan een gimmick

De tijdgrafiek van het huis (uPlot, dubbele y-as) toont niet alleen energiestromen maar ook een event-overlay: bedieningsmomenten uit de tweeling verschijnen als markeringen op dezelfde tijdlijn als het energieverbruik. Daardoor is "waarom steeg het verbruik om 21:40" in twee klikken te herleiden tot een concrete handeling.

03

Van data naar euro's — wat het je kost en oplevert

Ruwe kwartierdata is niets waard zonder een consistent rekenmodel erboven. Dat rekenmodel — welke kWh telt als "import", wanneer telt zonnestroom mee, hoe wordt een negatief PV-signaal genegeerd — zit in één module (routes/energy-model.js) die door alle afgeleide schermen wordt hergebruikt, bewaakt door een parity-script dat serverbrede aggregaten tegen de module-uitkomst vergelijkt (npm run parity, altijd 0,0000 verschil). De vuistregel daarachter — "consistent wint van exact" — betekent dat een maand altijd exact de som van zijn dagen is en een jaar exact de som van zijn maanden, ook als dat een paar kWh afwijkt van een los, ouder brondocument.

Op die basis staan tegels en pop-ups die met concrete euro's rekenen in plaats van kale kWh's:

WeergaveWat het laat zien
Zelfvoorziening / direct-solarwelk deel van het verbruik uit eigen zon komt, incl. het "structurele verlies" wanneer de batterij bewust geparkeerd staat terwijl de auto laadt
Effect-tegel (zon + batterij + gasloos)euro-effect per volle maand van drie maatregelen samen — kan ook negatief uitvallen als de telemetrie een gat heeft, en dat wordt dan ook zo getoond in plaats van als nul geraden
Sluipverbruikbaseload = mediaan van het laagste-uur-huisverbruik per dag (excl. zon/batterij), plus een top-10 van vermoedelijke boosdoeners met terugverdientijd, met een harde uitsluiting voor koelkast/vriezer/server/alarm e.d.
Laadprijs per autogemiddelde laadkostprijs per kWh, uitgesplitst per auto via de HomeyScript-log die bijhoudt welke auto actief laadt — ruim 24% goedkoper dan de kale netprijs door slim te timen
Gasloos-effectbruto kWh van warmtepomp en warmwater (de boiler; via de logische-device-merge één doorlopende reeks met de eerdere douche-doorstromer) omgerekend naar een hypothetisch gasscenario, met actuele gasprijzen via de EnergyZero-API

De grafiek-tab zelf is één doorlopende tijdlijn (uPlot) die zelf de korrel kiest — kwartier, dag, maand, jaar — in plaats van een aparte "historie"-sectie: minder navigatie, en de app beslist zelf wanneer het detail te fijn wordt om nog zinnig te tonen.

Historische zon tot 2018. De volledige productiehistorie staat op kwartierniveau in de database, terug tot de installatie in januari 2018, opgehaald via de SolarEdge-API. Beide omvormers (garage sinds 2018, zolder sinds april 2019) hangen achter één SolarEdge-site, terwijl de database ze als aparte velden kent; een "split"-modus verdeelt de site-reeks daarom per maand volgens de cumulatieve productieteller van elke omvormer op de maandgrenzen (3 API-calls per maand in plaats van honderden). Tellerstanden en een hoogwatermerk worden gecacht zodat een backfill na de SolarEdge-daglimiet (300 calls/dag) verdergaat waar hij bleef; bestaande data blijft onaangeroerd (INSERT OR IGNORE). De opbrengst reconcilieert tot op ~2% met het SolarEdge-portaal, en de live-pipeline matcht de omvormertellers tot op twee decimalen — SolarEdge is daarmee de onafhankelijke grondwaarheid voor de zonproductie.

Boiler-tegel: zon versus import, tot op de sessie. De boiler-tegel in de Usage-tab splitst het verbruik per dag in twee stromen — wat op zonoverschot ging (gratis) en wat op import van het net kwam (betaald, tegen de kwartierprijs van dat moment) — met een staafgrafiek van de gekozen periode. Daaronder staat de lijst met losse laadsessies die wél import bevatten: per sessie het tijdvak, de kWh, hoeveel daarvan import was, en de kosten tegen de kwartierprijs. Sessies die volledig op zon liepen kosten niets en komen daarom niet los in de lijst — alleen als één totaalregel ("N sessie(s) volledig op zon, X kWh — gratis").

04

De AI die er dagelijks doorheen gaat

Het hart van het systeem is een agent-pijplijn (routes/agent-pipeline.js) die dagelijks en — dieper — wekelijks door de data heen gaat. De analist krijgt geen los promptje, maar een opgebouwde context:

  • een huisprofiel (house_facts): geometrie automatisch afgeleid uit het 3D-model (oppervlakte, inhoud, per kamer), aangevuld met handmatige feiten, en zelf uit te breiden — de AI mag zelf een nieuw "feit" voorstellen als het iets structureels signaleert, dat dan als voorgesteld feit wacht op goedkeuring;
  • een schema-gids: een levend document dat precies beschrijft welke tabel voor welke vraag de waarheid is, inclusief valkuilen ("gebruik nooit daily_rollup voor dit ene randgeval") — feitelijk de opgebouwde tribale kennis van maanden debuggen, zodat de AI die niet steeds opnieuw hoeft te herontdekken;
  • actieve regels en troubleshoot-recepten die Marc heeft vastgelegd;
  • een groeiend geheugen van geleerde lessen (zie verderop) — dingen die eerder wél of niet bleken te werken in dít huis.

Voor het redeneren zelf heeft de analist een read-only SQL-tool over de eigen database — geen vaste rapportvorm, maar vrij bevragen, zodat een hypothese ("klopt dit patroon ook vorige maand?") direct te toetsen is. Het taalmodel zelf is uitwisselbaar: een eigen abstractielaag (routes/llm-client.js) ondersteunt Anthropic, OpenAI, OpenRouter en een lokaal model, met per taak — dagpijplijn, chat, zelfcontrole — een eigen instelbare provider, zodat kwaliteit, kosten en privacy per taak afgewogen kunnen worden. Elke aanroep wordt met tokenkosten gelogd, tot op een eigen "AI-kosten"-tabblad met een gestapelde grafiek per taak.

Zelflerend

Na elke run mag de analist maximaal drie generaliseerbare lessen vastleggen — methode-lessen ("deze blinde vlek moet ik voortaan checken") of uitkomst-lessen ("deze maatregel bleek in dit huis wél/niet te werken") — en, als nieuwe data een eerdere les tegenspreekt, die zelf intrekken. Marc kan elke les altijd terugzetten: het systeem mag zichzelf bijsturen, maar niet buiten zicht.

05

Voorstellen in plaats van een black box

Een bevinding van de analist wordt nooit stilzwijgend uitgevoerd. Elke bevinding krijgt een kind: een constatering (iets is opgevallen, geen beslissing nodig — alleen "gelezen" af te vinken) of een actie (een concreet voorstel met knoppen: goedkeuren, afwijzen met notitie, of later beoordelen). Een badge in de navigatie telt alleen de echt actionable voorstellen, zodat constateringen niet als ruis in de weg zitten.

Naast de handmatige beoordeling door Marc kent diezelfde pijplijn drie vormen van gecontroleerde zelfsturing:

TrapDoet
Tier 1 — auto-verificatiesluit voorstellen automatisch af die aantoonbaar niet kloppen: een probleem dat al eerder langskwam en zichzelf herstelde, een titel die niet meer bij de datum past, of een claim ("script X heeft hier al een guard tegen") die het systeem zelf tegen de scriptcode natrekt
Tier 2 — experiment-routingvertaalt een toetsbare hypothese automatisch naar een concept-experiment, gevalideerd tegen de écht bestaande Homey-variabelen — pas uitgevoerd na goedkeuring in de Experimenten-tab
Tier 3 — scriptvoorstellengenereert een volledig nieuw scriptvoorstel mét unified diff tegen het huidige script; een strikte controle wijst diffs af die alleen commentaar of tekst wijzigen, zodat er geen loze "voorstellen" blijven hangen

Een ontwerpkeuze die daarbij expliciet is gemaakt: geen aparte "twijfelachtig, beoordeel dit zelf"-categorie voor grensgevallen. Als iets aantoonbaar niet klopt, sluit het systeem het volledig af — een tussencategorie zou alleen maar extra beoordeelwerk toevoegen zonder waarde.

06

Het huis experimenteert met zichzelf

De verst gevorderde vorm van zelfsturing is de Experimenten-fase: de analist mag, als een hypothese aan strenge voorwaarden voldoet, een gerichte, tijdgebonden proef voorstellen in plaats van er alleen over te rapporteren. De voorwaarden zijn hard: de hypothese moet meetbaar zijn op een vaste lijst KPI's (kosten, import, export, zonopbrengst, batterijgedrag …), de wijziging moet op een korte witte lijst staan — alleen een logica-variabele op een waarde zetten, of een specifieke flow aan/uit — en het systeem moet vooraf zelf de beginwaarde inlezen zodat terugdraaien gegarandeerd is.

Rond die kern staat een aantal vangnetten die stuk voor stuk uit een expliciete eis van Marc voortkwamen:

  • een hoofdschakelaar, standaard uit — voorstellen doen en goedkeuren zit erachter, maar afbreken en terugdraaien werkt altijd, ook met de schakelaar uit;
  • een A/B-schema dat dag voor dag afwisselt tussen de nieuwe en de oude instelling, zodat een effect niet met toeval te verwarren is;
  • een automatische noodstop op bewaakte grensmetrieken: schiet de kostprijs of het importverbruik buiten een vooraf ingestelde marge, dan wordt het experiment direct teruggedraaid, met een luide melding en een geforceerde "dit werkte niet"-les.
Voorbeeld · lopend experiment LIVE

Een thuisbatterij die tijdens het nachtelijk laden van de auto normaliter simpelweg wordt "geparkeerd" (uitgezet) loopt het risico om, zodra hij weer actief wordt, de auto mee te laten leegtrekken. Het experiment stuurt de batterij in die situatie niet naar stilstand maar naar een balanceerpunt "nul plus het laadvermogen van de auto" — via een parameter op het batterijregelsysteem die normaliter naar nul op de meter balanceert, maar hier een verschoven doel meekrijgt. Dat stuurmechanisme is tekenzuiver: een positieve waarde laat de meter precies dat vermogen aan invoer staan, een waarde van nul balanceert terug naar nul-op-de-meter. Kostprijs en importverbruik zijn de bewaakte grenzen die het experiment desnoods automatisch terugdraaien.

Onderweg dook precies het soort fout op waar dit hele systeem voor bedoeld is om te vinden: een nachtelijke laadsessie startte niet, ondanks een correct laadplan. Uitzoekwerk via de logs bracht een gat in de uitvoeringsketen aan het licht — de flows die de laadpaal daadwerkelijk aan moesten zetten stonden al weken uit "omdat een script die rol zou overnemen", een overname die feitelijk nooit was afgemaakt. Binnen een dag gevonden, verholpen en binnen twee minuten na de fix bewezen werkend op de echte paal.

07

Wat Homey zelf al regelt — en hoe de AI-laag daarop meekijkt

Homey_Log observeert en stelt voor; het is niet de plek waar de dagelijkse energiesturing zelf gebeurt. Die sturing draait, zoals bij een uitgebreide Homey-installatie past, in een netwerk van flows en HomeyScripts. De belangrijkste stukken:

Slim laden op dag-vooruitprijzen

Een dagelijks planscript verdeelt de nacht in kwartieren en wijst per kwartier een strategie toe — laden, ontladen, vasthouden, of een apart regime voor kwartieren met een negatieve prijs — voor zowel de twee thuisbatterijen als de laadpaal, met een harde deadline zodat de auto altijd op tijd de gevraagde lading heeft, ook als dat de goedkoopste kwartieren negeert.

Dat plan is maar zo goed als de accustand waarmee het rekent, en daar zit een valkuil: een auto die aan de laadpaal slaapt geeft geen accupercentage meer door, zodat het plan anders met een dagen oude waarde zou rekenen. De remedie is een SoC-peiling bij het inpluggen: de laadpaal geeft eerst vijf minuten een laadpuls (die wekt de auto, waarna het echte accupercentage vanzelf binnenkomt) en gaat pas daarna in de zonnestroomstand. Dat zit in één flow; alle plannende scripts blijven ongemoeid. Zie PLAN-soc-peiling-inpluggen.md.

Laadpaal via Modbus TCP

De laadpaal wordt aangestuurd via Modbus TCP — Homey is daarmee het energiemanagementsysteem van de paal. Dat omzeilt bewust de eigen webserver van de paal, waarin een geheugenlek zat dat werd aangejaagd door een inlog elke 30 seconden; via Modbus is die logincadans weg en blijft de paal rustig. De kwartier-actuator is pauzeren/hervatten met stroomlimiet. Op die basis draait Green_Tick: een server-side regelaar die de auto overdag puur op teruggeleverde zon laadt. Elke minuut berekent hij het beschikbare overschot (auto-afname + teruglevering), vertaalt dat naar een max-stroom (6–16 A) en stuurt die naar de paal — een gesloten regellus, dus zodra de auto de zon opeet blijft de meter rond nul in plaats van te klapperen. Het zonvenster loopt tot de werkelijke zonsondergang van die dag, berekend uit de locatie in plaats van een vaste klok.

Green meet per laadsessie of de auto op 1 of 3 fasen laadt — uit de werkelijke fasestroom van de paal tijdens de eerste minuten — en rekent daar de drempels en stroom op: een 3-fase-auto start rond ~4,5 kW overschot, een 1-fase-auto al vanaf ~1,5 kW en mag tot 16 A op zijn ene fase (~3,5 kW in plaats van gekapt op ~2,3 kW). Laadt een auto normaal op drie fasen terwijl er te weinig zon is voor drie, dan kan Green hem tijdelijk op één fase zetten, en gaat vanzelf terug naar drie zodra dat kan of nodig is — altijd via een pauze, want een fasewissel tijdens het laden is niets voor een auto.

De regeling is bewust asymmetrisch: bij een wolk gaat het vermogen meteen omlaag, maar voor starten of verder opschakelen wacht Green eerst een paar minuten aanhoudend overschot af, zodat een kort zonnetje geen sessie triggert die er een minuut later alweer af moet. Pauzeren gebeurt zodra het overschot het minimale laadvermogen niet meer dekt — de drempels zijn van dat minimum afgeleid in plaats van los ingesteld, zodat er geen band bestaat waarin Green "genoeg zon" ziet terwijl de auto op zijn laagste stand al meer trekt. 🤖 Green bewaakt óók laadsessies die hij niet zelf startte: laadt de auto overdag zonder dat de zon het dekt, en zonder laadplan of handbediening, dan neemt hij de sessie na ~8 minuten over en pauzeert hem. De 5-minuten-SoC-puls bij het inpluggen blijft ongemoeid; die is allang voorbij tegen de tijd dat Green zou ingrijpen.

Loslaten doet Green afhankelijk van de reden. Bij uitpluggen of handbediening gaat de klem terug naar 16 A (anders zou een handmatig gestarte sessie op 6 A blijven hangen), maar zolang er een auto aan hangt die Green zelf heeft gepauzeerd, blijft de paal staan zoals hij is: geen ongestuurde vrijgave aan het eind van de dag, dus geen auto die 's avonds ongemerkt op netstroom doorlaadt. Neemt het nachtelijke laadplan het over, dan krijgt de paal zijn drie fasen terug als Green op één stond. Alle drempels en wachttijden zijn instelbaar via Instellingen ("Zon-laden — regelparameters"), met een zonstand-terugkoppeling zodat meteen zichtbaar is of de ingevoerde locatie klopt.

De meetreeks loopt via het logisch/fysiek-model naadloos door over de omzetting heen. Een eigen logcollector haalt 1× per uur de logfile van de paal op (met een responstijd-kanarie die geheugendruk vroeg signaleert), zodat paal-herstarts, webserver-drukte en loadbalancing-gedrag vanuit Homey_Log zelf te onderzoeken zijn. De line_id in die logfile is geen oplopende teller maar een positie in een circulaire buffer die elke ~21,5 uur wrapt; de collector dedupliceert daarom op ts (echte kloktijd, altijd oplopend) met UNIQUE(ts, line_id), zodat een wrap geen regels laat wegvallen. De herstart-signalering let alleen op woorden die in normaal bedrijf niet voorkomen: "power on" alléén is misleidend, want zo heet ook de doodnormale regel waarmee de paal meldt dat het laadcontact dichtgaat — elke laadstart zou anders een waarschuwing "mogelijke paal-herstart" opleveren. Zie PLAN-alfen-modbus-migratie.md en PLAN-alfen-logcollector.md.

Twee thuisbatterijen, meerdere regelstrategieën

De batterijen kunnen worden gezet op een dynamische strategie die het kwartierplan volgt, op een vaste balanceer-modus die simpelweg near-realtime naar nul-op-de-meter regelt, of op stilstand. Welke van de twee actief is, wisselt zelfs mee met winter- versus zomerseizoen: 's zomers draait het huis continu op de balanceer-modus, 's winters op het fijnmazige kwartierplan.

Logbron: de thuisbatterijen zelf

Naar het model van de paal-logcollector kijkt Homey_Log ook rechtstreeks mee met de accu's, via hun lokale API. De Sessy heeft geen logfile zoals de Alfen, wél een rijke state-machine (14 systeemstates, waaronder fout- en override-toestanden, plus strategie-, firmware- en P1-statussen); de collector pollt elke minuut de status-API en logt alléén de overgangen — dat is het gebeurtenissenlog. Te lezen in een eigen tab (Base▾ → Sessy-log) én verweven in de algemene Logs-tab (origin "Sessy"; storingen als warn/error, strategie- en firmware-events als info, routinewissels als debug). De verdiepende events (strategie-wissels, firmware/OTA en de P1-dongel-status, die op firmware v5.x alleen nog de v2-API serveert) lopen zodra de sticker-credentials per apparaat zijn ingevuld (Instellingen → "Thuisbatterijen (Sessy) — log-toegang"). De Sessy bewaart zelf géén foutenlog-historie — de web-UI leidt meldingen live af uit system_state_details — dus het transitie-log van Homey_Log is de enige plek waar die geschiedenis wordt opgebouwd. De collector doet uitsluitend lees-verzoeken en raakt de sturing niet aan. Zie PLAN-sessy-logcollector.md.

Logbron: de omvormers zelf

Naast de paal en de accu's kan Homey_Log ook rechtstreeks meekijken met de SolarEdge-omvormers, via dezelfde cloud-API als de zon-backfill. Ook hier is er geen kant-en-klaar foutenlog (/alerts en /changeLog geven 403), dus wordt het afgeleid uit de telemetrie. De omvormer-modus zelf is onbruikbaar als event-bron — 's nachts staat hij óók op STARTING — dus alleen de storingsmodi (ERROR/FAULT/SHUTTING_DOWN/THROTTLED/LOCKED_*) worden gelogd. Wat verder als signaal telt: telemetriegaten overdag (een gat is bewijsbaar een storing), isolatieweerstand (groundFaultResistance, veiligheidssignaal), oververhitting, en het aantal aangesloten optimizers (uitval = paneelstoring). Dat laatste en de firmware-versies zitten niet in de 5-minuten-telemetrie maar in de dagelijkse /inventory-call. Uurlijkse poll, 150 minuten terugkijk-venster (dekt een gemiste run), callbudget ruim binnen de SolarEdge-limiet (~7 van de 300/dag). Te lezen in een eigen tab (Base▾ → SolarEdge-log) én verweven in de algemene Logs-tab (origin "SolarEdge"). De collector doet uitsluitend lees-verzoeken; de kill-switch staat in Instellingen → "Zonnepanelen (SolarEdge) — omvormerlog". Zie PLAN-solaredge-logcollector.md.

Netwerk-apparaten: IP/MAC-naslag voor troubleshooting

Marc heeft daarnaast een eigen netwerktool draaien dat IP-adressen, MAC-adressen, vendor, locatie en online-status van alle LAN-apparaten bijhoudt, en dat tool heeft een eigen JSON-endpoint (/api/homey-log/devices) speciaal voor Homey_Log. Dat tool zorgt zélf al voor een logregel bij een nieuw apparaat of een IP-wijziging (rechtstreeks naar /api/log) — Homey_Log doet hier iets anders: 1x per dag de volledige lijst ophalen en als actuele snapshot bewaren (network_devices, upsert per MAC-adres), géén event-log. Dit is een naslagtabel zoals devices of mapping: te bekijken in een eigen tab (Base▾ → Netwerk) en rechtstreeks met SQL opvraagbaar door de support-agent (schema-guide.md), zodat bij troubleshooting meteen duidelijk is welk IP/MAC bij welk apparaat hoort en of het online is. Instellingen → "Netwerk-apparaten — IP/MAC-overzicht" heeft de endpoint-URL, poll-interval en een "Nu ophalen"-knop.

De laadpaal en de batterij: wie wint? (XOM)

Zodra de auto begint te laden, moeten de batterijen zich anders gedragen dan normaal — ze mogen niet zomaar meebalanceren, want dan zou een deel van het autolaadvermogen ongemerkt uit de batterij komen. In plaats van de batterijen dan stil te zetten (het veilige, maar verspillende gedrag), balanceren ze tijdens auto-laden door naar "nul + laadvermogen": ze dekken alleen het niet-auto-huisverbruik en kunnen de auto dus nooit in leeglopen. Het mechanisme is een Homey-script dat elke 5 minuten een importdoel op de batterij-regeling zet, teruggekoppeld via de laadpaal. De aanleiding zit in de hindsight-benchmark (zie hoofdstuk 08): die laat zien dat de batterijen structureel maar ~79% van hun theoretisch optimale rendement halen — een gat van zo'n €16/maand — waarvan een flink deel komt doordat de batterijen tijdens auto-laden stilstaan in plaats van door te werken.

Dit mechanisme staat los van "alleen de laadpaal": Instellingen heeft een configureerbare uitsluitlijst — apparaten die de batterijen niet compenseren, met een instelbare drempel (vanaf hoeveel watt het meetelt) en venster (alleen 's nachts, of altijd). Een ingebouwde veiligheidsclamp zorgt ervoor dat het systeem nooit méér "laat staan" dan wat het huis op dat moment werkelijk uit het net en de batterijen trekt — bij zonne-overschot gebeurt er dus niets, ook al staat een apparaat op de lijst.

De Usage-grafiek toont op élke tijdschaal — kwartier, dag, week, maand en jaar — dezelfde gele zon-laag met een groene accu-laag erboven; ook de jarenvergelijking (meerdere jaren náást elkaar) toont de zonneopbrengst als zo'n laag. Die band maakt de uitsluitlijst ook zichtbaar: de uitgesloten verbruikers worden bovenaan de staaf gestapeld (met een donkere rand en een ⚡-merkteken in de legenda), dus bóven de zon/accu-dekkingsband die áchter de staven ligt. Zo zie je op elke schaal in één oogopslag dat juist dát verbruik niet door de accu gecompenseerd wordt — het steekt boven de gekleurde band uit.

Vaatwasser-planning

Een script herberekent doorlopend de goedkoopste starttijd voor de vaatwasser op basis van de actuele kwartierprijzen, met drie mogelijke uitkomsten die elk hun eigen betekenis hebben: een vers plan, "geen vaatwasser gepland" (er staat simpelweg niets klaar), en "bestaand plan is nog actueel" (het script herberekende alleen niet, wat geen synoniem is voor "niet gepland").

Auto-telemetrie

Een aparte integratie levert de écht gemeten batterijstand van beide auto's plus laad- en locatiestatus, in plaats van een schatting — de mobiele en desktop-tegels tonen daardoor een percentage dat overeenkomt met wat de auto zelf rapporteert, niet met een indirecte Homey-inschatting.

08

Bewijslast: klopt het ook echt?

Een systeem dat zichzelf beoordeelt heeft een onafhankelijke meetlat nodig — anders controleert het alleen zichzelf. Daarom is er, los van de AI-laag, een deterministische validatiestapel:

  • Hindsight-benchmark: voor elke dag wordt achteraf met een exact dynamic-programming-optimum berekend wat de best mogelijke batterijinzet had opgeleverd, gegeven de werkelijke zon en het werkelijke huisverbruik van die dag. Het verschil tussen dat optimum en de werkelijke uitkomst is de harde meetlat voor "hoeveel valt hier nog te winnen" — de benchmark laat een batterijbenutting van ~79% zien, een gat van zo'n €16 per maand en de directe aanleiding voor het batterij-tijdens-autoladen-mechanisme hierboven.
  • Golden days: een bevroren set van acht controles tegen de echte API-eindpunten, die na elke ingrijpende datamodel-wijziging bewust opnieuw wordt bevroren — een regressietest voor de cijfers zelf.
  • Externe reconciliatie: maandelijkse en dagelijkse cijfers worden teruggelegd naast de energieleverancier (Tibber) als onafhankelijke tweede bron. Die vergelijking legde overigens een structurele ondertelling van het live importverbruik bloot in bepaalde overgangsuren — een bevinding die zonder die externe toets vermoedelijk nooit was opgevallen.
  • Consistency-watchdog: een dagelijkse achtergrondcontrole die willekeurige dagen herafleidt uit de kwartierbron en vergelijkt met de opgeslagen dagwaarde, en maand- tegen jaartotalen — puur om te voorkomen dat twee tabbladen ooit stilzwijgend uiteen gaan lopen.
  • VM-usage-monitor: een nachtelijke read-only check van de server zelf — schijfgebruik, database-grootte en rij-tellingen gaan als tijdreeks naar een system_metrics-tabel én als regel in het log, met een groeigrafiek onder de "VM Usage"-tab (in het "Homey_Log"-menu). Configureerbare drempels sturen een Pushover-melding voordat de schijf vol raakt, zodat datagroei zichtbaar wordt vóór het een probleem is.
  • Green-telemetrie ("Zon-laden"): een losse server-side sampler die, náást Green's eigen beslislogica, de realiteit van het zonneladen vastlegt — paal-toegepaste stroom per fase, P1-fasestromen en -belasting, Sessy-vermogen/SOC — in een brede, ruwe green_telemetry-tabel. Adaptieve cadans: elke 5 seconden zodra de auto is ingeplugd, anders elke 30 seconden, zodat elke bijsturing van Green in hoge resolutie wordt gevangen zonder een aparte "burst rond het setpoint" nodig te hebben. De tab (Base▾ → Zon-laden) toont een uPlot-grafiek met setpoint-commando vs. paal-toegepast en Green_Tick-events als tijdlijnmarkers, plus een tabel met CSV-export. In de grafiek staan ook de fasestromen van huis én laadpaal naast elkaar: de kleur zegt welke fase (licht, midden, donkerblauw voor L1, L2, L3), de lijnstijl zegt welk apparaat — gestippeld is het huis, doorgetrokken is de paal. Een donkere verticale lijn markeert elk moment waarop de regie wisselt tussen zon-laden en het nachtplan. Bewust zonder automatische analyse of verdicten: de tabel is de ruwe meetbron, de duiding gebeurt in de reviews.

De les die uit deze hele stapel is blijven hangen: interne consistentie-checks zien het soort fout dat hier keer op keer opdook niet — alleen toetsing tegen een externe, onafhankelijke bron (de energieleverancier, de brontabel, de werkelijke Homey-flow) legt het bloot.

09

Hoe dit gebouwd wordt

Het hele project staat sinds half juni onder git-versiebeheer. De werkwijze is zelf een klein staaltje van het onderwerp van dit artikel: een AI-model schrijft een specificatie en doet achteraf review, een tweede model voert de implementatie uit, en elke niet-triviale wijziging wordt live tegen de echte Homey geverifieerd voordat hij als "af" geldt — inclusief, bij de experimenten-fase, een bewuste test met een écht getriggerde noodstop, niet alleen een simulatie.

10

Het gezinsdashboard: een derde ingang, voor het hele huishouden

Naast de desktop-beheeromgeving is er /dashboard/, bedoeld als vast wandtablet voor het hele gezin — niet alleen voor degene die het bouwde. Op het wandtablet is dit een kiosk: donker als standaard (avondgebruik aan de muur), grote tegels en cijfers die op ~2 meter afstand leesbaar zijn, geen navigatie naar beheer, logs of experimenten, en een timer die na een minuut inactiviteit automatisch terugkeert naar het overzicht. 📱 Hetzelfde dashboard is de mobiele ingang: /m stuurt door naar /dashboard/?view=phone, en die URL — niet de schermbreedte — bepaalt de weergave: tegels vol-breed onder elkaar, de agenda verborgen, popups als bottom-sheet in plaats van een los venster. Schermbreedte-detectie is bewust vermeden: die is op een echte telefoon niet betrouwbaar, vermoedelijk door hardnekkig gecachete CSS. /dashboard/ zonder parameter — het wandtablet — is altijd de tabletweergave, ook op een telefoonformaat scherm. 🤖 Bewust één component in plaats van twee: een tegel die op het wandtablet verandert, verandert vanzelf mee op de telefoon. Beheerwerk (voorstellen, logs, chat) hoort daar niet thuis; dat gebeurt op de gewone desktopsite, die directe tab-links (/#voorstellen) ondersteunt. Ook hier geldt: puur een presentatielaag, geen nieuwe databron, alleen hergebruik van bestaande /api/...-endpoints en de bestaande schrijf-whitelist voor lampen.

Beide weergaven delen één stijlblad, maar niet één maatvoering. Alle maten staan als clamp(minimum, breedte-afhankelijk, maximum), en op een telefoonscherm van 390 pixel breed is die middelste term zó klein dat alles op het minimum zou landen — de kleinste variant die het ontwerp kent. Daarom zet de telefoonweergave haar eigen maten: meetwaarde 58px, label 20px, tegels 220px hoog, met de topbalk (titel, klok, statuspil, gezichten) en de popup-inhoud (regels, lijsten, knoppen, grafiek-labels) in verhouding mee. Het wandtablet zit juist bovenin diezelfde reeksen — 36px, 152px hoge tegels — en blijft daar staan, want alle telefoon-maten hangen onder de weergave-schakelaar en gelden nooit voor het tablet. Het energiestromen-diagram houdt bewust zijn eigen maat: dat is een tekening met vaste geometrie, waarin grotere labels over de cirkels heen zouden lopen.

Voor een snelkoppeling op het beginscherm levert het dashboard een apple-touch-icon en een manifest — zonder die twee maakt iOS zelf een schermafdruk van de pagina als icoon; een SVG-favicon gebruikt het daar niet voor. Het zijn er twee: de telefoon krijgt een manifest dat opstart in telefoonweergave, het wandtablet één dat opstart in tabletweergave, zodat een bewaarde app nooit in de verkeerde weergave opent. Het head-script kiest de juiste en maakt die link zelf, in plaats van een vaste link achteraf te corrigeren: Safari haalt het manifest al tijdens het parsen op en zou dan de verkeerde te pakken hebben.

Hoofdscherm van het gezinsdashboard: twaalf tegels (energie, thuisbatterijen, beide auto's, lampen, boiler, vaatwasser, stofzuigrobot en de energie-KPI's) links, de familie-agenda rechts
Het hoofdscherm: twaalf tegels in drie rijen met rechts de familie-agenda (hier geblurd), de aanwezigheidsrij (wie is er thuis) bovenin, en onderaan de snelle acties. De gloed/pill volgt de huisstatus.
TegelDatabron
Energie nulaatste volledige kwartier uit energy_quarterly (grid_import/export, solar) — geen live wattmeting beschikbaar, dus expliciet gelabeld met "bijgewerkt X min geleden"
Thuisbatterijengemiddelde sessy_soc_1/2, laad/ontlaad-richting uit het verschil met het vorige kwartier
Auto's (2×)hetzelfde /api/car-status-eindpunt als desktop — dynamisch, geen hardcoded aantal auto's; ook live laadpaal-telemetrie (of, hoe snel en hoe een auto laadt)
Lampen & sferendevices-tabel (class light) × live onoff-status; per lamp of in bulk uit te zetten via de bestaande write-whitelist. Tweede tab: Homey's Moods ("Sferen")
Boilermidden groot: watertemperatuur (placeholder zolang die sensor ontbreekt); onderin: actueel vermogen van de boiler-meter ('Plug-Boiler', via de logische-device-laag de opvolger van het 'Douche'-stopcontact); douche-indicator uit de 'Presence douche'-bewegingssensor. Popup met twee tabs: Boiler-verbruik (uur-staafgrafiek vandaag, kWh/€-toggle, + douchebeurten-schatting) en Douche-verbruik (leeg zolang er geen waterflow-sensor is)
Vaatwassercar-status.js's buildDishwasherStatus() + live Bosch-telemetrie; starttijd/resterende tijd ook op de tegel zelf, plus een "Start nu"-knop
Stofzuigrobotlogica-variabelen 'Stofzuiger Actief ?'/'Stofzuiger Geforceerd Gestart ?' (grondwaarheid van de Homey-flows zelf) + live automatisch-schema uit de flow 'Stofzuigrobot starten'
Zelfvoorziening / Direct solar / Verbruik in EUR / Feitelijke kostendezelfde compute-functies als de Usage-tab (computeZelfvoorziening/computeDirectSolar/buildEnergyAI), venster altijd "vandaag"

De tegelset is bewust smal gehouden (een vaste, dynamisch-gecategoriseerde apparatenlijst in plaats van een volledige ontdekkings-UI) en toont geen misleidende tegels — dit huis heeft bijvoorbeeld geen echte raam- of deursensoren, dus die tegel bestaat niet. Elke schrijfactie loopt via de bestaande whitelist. De enige globale snelactie is "alle lampen uit" (met bevestiging); daarnaast triggert elke losse tegelknop een bestaande, ge-whiteliste Homey-flow — vaatwasser starten, stofzuiger starten of naar het dock sturen, handmatig laden starten, een sfeer zetten. Zwaardere snelacties zijn er bewust niet: alleen wat via een ge-whitelist schrijfpad kan, staat op het scherm.

Achter de Energie-tegel zit een energiestromen-diagram (net/zon/batterij/beide auto's → huis, met een "nu"/"vandaag"-toggle en een kWh/€-schakelaar, samengebracht in één rustige control-strip). Het huis is het ademende hart in het midden — een zachte gloed in de status-kleur, dezelfde taal als de ademende gloed van het dashboard zelf — en energie stroomt als licht (deeltjes) door gebogen conduits: naar binnen bij import, zon of ontladen, naar buiten bij laden. Actieve knopen krijgen een kleur-halo, inactieve blijven rustig, zodat je in één oogopslag ziet wat er leeft. De stroom-splitsing bouwt op routes/energy-model.js — dezelfde module die de Zelfvoorziening/Direct-solar-popups voedt — en respecteert de balansidentiteit net-in + zon + batterij-uit = huis + auto + net-uit + batterij-in zonder dat er iets los bijgeteld wordt. In de €-stand waardeert elke stroom tegen de netprijs van dat kwartier (een bewuste vereenvoudiging: geen apart teruglever- of laadpaal-tarief zoals /api/effect-split dat wél kent). De popup ververst zichzelf elke 20 seconden zolang hij open staat; écht live kan niet, want de onderliggende meterdata ververst maar eens per kwartier.

Een tweede tab "Verloop" toont een uurgrafiek van vandaag — dezelfde areas-lezing als de Usage-tab, in de rustige kiosk-variant: één verbruiksbalk per uur met dáárachter de zonneopbrengst (geel) en de accu-levering (lichtgroen, gestapeld op de zon), plus de import als gestippelde lijn. Valt een balk binnen de gekleurde band, dan werd dat uur door zon/accu gedekt; wat erbovenuit steekt kwam van het net. Bron is /api/verbruik-data (uur-korrel), bewust handgetekend in SVG in plaats van via een chart-library, zodat het dashboard z'n eigen stijl houdt én dependency-vrij blijft.

Energiestromen-diagram: het huis als gloeiend hart in het midden, met net, zon, thuisbatterij en beide auto's als knopen eromheen en energie die als licht door de conduits stroomt
Het energiestromen-diagram in "Nu"-stand: het huis is het ademende hart en energie stroomt als licht door de conduits — naar binnen bij import/zon, naar buiten bij laden. Hier levert de zon 6,3 kW; het teveel gaat deels de accu in (laden, 78%) en deels terug het net op (1,7 kW), terwijl de auto op zon laadt. Elke knoop is ook klikbaar voor een dagverloop (zie verderop).

De aanwezigheidsrij rechtsboven in de kopbalk kent één grondwaarheid-valkuil: de "Smart Presence"-Homey-app gebruikt exact dezelfde presence-capability voor gezinsleden als voor generieke netwerkapparaten, dus "Internetverbinding" of een vriezer in de schuur melden zich net zo goed als "aanwezig" als een gezinslid. Een naam-allowlist (net als de auto-identifiers in car-status.js) filtert dat eruit. Voor mensen en auto's staan er bewust abstracte placeholders (initialen-cirkels, een silhouet-kaart) in plaats van foto's.

De vaatwasser is een Bosch Home Connect-apparaat met echte statustelemetrie (voortgang, resterende tijd), met één eigenaardigheid: idle levert het een absurd hoge "resterende tijd" (241 uur) als sentinel voor "geen actief programma", dus de tegel behandelt alles boven 5 uur als "niet actief". De detailpopups van de thuisbatterijen (per accu) en beide auto's tonen een dagverloop-grafiek (00:00 → nu): batterij-historie uit energy_quarterly (dezelfde sessy_soc_1/2 als de tegel), auto-historie uit de Tronity_Publish_Vehicles-logregels (~elke 2 minuten een sample). Het is een handgetekende SVG-lijngrafiek met een kleurenpaar dat door de dataviz-validator is gehaald (kleurenblind-scheiding, contrast op het donkere oppervlak).

Autopopup met laadstand-lijngrafiek van vandaag, van middernacht tot nu
De auto-popup met het dagverloop onderaan: laadstand vanaf middernacht, inclusief de laadsessie die het percentage in één keer optilt.

Een fullscreen camerapopup verschijnt automatisch zodra er wordt aangebeld. Homey heeft Ring volledig geïntegreerd (7 camera's + 1 deurbel), dus het camera-snapshot-mechanisme van de 3D-huis-tab is direct herbruikbaar voor het tónen; voor het "automatisch bij het belmoment"-deel logt een kleine Homey-flow (Deurbel_Gebeld) het belmoment via de generieke postlog-route. Een échte videostream is niet mogelijk — Homey's camera-video en WebRTC-laag zijn producer-only en niet blootgesteld aan externe API-clients — dus de popup ververst een losse snapshot elke anderhalve seconde. De "actie nodig"-statuspill telt alleen een échte error; de pill en de Meldingen-status doven na het openen van de Meldingen-popup tot er iets ná dat moment bijkomt ("gezien"-status client-side in localStorage, de server blijft stateless).

Elke knoop in het energiestromen-diagram (net, zon, thuisbatterijen, en beide auto's apart) is klikbaar en opent een staafgrafiek van vandaag (per uur sinds middernacht) die bóven de open Energiestromen-popup stapelt zonder die te sluiten. Net en batterijen krijgen een tweerichtingen-staafgrafiek (afname/teruglevering, laden/ontladen); zon en de auto's een enkelvoudige. Er is maar één laadpaal-meting, dus de toeschrijving welke kWh bij welke auto hoort gebruikt de Alfen_Resolve_Active_Car-logregels: per kwartier wordt gekeken welke auto actief was. In het diagram heeft elke auto een eigen knoop (de Tiguan en de A3, elk met eigen laadvermogen), toegeschreven aan wie er op dat moment daadwerkelijk laadt.

Staafgrafiek-drill-down: zonopwek per uur sinds middernacht, gestapeld boven de energiestromen-popup
Klik op de zon-knoop → een staafgrafiek van vandaag, gestapeld bóven de Energiestromen-popup (te zien op de achtergrond) zonder die te sluiten.

De lampen staan gegroepeerd per kamer (de kamer-naam uit de devices-tabel wordt een sectiekop). De Lampen-tegel heeft een tweede tab met Homey's Moods ("Sferen"), elk met een passend icoon. Homey's Moods-manager bestaat niet in de lokale Web-API die deze app gebruikt, dus elke sfeer loopt via een minimale 1-op-1-doorgeefflow (Dashboard_Sfeer_<naam>), ge-whiteliste in TRIGGERABLE_FLOWS — hetzelfde patroon als de andere flow-knoppen. Homey kent geen "welke sfeer is nu actief"-status (een mood is een momentane actie), dus de sferen-lijst toont bewust geen highlight. Het 3D-huis is fullscreen op te roepen vanaf het dashboard als alternatieve bedieningsingang: geen aparte rendering, maar de bestaande Huis-tab via een "embed-modus" (?embed=huis) in een fullscreen iframe.

De Boiler-tegel toont groot de watertemperatuur (placeholder tot die sensor gekoppeld is), onderin het actuele opgenomen vermogen van de boiler-meter, en een 🚿-indicator "er wordt gedoucht" (afgeleid uit de bewegingssensor, zolang er geen waterflow-sensor hangt). De popup heeft twee tabs die "welk getal is de waarheid"-verwarring voorkomen: Boiler-verbruik is de harde meting (uur-staafgrafiek van vandaag met kWh/€-toggle + dagtotaal, plus een douchebeurten-schatting uit aaneengesloten verwarmings-kwartieren × de netFlow-kwartierprijs), en Douche-verbruik blijft bewust leeg tot er een echte waterflow-sensor is. De Thuisbatterijen-popup toont per accu ook het maandelijkse laad/ontlaad-rendement (ηcum) — hetzelfde groene percentage als de Accu-tab, geen State-of-Charge.

De auto-tegel toont niet alleen de SOC, maar ook of, hoe snel en hoe een auto laadt. Het "hoe" (laadplan / zonneladen / handmatig) kan niet uit het laadplan alleen komen — een auto die laadt terwijl het plan al "doel bereikt" meldt, is een handmatige override — dus car-status.js leest ook het laadpaal-device (evcharger_charging + measure_power) en redeneert: een geldige plan-reden wint, anders is "handmatig" de verklaring. Een "Start handmatig laden"-knop in de auto-popup verschijnt zodra die auto aan de lader hangt. Een stofzuigrobot-tegel (Roomba) leest zijn automatische starttijd live uit de Homey-flow Stofzuigrobot starten zelf (geen hardcoded tijd); of de robot nú loopt komt uit dezelfde twee logica-variabelen die Homey's eigen stofzuig-flows als grondwaarheid gebruiken, zodat de tegel altijd in sync is met wat de automatisering denkt.

Een familie-agenda (Google Calendar-embed) staat permanent in beeld, niet achter een tegel. Het hoofdscherm is een tweekoloms-layout: links de tegel-rijen met de snelle acties, rechts de agenda-kaart over de volle hoogte; erop tikken opent een fullscreen-popup waarin wél te scrollen valt. Omdat het een cross-origin Google-iframe is, kan de Google-chrome wél weggestript worden (via de show*=0-embedparameters) maar niet de interne opmaak — Google rendert die zelf en volgt de donkere modus van het apparaat. Een transparante "click-catcher"-laag boven de iframe vangt de tik op (de iframe zelf zou clicks afvangen); de kaart ververst elke 30 minuten met een cache-bust zodat "vandaag" blijft kloppen op een tablet dat nooit uitgaat.

De aanwezigheids-avatars zijn klikbaar: een popup per persoon met thuis/weg, "laatst gezien" en een "Vandaag"-lijst van thuisgekomen/vertrokken-momenten sinds middernacht. Die tijdlijn komt uit device_events: de generieke "device events sampling"-flow stuurt elke minuut van elk apparaat een vaste lijst capabilities naar de server, die zelf bepaalt wat een event is — presence zit in EVENT_CAPS, hetzelfde pad als lamp aan/uit. (Smart Presence hangt presence ook aan niet-mens-apparaten; die worden bij het tonen uitgefilterd, niet in het generieke script zelf.)

Als kiosk ververst het dashboard zichzelf elke 5 minuten (een wandtablet hoeft niet vaker), met een handmatige ververs-knop in de topbar en een "Bijgewerkt X geleden"-label dat elke 15 seconden meetikt — óók als het verversen stilvalt. Veroudert het label zichtbaar, dan weet je dat er iets hapert in plaats van naar een bevroren maar actueel-ogend scherm te kijken.

11

Waar dit heen gaat

Een aantal sporen ligt bewust geparkeerd in plaats van afgesloten. Het meest in het oog springend: een idee om de batterijstrategie ook overdag tijdens zonneladen aan te passen, zodat een auto die op overschot laadt nooit méér uit de batterij trekt dan nodig — pas op te pakken zodra het huidige nachtelijke experiment is afgerond en geëvalueerd, om te voorkomen dat twee gelijktijdige wijzigingen elkaars effect verhullen. Andere open punten zijn kleiner van aard: de vraag of goedkopere, third-party taalmodellen dezelfde analysekwaliteit halen als de huidige standaardkeuze tegen minder kosten per token.

Het overkoepelende doel: volledig inzicht in wat het huis doet, en een huis dat zichzelf periodiek doorlicht, met voorstellen die een mens beoordeelt — niet een huis dat zonder toezicht aan de knoppen zit.