theoandClaude Opus 5 bfc96acb5a feat: temperatuurverloop van vandaag ook bij de binnenmodules
Naast het getal in elke modulekaart staat nu hetzelfde soort lijntje als
bij buiten (200x40). Zonder labels: daar is in zo'n kaart geen ruimte
voor, de schaal staat al op de regel eronder en het venster is hetzelfde
"vandaag" als bij het grafiekje van buiten ernaast.

Per module een eigen getmeasure, gecachet onder temperature-<module> met
een gedeelde TTL; fetch() heeft daarvoor een losse ttlKey gekregen, want
anders zou de TTL-lookup per module mislukken en zou er helemaal niet
gecachet worden. Het basisstation meet zelf temperatuur en wordt zonder
module_id opgevraagd, dus die parameter is optioneel geworden. In de
payload als indoor[].history; mislukt er een, dan blijft die lijst leeg
en verdwijnt alleen dat lijntje, met de foutcode in errors. Een
stale-vlag per kamer zou het contract nodeloos zwaar maken.

De hoogte is overal 40px, ook bij een vastgezette module: gelijke hoogte
houdt de kamers onderling vergelijkbaar, en een hoger lijntje zou de
toch al krappe speling onder de kolom opeten. De grafiekjes voegen geen
hoogte toe aan de kaarten.

FakeSource houdt nu alle aanroepen bij in plaats van alleen de laatste,
zodat een test de volgorde van de vier temperatuur-calls kan nalopen.
Servicewerker naar v11.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 12:10:40 +02:00
2026-09-11 15:13:54 +02:00

Weerscherm

Een PWA voor een iPad mini (liggend) in de woonkamer. Toont de actuele gegevens van een Netatmo-weerstation (buiten, drie binnenruimtes, regen) en een verwachting voor vijf dagen van Open-Meteo.

iPad (PWA) ──GET /api/data.php──▶ PHP-backend ──▶ Netatmo API
                                      │      └──▶ Open-Meteo API
                                      └── storage/ (tokens + cache)

Vereisten

  • PHP 8.3 met ext-curl en ext-json. Geen framework, geen runtime-dependencies.
  • Composer alleen voor PHPUnit (dev). Node 20+ alleen voor de JS-tests (dev).
  • Een webserver met public/ als document root. storage/ ligt daarbuiten en moet schrijfbaar zijn voor de webserver.

Installatie

git clone <repo> weerscherm
cd weerscherm
composer install            # alleen nodig voor tests; de app draait ook zonder vendor/
cp config.example.php config.php
mkdir -p storage && chmod 750 storage

Vul config.php in. De sleutels:

Sleutel Betekenis
data_source fixture (voorbeelddata, geen koppeling nodig) of live
netatmo_client_id, netatmo_client_secret uit de app op dev.netatmo.com
netatmo_device_id optioneel, _id van het basisstation; leeg = eerste station
timezone fallback als het station geen tijdzone meldt (standaard Europe/Berlin)
forecast_lat, forecast_lon optioneel; leeg = locatie van het station
indoor_modules optioneel, _id => label in de gewenste volgorde; leeg = Netatmo-namen
cache_ttl seconden per bron: stations 300, measure 900, forecast 3600
storage_path map voor tokens en cache, buiten de docroot

config.php en storage/ staan in .gitignore en komen nooit in git.

Netatmo-koppeling

  1. Maak een app aan op https://dev.netatmo.com. Zet client_id en client_secret in config.php.
  2. Genereer met de token generator op de app-pagina een token met scope read_station.
  3. Sla de refresh token op:
php bin/netatmo-init.php '<refresh_token>'

Dat schrijft storage/netatmo-token.json (mode 600). Bij het eerste verzoek wordt het access token ververst. De refresh token roteert bij elke verversing; de nieuwste wordt automatisch bewaard. Gebruik een oude nooit opnieuw.

Toont het scherm "Netatmo-koppeling verlopen", genereer dan een nieuwe token en voer stap 3 opnieuw uit.

Tests

composer test    # PHPUnit: mapping, regenberekening, tokenrotatie, cache, stale-fallback
npm test         # node --test: formattering, WMO-mapping, service worker

Lokaal draaien

Snel, zonder Docker:

php -S 127.0.0.1:8080 -t public

Open http://127.0.0.1:8080/. Met data_source = fixture is er geen koppeling nodig. Visuele check: een browservenster van exact 1133 × 744 naast de mockup (tab A).

Via de OrbStack shared-Apache-setup (weerscherm.test):

# eenmalig: hostnaam
sudo sh -c 'echo "127.0.0.1 weerscherm.test" >> /etc/hosts'
# shared apache herstarten (vhost en mount staan al in orbstack/shared)
cd /Volumes/development/sites/orbstack/shared && docker-compose up -d --build
# php-container van dit project
cd /Volumes/development/sites/orbstack/divers/weerscherm && docker-compose up -d --build

De bestanden staan in orbstack/divers/weerscherm/ (Dockerfile, compose) en orbstack/shared/apache/vhosts/weerscherm.conf.

Deploy (productie)

Productie-URL: https://homedisplay.bahne-drei.de/ op pensare-server, beheerd met Ansible (infra-ansible-pensare, app weerscherm in inventories/production/host_vars/pensare-server.yml).

  • Code komt uit de publieke Gitea-repo https://git.pensare.nl/theo/weerscherm.git (branch main); de role cloont of pullt als Linux-user weerscherm.
  • config.php wordt gerenderd uit roles/webapp/templates/weerscherm.config.php.j2 met de Netatmo-gegevens uit de vault (vault_weerscherm_netatmo_client_id, vault_weerscherm_netatmo_client_secret).
  • storage_path is /var/www/weerscherm-storage (www-data, 0770), buiten de release-map, zodat de token en de cache deploys overleven.
  • Document root is public/; src/, bin/ en config.php zijn niet via HTTP bereikbaar.
  • composer install is op de server niet nodig: src/autoload.php laadt de klassen zelf.
  • SSL via certbot in dezelfde playbook-run.

Nieuwe versie uitrollen:

git push origin main
cd /Volumes/development/sites/pensare/infra-ansible-pensare
ansible-playbook playbooks/deploy-app.yml -e app=weerscherm

Verhoog bij frontend-wijzigingen VERSION in public/sw.js; de app herlaadt zichzelf bij controllerchange en in elk geval dagelijks rond 04:00.

Token opnieuw koppelen (melding "Netatmo-koppeling verlopen"):

ssh weerscherm@65.108.40.86 'cd /var/www/weerscherm && sudo -u www-data php8.3 bin/netatmo-init.php "<refresh_token>"'

Lukt sudo -u www-data niet als weerscherm, doe het dan als deploy met sudo. Controleer daarna https://homedisplay.bahne-drei.de/api/data.php op "errors": [].

  • De app staat bewust zonder login. robots.txt en X-Robots-Tag: noindex houden zoekmachines buiten de deur.

iPad-instellingen

  1. Open de URL in Safari, tik op Delen → Zet op beginscherm. Start de app daarna altijd vanaf het beginscherm (fullscreen, zonder Safari-balken).
  2. Instellingen → Scherm en helderheid → Automatisch vergrendelen → Nooit.
  3. Optioneel: Instellingen → Toegankelijkheid → Begeleide toegang inschakelen en starten met drie keer op de zijknop, zodat het scherm niet per ongeluk verlaten wordt.
  4. Houd de iPad liggend; in portretstand verschijnt "Draai de iPad een kwartslag".

Gedrag bij storingen

Situatie Wat het scherm doet
Meting ouder dan 30 minuten of Netatmo tijdelijk onbereikbaar "meting HH:MM · verouderd" in oranje, laatste data blijft staan
Server of wifi onbereikbaar laatste data blijft staan, kopregel toont "geen verbinding sinds HH:MM"; herstelt vanzelf
Refresh token ongeldig (netatmo_auth) melding over de bovenzone met de herstelinstructie
Open-Meteo onbereikbaar laatst gecachte verwachting, forecast_stale: true
Allereerste start zonder data "Gegevens laden…", daarna een concrete foutmelding

Acceptatiecriteria (spec §11)

  • Op de iPad mini, gestart vanaf het beginscherm, draait de app fullscreen en klopt de layout met ontwerp A.
  • Alle waarden komen overeen met de Netatmo-app: buiten, 3× binnen, min/max met tijdstippen, regen vandaag, de afgelopen 3 dagen en het maandtotaal.
  • De verwachting toont 5 dagen vanaf morgen met het juiste icoon per WMO-code.
  • De data ververst zichzelf. Na middernacht staat de nieuwe datum er binnen 5 minuten.
  • Wifi uit: de laatste data blijft staan met "geen verbinding sinds". Wifi aan: herstelt vanzelf.
  • De Netatmo-token ververst meerdere dagen achter elkaar zonder handmatig ingrijpen.
  • Geen secrets in git, in de browser of in responses. storage/ is niet via HTTP bereikbaar.
  • composer test en npm test slagen.

Structuur

public/            document root: index.html, styles.css, app.js, format.js, icons.js, sw.js, manifest, icons/, api/data.php
src/               Config, Http, TokenStore, NetatmoClient, OpenMeteoClient, Cache, mappers, DashboardService
bin/               netatmo-init.php
storage/           tokens en cache (niet in git, niet in docroot)
tests/             PHPUnit + JSON-fixtures; tests/js voor node --test
docs/              spec, mockup, plan
S
Description
Weerscherm: iPad-PWA met Netatmo-stationsdata en Open-Meteo-verwachting
Readme
516 KiB
Languages
PHP 65.1%
JavaScript 25.8%
CSS 6.3%
HTML 2.8%