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>
This commit is contained in:
2026-09-13 12:10:40 +02:00
co-authored by Claude Opus 5
parent 4c4881fd11
commit bfc96acb5a
11 changed files with 124 additions and 20 deletions
+34 -2
View File
@@ -87,7 +87,7 @@ final class DashboardServiceTest extends TestCase
{
$p = $this->service()->build();
[$deviceId, $moduleId, $begin, $end] = $this->source->lastArgs['temperature'];
[$deviceId, $moduleId, $begin, $end] = $this->source->allArgs['temperature'][0];
self::assertSame('70:ee:50:00:00:01', $deviceId);
self::assertSame('02:00:00:00:00:01', $moduleId, 'de buitenmodule, niet het basisstation');
self::assertSame((new \DateTimeImmutable('2026-09-11 00:00', new \DateTimeZone('Europe/Berlin')))->getTimestamp(), $begin);
@@ -98,6 +98,37 @@ final class DashboardServiceTest extends TestCase
self::assertFalse($p['outdoor_history']['stale']);
}
public function testEveryIndoorModuleGetsItsOwnHistory(): void
{
$p = $this->service()->build();
foreach ($p['indoor'] as $room) {
self::assertNotEmpty($room['history'], "kamer {$room['name']} heeft een verloop");
self::assertSame(['at', 'temp'], array_keys($room['history'][0]));
}
// Het basisstation meet zelf; dat vraag je op zonder module_id.
$modules = array_map(fn(array $args) => $args[1], $this->source->allArgs['temperature']);
self::assertSame(
['02:00:00:00:00:01', null, '03:00:00:00:00:02', '03:00:00:00:00:03'],
$modules,
'buiten, dan het basisstation zonder module_id, dan de twee losse modules',
);
}
public function testIndoorHistoryFailureOnlyEmptiesThatList(): void
{
$this->source->responses['temperature'] = new UpstreamException('500');
$p = $this->service()->build();
foreach ($p['indoor'] as $room) {
self::assertSame([], $room['history']);
}
self::assertSame(21.3, $p['indoor'][0]['temp'], 'de actuele waarden komen uit stationsdata en blijven staan');
self::assertSame(['netatmo_unavailable'], $p['errors']);
}
public function testOutdoorHistoryFailureLeavesTheRestIntact(): void
{
$this->source->responses['temperature'] = new UpstreamException('500');
@@ -231,10 +262,11 @@ final class DashboardServiceTest extends TestCase
$this->service()->build();
$this->service('2026-09-11 12:36:05 UTC')->build();
self::assertSame(['stations' => 1, 'measure' => 1, 'pressure' => 1, 'temperature' => 1, 'forecast' => 1], $this->source->calls);
self::assertSame(['stations' => 1, 'measure' => 1, 'pressure' => 1, 'temperature' => 4, 'forecast' => 1], $this->source->calls);
$this->service('2026-09-11 12:38:05 UTC')->build();
self::assertSame(2, $this->source->calls['stations'], 'stations na 5 min opnieuw');
self::assertSame(4, $this->source->calls['temperature'], 'verloop per module nog binnen 15 min');
self::assertSame(1, $this->source->calls['measure'], 'measure nog binnen 15 min');
}
}
+5 -1
View File
@@ -18,6 +18,9 @@ final class FakeSource implements WeatherSource
/** @var array<string, array> */
public array $lastArgs = [];
/** @var array<string, list<array>> alle aanroepen per soort, in volgorde */
public array $allArgs = [];
public static function fromFixtures(): self
{
$s = new self();
@@ -49,7 +52,7 @@ final class FakeSource implements WeatherSource
return $this->answer('pressure', func_get_args());
}
public function temperatureHistory(string $deviceId, string $moduleId, int $begin, int $end): array
public function temperatureHistory(string $deviceId, ?string $moduleId, int $begin, int $end): array
{
return $this->answer('temperature', func_get_args());
}
@@ -63,6 +66,7 @@ final class FakeSource implements WeatherSource
{
$this->calls[$key]++;
$this->lastArgs[$key] = $args;
$this->allArgs[$key][] = $args;
$r = $this->responses[$key] ?? new \RuntimeException("geen response voor $key");
if ($r instanceof \Throwable) {
throw $r;