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:
@@ -70,7 +70,7 @@ final class DashboardService
|
||||
'humidity' => $station['outdoor']['humidity'],
|
||||
],
|
||||
'outdoor_history' => $outdoorHistory,
|
||||
'indoor' => $station['indoor'],
|
||||
'indoor' => $this->indoorWithHistory($station['indoor'], $station['device_id'], $now),
|
||||
'rain' => $rain,
|
||||
'pressure' => $pressure,
|
||||
'forecast' => $forecast['days'],
|
||||
@@ -128,11 +128,22 @@ final class DashboardService
|
||||
if ($station['device_id'] === null || $moduleId === null) {
|
||||
return ['points' => [], 'stale' => false];
|
||||
}
|
||||
return $this->temperatureHistory($station['device_id'], $moduleId, $now);
|
||||
}
|
||||
|
||||
/**
|
||||
* Temperatuurverloop van vandaag voor één module, apart gecachet per module.
|
||||
*
|
||||
* @return array{points: list<array{at: string, temp: float}>, stale: bool}
|
||||
*/
|
||||
private function temperatureHistory(string $deviceId, ?string $moduleId, \DateTimeImmutable $now): array
|
||||
{
|
||||
$begin = $now->setTime(0, 0, 0)->getTimestamp();
|
||||
$measure = $this->fetch(
|
||||
'temperature',
|
||||
fn() => $this->source->temperatureHistory($station['device_id'], $moduleId, $begin, $now->getTimestamp()),
|
||||
'temperature-' . ($moduleId ?? $deviceId),
|
||||
fn() => $this->source->temperatureHistory($deviceId, $moduleId, $begin, $now->getTimestamp()),
|
||||
self::ERR_NETATMO,
|
||||
'temperature',
|
||||
);
|
||||
return [
|
||||
'points' => $measure['data'] === null ? [] : MeasureSeries::map($measure['data'], $now->getTimezone(), 'temp'),
|
||||
@@ -140,6 +151,27 @@ final class DashboardService
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Zet het temperatuurverloop van vandaag bij elke binnenmodule. Mislukt er één, dan blijft die lijst
|
||||
* leeg (het grafiekje verdwijnt) en staat de foutcode al in errors; een eigen stale-vlag per kamer
|
||||
* zou het contract nodeloos zwaar maken.
|
||||
*
|
||||
* @param list<array<string, mixed>> $rooms
|
||||
* @return list<array<string, mixed>>
|
||||
*/
|
||||
private function indoorWithHistory(array $rooms, ?string $deviceId, \DateTimeImmutable $now): array
|
||||
{
|
||||
if ($deviceId === null) {
|
||||
return $rooms;
|
||||
}
|
||||
foreach ($rooms as &$room) {
|
||||
$moduleId = $room['id'] === $deviceId ? null : $room['id'];
|
||||
$room['history'] = $this->temperatureHistory($deviceId, $moduleId, $now)['points'];
|
||||
}
|
||||
unset($room);
|
||||
return $rooms;
|
||||
}
|
||||
|
||||
/** @return array{days: list<array>, stale: bool} */
|
||||
private function forecast(array $station, \DateTimeZone $tz): array
|
||||
{
|
||||
@@ -175,12 +207,13 @@ final class DashboardService
|
||||
* Cache-first per bron. Bij een fout: stale cache (ongeacht leeftijd) en een foutcode, anders null.
|
||||
*
|
||||
* @param callable(): array $fetcher
|
||||
* @param ?string $ttlKey losse TTL-sleutel wanneer de cachesleutel per module verschilt
|
||||
* @return array{data: ?array, stale: bool}
|
||||
*/
|
||||
private function fetch(string $key, callable $fetcher, string $errorCode): array
|
||||
private function fetch(string $key, callable $fetcher, string $errorCode, ?string $ttlKey = null): array
|
||||
{
|
||||
$now = ($this->now ?? new \DateTimeImmutable('now'))->getTimestamp();
|
||||
$ttl = $this->ttl[$key] ?? 0;
|
||||
$ttl = $this->ttl[$ttlKey ?? $key] ?? 0;
|
||||
if ($ttl > 0 && $this->cache->isFresh($key, $ttl, $now)) {
|
||||
return ['data' => $this->cache->read($key)['data'], 'stale' => false];
|
||||
}
|
||||
|
||||
+11
-3
@@ -69,9 +69,17 @@ final class FixtureSource implements WeatherSource
|
||||
return $body;
|
||||
}
|
||||
|
||||
public function temperatureHistory(string $deviceId, string $moduleId, int $begin, int $end): array
|
||||
public function temperatureHistory(string $deviceId, ?string $moduleId, int $begin, int $end): array
|
||||
{
|
||||
// Dagcurve tussen de min en max uit stationsData(): koudst rond 06:00, warmst rond 15:00.
|
||||
// Dagcurve tussen de min en max die stationsData() voor deze module meldt: koudst rond 06:00,
|
||||
// warmst rond 15:00. Binnen beweegt minder en later dan buiten, zodat de grafiekjes verschillen.
|
||||
[$min, $max, $shift] = match ($moduleId) {
|
||||
'02:00:00:00:00:01' => [9.2, 19.8, 10], // buiten
|
||||
'03:00:00:00:00:02' => [18.1, 19.6, 13], // slaapkamer
|
||||
'03:00:00:00:00:03' => [19.0, 21.0, 14], // kantoor
|
||||
default => [20.4, 22.1, 12], // basisstation
|
||||
};
|
||||
|
||||
$body = [];
|
||||
for ($ts = $begin - ($begin % 1800); $ts <= $end; $ts += 1800) {
|
||||
if ($ts < $begin) {
|
||||
@@ -79,7 +87,7 @@ final class FixtureSource implements WeatherSource
|
||||
}
|
||||
$at = (new \DateTimeImmutable('@' . $ts))->setTimezone($this->now->getTimezone());
|
||||
$hour = (int) $at->format('G') + (int) $at->format('i') / 60;
|
||||
$t = 9.2 + (19.8 - 9.2) * (0.5 + 0.5 * sin((($hour - 10) / 24) * 2 * M_PI));
|
||||
$t = $min + ($max - $min) * (0.5 + 0.5 * sin((($hour - $shift) / 24) * 2 * M_PI));
|
||||
$body[(string) $ts] = [round($t, 1)];
|
||||
}
|
||||
return $body;
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ final class LiveSource implements WeatherSource
|
||||
return $this->netatmo->getPressureHistory($deviceId, $begin, $end);
|
||||
}
|
||||
|
||||
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->netatmo->getTemperatureHistory($deviceId, $moduleId, $begin, $end);
|
||||
}
|
||||
|
||||
+10
-3
@@ -63,10 +63,17 @@ final class NetatmoClient
|
||||
return $this->measureSeries('Pressure', ['device_id' => $deviceId], $begin, $end);
|
||||
}
|
||||
|
||||
/** @return array<string|int, mixed> timestamp => [°C], per 30 minuten, van de opgegeven module */
|
||||
public function getTemperatureHistory(string $deviceId, string $moduleId, int $begin, int $end): array
|
||||
/**
|
||||
* @param ?string $moduleId null meet het basisstation zelf; Netatmo verwacht dan geen module_id
|
||||
* @return array<string|int, mixed> timestamp => [°C], per 30 minuten
|
||||
*/
|
||||
public function getTemperatureHistory(string $deviceId, ?string $moduleId, int $begin, int $end): array
|
||||
{
|
||||
return $this->measureSeries('Temperature', ['device_id' => $deviceId, 'module_id' => $moduleId], $begin, $end);
|
||||
$target = ['device_id' => $deviceId];
|
||||
if ($moduleId !== null) {
|
||||
$target['module_id'] = $moduleId;
|
||||
}
|
||||
return $this->measureSeries('Temperature', $target, $begin, $end);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,8 +16,11 @@ interface WeatherSource
|
||||
/** @return array<string|int, mixed> de "body" van Netatmo getmeasure (timestamp => [hPa]) voor het basisstation */
|
||||
public function pressureHistory(string $deviceId, int $begin, int $end): array;
|
||||
|
||||
/** @return array<string|int, mixed> de "body" van Netatmo getmeasure (timestamp => [°C]) voor de buitenmodule */
|
||||
public function temperatureHistory(string $deviceId, string $moduleId, int $begin, int $end): array;
|
||||
/**
|
||||
* @param ?string $moduleId null meet het basisstation zelf
|
||||
* @return array<string|int, mixed> de "body" van Netatmo getmeasure (timestamp => [°C])
|
||||
*/
|
||||
public function temperatureHistory(string $deviceId, ?string $moduleId, int $begin, int $end): array;
|
||||
|
||||
/** @return array{daily: array<string, mixed>, hourly: array<string, mixed>} de "daily"- en "hourly"-blokken van Open-Meteo */
|
||||
public function forecast(float $lat, float $lon, string $timezone): array;
|
||||
|
||||
Reference in New Issue
Block a user