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: 13 september 2026
2.796
commits sinds 12 juni
105k
regels servercode
116
databasetabellen
380
modules in routes/
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
Proposals- en Experiments-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.

De beheeromgeving waarin dat allemaal samenkomt is één pagina met een navigatie in twee rijen: bovenin vijf groepen — Energy, House, Agent, Sources en System — en daaronder de pagina's van de groep waarin je staat. In beide rijen licht er iets op, zodat altijd zichtbaar is waar je bent én waarbinnen. De indeling volgt de vraag die je stelt: wat doet mijn energie, wat doet mijn huis, wat vindt het systeem ervan, wat is er ruw gebeurd, en hoe staat de app er zelf voor. Elke analysepagina opent met één regel die vertelt wat je ziet — bijvoorbeeld dat een onderbreking in een klimaatlijn een periode zonder meting is en geen waarde van nul, en dat de watermeter het hele huis meet terwijl de doucheteller maar één leiding volgt. Zulke uitleg staat boven de grafiek en niet eronder: eronder lees je hem pas nadat je de lijnen al verkeerd hebt uitgelegd. De teksten staan als standaard in de code en zijn per pagina aan te passen in Instellingen, waarbij alleen een aangepaste regel apart bewaard wordt — wat je niet aanraakt, loopt vanzelf mee met de app. De beheeromgeving opent op de pagina waar je gebleven was, tenzij de link zelf een pagina noemt: een gedeelde /#voorstellen wint altijd van die herinnering.

Die beheeromgeving is één van drie ingangen, elk met een eigen publiek. De desktopsite is het volledige gereedschap: 33 pagina's, alle detail, alle knoppen. Het wandtablet in de eetkamer draait het gezinsdashboard — grote tegels, geen beheer. En op de telefoon staat de app "Thuis", die het huis samenvat tot wat je onderweg wilt weten. De regel daartussen is streng: de telefoon toont nooit iets wat de hoofdapp niet heeft. Pariteit of minder, nooit meer — anders ontstaan er twee waarheden over hetzelfde huis.

Inloggen, per gezinslid

De app kent persoonlijke accounts. Ieder gezinslid meldt zich één keer per apparaat aan en de app onthoudt dat; wat je daarna ziet en mag, hangt af van je rol. Die rollen zijn één ladder van vier treden, en elke trede omvat alles van de treden eronder: lezen is alleen kijken; huisgenoot mag daarbovenop het comfort in huis — lampen, sferen, gordijnen en de stofzuigrobot; bedienen mag alles aansturen, van de auto en de vaatwasser tot de boiler en de bedrijfsmodi; en beheren is het volledige gereedschap: instellingen, regels, accounts en de technische bronnen. Wie kijkt of bedient, krijgt de beheerpagina's simpelweg niet te zien: de navigatie toont alleen wat bij je rol hoort. Het gesprek met de analist staat daar los van, per persoon aan of uit — een chatvenster dat het hele huis als context heeft, geef je bewust en niet automatisch.

Die ladder staat op één plek, en "mag dit?" is één pure functie zonder database of omgeving eromheen: een onbekende rol haalt niets. Een route noemt zijn eis met een bewaker — minstens huisgenoot, minstens bedienen — en een handler vraagt daarna hoogstens nog van wíe een rij is, nooit óf iemand mag. De browser kent de rolnamen niet eens: hij krijgt bij het aanmelden alleen de lijst treden die dit account haalt, en verbergt daarmee wat er niet bij hoort. Zonder profiel staat er geen enkele bedienknop — en de server weigert sowieso, want een verborgen knop is comfort en geen beveiliging. Drie tests bewaken dat mechanisch: elke schrijfroute is beschermd of staat mét reden op een open lijst, elke route geeft de trede eronder werkelijk een weigering, en elke bedienknop draagt op elk van de drie schermen zijn eigen klasse.

Álles onder /api zit achter diezelfde poort, ook het lezen. De open lijst is kort en per regel beargumenteerd, en daar zitten de twee uitzonderingen in die met opzet bestaan. Het wandtablet in de eetkamer heeft geen inlogscherm — een muurscherm waarop niemand meer iets bedient omdat er eerst een wachtwoord in moet, is zijn functie kwijt. En de ingang waarlangs Homey zelf zijn logregels aanlevert blijft open: die moet ook blijven werken op een moment dat niemand kijkt, anders valt de databron stil zonder dat iemand het merkt.

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.

Hoe belangrijk is een logregel? — het vijfde niveau "onbekend"

Elke regel in het logboek krijgt een belangrijkheid: debug (routine), info (een uitkomst), waarschuwing of fout. Het probleem was hoe die belangrijkheid werd bepaald. Apparaten spreken hun eigen taal — de laadpaal zegt bijvoorbeeld "WARNING" of "COM" — en alles wat niet precies een van de vier bekende woorden was, werd stilzwijgend als routine weggezet. Zo bleken 74 échte waarschuwingen van de laadpaal maandenlang onzichtbaar: het Logboek toont routine namelijk niet, dus niemand wist dat ze er waren.

Nu is er één plek die beslist, in drie stappen: zegt het apparaat zelf een van de vier woorden, dan geldt dat. Zo niet, dan kijkt een lijstje vertaalregels ("zegt de laadpaal WARNING, dan is dat een waarschuwing"), en die regels kan Marc zelf beheren in Instellingen. En matcht er niets, dan krijgt de regel het vijfde niveau: onbekend. Dat is geen foutmelding maar een to-do-lijstje — Instellingen laat zien welke onbekende soorten er binnenkomen, en Marc besluit per soort hoe belangrijk die is. De oorspronkelijke term van het apparaat blijft altijd bewaard, dus een verkeerde inschatting is later te herstellen: hij kan de regels aanpassen en ze opnieuw over de bewaarde logregels laten lopen.

De regels worden op volgorde afgelopen: de eerste die past wint. Daardoor kan een regel die eronder staat stilletjes nooit aan de beurt komen, en dat is precies wat er gebeurde — bij een opruimronde bleken zes van de 39 regels niets te doen: dubbel ingevoerd, of afgevangen door een regel erboven. Het systeem weigert zo'n regel op te slaan en zegt het erbij welke regel hem al afvangt, zodat de lijst die je leest ook echt de lijst is die draait. Alleen bewijsbare gevallen worden geweigerd; bij twijfel laat het systeem de regel gewoon toe, want een onterechte weigering is erger dan een overbodige regel.

De achterliggende gedachte is dezelfde als bij de rest van het systeem: liever hardop "dit weet ik niet" dan een stille aanname die als feit gaat leven.

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.

Retentie: hoelang blijft iets bewaard?

Al dat vastleggen betekent ook: de database groeit, elke dag, en ruimt zichzelf nooit vanzelf op. Daarvoor draait een configureerbaar opruimsysteem. Het kijkt niet per tabel, maar per rol die een tabel in het huis speelt: is dit de onvervangbare meetbasis, is dit hoogfrequente diagnostische ruis, is dit een zeldzame betekenisvolle gebeurtenis, of hoort dit nooit weggegooid te worden? Elke rol krijgt zijn eigen bewaartermijn; een nieuwe tabel die er later bijkomt kiest gewoon een van die vier rollen en hoeft geen eigen termijn te verzinnen.

Er zit een vangnet in: elke rol heeft een harde ondergrens, gekoppeld aan wat er daadwerkelijk mee gedaan wordt — bijvoorbeeld "de Logs-tab laat een jaar terug lezen". Zet je de termijn per ongeluk korter dan die ondergrens, dan wordt die tabel simpelweg overgeslagen in plaats van opgeruimd — een te krappe instelling kan dus nooit in het geniep een functie stukmaken.

De aanleiding was een pijnlijke les: een oudere, botte regel ruimde alles ouder dan twee jaar op zonder onderscheid — en veegde daarmee ook de zonne-opbrengsthistorie weg die met veel moeite tot 2018 was teruggehaald uit de omvormer-API. Ruim twee ton meetregels verdwenen in één klap; het zonarchief begon daardoor tijdelijk pas medio 2024. Precies dát mag met dit systeem niet meer gebeuren: één blinde regel voor alles is vervangen door een bewuste keuze per rol, met een ondergrens van 4.000 dagen juist voor deze reeks. De weggeveegde historie zelf staat er weer: 231.479 kwartieren opnieuw opgehaald uit de SolarEdge-API, zonder dat er iets van de tussentijds al binnengehaalde data is aangeraakt. De zonreeks loopt vanaf januari 2018 en de jaartegels kloppen over de hele reeks.

👤 Marc bepaalt per rol de stand: uit, proefdraaien (het systeem berekent en logt wat het zóu weghalen, zonder ook maar iets aan te raken) of actief. 🤖 Elke nacht, ruim voor het ochtendverbruik, past het systeem toe wat er ingesteld staat. De meetbasis en de betekenisvolle gebeurtenissen staan op uit — daar gaat niets weg. Alleen de diagnostische ruis staat op proefdraaien met een termijn van een jaar: het systeem rekent elke nacht uit wat het zou opruimen en schrijft dat op, zonder een rij aan te raken. Zo is er een cijfer om op te beslissen voordat er ooit iets écht verdwijnt. De knoppen staan in Instellingen → Gegevens & systeem → "Retentie".

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 House-tab van de desktop-app
De House-tab: dakaanzicht met de zonnepanelen zichtbaar. De laagknoppen staan in de volgorde van het huis zelf, van boven naar beneden — Dak, Zolder, Eerste verdieping, Begane grond — en schakelen samen met de Live/Comfort-toggle 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.

Naast het aan- en uitzetten van lagen kent de weergave ook een Exploded-stand: één knop legt het huis uit elkaar, met dak, zolder, verdieping en begane grond als losse panelen naast elkaar op de grond. Zo kijk je in álle lagen tegelijk — handig om in één oogopslag te zien welke lamp er ergens in huis nog aan staat, zonder verdieping voor verdieping af te pellen.

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.

Die overlay haalt precies de niveaus op die ook aan staan. Dat lijkt een detail, maar het is het verschil tussen een meetinstrument en een gerustheid: haalt de tab een vaste hoeveelheid regels op en filtert hij daarna pas in de browser, dan gaat het budget op aan routineregels en vallen juist de errors weg — en omdat de oudste regels het eerst binnenkomen, ontbreekt dan het recentste deel van het venster. De tab kan dan "geen events" tonen terwijl er wél errors zijn. Past een venster alsnog niet in één keer, dan zegt de statusregel tot welk moment je kijkt in plaats van alleen dát er een grens is, en legt de server een waarschuwing in het logboek zodat de dagelijkse analyse het ziet.

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.

Die kern is bewust begrensd. Welke meetreeks "zon" is, welke "accu", welke "net" en welke "laadpaal" wordt afgeleid uit één tabel in de mapping-laag; de rekenmodule zelf kent alleen die begrippen. De historische meetreeksnamen dragen nog een merknaam — hernoemen raakt tientallen namen, tientallen tests en de scripts op de Homey — maar alleen dat ene mappingbestand mag ze nog spellen, en een test maakt elke andere plek rood, met een uitzonderingslijst die alleen mag krimpen. Lezen en schrijven van de kwartiertabel gaat door één deur; rauwe query's daarbuiten staan mét reden op een lijst. Het doel is heel concreet: een dérde thuisbatterij hoort één regel in de mapping te zijn.

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 niet uit het net kwam, gemeten op de import van de hoofdmeter zelf. De auto en de boiler blijven buiten het hoofdpercentage — die laden bewust op prijs, niet op zon, en zouden een cijfer over het hùis anders elke laadnacht platdrukken. De grafiek eronder splitst datzelfde percentage in wat het hele huis écht haalde en wat er alleen bij komt doordat die twee apparaten buiten de som staan
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 over de gekozen periode, uitgesplitst per auto via de HomeyScript-log die bijhoudt welke auto actief laadt — ruim 24% goedkoper dan de kale netprijs door slim te timen. Zit in de popup van de auto-tegels, per auto apart: de laadsessies van díe auto, de zon/net-verdeling en de actuele stand, met eronder één regel met de gemiddelde laadprijs en hoeveel die afwijkt van de gemiddelde netprijs. De laadplan-benutting (hindsight-benchmark) blijft in de API beschikbaar maar staat niet in beeld
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
Welke kilowattuur kwam er nou écht van het net?

Zelfvoorziening leek lang een simpele som, maar er zit een keuze in die het antwoord tientallen procenten kan verschuiven: als in hetzelfde kwartier de auto laadt én het huis draait, en de meter meldt import — van wie is die stroom dan? Het antwoord komt nu uit de meter zelf, niet uit een reconstructie. Per kwartier wordt de gemeten import verdeeld: eerst krijgt het huis zijn eigen, ongemeten rest-last, daarna gaan de bewust uitgesloten apparaten (auto, boiler) tot hoogstens hun eigen gemeten energie. Zo kan een laadsessie de huiscijfers niet vervuilen, en het huis andersom ook niet de laadcijfers.

Er blijft een rest over die logisch niet kan bestaan: import bóven de totale huislast. Die is echt en meetbaar — de klok van de slimme meter loopt zo'n kwartier voor op die van de apparaten, dus een laadpiek verschijnt op de hoofdmeter net iets eerder dan bij de bron. Dat overschot volgt daarom de buurkwartieren: was er vlak ervoor of erna laadactiviteit, dan hoort het bij de veroorzaker, anders bij het huis. De grens die dit bewaakt is een harde: huis-import plus apparaat-import is altijd exact de gemeten import, en geen van beide kan negatief worden — ook niet als een teller terugspringt.

De Power-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").

Twee momentopnames in de Usage-tab: accuvulling en boilerwater. Naast alle periode-cijfers staan er twee tegels die simpelweg nu tonen. De tegel Thuisbatterijen laat de gemiddelde vulling van de twee Sessy-accu's zien, met een vulbalk en per accu het losse percentage; klikken opent het verloop van vandaag per accu en het laad/ontlaad-rendement sinds we begonnen te meten. De Boiler-tegel houdt zijn kosten-cijfer maar vermeldt er de actuele watertemperatuur bij (met een douche-icoon als er op dat moment gedoucht wordt), en de popup opent met het temperatuurverloop van vandaag waarin de gemeten douchebeurten als blauwe staafjes staan — je ziet zo direct de dip die een douche in de boiler slaat. Beide getallen komen uit exact dezelfde bron als de wandtablet, zodat de twee schermen nooit iets anders kunnen beweren. Ze bewegen bewust niet mee met de periode-kiezer: het is een momentopname, geen periode-analyse, ook als je hierboven op "maand" of "jaar" staat.

Boiler-watertemperatuur, echt gemeten. De boiler heeft een Shelly ("Boiler Control", zone Zolder) die het water bemeet (measure_temperature.1); die meting voedt zowel het grote getal op de dashboard-tegel als de analyse-tab "Boiler" in de groep Energy, met dezelfde standaard tijddimensies als de rest van de app (uur/dag/week/maand/jaar), een grafiek met een gemiddelde-lijn en desgewenst een lichte min/max-band (de dagelijkse op- en afkoel-swing van de boiler), een tabel en CSV-export. De reeks loopt vanaf de eerste meting en heeft geen retroactieve historie — hij vult zich vooruit. Deze meting is de meetbasis onder de boilersturing die verderop staat: warm water maken op zonoverschot en op goedkope kwartieren in plaats van op een vast tijdstip.

Een wachter op de boilermeting, en waarom die niet schrikachtig mag zijn. Naast de meting loopt een wachter die 🔔 een bericht stuurt als de boilermeting stilvalt. Die kijkt naar drie dingen: komt er nog wel een nieuwe meting binnen, staat er een waarde in, en is die waarde ook écht vers (Homey blijft een dode sensor namelijk onbeperkt doorserveren alsof alles in orde is — zie de les daarover verderop). 👤 Marc kreeg er een paar keer per dag een melding van dat de temperatuur "onleesbaar" was. 🤖 Nagerekend bleek er niets aan de hand: Homey geeft af en toe één losse meting terug met een verse tijdstempel maar zonder waarde, en een minuut later staat hetzelfde getal er weer. Negen van die hikjes op tienduizend metingen in een week, en omdat de wachter zijn oordeel op één enkele meting baseerde leverde elk hikje twee berichten op — het alarm én een "het is weer goed". Achttien berichten voor nul storingen. De wachter wacht nu drie mislukte metingen op een rij af voordat hij iets zegt, en stuurt geen "het is weer goed" meer voor een storing die nooit gemeld is. Belangrijk detail: een stilgevallen meting meldt hij nog steeds meteen, zonder die drie af te wachten — als er geen metingen meer bijkomen zou zo'n reeks van drie zich immers nooit vullen, en dan zou juist het ernstigste geval stil blijven.

Douche-waterpuls: van pulsen naar liters per douche. Diezelfde Shelly telt nu ook de waterpulsen op de warmwaterleiding van de douche (input_pulse_counts_total). Homey herkent met een flow al dat er gedoucht wordt (en zet het WTW-systeem harder); Homey_Log haakt daarop aan: bij douche-activiteit leest de server zelf de cumulatieve pulsteller live uit, opent een sessie en kijkt daarna zélf wanneer het klaar is — zodra de teller een paar minuten niet meer oploopt sluit de sessie (zeep-pauzes en een korte kraanpauze blijven dus één douche).

Waarom een korte douche veel te laag zou uitkomen. 👤 Marc merkte op dat sommige douches "HEEEL weinig liters" hadden. 🤖 Nagerekend bleek de nulmeting het probleem: Homey's melding komt structureel zo'n anderhalve minuut ná het opendraaien van de kraan, en het water dat in die tijd al door de meter liep viel buiten de sessie. In liters is dat verlies vrij constant — altijd diezelfde anderhalve minuut, zo'n 3 tot 5 liter — en dus verwoestend bij een korte douche: één douche stond op 1,3 liter waar er in werkelijkheid 5,5 door de meter ging. Over alle douches samen was 15% onzichtbaar. De sessie begint daarom nu bij de tellerstand die de minuutmeting twee minuten vóór de melding al had vastgelegd. Waarom precies twee minuten? Doorgerekend op alle gemeten douches geeft één, twee of drie minuten exact dezelfde uitkomst, want de teller ligt tussen twee douches uren stil — twee minuten zit veilig in het midden. De kant van het einde hoefde niet bijgesteld: die was aantoonbaar al compleet, omdat de server na de laatste ophoging nog drie minuten doorkijkt. De ruwe meting blijft in de database staan naast de gecorrigeerde, zodat de ingreep controleerbaar en terug te draaien is. De vijftien douches uit de meetfase zijn herberekend: 52 liter die eerder in niemands sessie viel is nu toegerekend.

De watermeter van het hele huis. Naast de doucheteller — die alleen het water door die ene leiding ziet — hangt er in de meterkast een watermeter met een eigen netwerkaansluiting. De server vraagt hem standaard elke tien seconden rechtstreeks naar zijn tellerstand, precies zoals bij de slimme meter voor stroom. Hoe vaak dat gebeurt — en hoe vaak daar een regel van bewaard wordt — is instelbaar, en een wijziging gaat meteen in zonder de server te herstarten. Bewust niet via Homey, want Homey blijft de laatst bekende waarde doorgeven als een apparaat zwijgt — en een waterteller die stilstaat is dan niet te onderscheiden van een huis waar even geen water loopt. Het verbruik wordt per kwartier vastgelegd in een eigen tabel; liters horen niet thuis in de tabel waar de kilowatturen staan.

Een lek verraadt zich door wat het níet doet: stoppen

Water dat lang onafgebroken loopt is het patroon van een lopende wc-vlotter of een lek; een douche (zo'n zeven minuten), de wasmachine en de afwas houden allemaal vanzelf op. Loopt er langer dan drie kwartier onafgebroken meer dan een halve liter per minuut, dan stuurt Homey_Log 👤 Marc een melding. Een gevonden lek meldt zich daarna hooguit één keer per zes uur, zodat één druppelende kraan geen stroom aan berichten wordt. De drempels staan in Instellingen. Er wordt hierbij niets afgesloten of dichtgedraaid — het huis waarschuwt alleen.

Wat "onafgebroken" betekent, is nagemeten in plaats van aangenomen. Een lopende episode bleek regelmatig te worden afgebroken door een dip van precies nul liter per minuut — geen kwestie van een te scherpe drempel, want de dip zít op nul. Vermoedelijk is dat de meter zelf die een moment overslaat. Een onderbreking korter dan tien minuten (instelbaar, "Dip mag") zet de klok daarom niet meer terug op nul; duurt hij langer, dan begint de telling opnieuw. Daardoor telt een episode eerlijk door en meldt hij op een gegeven moment "dagen" in plaats van elk uur opnieuw bij nul te beginnen.

Wat de huismeter niet is, is een ijking van de doucheteller — en dat is een denkfout die hier eerst wél in stond. 👤 Marc wees erop dat de twee metingen iets anders meten: de pulsteller zit op de warmwaterleiding van de douche, de meter in de meterkast telt al het water van het hele huis, koud inbegrepen. Tijdens één douche staat er dus altijd fors meer op de huismeter, en dat verschil is grotendeels het koude water dat de thermostaatkraan erbij mengt — niet, zoals hier eerst stond, ander verbruik elders in huis. Als controle blijft alleen de grove richting over: ziet de huismeter ooit mínder dan de doucheteller, dan is er iets grondig mis. Een echte ijking vraagt één bewuste meting: de douche voluit op warm zonder koud, met verder niets aan water in huis — dan is de aanwas op de huismeter precies het water dat door de pulsteller ging.

Het huis kijkt vooruit: wat gaat de zon doen? Alles hierboven gaat over meten wat er gebeurd is. Om te kúnnen plannen — wanneer laadt de auto, wanneer stookt de boiler, wanneer laadt de accu — moet het huis ook iets kunnen vermoeden over morgen. Daarvoor haalt Homey_Log elk uur een zonvoorspelling op, en bewaart die. Dat bewaren is de hele truc: een voorspelling is achteraf nergens meer op te halen. Wat het huis gisteren dacht dat de zon vandaag zou doen, bestaat morgen niet meer — tenzij je het opschrijft. En juist het verschil tussen wat er verwacht werd en wat er kwam, is wat je later slimmer maakt.

Een voorspelling die de installatie niet kent, voorspelt iets anders

In Homey stond al een Forecast.Solar-apparaat, en dat leek te werken. Bij het aansluiten bleek waarom je zoiets moet nameten: het beschreef een installatie van 3,6 kilowattpiek op één dakvlak, terwijl er in werkelijkheid 11,6 kilowattpiek op vier dakvlakken ligt. De voorspelling voor die dag stond op 7,6 kWh, terwijl er 40 kWh binnenkwam. De dienst was niet stuk — hij was verkeerd ingevuld, en gaf keurig antwoord op een vraag over een huis dat hier niet staat. Homey_Log vraagt de dienst daarom nu rechtstreeks, met de gegevens die de omvormerfabrikant zelf over de installatie bijhoudt.

Twee dingen doet die eigen aanvraag beter dan de kant-en-klare app. Ten eerste vraagt hij per dakvlak: vier richtingen, twee omvormers, elk met een grote groep panelen en een paar aan de andere kant van de nok. Eén gemiddelde richting kan dat niet beschrijven. Ten tweede houdt hij er rekening mee dat de omvormers afknijpen: er ligt anderhalf keer zoveel paneelvermogen op het dak als de omvormers kunnen doorlaten, dus op een felle middag gaat de top er simpelweg af. Dat afknijpen geldt per omvormer, over twee dakvlakken samen — en dat is precies wat de kant-en-klare app niet kan uitrekenen.

De hele installatie staat in Instellingen en is daar aan te passen: per dakvlak de windrichting, de dakhelling, het aantal panelen en het vermogen per paneel. Zolang 👤 Marc een dakvlak nog niet zelf heeft ingevuld, staat er zichtbaar schatting bij — het huis doet niet alsof het iets weet wat het gegokt heeft. De kaart zet de voorspelling van vandaag naast de werkelijke opbrengst tot nu toe, zodat een verkeerde aanname zichzelf verraadt in plaats van jarenlang mee te lopen.

Bijsturen met wat er werkelijk binnenkomt. De voorspelling van de dienst zelf zit er stelselmatig naast — over de gemeten dagen 18 tot 49 procent te laag — en corrigeert zichzelf niet naarmate de dag vordert. Uitgesloten als oorzaak: de optelling, de opdeling in dakvlakken, de windrichtingen, het opgegeven vermogen, en zelfs het betaalde abonnement (de voorspelling voor mórgen is met abonnement 0,3 procent anders dan zonder). Het is de dienst zelf.

Wat dat abonnement wél biedt: je mag doorgeven hoeveel er vandaag al geoogst is, waarna de dienst de rest van de dag bijschaalt. Homey_Log doet dat elke ronde. Het effect is fors — op een felle dag verspringt de verwachting voor die dag van 29,7 naar 51,5 kWh, terwijl er om vier uur 's middags al 39,2 kWh binnen was en het lage getal dus allang door de werkelijkheid was ingehaald. Voor morgen verandert dat niets; die afwijking is een apart verhaal, en daarom staat er een tweede voorspelbron naast deze.

Het bijsturen gebeurt per omvormer, niet per dakvlak, en dat is een bewuste grens: de omvormer meldt één totaal en kan niet zeggen welk dak wat deed. Per dakvlak bijsturen zou nauwkeuriger lijken maar zou een meting verdelen die niet bestaat. Om diezelfde reden wordt de voorspelling ook per omvormer opgehaald — twee verzoeken per ronde in plaats van vier.

Die tweede bron is een gratis weermodel dat dezelfde vraag krijgt: Open-Meteo, en dat is de bron waar het huis op plant. De keuze viel op de ongecorrigeerde cijfers — welke bron doet het uit zichzelf het beste — over de dagen waarop beide een eerlijk antwoord gaven: 23,9 procent gemiddelde afwijking tegen 26,7. Wat daarbij telt is de absolute afwijking, niet het gemiddelde: een bron die de ene dag dertig procent te hoog zit en de andere dertig procent te laag, komt gemiddeld prachtig uit en is alsnog onbruikbaar. Pas op die winnaar komt een correctiefactor, geleerd uit de eigen meetreeks en met de hand te overschrijven in Instellingen — niet vastgelegd in code, zodat terugvallen op de andere bron één schakelaar is. De betaalde dienst loopt gewoon door als vergelijking, want een bron die je niet meer naast de werkelijkheid legt, weet je ook niet meer terug te winnen.

Eén eigenaardigheid hoort erbij en is met opzet niet weggepoetst: de factor werkt op de dag, niet op het uur. Binnen een dag mag de voorspelling er daardoor zichtbaar naast zitten terwijl het dagtotaal klopt. Dat is de eerlijke vorm — het model weet iets over hoeveel zon er komt, en veel minder over precies wanneer.

En dan de vraag die ertoe doet: klopte het? Een voorspelling bewaren heeft pas zin als je hem naast de werkelijkheid kunt leggen. In de Power-tab staan daarom twee extra lijnen aan te zetten: wat het huis de avond ervoor dacht dat de zon zou doen, en wat het er gaandeweg van maakte toen de dag vorderde. Die eerste is de lijn die telt — dat is namelijk wat je 's avonds weet als je moet besluiten of de auto vannacht laadt of morgenmiddag. De tweede staat ernaast om te zien hoeveel het weermodel onderweg nog bijdraait.

Dat brengt een regel mee die in élke grafiek van de app geldt: doorgetrokken is gemeten, gestippeld is voorspeld. Een gemeten lijn houdt op bij het laatste volle uur — het uur waar je nu middenin zit is nog niet af, en een half uur naast een heel uur tekenen geeft een knik die niets betekent. Precies daar pakt de stippellijn hem op, zodat de overgang van "dit is gebeurd" naar "dit verwachten we" vloeiend loopt en toch zichtbaar blijft. Elke lijn die getekend wordt staat in de legenda, ook de voorspelde: een lijn zonder naam is een raadsel dat je zelf mag oplossen.

Eén percentage zou verbergen waar het aan ligt

Naast de grafiek staat een oordeel, en dat bestaat bewust uit twee getallen. Het eerste is de scheefheid: voorspelt het huis stelselmatig te laag of te hoog? Dat zegt niets over het weer — dat zegt dat de dakgegevens of de demping nog niet kloppen, en dáár kun je iets aan doen. Het tweede is de typische afwijking: hoe ver zit de voorspelling er op een willekeurig uur naast, ongeacht de richting. Dat is de onvoorspelbaarheid van het weer zelf, en dat is de bovengrens van wat je met vooruit plannen ooit kunt winnen. Samengevat tot één percentage zou je zien dát het misging, maar niet of je er iets aan kon doen.

Twee dingen die dit eerlijk houden. Uren waarin nauwelijks zon viel — schemer, een winterse namiddag — tellen niet mee: een afwijking van 300% op een honderdste kilowattuur zegt niets, maar zou het gemiddelde wel overheersen. En zolang er te weinig bruikbare uren zijn, toont het scherm geen percentage maar de mededeling dat er nog te weinig gegevens zijn. Een nauwkeurigheid die op drie metingen rust is geen nauwkeurigheid.

De periode-kiezer heeft in deze tab bovendien iets wat hij nergens anders heeft: Morgen. Tot nu toe keek Homey_Log uitsluitend achteruit, en dat was terecht — er viel niets over de toekomst te tonen. Met een bewaarde voorspelling is dat voor het eerst anders. In dat venster staat er nog geen gemeten opbrengst tegenover, en dan zegt het scherm dat ook: dit is een verwachting, geen oordeel.

Eén beperking, expliciet: de vergelijking begint op 31 juli 2026. Voor elke dag daarvóór is er geen voorspelling bewaard, en dus blijven de lijnen daar leeg. Ze worden bewust niet op nul getekend — het huis verwachtte toen niet nul zon, het verwachtte niets.

Om diezelfde reden staat het huiswater niet als extra lijn in de boilergrafiek, maar in een eigen tab: twee reeksen die allebei "liters" heten maar een ander bereik meten, lezen als een vergelijking die ze niet zijn.

De pulsen worden liters via een kalibreerbare sleutel: in Instellingen staat een kaart "Waterpuls-kalibratie" die zegt "tap nu een bekende hoeveelheid af" en uit begin- en eindstand van de teller zelf de liter-per-puls berekent (te herijken wanneer nodig, met historie). Onder de analyse-tab "Boiler" staat een blokje Douchesessies: een tabel met start, stop, duur en liters plus CSV-export, en een dagtotaal. Daar staat ook per douche de temperatuurval: hoeveel graden de boiler zakte, gemeten van vlak vóór de douche tot het diepste punt vlak erna. Een korte douche van 7,5 liter kost een halve graad, een bad van bijna 49 liter kostte er 26. De boiler koelt namelijk nog even dóór als de kraan al dicht is, dus het diepste punt valt meestal een paar minuten na het einde; verder dan drie minuten kijkt Homey_Log niet, want daarna zakt hij gewoon van het staande warmteverlies en dat is niet meer van de douche.

Eén grafiek, drie verhalen die je los aan- en uitzet. De Boiler-tab zet drie reeksen in dezelfde grafiek: de watertemperatuur, de liters per douche en het stroomverbruik van de boiler in kWh. Elk heeft zijn eigen maatstreepjes langs de rand — graden links, liters en kWh rechts — want liters en graden passen niet op één lat. In de balk bovenaan staat een vinkje per reeks, in de kleur van die reeks: aanvinken zet hem erbij, uitvinken haalt hem én zijn maatstreepjes weg. Standaard zie je de temperatuur en de douches; wat je aan- of uitzet blijft bewaard voor de volgende keer. Zo zie je in één blik het verhaal achter een dag: de boiler koelt langzaam af, iemand doucht, de temperatuur zakt door, en even later trekt de boiler stroom om weer op te warmen. Er is ook een periodestand "Uur" voor als je één douche van dichtbij wil bekijken.

Eén ding valt daarbij op en dat hoort zo: waar een tijdje geen temperatuur gemeten is, laat de lijn een gat zien in plaats van een kaarsrechte streep tussen twee metingen. Dat gat is eerlijker — het zegt "hier weet ik het niet" in plaats van een verloop te suggereren dat niemand gemeten heeft.

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.

Drie schrijfroutes, drie bestemmingen. De analist mag zelf niets aan het huis veranderen, maar wel drie soorten aantekeningen achterlaten — en die belanden bewust op verschillende plekken: een huisfeit (house_facts, wacht op bevestiging in Instellingen), een regel voor zijn eigen interpretatie (agent_rules), en ook een voorstel voor een concrete wijziging (proposals, tool propose_change) dat gewoon in de Proposals-tab verschijnt en door dezelfde Tier 1-zelfcontrole loopt als een voorstel uit het dagrapport. Dat laatste is er met reden: vraag je in de chat om iets vast te leggen, dan hoort dat niet als agent-regel weggeschreven te worden — semantisch verkeerd, en je vindt het niet terug waar je het zoekt. Voorstellen uit een gesprek dragen een label "uit de chat", zodat zichtbaar blijft waar ze vandaan komen.

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.

Die lessen komen in de dagprompt terecht binnen een vast tekenbudget, en dat budget wordt bewaakt met twee regels. Per onderwerp halen hoogstens twee lessen de prompt, zodat één onderwerp de rest niet kan verdringen; en Marcs eigen correcties staan vooraan, omdat het budget van voor naar achter wordt gevuld en wie achteraan staat de analist dus nooit bereikt. Zonder die twee regels wint de stapel: acht herformuleringen van dezelfde zelfgeschreven conclusie duwen samen elke correctie van Marc buiten beeld, en de analist leest vervolgens vooral zichzelf terug.

Om diezelfde reden houdt het systeem zijn eigen oordeel en dat van Marc uit elkaar. Wijst Marc een voorstel af met zijn eigen motivering, dan is dat feedback en weegt het zwaar. Sluit de zelfcontrole een voorstel af, dan is dat een machinebesluit en wordt er geen les van gemaakt — een systeem dat zijn eigen verdicten als menselijk oordeel opslaat, wordt met de tijd zekerder van zijn eigen aannames.

De echoput: een probleem dat zichzelf in leven houdt

Het gevaarlijkste dat een zelflerend systeem kan doen, is zichzelf citeren. Een bevinding meldde "vijfde voorkomen in drie weken" van een storing die al lang verholpen was — en het enige bewijs daarvoor waren eerdere rapporten die precies hetzelfde beweerden. De teller liep elke dag op zonder dat er iets gebeurde, en het bijbehorende voorstel hield zichzelf zo weken in de lucht. Dat kost meer dan tijd: het advies dat eruit rolde was een garantieclaim indienen bij een fabrikant voor een defect dat er niet was.

Claimt een bevinding nu herhaling, dan moet er minstens één logregel uit het eigen analysevenster tegenover staan die dat staaft. Lukt dat niet, dan verdwijnt de "opnieuw"-bewering en blijft alleen de bevinding zelf over. Bewust streng: ook een claim die uitsluitend op eerdere rapporten of op losse datapunten rust wordt geneutraliseerd, want precies die vorm wás de echo. De prijs van een onterechte neutralisatie is klein — de waarneming blijft staan, alleen de teller verdwijnt — en die van een gemiste echo is een advies waar niemand iets aan heeft.

De dagelijkse analyse draait 's ochtends om half zeven, de diepere weekreview op zaterdagochtend. Per run is niet alleen het model instelbaar maar ook hoeveel denkwerk het mag doen — en dat wordt alleen meegestuurd naar modellen die dat begrip kennen, zodat een model dat het niet snapt geen foutmelding oplevert maar gewoon een logregel.

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, een claim ("script X heeft hier al een guard tegen") die het systeem zelf tegen de scriptcode natrekt, of bewijs dat in geen enkele logregel terug te vinden is. Dat laatste vangt het geval waarin de analist zijn eigen eerdere rapporten citeert in plaats van een waarneming: de aangehaalde tekst wordt op zijn langste cijfervrije deel teruggezocht in het logboek, en levert dat niets op, dan gaat het voorstel dicht met die reden erbij. Bewijs dat zich niet uit logregels laat staven — een datapunt uit de database bijvoorbeeld — valt daar nadrukkelijk buiten en sluit nooit op deze grond. De dagelijkse Pushover-melding telt pás ná deze zelfcontrole, zodat een voorstel dat seconden later wordt afgewezen niet als "1 nieuw actie-voorstel" langskomt; zelf-gesloten voorstellen worden benoemd in plaats van meegeteld
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 Experiments-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. Het hoofdstuk hierna gaat over de plek die dat werk overneemt zodra ze heeft bewezen het beter te doen; dit hoofdstuk gaat over wat er vandaag werkelijk aan de knoppen zit. 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.

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.

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 (Sources → 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)"). 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.

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 (Sources → SolarEdge log) én verweven in de algemene Logs-tab (origin "SolarEdge"). De collector doet uitsluitend lees-verzoeken; de kill-switch staat in Instellingen → "SolarEdge — omvormerlog".

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 (House → Network) 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" 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 minuut 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, altijd, of "vast vermogen"). Die laatste stand is de interessantste: hij zegt "dit apparaat laadt op een vast vermogen — plan of handbediening — dus tel het mee", en schakelt zichzelf uit op de momenten dat het zon-laden de laadpaal juist zélf regelt. De batterijen staan daardoor niet stil zodra de auto begint te laden: ze dekken gewoon het huis door, het hele etmaal. Tijdens zon-laden houden ze het huis op nul en gaat het zonneoverschot eerst naar de batterijen en de boiler, en pas daarna naar de auto — die heeft het goedkope nachtplan als alternatief. Om te voorkomen dat de auto de batterij leegtrekt, kijkt de zon-regelaar door de batterij heen: stroom die uit de batterij komt telt niet als zon. 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.

Er staat nóg een blok in diezelfde staaf, net ónder de auto en de boiler: het opladen van de batterijen zelf. Ook dát kost namelijk energie, net als bij de auto en de boiler. Ontbreekt het, dan ziet de ruimte tussen de staaf en de gele zon-laag eruit als teruglevering terwijl er in werkelijkheid stroom de batterij in gaat. Of de batterij daarbij met zon of met stroom van het net laadt, maakt voor dit blok niet uit: het is gewoon verbruik. Het blok staat met opzet vlak ónder de auto en de boiler, niet erboven: de batterijen laden alleen op bij zonneoverschot, dus je verwacht dat blok altíjd binnen de gele zon-laag blijft. De auto en de boiler mogen er wél overheen steken, want die worden ook rechtstreeks uit het net gevoed. Zo is in één oogopslag te zien of dat klopt — met één bekende uitzondering: 's nachts laadt de batterij soms tóch uit het net bij, in de goedkoopste uren, en dan steekt het blok boven een lege zon-laag uit. Het terugleveren van de batterijen is de groene band die áchter de staven ligt; wat daar bovenop nog aan geel uitsteekt, is de échte teruglevering aan het net. Die band moet je op elke schaal apart uitrekenen: tel je laden en ontladen in één som op, dan heffen ze elkaar op de dag- en maandweergave gewoon op en staat er stelselmatig nul.

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").

Wat hij zoekt is het goedkoopste uur, en dat volgt uit wat de machine werkelijk draait. Het korte programma van drie kwartier doet dit huishouden prima — het is sneller, het wordt even schoon, en gemeten aan de stekker gebruikt het mínder dan het lange "eco"-programma, wat tegen het energielabel in gaat maar twee keer onafhankelijk uit de eigen meetreeks komt. Een machine die drie kwartier draait, hoort niet gepland te worden alsof hij vier uur bezig is: een blok van vier uur bevat per definitie ook de dure uren ernaast, en het goedkoopste blok is zelden hetzelfde als het goedkoopste uur.

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.

Lokaal eerst: rechtstreeks met het apparaat praten

Een apparaat in huis is bijna altijd ook zónder tussenpersoon aanspreekbaar. De weg via een clouddienst of via de huisautomatisering is een omweg: trager, armer aan gegevens, en afhankelijk van twee partijen die allebei onderuit kunnen gaan. Waar het kan praat Homey_Log daarom rechtstreeks met het apparaat op het eigen netwerk, en per apparaat wordt apart besloten of dat lokale pad de besturing overneemt of er alleen naast komt te liggen.

De stofzuigrobot is het duidelijkste voorbeeld. Waar Homey één bruikbaar getal gaf — het accupercentage — levert de lokale verbinding er in enkele seconden vijfenveertig, inclusief het aantal gedraaide beurten. Het detailscherm toont daardoor de laatste beurten met hun afloop (gedockt, gestopt, storing) en de dagen waarop hij níét ging, met de reden erbij; plannen en starten gebeurt hier ook lokaal. De gordijnen hebben een lokale stand met een eigen interface, en daar is de app het enige pad dat de motor aanraakt — Homey blijft beslissen (avondprotocol, spraak, een virtueel apparaat) maar belt daarvoor de app. De vaatwasser spreekt lokaal een versleuteld protocol met een sleutel per apparaat; daar leest de app bewust alleen mee, parallel aan Homey, tot er een besluit ligt om ook lokaal te starten. En de gateway van het weerstation levert in dezelfde aanroep waarmee elk kwartier de bodemvochtsensoren binnenkomen ook temperatuur, vocht en luchtdruk van de meterkast waarin hij hangt: geen extra verzoek, geen extra poller, en een luchtdrukmeting die de omweg wél beloofde maar nooit vulde.

08

De regisseur: wie krijgt de zon, en wanneer

Alles in het vorige hoofdstuk plant voor zichzelf. Het laadplan verdeelt de nacht alsof de auto de enige verbruiker is, de vaatwasser zoekt zijn goedkoopste uur alsof de accu niet bestaat, de accu vult zich op de goedkoopste kwartieren alsof de boiler nooit stroom vraagt. Vier rekenpartijen, elk overtuigd dat ze alleen op de wereld zijn — en vier keer dezelfde kilowattuur die aan drie apparaten tegelijk is beloofd. Daar staat één plek tegenover die de vraag in één keer beantwoordt: wie krijgt het zonoverschot, wie krijgt de goedkope kwartieren, en wie wacht?

Elke minuut haalt de regisseur het zonbudget op, vraagt elke deelnemer wat hij nodig heeft, verdeelt, en legt per deelnemer vast wat eruit kwam — steeds over een horizon van veertig uur, in kwartieren. Het is bewust geen gezamenlijke optimalisatie maar een waterval langs een ladder: een deelnemer ziet als gratis alleen wat de treden bóven hem hebben laten liggen. Die volgorde is een instelling en geen code — vandaag komt de basisbehoefte van de boiler eerst, dan de vaatwasser, de thuisaccu, de auto, en pas daarna de bonusdelen van boiler en auto. Daarna loopt er een klein, vast stel vangnetten overheen: negatieve prijzen, comfortondergrenzen, fasehulp. Wie een harde eindtijd heeft — de vaatwasser moet klaar zijn, de auto moet rijden — haalt zijn deadline altijd, desnoods op netstroom.

Wélke apparaten meedoen weet de regisseur niet uit zichzelf. Elk stuurbaar apparaat meldt zich zelf aan vanuit zijn eigen adapterbestand, met een klein contract: hoeveel kilowattuur heb ik nodig, tot wanneer, tussen welk minimum- en maximumvermogen, in welke minimale blokken, mag ik onderbroken worden, waar ligt mijn comfortgrens, en wat is het waard om eerder klaar te zijn. Het register zelf kent geen enkel apparaat; een adapter die een fysiek apparaat nodig heeft vraagt dat op via zijn functionele rol. Een fout in zo'n aanmelding — een dubbele sleutel, een ontbrekende functie — laat de opstart struikelen in plaats van pas op te vallen wanneer de regisseur die deelnemer nodig heeft.

De dag als partituur

Wat daaruit komt is te zien als een planningsstrook: drie rijen van acht uur, per rij vier notenbalken — één per deelnemer, altijd in dezelfde volgorde — en per balk tweeëndertig cellen van een kwartier. Links van het lopende kwartier staat wat er werkelijk gebeurde, rechts wat er gepland staat, en het lopende kwartier is een afspeelkop dwars over alle vier de balken. Kleur draagt de betekenis (laden is niet hetzelfde als ontladen), de letter van een apparaat staat één keer links van zijn balk en niet in de cellen. Verder valt er niets aan te tikken: het is een partituur, geen bedieningspaneel. Dezelfde strook staat op de telefoon, op het wandtablet en op de desktop, en labels, letters, legenda en volgorde komen alle vier van de server — de browser rekent hier niets uit.

En hij stuurt nog niets. De hoofdschakelaar staat op schaduw, elke deelnemer staat op droogloop, en een ronde schrijft aantoonbaar geen enkele variabele naar Homey — ook geen "uit". De regisseur rekent, plant en legt vast; de bestaande scripts blijven gewoon sturen. Dat is geen voorzichtigheid uit gewoonte maar de enige manier om de vraag te beantwoorden die ertoe doet: is het plan van de regisseur werkelijk beter dan wat er nu gebeurt?

Die vraag heeft een eigen meetlat. Per apparaat wordt achteraf de rekening van het bestaande proces naast die van het schaduwplan gelegd — dezelfde hoeveelheid verbruik, afgerekend tegen het moment waarop het schaduwplan werd gemaakt, met zonoverschot als gratis kwartier. Een apparaat mag pas echt gaan sturen als het over minstens tien vergelijkbare dagen een mediaan kostenverschil haalt dat niet slechter is dan het oude proces, niet méér gemiste eisen oplevert, en een zonaandeel houdt dat binnen twee procentpunt van het oude ligt. Mediaan en niet som: anders draagt één uitschieter de hele uitslag.

De boiler, als eerste deelnemer

Warm douchewater is het dankbaarste apparaat om zo te plannen: het vat is een accu, alleen dan met water erin. De boilerregelaar beslist elke minuut opnieuw of het element op zon, op het net of in rust hoort te staan, langs vier regels in vaste voorrangsvolgorde. Zon is de standaardstand: het element moduleert mee op wat er teruggeleverd wordt, mínus wat de thuisaccu op dat moment ontlaadt — want een ontladende accu is geen zon. De vloer gaat voor alles: zakt het vat onder de 50 °C, dan wordt er prijsblind bijverwarmd. Het plan rekent de douches die tot het volgende gratis of goedkope venster te verwachten zijn om naar kilowatturen, en legt die in de goedkoopste kwartieren; in kwartieren waarin een douche verwacht wordt ligt de reserve hoger, want twee douches achter elkaar laten het vat zo'n 19 graden zakken en de mengkraan houdt ergens rond de 45 °C op met mengen. En de hygiëne-regel zorgt dat het vat minstens eens per week naar 60 °C gaat, in het goedkoopste kwartier dat er die dag is. Zegt het locatiesysteem dat er langer dan een etmaal niemand thuis is, dan vervallen de vloer en het plan en blijven zon en hygiëne staan.

Wat dat mogelijk maakt is een thermisch model van het vat, en dat is er niet voor de sier. De sensor zit onderin en loopt na een douche uren achter: de directe val meet zo'n 3 graden waar er in werkelijkheid 10 tot 12 uit het vat verdwenen. Er wordt daarom op minuutkorrel een energiebalans bijgehouden — het staande verlies, de inhoud van het vat per graad, het rendement van het element, en wat elke gemeten liter tapwater eruit haalt. De sensor telt pas weer als ijkpunt na uren zonder tappen én zonder verwarmen; is er geen ijkpunt, dan zegt het model gewoon dat het zichzelf niet vertrouwt.

Op de telefoon staat één knop: Nu verwarmen. Die zet een override die vóór het plan en vóór de zonstand gaat — opwarmen op het net, tot een grens, voor hooguit een uur, altijd af te breken. Twee keer drukken geeft hetzelfde antwoord als één keer.

Een apparaat dat niets terugzegt

De boiler wordt over de radio aangestuurd en meldt niets terug: eenrichtingsverkeer. Het enige bewijs dat een commando is aangekomen, is de slimme stekker ervoor. De eis is daarom niet "sta jij in de goede stand" maar ik wil de overgang zien: na een commando "net" hoort het verbruik binnen drie minuten boven de 2.000 watt te liggen, na "rust" eronder de 50, en bij terugvallen naar zon zonder overschot hoort het element aantoonbaar uit te gaan. Blijft dat bewijs uit, dan volgt één herhaling, daarna een melding, een foutregel en een terugval naar de veilige zonstand met een blokkade van een half uur.

Eén onderscheid is daarbij hard, en het is precies de valkuil die elders in dit verhaal ook opdook: een bevroren meting bewijst niets. Staat het vermogen boven de grens terwijl dezelfde waarde er al minuten onveranderd staat, dan meldt het systeem "de meting staat stil" en niet "de boiler reageert niet". Twee verschillende storingen, twee verschillende oplossingen.

De vaatwasser, op schaduwprijs

De goedkoopste stroom is niet altijd de goedkoopste stroom. Draaien op een middag met zonoverschot dat anders voor een dubbeltje het net op gaat, kost minder dan een nacht met een lage prijs waar wél voor betaald moet worden. De schaduwprijs zet dat op één noemer: eigen zonoverschot telt als nul, de rest tegen de kwartierprijs van dat moment.

Naïef toegepast kiest die som het vroegste gratis moment, en dat is precies de verkeerde keuze. Vroeg in de ochtend dekt het voorspelde overschot de beurt op een mespunt; midden op de dag is het vier tot zes keer zo groot. Beide heten "gratis", maar alleen de tweede blijft gratis als de voorspelling een half uur tegenvalt. De regel neemt daarom eerst alle starttijden die binnen vijf cent van de goedkoopste liggen, en kiest daarbinnen de vroegste waarop het verwachte overschot de beurt minstens drie keer dekt; is die er niet, dan het moment met de meeste speling. Over een terugblik van drieëntwintig nachten zakt de gemiddelde prijs per beurt daardoor van bijna dertig naar negen cent, met ruim een derde van de beurten werkelijk gratis — al is dat gemeten in de gunstigste maand van het jaar, en doet de zonstap in de donkere maanden vrijwel niets.

Ook deze planner draait in schaduw: hij rekent, legt vast en start niets. Wie er plant zit in één schakelaar — de starter houdt een overdrachtsvlag in Homey gelijk aan zijn eigen stand en start niet als die overdracht niet bevestigd kan worden, zodat er nooit twee planners tegelijk aan dezelfde machine staan.

09

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.
  • Invarianten: twaalf uitspraken die altijd waar horen te zijn — de dagsom is de som van zijn kwartieren, de zon is nooit negatief, de waterteller loopt nooit achteruit, de accu kan niet meer leveren dan de natuurkunde toestaat, het schema in de database is hetzelfde als dat in git. Elke nacht rond vier uur loopt die lijst langs de verse dag. Bij elke uitspraak staat niet alleen de marge maar ook waarom die marge er is; een tolerantie zonder reden is per afspraak een fout in het ontwerp. Een invariant die hier niet van toepassing is — geen data, een gepauzeerd meetpunt — meldt dat als "overgeslagen" en niet als "goed", want dat zijn twee heel verschillende dingen.
  • Nachtwacht: om kwart over vijf draait de server zijn eigen volledige testsuite plus een rooktest door de schermen — op de code die op dat moment werkelijk gedeployed is, niet op wat er in een werkmap staat. Het tijdstip is bewust een ander dagdeel dan dat waarin er gebouwd wordt: een test die stiekem van de klok afhangt was 's avonds groen en verraadt zich hier. Blijft het drie nachten achtereen mis, dan volgt een telefoonmelding.
  • Rampoefeningen: bewaking die nooit is uitgeprobeerd, is een aanname. Daarom kan een storing met opzet nagebootst worden — de weerstations onbereikbaar maken, de stroomprijs laten wegvallen, de energie-ingest laten zwijgen — waarna een scorecard toont wat het systeem eigenlijk zélf had gemerkt en of de gemiste gegevens vanzelf terugkwamen. Elke oefening heeft een eindtijd die ook zonder ingrijpen afloopt, zodat een vergeten oefening geen echte storing wordt. De scorecard telt bewust alleen meldingen die een mens ook echt onder ogen krijgt: een waarschuwing die alleen in de diepste logregels landt, geldt als niet gevangen.
  • 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 (Energy → Solar charging) 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.
Vers of bevroren? — de meetlaag onder alle sensoren

Een terugkerend patroon in dit huis: als een apparaat ophoudt met doorgeven, merkt niemand dat automatisch. Homey blijft namelijk gewoon de laatst bekende waarde herhalen alsof er niets aan de hand is — available blijft doodleuk true staan. Zo'n stilgevallen apparaat ziet er dus niet uit als een storing, maar als een apparaat dat toevallig niets verbruikt.

Dat is drie keer misgegaan, en telkens bij toeval ontdekt in plaats van door een waarschuwing. Het duurste geval: de zonne-omvormer in de garage stond vijf dagen stil terwijl de meting keurig 0,00 kWh bleef doorgeven, en zo verdween 109 kWh zonne-opbrengst uit de boekhouding — pas gevonden doordat er voor een heel ander onderwerp toevallig de energiebalans werd nagerekend.

Elke kwartiermeting legt daarom niet alleen een waarde vast, maar ook wanneer het apparaat die waarde voor het laatst écht ververste. Dat is te zien in Instellingen → Gegevens & systeem, in de kaart "Meetgaten", direct onder Retentie. Een paar meetpunten — zoals de accu-belasting, die uit een berekening komt in plaats van rechtstreeks van een apparaat — kunnen zo'n versheidsstempel structureel niet hebben; die staan apart als "geen versheidsbewijs" en tellen niet mee als misser. De kaart noemt elk meetpunt bij zijn gewone apparaatnaam ("Buitenlampjes"), met de technische sleutel er klein achter, en met een keuze voor de periode: de laatste 14, 30 of 60 dagen.

Belangrijk: die kaart meet, maar houdt niets tegen op grond van ouderdom alleen. Dat is geen onaf werk maar een gemeten conclusie. Een automatische regel die metingen zou weigeren zodra hun stempel te oud werd, is gebouwd, meegekeken zonder iets te blokkeren, en op de eigen cijfers afgekeurd: hij zou vooral geldige metingen hebben geweigerd en geen enkele echte bevriezing hebben gevangen. De regel is daarna verwijderd in plaats van slapend bewaard — code die niets doet maar er wel uitziet als bewaking is gevaarlijker dan geen bewaking.

De reden dat zo'n regel niet kán werken zit in het stempel zelf: het zegt wanneer een waarde voor het laatst veranderde, niet wanneer een apparaat voor het laatst iets liet horen. Een apparaat dat uit staat blijft op nul en krijgt dus nooit een nieuw stempel. Het bewijs is sluitend: bij de wasdroger en de elektrische bijverwarming valt het stempel tot op de minuut samen met hun laatste kwartier mét verbruik. "Bevroren" en "uit" zijn zo niet te onderscheiden. Dat verklaart ook de valse alarmering waarmee de kaart opende: zes stopcontacten die maanden stil leken te staan, bleken stuk voor stuk apparaten die gewoon uit staan — de kerstverlichting ligt in een doos, de buitenlampjes zijn seizoenswerk. Wat overblijft is een waarnemer met een mens erachter, en dat is hier de eerlijke uitkomst.

Bewust stil: de pauze-agenda. Een apparaat dat opzettelijk uit staat hoort niet te blijven klagen. Elk meetpunt is daarom te pauzeren — met of zonder einddatum, en met een reden erbij. De buitenlampjes en de kerstspullen staan permanent stil, de wasmachine tijdens een vakantie. Eén schakelaar zet zo'n hele set tegelijk stil (vakantiemodus) en draait dat bij uitzetten, of op de einddatum, vanzelf terug; pauzes die Marc zelf met de hand heeft gezet blijven daarbij ongemoeid. Aanvinken pauzeert meteen, uitvinken hervat meteen, ook op een modus die al loopt — een vinkje dat pas de volgende keer geldt, is een vinkje dat liegt.

Eén onderscheid is daarbij hard: pauzeren onderdrukt de meldingen, niet de metingen. Er wordt onverminderd doorgemeten en opgeslagen. Zou het meten ook stoppen, dan zou niemand ooit zien dat het apparaat het weer doet — en dan is een pauze geen rust maar een blinde vlek.

Eén ding houdt wél iets tegen. Toen de garage-omvormer na zijn stilstand weer ging meten, is de verdwenen zonne-opbrengst twee keer uit de gegevens van de omvormerfabrikant teruggezet — en twee keer zette Homey diezelfde kwartieren daarna weer op nul. De verklaring bleek meetbaar: Homey herhaalt zijn eigen metingen precies 24 uur lang, dus elke reparatie binnen dat etmaal werd stelselmatig platgeslagen. Een herstelde zonwaarde kan nu niet meer door een nul worden gewist zodra het kwartier langer dan twee uur voorbij is; verse kwartieren mogen zichzelf nog wel corrigeren. Zo'n weigering verschijnt als waarschuwing in het log, want een stille correctie is precies wat je hier níét wilt.

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.

10

Hoe dit gebouwd wordt

Het hele project staat 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.

Drie lagen, en één toets. De code kent drie lagen die elkaar niet mogen binnendringen. Onderin de data: tabellen met metingen, zoals ze binnenkwamen. Daarboven de mapping: adapters, registers en rollen — dáár, en alleen dáár, staat welk apparaat de laadpaal is, welke meetreeks "zon" heet en welke sensor het boilerwater meet. En bovenop de schermen, die alleen functionele begrippen kennen: geen apparaat-identificatie, geen apparaatnaam als selectiemechanisme, geen formule in de browser. De toets past in één zin: een bron verwisselen mag boven de mapping niets breken. Een kapotte sensor vervangen hoort één regel in de mapping te zijn, geen zoektocht door de schermen.

Hoeveel bewijs een wijziging moet leveren, hangt af van wat hij stuk kan maken, niet van de map waarin het bestand staat. Aan de opslag — schema, migraties, opruimen van data — mag niets zonder testen vooraf, een backup en een uitgeschreven weg terug; een fout daar is onherstelbaar. Aan het rekenmodel evenmin, want een fout daar besmet jaren historie en elk voorstel dat erop rust. Aan de processen die iets naar Homey terugsturen hangt de eis van een noodrem en een verificatie op echt gedrag: die fouten komen in de fysieke wereld terecht, als een auto die niet laadt. Alleen bij puur tonen — een kleur, een tekst, een andere indeling — volstaat kijken of het klopt in de draaiende app. De grens houdt zichzelf schoon: zodra er in een scherm een nieuw getal wordt uitgerekend, hoort die berekening niet in de browser maar op de server, mét tests.

Eén bouwsteen per zorg — en een test die zelf uitzoekt wie hem omzeilt. Elke dwarsdoorsnijdende zorg heeft er precies één: een logregel wegschrijven, met Amsterdamse tijd rekenen (zomertijd inbegrepen), kwartieren afronden, een configbestand lezen, een geheim ophalen, periodiek pollen, melden dat een bron uitvalt, een proces starten, een tabel aanmelden, een bewaartermijn kiezen. Wat dat afdwingbaar maakt is de vorm van de bijbehorende test: die leidt uit de code zélf af wie de bouwsteen zou moeten gebruiken, in plaats van een handgeschreven lijst af te vinken. Een omweg verschijnt daardoor vanzelf; elke uitzondering moet een reden dragen; en een uitzondering die niemand meer nodig heeft maakt de test rood in plaats van stilletjes mee te liften. Zesentwintig van de testbestanden zijn van die soort.

Laden doet niets. Een module die binnengehaald wordt raakt geen database, geen bestand, geen netwerk en geen timer aan. De database is een luie façade: de verbinding ontstaat pas bij de eerste échte databasehandeling, en dán pas draaien de schema's die de modules hebben aangemeld. Dat is geen netheid om de netheid — zolang het laden zelf al tabellen aanmaakte en verbindingen opende, deed elk los hulpscriptje ongemerkt mee. De opstart is in hetzelfde spoor een register van verklaringen in plaats van een rij losse aanroepen: elk proces zegt hoe het start, of het vereist is, waar het achteraan komt en welke collectors het voedt. Is het vereist, dan stopt een fout de opstart. Is het dat niet, dan komt de server gewoon op, gaat de fout naar het logboek, én krijgt de bijbehorende collector "niet gestart" mee. Juist dat laatste is het punt: een collector die stilviel door een fout bij de start was anders niet te onderscheiden van een collector die terecht niets te melden had.

Onderin ligt een vangnet van bijna zevenhonderd testbestanden. Bij elke commit draait de altijd-set — alle contract-tests, alle rollen-tests — plus precies die tests die een gewijzigd bestand als het hunne opeisen: tien tot dertig seconden. Raakt de commit de server zelf, de database, de afhankelijkheden of meer dan vijfentwintig bestanden, dan gaat de hele suite alsnog langs. De ruil is bewust gekozen: een test die het gewijzigde bestand niet opeist wordt pas 's nachts rood. Twee gaten zitten er per constructie in — de controle kijkt naar de werkmap in plaats van naar wat er precies wordt vastgelegd, en git slaat hem over bij het samenvoegen van takken. Na een samenvoeging draait daarom alsnog de hele suite, luid maar zonder te kunnen blokkeren, en de nachtwacht op de werkelijk draaiende code blijft het sluitende bewijs. En verandert er iets aan de database-structuur, dan moet een verse afdruk van dat schema mee de commit in; wie dat vergeet, hoort het van de nachtelijke invariant die git en database naast elkaar legt.

Wat er van de fouten overblijft

Buiten deze repo staat een centrale lessenlaag: korte, harde regels die uit gemaakte fouten getrokken zijn, projectoverstijgend, met een limiet op de lengte en zonder mogelijkheid er één te wissen. Aan het begin van substantieel werk worden ze opgehaald en gevolgd, met een leesclient die per constructie alleen kan lézen en die de nieuwste altijd bovenaan zet — een les die je niet leest is hetzelfde als een les die er niet is. Een les kan verouderen, en een les kan opnieuw bekrachtigd worden.

Dezelfde plek draagt een postbus tussen projecten. Loopt het werk hier vast op iets dat alleen een ander project kan beantwoorden, dan blijft die vraag daar staan tot hij wordt opgepakt: niemand zit te wachten, en er geldt een budget per onderwerp zodat een postbus geen chatvenster wordt.

11

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. 📱 Datzelfde dashboard heeft ook een telefoonweergave: /m stuurt door naar /dashboard/?view=phone, en die URL — niet de schermbreedte — bepaalt hoe het eruitziet: 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. Voor dagelijks gebruik op de telefoon staat daarnaast de eigen app (het hoofdstuk hierna); deze weergave blijft bestaan als directe blik op precies hetzelfde scherm dat aan de muur hangt. 🤖 Bewust één component in plaats van twee: een tegel die op het wandtablet verandert, verandert vanzelf mee. 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, een live meting van de Shelly op de boiler ('Boiler Control', zone Zolder); 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: Verloop (de watertemperatuur van vandaag als lijn met de gemeten liters per douche als staafjes eronder, plus het dagcijfer in kWh en €) en Douche-verbruik (de gemeten liters uit de pulsteller op de warmwaterleiding)
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. Elk gezinslid heeft een eigen foto in de rij; wie er geen heeft, valt terug op een cirkel met initialen in zijn eigen kleur. Die foto's zijn bewust klein gehouden — een portret rechtstreeks uit een telefooncamera weegt al gauw meer dan de hele rest van de app, en dat betaalt iemand op een mobiele verbinding elke keer dat hij hem opent.

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-weergave in de House-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 House-tab via een "embed-modus" (?embed=huis) in een fullscreen iframe.

De Boiler-tegel toont groot de watertemperatuur — een echte meting van de Shelly op de boiler ("Boiler Control", zone Zolder) op capability measure_temperature.1, live doorgezet naar het grote getal. Daaronder staat het actuele opgenomen vermogen van de boiler-meter, en een 🚿-indicator "er wordt gedoucht", afgeleid uit de bewegingssensor. De popup heeft twee tabs. Verloop zet de watertemperatuur van vandaag als lijn neer met de gemeten liters per douche als staafjes in hetzelfde beeld: zo zie je in één oogopslag dat een douche een dip in de boiler slaat, wat met twee losse grafieken naast elkaar niet te zien is. Het stroomverbruik zit daar bewust niet als derde reeks in — het dagcijfer in kWh en € staat als regel onder de grafiek, dus dat getal is er wel, maar het vervuilt het beeld niet. Douche-verbruik toont de gemeten liters uit de pulsteller op de warmwaterleiding. Die twee uit elkaar houden is opzet: een schatting uit verwarmingsmomenten en een echte watermeting lijken op elkaar en zijn het niet, en juist die verwarring was de aanleiding om er expliciet bij te zetten waar elk getal vandaan komt.

De Thuisbatterijen-popup toont per accu het maandelijkse laad/ontlaad-rendement (ηcum) — hetzelfde groene percentage als de Battery-η-tab, geen State-of-Charge. De grafiek staat direct onder het gecombineerde percentage (eerst de vorm van de dag, dan de details), met het niveau en het rendement per accu naast elkaar in twee kolommen.

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.

Of een auto aan de lader hangt is niet alleen Tronity's woord. Tronity levert per auto een "ingeplugd"-vlag, maar die kan dagen blijven hangen als de auto slaapt: de auto stond een keer twee dagen op "ingeplugd en vol" terwijl de laadpaal al die tijd leeg was. De laadpaal is de enige die het fysiek wéét — maar er is één paal en twee auto's, dus hij weet niet wélke auto eraan hangt. Vandaar de regel: de paal mag alleen tegenspreken. Meldt hij dat er niets hangt, dan is geen enkele auto ingeplugd; meldt hij dat er wél iets hangt, dan bepaalt Tronity welke. Is de paal even niet uitleesbaar, dan blijft Tronity leidend — een netwerkhik mag een lopende laadsessie niet verbergen. Stroom door de kabel telt daarbij als bewijs dat er iets hangt, ook als het toestandsveld iets anders beweert; vermogen liegt niet.

Welk laadpaal-apparaat daarvoor geraadpleegd wordt, gaat bewust niet op naam. Bij een migratie blijft de oude naam bestaan terwijl het apparaat erachter niets meer levert — en dat is de stilste storingsvorm die er is: het laadvermogen en "laadt nu" stonden zo weken leeg zonder één foutmelding. Er kwam geen fout, er kwam níets. De keuze valt daarom op het laadpaal-apparaat dat daadwerkelijk een toestand teruggeeft, zodat een volgende apparaatwissel zichzelf herstelt. 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.

Waar "vandaag" begint. De server draait zelf op UTC, en dat is in de zomer twee uur vóór de Amsterdamse middernacht. Elk dagvenster op dit dashboard — het energiestromen-diagram, de uurgrafiek "Verloop", de dagverloop-grafieken van auto's, accu's en boiler, de "Vandaag"-aanwezigheidslijst en de zelfvoorzieningscijfers — rekent daarom expliciet met de Amsterdamse middernacht, zomer- en wintertijd meegerekend. Zonder die omrekening ontbreekt stelselmatig de eerste helft van de nacht, en dat verraadt zich pas als je er toevallig naar kijkt: een douche vlak vóór tweeën die simpelweg niet in de grafiek van die dag stond.

12

De telefoon-app: het huis in je zak

Het wandtablet hangt aan de muur en de beheeromgeving staat op een groot scherm. Wat daartussen ontbrak, is de vraag die je onderweg stelt: doet het huis het nog? Daarvoor staat er een eigen app op de telefoon, met als naam simpelweg Thuis. Het is een webapp die zich als een echte app gedraagt: je zet hem op je beginscherm, hij start zonder adresbalk in zijn eigen venster, en hij opent ook als je geen verbinding hebt.

Ook hier meld je je één keer aan met je eigen account, en bepaalt je rol wat je ziet: wie alleen mag kijken, krijgt geen bedienknoppen voorgeschoteld. De app heeft vier schermen. Nu is de stand van het huis in tegels — energie en de energieregie, de thuisbatterijen, beide auto's, de zon van vandaag, zelfvoorziening, het klimaat, de tuin, de boiler, de warmtepomp, de vaatwasser, de lampen, de gordijnen, de stofzuigrobot, de afvalkalender, het dagrapport, de meldingen, waar de gezinsleden zijn en in welke kamer je zelf bent. Inbox zijn de voorstellen en constateringen van de analist. Grafiek is het energieverloop met een periodekiezer. Chat is het gesprek met dezelfde analist die 's ochtends het dagrapport schreef — voor wie dat gesprek aanstaat. De beheerder ziet daarnaast een vijfde scherm, Beheer: dezelfde instellingen als op de desktop, maar dan in de hand. Tussen de schermen veeg je horizontaal heen en weer; de balk onderin doet hetzelfde met een tik.

Achter elke tegel zit een vol scherm met detail, en die schermen liggen als een stapel kaarten achter elkaar: je veegt van de ene naar de andere zonder terug te hoeven naar het overzicht. Het deck tekent zijn twee buren alvast, zodat een veeg niet op een leeg scherm uitkomt. Bedienen kan ook — handmatig laden starten, de vaatwasser of de stofzuiger aanzetten, de lampen uit, de boiler een uur laten opwarmen — telkens via precies dezelfde beveiligde route en dezelfde witte lijst als op het wandtablet, met een bevestigingsvraag ervoor.

Eén huisregel geldt op de telefoon zonder uitzondering: een grafiek onder een tegel gaat over vandaag. Staat er toch een andere tijdsdimensie — de laatste 24 uur, een week — dan staat dat er letterlijk bij. Onder elke grafiek staat namelijk één regel die benoemt wat er op de horizontale as gebeurt, want een staafje zonder as is een plaatje waar iedereen zijn eigen verhaal bij verzint.

Het Nu-scherm van de telefoon-app: aandachtspunten, aanwezigheid, dagverloop en tegels voor energie, batterijen, auto's, zon en boiler Detailscherm van de thuisbatterijen: laadpercentage en het verloop van vandaag, met bovenin de stapel-indicator van het deck Het Grafiek-scherm: verbruik en zon per periode, met de periodekiezer bovenin
Drie van de schermen: Nu met de tegels en rechtsboven wie er is aangemeld, een detail uit het deck (de thuisbatterijen, met bovenin de stippenrij van de stapel) en Grafiek met de periodekiezer. Gezichten en namen van gezinsleden zijn hier onleesbaar gemaakt.
Wat een app onderscheidt van een website

Drie dingen maken het verschil, en ze zijn geen van drieën cosmetisch.

Hij opent offline. Een kleine achtergrondlaag bewaart de schil van de app — de opmaak, de code, de lettertypes — plus een handvol gegevens die het Nu-scherm nodig heeft om íets te tonen. Alleen die paar leesroutes staan op een witte lijst; alles wat schrijft gaat er volledig langs, want een opgeslagen antwoord op "zet de lampen uit" is een ramp in wording. Wat uit de opslag komt in plaats van vers van de server, wordt als zodanig gemerkt: liever een zichtbaar oud cijfer dan een vers ogend cijfer dat van gisteren is.

Hij kan je bereiken. Meldingen komen rechtstreeks op de telefoon binnen, ook als de app dicht is, en het aantal wachtende zaken staat als cijfer op het app-icoon. Erop tikken brengt je naar de melding zelf, niet naar het beginscherm. Een aparte noodrem kan het hele kanaal in één regel stilzetten zonder de rest van het systeem aan te raken. Dat kanaal verstuurt bewust echt, in plaats van een tijdlang alleen te loggen wat het gestuurd zou hebben. Meeloggen kan de faalrichting van meldingen namelijk niet meten — blijft het kanaal door een fout helemaal stil, dan staat er niets in de log, en niets ziet er precies zo uit als een rustig huis. De harde alarmen lopen daarnaast over het Pushover-kanaal, dus een alarm zonder pushmelding is een meetbare misser.

Álles wat het huis te melden heeft gaat daarbij door één deur. De soorten staan in een register — storingen, een gepland laadmoment, de vaatwasser, het dagrapport, de tuin, de stofzuigrobot, een comfortvraag — elk met zijn eigen uitlegzin, een minimale rol, en twee losse schakelaars: één voor de melding in de app en één voor Pushover. Pushover is daarmee extra en nooit vervangend: een app-melding wordt nooit onderdrukt omdat de telefoonmelding aanstaat, en Pushover is een huis-kanaal en geen persoonlijk kanaal — wat aan één persoon gericht is, zoals een chatantwoord, gaat daar niet overheen. Elke melding zegt zelf van welke bewaker hij komt; wie al een eigen bewaker heeft wordt niet nóg eens generiek gemeld, zodat er naast de echte melding geen inhoudsloze echo staat.

En hij herhaalt zichzelf niet. Een melding die telkens opnieuw binnenkomt is geen tien meldingen maar één probleem. Komt precies dezelfde melding binnen het uur terug, dan groeit er een teller op de rij die er al staat, in plaats van dat er een nieuwe rij en een nieuwe bezorging ontstaat: "94× sinds gisteravond, laatste drie minuten geleden". Twee soorten dempen met opzet niet — de testknop, want twee keer drukken hoort twee keer aan te komen, en het chat-antwoord, want twee identieke antwoorden zijn twee antwoorden. De aanleiding was meetbaar: in één nameting waren 94 van de 130 meldingen dezelfde waarschuwing, om de zes minuten.

Hij kost geen bundel. Een app die je tien keer per dag openslaat, mag geen dataverbruik hebben. De grootste post daarin zijn de foto's van de gezinsleden: onbewerkt en telkens opnieuw opgehaald kosten die bijna acht megabyte per keer dat de app opengaat. Ze zijn daarom vijftien keer verkleind en worden hoogstens één keer per etmaal ververst. Zoiets zie je niet aan een app — je ziet het alleen als je er expliciet naar meet.

Het Nu-scherm is van jou, niet van de app. Welke tegels erop staan en in welke volgorde, kiest ieder gezinslid zelf: in Mijn instellingen staat de hele lijst, elk met een greepje om hem te verslepen en een schakelaar om hem weg te laten. Die keuze hangt aan het account en niet aan het toestel, dus hij verhuist mee naar een nieuwe telefoon. Welke tegels er te kíézen zijn staat op de server, niet in de browser — komt er een tegel bij, dan verschijnt die vanzelf in ieders lijst zonder dat er iets bijgewerkt hoeft te worden. In datzelfde scherm staat ook je eigen profielfoto, en de knoppen waarmee je per soort melding zegt of je hem wilt hebben: wie geen bericht wil over een geplande vaatwasser krijgt die niet meer, terwijl de harde alarmen bij iedereen blijven aankomen.

Tegen het huis praten

Het gesprek met de analist kan ook hardop. De telefoon neemt op, de server verstaat, de analist antwoordt en de telefoon leest dat antwoord voor. Verstaan én spreken gebeuren lokaal: naast de app draait een klein proces dat een spraakmodel en een Nederlandse stem warm houdt, want koud inladen kost anderhalve seconde per beurt. Dat proces luistert alleen naar de eigen machine, de opname gaat naar een tijdelijk bestand dat hoe dan ook weer wordt opgeruimd, en de cloudterugval staat uit: valt het lokale pad weg, dan komt er een nette melding en gaat er niets naar buiten. Een teller per pad maakt zichtbaar of er tóch ooit naar buiten wordt uitgeweken.

De herkenning krijgt de namen van dit huis mee — gezinsleden, apparaten, merken — en daarachter staat een na-correctie op precies díe eigennamen: voorspelbaar en toetsbaar in plaats van fonetisch gokken. En omdat verstaan ongeveer een derde van de opnameduur kost, is de opname begrensd en wordt een te lange opname geweigerd vóórdat het model eraan begint. Dat scheelt een storingsmelding die geen storing is.

De belangrijkste vangrail gaat over stilte. Een spraakmodel dat niets hoort, verzint een zin — gemeten: één seconde stilte leverde een compleet verzonnen vraag op, mét bijbehorende rekentijd. Er staan daarom twee onafhankelijke vangrails: het model krijgt de stilte niet eens te horen, en daarbovenop geldt dat te weinig échte spraak — of een van de bekende verzinsel-zinnetjes — hetzelfde behandeld wordt als een lege opname. "Ik heb niets verstaan", nooit een verzinsel dat als vraag het gesprek in wandelt. Eén motor mag nooit de enige bewaker tegen zijn eigen verzinsels zijn.

Het gesproken antwoord is bewust kort: hooguit een paar zinnen, getallen uitgeschreven, en de volle tekst ingeklapt onder "Details". Levert het model zelf geen voorleesversie, dan maakt de server er een — er is dus altijd iets om voor te lezen. En er is een proefstand die wél verstaat maar niet verstuurt: de tekst komt in het invoerveld en jij drukt zelf op verzenden.

De grens met het wandtablet is bewust: alles wat de telefoon toont, bestaat ook in de hoofdapp. Wat de telefoon wél anders doet is de maat — leesbaar in de hand in plaats van op twee meter afstand — en de volgorde, want onderweg wil je eerst weten of er iets aan de hand is en pas daarna hoeveel de zon vandaag deed.

13

De afvalkalender — rechtstreeks van de gemeente, op het wandtablet

Het wandtablet toont al vele dingen: energie, huishoudapparaten, agenda's van het gezin. Daar komt bij: de eerstvolgende ophaaldag voor huisvuil — groot en duidelijk op de plek waar tot nu toe een knop "Alle lampen uit" zat (die functie zit ook in de lampen-tegel zelf). Elke dag van het jaar staat in een gemeentelijke afvalkalender; Homey_Log haalt die rechtstreeks op als een iCalendar-feed (.ics), parseert hem en houdt alle ophaaldata van het jaar bij.

Op de ophaaldag zelf gaat er om 06:00 's ochtends een melding af — bijvoorbeeld "GFT vandaag" — en die kun je niet zomaar wegklikken. De melding verdwijnt alleen als je expliciet op "Ik heb hem aan de weg gezet" drukt. Niet door ernaast te klikken, niet door het venster weg te swipelen, niet vanzelf. Dat bewuste moment van bevestiging geldt daarbij voor het hele huis tegelijk: druk je op het wandtablet op de bevestiging, dan is de melding ook van je telefoon verdwenen. Geen twee verschillende meldingen op twee verschillende apparaten, die je allebei apart af moet zeggen.

Eenvoudig gezegd: de gemeente stuurt de data, Homey_Log zorgt dat het huis weet wanneer wat wordt opgehaald, en de wandtablet — die in de eetkamer aan de muur hangt — helpt ervoor te zorgen dat het vuil werkelijk aan de weg komt. Niet meer, niet minder.

De bestaande Homey-app Trash Checker die de melding op de LaMetric-klok in de keuken toont, draait gewoon door. Homey_Log raakt die app niet aan — het is een parallel systeem, geen vervanging. Marc en het gezin krijgen dus op twee plekken hetzelfde bericht, en beiden werken onafhankelijk.

Waarom rechtstreeks van de gemeente?

Veel afvalkalenders worden door lokale gemeenten aangeboden als een webformulier: je vult je postcode en huisnummer in en de gemeente geeft je terug welke soorten afval, op welke dagen, door welke route worden opgehaald. Dat webformulier stuurt een iCalendar-link terug. Homey_Log vraagt die feed op — één keer bij de opstart en daarna op vier vaste momenten per dag (05:00, 11:00, 17:00 en 23:00) — zodat er geen vertraging ontstaat en er geen aparte Homey-app of -integratie voor nodig is. Als de gemeente ooit de route aanpast (wat regelmatig gebeurt), wordt dat vanzelf zichtbaar zodra de feed ververst.

De vier soorten afval die hier worden opgehaald zijn: GFT (wekelijks), PMD oftewel plastic, metalen en drankkartons (tweewekelijks), papier (maandelijks) en restafval (maandelijks). Dat zijn ruim honderd ophaalmomenten per jaar — geen enkele dag heeft twee soorten tegelijk. Als je op de tegel klikt, zie je de volgende twee weken in detail; een knop "Toon de rest van het jaar" vouwt daarna de rest uit.

14

Binnen- en buitenklimaat: de meetlaag onder de warmtepomp

Vijf Netatmo-modules hangen door het huis: Woonkamer, Slaapkamer M&M, Bijkeuken (dat is het basisstation), Achtertuin (buiten) en een Regenmeter. Elke module meet een deel van temperatuur, luchtvochtigheid, CO₂, luchtdruk, geluidsniveau, regen en batterijstand — welke precies hangt van de module af: alleen de Bijkeuken meet luchtdruk en geluid, de Achtertuin meet geen CO₂, en de Regenmeter meet uitsluitend regen.

Homey_Log haalt die metingen niet via Homey op, maar rechtstreeks bij Netatmo. Niet als losse momentopname, maar als stuk meetreeks: elk uur wordt per module de laatste vier uur opgehaald. Elke meting krijgt het tijdstip mee dat Netatmo zélf eraan hangt, niet het moment waarop Homey_Log hem ophaalde. Dat heeft twee prettige gevolgen: dezelfde meting twee keer ophalen levert nooit een dubbele rij op, en een module die is vastgelopen of leeg is valt op, omdat er simpelweg geen nieuw tijdstip meer bijkomt.

Dat die vier uur elkaar overlappen is precies de bedoeling. Wie simpelweg elke tien minuten de huidige stand ophaalt, heeft een stille fout in huis: mislukt een verzoek, dan is die meting voorgoed weg — het volgende verzoek geeft immers de nieuwe waarde en niet de gemiste. Dat is geen theorie; toen Netatmo een tijdlang een kwart van de verzoeken weigerde, ontbraken er in één etmaal 21 van de 96 kwartieren. Elke ronde maakt nu de vorige drie goed, en dat kost geen enkel extra verzoek: zes per uur.

Daaronder ligt een vangnet voor wat tóch is blijven liggen — een storing die langer duurde dan vier uur, of een server die een nacht uit stond. Elke nacht om half vijf loopt Homey_Log het etmaal na dat 24 tot 48 uur oud is, en haalt ontbrekende stukken alsnog op. Lukt dat, dan is er één regel in het logboek; lukt het niet, dan is die meting definitief weg en wordt dat als fout gemeld in plaats van stilzwijgend te verdwijnen.

Op de kaart Netatmo in Instellingen staat hoe lang geleden de laatste geslaagde meting binnenkwam, wat de nachtelijke controle vond, welke modules Netatmo als onbereikbaar meldt, en de laatste storing als die er is. Daar zijn ook de vier knoppen te vinden: hoe vaak er opgehaald wordt, hoe ver er wordt teruggekeken, vanaf welke onderbreking iets een gat heet, en hoe laat de nachtelijke controle draait.

Elke module is ook los te pauzeren, net als elk ander meetpunt in huis — een lege batterij in de regenmeter hoeft niet dagenlang dezelfde waarschuwing op te leveren terwijl er niemand thuis is om hem te vervangen. Ook hier geldt dat pauzeren alleen de meldingen stilzet: de collector blijft ophalen en opslaan, zodat de module zich vanzelf weer meldt zodra hij het weer doet.

Draait het handmatige historie-script (hieronder), dan pauzeert dat de live-collector voor de duur van die run: beide tegelijk zouden om hetzelfde toegangsbewijs vechten. Die pauze is een houdbaarheidsdatum in de database, geen aan/uit-schakelaar — crasht het script, dan loopt de pauze vanzelf af in plaats van de klimaatmeting stilletjes voor altijd te laten staan. Terwijl de pauze loopt, is dat op de kaart Netatmo te zien als een aparte, neutrale melding — bewust niet als storing, want dat is het niet.

De koppeling loopt via een eigen Netatmo-app met een refresh-token in de secret store; de collector houdt die koppeling zelf in stand en herstelt zichzelf als Netatmo het toegangsbewijs een keer afwijst. Blijkt het refresh-token echt kapot, dan komt daar één telefoonmelding van — en pas na zes uur nog een, niet bij elke poll opnieuw. Die melding maakt onderscheid tussen twee storingen die er van buiten hetzelfde uitzien maar een tegengestelde oplossing hebben: een versleten refresh-token (er moet een vers token geplakt worden) en afgewezen clientgegevens (dán helpt een vers token juist niet, en moet de client id of het secret nagekeken worden). Netatmo zegt zelf welke van de twee het is. Beide onder één noemer "tokenvernieuwing mislukt" schuiven kost een half uur zoeken in de verkeerde richting — dat is een keer gebeurd. In het verlengde daarvan controleert het invoerveld nu ook de vórm van de client id — 24 tekens, cijfers en a-f — zodat een half overgenomen waarde niet stilzwijgend wordt opgeslagen. Te bekijken in een eigen Climate-tab in de groep House, met een tijdlijn per kamer. Standaard staat alleen de temperatuur aan; vocht, CO₂, luchtdruk, geluid en regen zijn er met een schakelaar bij te zetten. Naast de vijf Netatmo-ruimtes staat daar een zesde: de meterkast, gemeten door de gateway van het weerstation die daar toch al hangt. Die reeks komt uit een heel andere bron, maar de klimaatlaag is een lijst adapters — een ruimte houdt daardoor één doorlopende historie, ook als de meter eronder wisselt. Naast de losse metingen houdt Homey_Log ook een samenvatting per kwartier bij — bedoeld om naast het energieverbruik te kunnen leggen — met daarin per kwartier óók hoevéél metingen eronder liggen én hoeveel daarvan uit de historische ophaal komen. Dat tweede getal is nodig omdat de historie op halfuurkorrel staat en de modules zelf ongeveer elke vijf minuten meten: aan het aantal metingen alleen is een kwartier van vandaag niet van een kwartier uit 2015 te onderscheiden.

Dat verschil in korrel is geen detail. Regen wordt geteld als "hoeveel er viel sinds de vorige meting", dus die getallen worden bij elkaar opgeteld. Wordt er maar één op de twee metingen opgehaald, dan verdwijnt een korte bui volledig uit beeld — gemeten aan een buitje van 0,1 mm dat niet in de reeks stond terwijl Netatmo het gewoon nog had. Dat is de reden dat er een stuk reeks wordt opgehaald en geen momentopname. Regencijfers van vóór 1 augustus 2026 staan nog op de halfuurkorrel van de historische ophaal — binnen die periode kloppen ze onderling, maar ze zijn niet één op één te vergelijken met de fijne reeks van daarna.

Waarom dit er is

Zonder binnen- én buitentemperatuur is "de warmtepomp verbruikte 12 kWh" een getal zonder betekenis. Mét die twee wordt het "12 kWh om bij 2 °C buiten de woning op 20 °C te houden" — en dát is pas een getal waarmee je iets kunt. Deze meetlaag is de basis onder de warmtepomp-analyse die in het volgende hoofdstuk staat.

De historie is binnengehaald tot 1 januari 2024: ruim 218.000 metingen over alle vijf de modules, aaneengesloten van die datum tot vandaag. Bij Netatmo staat nog méér klaar — Bijkeuken vanaf 7 juli 2015, Woonkamer vanaf 5 februari 2018, Achtertuin vanaf 11 december 2019, Regenmeter vanaf 2 juni 2022 en Slaapkamer M&M vanaf 3 november 2023. Dat oudere deel is bewust blijven staan; het script om het op te halen ligt klaar en Marc beslist of en wanneer dat gebeurt.

15

De warmtepomp van binnenuit

De warmtepomp is verreweg de grootste verbruiker in huis, en in de energieboekhouding ook de stilste: daar komt een kilowattuur-getal binnen en verder niets. Een NIBE SMO 40 weet intern veel meer — aanvoer- en retourtemperatuur, hoe hard de compressor loopt, of de elektrische bijverwarming vrijgegeven is, welk prijsniveau hij denkt te zien. Dat staat allemaal op de eigen bus van het toestel, en die bus wordt meegelezen door een klein kastje bij de pomp dat de berichten doorstuurt naar Homey_Log.

Elke vijf minuten loopt de collector 34 registers langs — één vraag per register, netjes op een rij, want de pomp beantwoordt er maar één tegelijk. Zo'n ronde duurt ruim een minuut en levert één rij per ronde op: alle waarden bij elkaar, plus hoeveel registers er níét antwoordden en hoe lang de ronde duurde. Die twee laatste getallen zijn geen bijvangst maar de bewaking zelf: een pomp die stopt met antwoorden ziet er in de grafiek anders uit dan een pomp die stilstaat, en dat verschil moet zichtbaar blijven.

Die lijst registers is meteen de enige plek waar per register staat wat het nummer is, hoe het geschaald wordt, in welke kolom het landt en hoe je het over een periode mag samenvatten. Een register erbij is daardoor één regel: de kolom, de opslag en de reeks komen vanzelf mee. Registers die als instelling gemarkeerd staan krijgen bovendien een logregel zodra iemand eraan draait.

Op de tab Heat pump staan die reeksen naast elkaar, met een statuskaart erboven die de huidige stand samenvat: draait de compressor, hoeveel graden zit er tussen aanvoer en retour, staat er een alarm. Alarmen en storingsknoppen melden zich ook uit zichzelf op de telefoon — als het alarmnummer verandert, als een alarm voorbij is, en als de pomp langer zwijgt dan afgesproken. Op de telefoon staat dezelfde pomp als tegel: de aanvoer, de buitentemperatuur, de retour, de compressorfrequentie, en één woord dat zegt wat hij doet — in rust, verwarmt, koelt, ontdooit, warm water. Dat woord wordt op de server afgeleid uit drie registers, waarbij ontdooien van alles wint; de telefoon leidt zelf niets af. Is de laatste geslaagde meting ouder dan drie meetrondes, dan wordt de tegel grijs en toont hij de leeftijd van die meting in plaats van een stand — een oude waarde als vers presenteren is precies de fout die in dit huis eerder tweeënhalve dag onopgemerkt bleef.

De pomp tekent zijn eigen stooklijn

De stooklijn — hoe warm het water moet worden bij welke buitentemperatuur — is een instelling met twee knoppen: een curvenummer en een verschuiving. De vorm van die curve staat in geen enkel register; die zit in de firmware van de pomp en nergens anders. Wat de pomp wél prijsgeeft is het punt op de curve van dit moment: welke aanvoertemperatuur hij bij de huidige buitentemperatuur berekent. Elke buitentemperatuur die voorbijkomt levert dus één punt, en over een koude periode tekent de pomp zo zijn eigen lijn.

De grafiek groepeert die punten per combinatie van curvenummer, verschuiving en hele buitengraad, en neemt daarvan de mediaan — niet het gemiddelde, want één uitschieter tilt anders de hele lijn op. De huidige instelling staat vooraan, eerdere combinaties blijven als eigen lijn zichtbaar, en een verschuiving is vooruit te tekenen omdat één stap een vast aantal graden is. Een ander curvenummer is dat niet: dat verandert de vorm, en die vorm ken je pas als de pomp hem zelf heeft gelopen. De onder- en bovengrens die de pomp op zijn aanvoer legt worden als register meegelezen en begrenzen de verwachte lijn.

Waarnemen en adviseren, nooit sturen

De scope ligt hier expliciet vast, en hij is smaller dan technisch mogelijk is: Homey_Log leest de warmtepomp uit en zegt er iets van, maar draait geen enkele knop. Comfort weegt in dit huis zwaarder dan de laatste procenten rendement, en een kille woonkamer is geen acceptabele prijs voor een experiment. Dat is geen tijdelijke voorzichtigheid: de winter is er om te meten, en de vraag of het huis mag bijsturen komt pas aan de orde als er een seizoen aan cijfers ligt om die sturing op te baseren.

Rendement in plaats van verbruik. Met aanvoer, retour en compressorgedrag naast het stroomverbruik is de vraag niet meer "hoeveel kilowattuur", maar "hoeveel warmte per kilowattuur" — de COP over de dag en over het seizoen. Die wordt over hele kwartieren gerekend, nooit over een halve emmer: een venster dat middenin een kwartier begint telt een stukje warmte mee waarvan de stroom buiten beeld valt, en dat vleit het cijfer.

Hoe het voelde, telt net zo hard. Een winter waarin alleen kilowatturen zijn opgeschreven, levert in het voorjaar een rendementsgrafiek op waar niemand iets mee kan: was het huis toen ook prettig? Daarom staat er naast de meetlaag een winterlogboek. In de telefoon-app zitten zes knoppen — te koud, goed, te warm, voor de woonkamer en voor de badkamer — en één tik legt vast hoe het op dat moment voelde, bij wie dat zei. Terugzien doe je op de Climate-tab, onder de grafiek, zodat een oordeel altijd naast de temperatuur van dat moment staat.

En omdat een knop waar niemand aan denkt geen data oplevert, vraagt de app er af en toe zelf om — maar alleen op een avond waarop het antwoord iets waard is: als het buiten koud was, als er die dag aan de pomp gedraaid is, of als de stroomprijs lang hoog stond. Hoogstens één keer per dag, en niet aan wie die dag al iets doorgaf. Ook dit heeft een einddatum in de code staan: het is bedoeld om te ijken, niet om te blijven vragen.

16

De tuin: bodemvocht in plaats van een klok

Een besproeiingssysteem op een tijdklok geeft water omdat het dinsdag is. In de grond staan nu acht draadloze vochtsensoren, verdeeld over de plekken die er los van elkaar toe doen — borders, potten, gazon — en die meten wat er werkelijk aan de wortels gebeurt. Ze melden zich bij een eigen gateway in huis, en Homey_Log haalt daar elk kwartier de stand op.

Op de tab Garden staat per plek een tegel met de huidige vochtstand en een advies in gewone taal: hoeft niet, mag straks, of nu wel. Daaronder ligt de dagweergave, en die tekent behalve het gemeten verloop ook wat er nog aankomt — de verwachte regen als een vlak, de verdamping als eigen reeks, en het te verwachten vochtverloop als gestippelde projectie. Die projectie is geleerd en niet gerekend: uit de eigen reeks blijkt hoeveel procent deze grond per millimeter verdamping zakt en hoeveel hij per millimeter regen stijgt, en met die twee getallen is de rest van de dag te schatten.

De regen- en verdampingsverwachting komt van hetzelfde weermodel dat de zonvoorspelling levert. Dat is geen toeval maar zuinigheid: één bron, één ophaalpad, en een voorspelling die zich hier net zo goed laat nameten als daar — wat het model gisteren over vandaag zei, ligt bewaard naast wat de regenmeter werkelijk telde.

Stilte is hier het goede nieuws

De tuin heeft één bewaker die uit zichzelf iets zegt: een sensor die te lang zwijgt of een vochtstand die door de bodem zakt. Hij kijkt elk kwartier en meldt zich alleen als er iets aan de hand is. Alle andere tuininformatie is er om op te zoeken, niet om ongevraagd langs te komen — een huis dat elke ochtend meldt dat de border nog vochtig is, leert je binnen een week zijn meldingen te negeren.

Wat er bewust nog niet is, is de kraan. De besproeiingscontroller staat klaar, maar de volgorde die dit hele project kenmerkt geldt ook hier: eerst een seizoen meten, dan pas iets aan een kraan hangen. Er ligt bovendien een bekende valkuil om vooraf op te lossen — automatisch sproeien ziet er voor de lekbewaking van de watermeter uit als precies datgene waar die voor waarschuwt.

17

Waar iedereen en elke auto is

Een huis dat wil weten of het zonneoverschot naar de auto mag, moet weten of die auto er straks nog is. En een analist die verbruik verklaart, heeft aan "er was niemand thuis" meer dan aan welke grafiek ook. Losse aanwijzingen zijn er genoeg — Homey weet of iemands telefoon op het wifi zit, de auto-integratie kent de coördinaten van de auto's, en binnenshuis hoort een net van kleine bluetooth-ontvangers welke telefoon waar ligt — maar een aanwijzing is nog geen antwoord. Daarom staat er per onderwerp één antwoord: vier gezinsleden, twee auto's en het huis zelf, met erbij welke bron dat zegt en hoe oud die waarneming is.

Dat antwoord is bewust gelaagd in plaats van gemiddeld. Het beste wat een systeem kan zeggen is een benoemde plaats: thuis, op het werk, bij de sportclub. Kan het dat niet, dan een coördinaat met zijn nauwkeurigheid erbij. Kan het dat ook niet, dan een aanwijzing ("waarschijnlijk niet thuis"). En anders eerlijk: onbekend. Elke laag is een stap minder zekerheid, en die stap is zichtbaar in plaats van weggerekend. Er is met opzet geen zekerheidsscore van 0 tot 100 waarin twijfel kan verdwijnen: de uitkomst moet in één zin uit te leggen zijn. Bij "thuis" kan er nog een kamer bij staan, maar dat is geen vijfde laag: het is een toevoeging aan die ene benoemde plaats, en alleen het bluetooth-net mag hem invullen.

Een momentopname en een toestand zijn niet hetzelfde

Het onderscheid dat dit systeem draagt: sommige bronnen meten waar iets op dat moment was, andere melden een verandering en zwijgen daarna. Een telefoonpositie is een momentopname en veroudert — een fix van uren geleden zegt niets over nu. Homey's aanwezigheid is een toestand: die meldt één keer dat je thuis bent en zegt dagenlang niets meer, en die stilte betekent "niets veranderd", niet "niets bekend". Wie beide als hetzelfde behandelt, krijgt óf een huis dat je 's nachts kwijtraakt, óf een huis dat volhoudt dat je thuis bent terwijl je in het buitenland zit.

Elke bron heeft daarom zijn eigen houdbaarheid, instelbaar per bron. Die van de telefoon staat op zes uur en niet op een kwartier, en dat is nagemeten: als de telefoon beweegt meldt hij ongeveer elke minuut, maar ligt hij stil dan kan het uren duren. Met een kwartier viel iemand op vakantie terug op "waarschijnlijk niet thuis" terwijl er een prima positie van een uur oud lag — een slechter antwoord dan de positie mét zijn leeftijd erbij. Dat kan veilig, omdat een benoemde plaats altijd wint van een coördinaat: een oude positie kan nooit "thuis" gaan claimen. Een toestandsbron heeft daarnaast een vangnet van twaalf uur, anders zou een Homey die 's nachts omvalt de volgende middag nog steeds "thuis" roepen. Voor de kamer is dat vangnet korter — twintig minuten — en om te voorkomen dat juist dát vangnet iemand die stilzit zijn kamer afpakt, schrijft het systeem elke tien minuten een levensteken zolang het toestel gewoon gezien wordt. En het huis zelf is geen meting maar een feit: dat staat waar het staat en veroudert nooit.

Eén laag dieper: in welke kamer

"Thuis" is voor de helft van de vragen te grof. Daarom hangen er tien kleine bluetooth-kastjes met open firmware door het huis — zes beneden (twee in de woonkamer, keuken, kantoor, bijkeuken, garage) en vier daarboven (hal, een slaapkamer, badkamer, strijkkamer) — die luisteren naar de telefoons en het horloge van het gezin. Ze praten met een berichtenmakelaar die in de app zélf draait, dus er staat geen losse dienst en geen Homey-app tussen. Een kastje kent alleen zijn eigen naam; wélke kamer dat is staat in de mapping-laag, en de ruimtelijst komt uit het 3D-model van het huis.

Signaalsterkte is geen afstand, en de kastjes schelen onderling tot zo'n 20 decibel in gevoeligheid. Elk kastje wordt daarom geijkt: een minuut lang een toestel op één meter afstand, en de mediaan die daaruit komt is vanaf dan de nulmaat van dát kastje. Pas daarna is "het dichtstbijzijnde kastje" een zinnige uitspraak. De metingen worden per minuut tot één mediaan samengevouwen, het dichtstbijzijnde kastje binnen vier meter wint, en een wissel telt pas na een minuut verblijf en een meter verschil — anders springt de kamer heen en weer tussen twee even sterke buren. Komen er wél metingen binnen maar geen enkele binnen bereik, dan laat het systeem de kamer na drie minuten los: liever "geen kamer" dan de kamer van een half uur geleden. En van twee toestellen van dezelfde persoon wint het toestel dat beweegt — een telefoon aan de lader zegt minder dan een horloge dat meeloopt.

Wat blijft staan is een verblijf: per persoon een aaneengesloten periode in één kamer, met welk toestel de doorslag gaf en waarom. De ruwe metingen zijn diagnostiek en mogen op termijn verdwijnen, de verblijven worden nooit opgeruimd, en een toestel dat niemand heeft ingeschreven wordt geteld maar niet bewaard.

Meedoen is een eigen keuze, per persoon: onder Mijn instellingen → Locatie delen schrijf je je eigen telefoon in, en wie dat doet leest de app altijd uit de aanmelding af — nooit uit het verzoek zelf. Wie ingeschreven staat krijgt op de telefoon de tegel Waar ben ik, waarop je met één tik kunt zeggen in welke kamer je écht bent. Dat is geen speeltje: het is de grondwaarheid waartegen het systeem zijn eigen trefzekerheid meet.

De bronnen zijn Homey's aanwezigheid, de telefoons — via OwnTracks en via de app zelf — de auto-telemetrie en het bluetooth-net binnenshuis. Welke bron voorrang krijgt voor welke soort vraag is instelbaar en geen code. Elke overgang van de ene geldende plaats naar de andere wordt bovendien als gebeurtenis vastgelegd: aankomen en vertrekken, met de bron en de laag erbij, zodat "er was niemand thuis" geen gevoel is maar een uitspraak met een tijdstip. Op de pagina Location staat per onderwerp het antwoord met zijn laag, zijn bron en zijn leeftijd, en of de bronnen het onderling oneens waren — een conflict wordt getoond, niet stil weggepoetst. Plaatsen teken je zelf in op een kaart: een cirkel om het huis, om het werk, om de sportclub, met een naam eraan. Vanaf de telefoon zit er bij een auto een knop "navigeer hierheen", die de kaart-app van het toestel opent — en die knop staat er alleen op de telefoon, want op het wandtablet zou een externe link een tabblad openen dat daar nooit meer weggaat.

Locatiegegevens vallen volledig achter de beveiliging: zonder aanmelding of sleutel levert de app hier niets, ook niet aan het eigen wandtablet. Dat is de enige plek in het systeem waar het ontbreken daarvan niet leidt tot een foutmelding maar tot een blok dat helemaal niet getekend wordt — wie precies waar is, hoort niet half zichtbaar te zijn.

18

Waar dit heen gaat

Het grootste onderwerp is een winter lang meten. De warmtepomp geeft zijn cijfers, het huis geeft zijn oordelen over hoe het voelde, en het weer staat ernaast — en pas als daar een heel stookseizoen van ligt, is er iets om op te bouwen. Wat daarna komt is de omkering die dit project van meet af aan voor ogen heeft: het huis krijgt beleid in plaats van losse regels. Niet "zet de laadpaal op zes ampère", maar "comfort gaat voor rendement, en binnen dat comfort mag het zo goedkoop mogelijk". Een besluitvormer toetst wat het huis van plan is aan dat beleid, eerst maandenlang in de schaduw — hij schrijft op wat hij zou hebben besloten, en niemand voert het uit — zodat er bewijs ligt vóór er ooit een knop overgaat. Dat is werk voor het voorjaar; deze winter is er om te meten.

Dichterbij ligt de eerste schakelaar van de regisseur. Hij plant alles al, hij meet zichzelf al na, en de vraag is niet meer of hij het kan maar of hij tien vergelijkbare dagen haalt waarop hij aantoonbaar niet slechter is dan wat er nu draait. Het eerste apparaat dat die meetlat haalt, krijgt de sturing werkelijk in handen — de boiler is de meest waarschijnlijke eerste, omdat een vat warm water de vriendelijkste fout maakt die er te maken valt. Daarna is de thuisbatterij als deelnemer aan de beurt: dat is vandaag de grootste post waar het plan van de regisseur en het bestaande kwartierplan nog langs elkaar heen rekenen.

Verder groeit de digitale tweeling naar buiten — tuin en garage erbij, met de laadpaal, de buitencamera's en de vochtsensoren op hun echte plek in het model. En de kraan van de tuin wacht nog altijd op een seizoen aan cijfers, in dezelfde volgorde die dit hele project kenmerkt: eerst meten, dan pas iets aan een kraan hangen.

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.