Files
weerscherm/src/FixtureSource.php
T
theoandClaude Opus 5 ca2fc76552 fix: modulekolom past zich aan het aantal modules aan
Er is een vierde binnenmodule bijgekomen. De kaart verscheen al vanzelf
(StationMapper leest alle NAModule4-modules uit), maar de kolom was op
drie kaarten ontworpen: vier kaarten van 120px duwden de rij op 481px en
daarmee de onderste strook 39px voorbij de onderrand van het canvas.

De kolom heeft nu een vaste hoogte van 412px met overflow verborgen, en
fitRooms() verkleint de inhoud met een schaal zodra die niet past. Bij
drie modules verandert er niets (schaal 1), bij vier wordt het 0,86 en
met een vastgezette module 0,78. Zo blijft het scherm kloppen, ongeacht
hoeveel modules erbij komen.

De dev-fixture heeft er een vierde module bij, zodat dit lokaal te zien
is zonder Netatmo-koppeling. Servicewerker naar v12.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 18:44:38 +02:00

144 lines
7.1 KiB
PHP

<?php
declare(strict_types=1);
namespace Weerscherm;
/**
* Vaste voorbeelddata, relatief aan het huidige moment, zodat de frontend zonder Netatmo-koppeling
* gebouwd en bekeken kan worden en altijd "vandaag" laat zien.
*/
final class FixtureSource implements WeatherSource
{
private const RAIN_PER_DAY = [4.3, 0, 3.2, 5.1, 0, 0, 15.5, 1.2, 6.8, 0.0, 2.4, 0.6, 3.9, 0, 1.1];
private readonly \DateTimeImmutable $now;
public function __construct(?\DateTimeImmutable $now = null, private readonly string $timezone = 'Europe/Berlin')
{
$this->now = ($now ?? new \DateTimeImmutable('now'))->setTimezone(new \DateTimeZone($this->timezone));
}
public function stationsData(): array
{
$at = fn(string $time) => (int) $this->now->modify($time)->format('U');
$today = fn(string $hm) => (int) $this->now->setTime((int) substr($hm, 0, 2), (int) substr($hm, 3, 2))->format('U');
return ['devices' => [[
'_id' => '70:ee:50:00:00:01', 'type' => 'NAMain', 'station_name' => 'Thuis', 'module_name' => 'Woonkamer', 'reachable' => true,
'place' => ['timezone' => $this->timezone, 'location' => [6.55, 51.82]],
'dashboard_data' => ['time_utc' => $at('-2 minutes'), 'Temperature' => 21.3, 'CO2' => 612, 'Humidity' => 48,
'Pressure' => 1014.2, 'AbsolutePressure' => 1011.8, 'pressure_trend' => 'up', 'Noise' => 38,
'min_temp' => 20.4, 'max_temp' => 22.1, 'date_min_temp' => $today('05:50'), 'date_max_temp' => $today('13:40'), 'temp_trend' => 'stable'],
'modules' => [
['_id' => '02:00:00:00:00:01', 'type' => 'NAModule1', 'module_name' => 'Buiten', 'reachable' => true,
'dashboard_data' => ['time_utc' => $at('-3 minutes'), 'Temperature' => 16.4, 'Humidity' => 71, 'min_temp' => 9.2, 'max_temp' => 19.8,
'date_min_temp' => $today('06:10'), 'date_max_temp' => $today('13:05'), 'temp_trend' => 'down']],
['_id' => '03:00:00:00:00:02', 'type' => 'NAModule4', 'module_name' => 'Slaapkamer', 'reachable' => true,
'dashboard_data' => ['time_utc' => $at('-4 minutes'), 'Temperature' => 18.9, 'CO2' => 1180, 'Humidity' => 52, 'min_temp' => 18.1, 'max_temp' => 19.6, 'temp_trend' => 'up']],
['_id' => '03:00:00:00:00:03', 'type' => 'NAModule4', 'module_name' => 'Kantoor', 'reachable' => true,
'dashboard_data' => ['time_utc' => $at('-4 minutes'), 'Temperature' => 20.2, 'CO2' => 1640, 'Humidity' => 44, 'min_temp' => 19.0, 'max_temp' => 21.0, 'temp_trend' => 'stable']],
['_id' => '03:00:00:00:00:05', 'type' => 'NAModule4', 'module_name' => 'Zolder', 'reachable' => true,
'dashboard_data' => ['time_utc' => $at('-5 minutes'), 'Temperature' => 17.4, 'CO2' => 780, 'Humidity' => 55, 'min_temp' => 16.2, 'max_temp' => 18.0, 'temp_trend' => 'up']],
['_id' => '05:00:00:00:00:04', 'type' => 'NAModule3', 'module_name' => 'Regenmeter', 'reachable' => true,
'dashboard_data' => ['time_utc' => $at('-3 minutes'), 'Rain' => 0, 'sum_rain_1' => 0, 'sum_rain_24' => 2.4]],
],
]]];
}
public function measure(string $deviceId, string $moduleId, int $begin, int $end): array
{
$body = [];
$count = count(self::RAIN_PER_DAY);
foreach (self::RAIN_PER_DAY as $i => $mm) {
$day = $this->now->setTime(3, 17)->modify('-' . ($count - 1 - $i) . ' days');
if ($day->getTimestamp() >= $begin) {
$body[(string) $day->getTimestamp()] = [$mm];
}
}
return $body;
}
public function pressureHistory(string $deviceId, int $begin, int $end): array
{
$body = [];
for ($ts = $begin - ($begin % 1800); $ts <= $end; $ts += 1800) {
// Hele halfuren sinds het einde; intdiv houdt $i een int, zodat de modulo hieronder geen
// deprecation geeft (en de waarschuwing niet vóór de JSON in de response belandt).
$i = intdiv($end - $ts, 1800);
$body[(string) $ts] = [round(1012 + 3 * sin($i / 8) + ($i % 5) * 0.1, 1)];
}
return $body;
}
public function temperatureHistory(string $deviceId, ?string $moduleId, int $begin, int $end): array
{
// 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
'03:00:00:00:00:05' => [16.2, 18.0, 15], // zolder
default => [20.4, 22.1, 12], // basisstation
};
$body = [];
for ($ts = $begin - ($begin % 1800); $ts <= $end; $ts += 1800) {
if ($ts < $begin) {
continue;
}
$at = (new \DateTimeImmutable('@' . $ts))->setTimezone($this->now->getTimezone());
$hour = (int) $at->format('G') + (int) $at->format('i') / 60;
$t = $min + ($max - $min) * (0.5 + 0.5 * sin((($hour - $shift) / 24) * 2 * M_PI));
$body[(string) $ts] = [round($t, 1)];
}
return $body;
}
public function forecast(float $lat, float $lon, string $timezone): array
{
$tmin = [11.0, 10.1, 11.3, 12.0, 9.4, 8.1];
$tmax = [17.9, 18.3, 20.1, 17.4, 15.2, 16.0];
$precipDay = [0.0, 0.4, 0.0, 8.2, 3.1, 0.2];
$dates = [];
$hourlyTime = [];
$hourlyTemp = [];
$hourlyPrecip = [];
$hourlyWind = [];
$hourlyDir = [];
for ($d = 0; $d < 6; $d++) {
$day = $this->now->modify("+$d days");
$dates[] = $day->format('Y-m-d');
for ($h = 0; $h < 24; $h++) {
$hourlyTime[] = $day->setTime($h, 0)->format('Y-m-d\TH:i');
$t = $tmin[$d] + ($tmax[$d] - $tmin[$d]) * (0.5 + 0.5 * sin((($h - 9) / 24) * 2 * M_PI));
$hourlyTemp[] = round($t, 1);
$p = ($precipDay[$d] > 0 && $h >= 14 && $h <= 16) ? round($precipDay[$d] / 3, 1) : 0.0;
$hourlyPrecip[] = $p;
$hourlyWind[] = round(3 + 6 * (0.5 + 0.5 * sin((($h + $d * 3) / 24) * 2 * M_PI)), 1);
$hourlyDir[] = ($d * 40 + $h * 7) % 360;
}
}
return [
'daily' => [
'time' => $dates,
'weather_code' => [3, 2, 0, 61, 80, 95],
'temperature_2m_max' => $tmax,
'temperature_2m_min' => $tmin,
'precipitation_sum' => $precipDay,
'precipitation_probability_max' => [10, 20, 5, 80, 60, 15],
],
'hourly' => [
'time' => $hourlyTime,
'temperature_2m' => $hourlyTemp,
'precipitation' => $hourlyPrecip,
'wind_speed_10m' => $hourlyWind,
'wind_direction_10m' => $hourlyDir,
],
];
}
}