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>
144 lines
7.1 KiB
PHP
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,
|
|
],
|
|
];
|
|
}
|
|
}
|