feat(api): humidity, CO2, current pressure and 24h pressure history

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-09-13 09:34:20 +02:00
co-authored by Claude Fable 5.1
parent 66bce8e8d0
commit dba1875683
16 changed files with 380 additions and 20 deletions
+1
View File
@@ -30,6 +30,7 @@ return [
'cache_ttl' => [
'stations' => 300,
'measure' => 900,
'pressure' => 900,
'forecast' => 3600,
],
+1
View File
@@ -18,6 +18,7 @@ final class Config
'cache_ttl' => [
'stations' => 300,
'measure' => 900,
'pressure' => 900,
'forecast' => 3600,
],
'storage_path' => null,
+23 -1
View File
@@ -18,7 +18,7 @@ final class DashboardService
private array $errors = [];
/**
* @param array{stations: int, measure: int, forecast: int} $ttl
* @param array{stations: int, measure: int, pressure: int, forecast: int} $ttl
* @param array<string, string> $indoorModules
*/
public function __construct(
@@ -47,6 +47,7 @@ final class DashboardService
$now = $nowUtc->setTimezone($tz);
$rain = $this->rain($station, $now);
$pressure = $this->pressure($station, $now);
$forecast = $this->forecast($station, $tz);
return [
@@ -63,9 +64,11 @@ final class DashboardService
'min_at' => $this->iso($station['outdoor']['min_at'], $tz),
'max' => $station['outdoor']['max'],
'max_at' => $this->iso($station['outdoor']['max_at'], $tz),
'humidity' => $station['outdoor']['humidity'],
],
'indoor' => $station['indoor'],
'rain' => $rain,
'pressure' => $pressure,
'forecast' => $forecast['days'],
'forecast_stale' => $forecast['stale'],
'errors' => $this->errors,
@@ -91,6 +94,25 @@ final class DashboardService
return RainCalculator::compute($measure['data'], $now, $station['sum_rain_24']) + ['stale' => $measure['stale']];
}
/** @return array{current: ?float, trend: ?string, history: list<array{at: string, hpa: float}>, stale: bool} */
private function pressure(array $station, \DateTimeImmutable $now): array
{
$base = ['current' => $station['pressure']['current'], 'trend' => $station['pressure']['trend']];
if ($station['device_id'] === null) {
return $base + ['history' => [], 'stale' => false];
}
$begin = $now->getTimestamp() - PressureHistory::HOURS * 3600;
$measure = $this->fetch(
'pressure',
fn() => $this->source->pressureHistory($station['device_id'], $begin, $now->getTimestamp()),
self::ERR_NETATMO,
);
return $base + [
'history' => $measure['data'] === null ? [] : PressureHistory::map($measure['data'], $now->getTimezone()),
'stale' => $measure['stale'],
];
}
/** @return array{days: list<array>, stale: bool} */
private function forecast(array $station, \DateTimeZone $tz): array
{
+16 -5
View File
@@ -27,16 +27,17 @@ final class FixtureSource implements WeatherSource
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, 'min_temp' => 20.4, 'max_temp' => 22.1,
'date_min_temp' => $today('05:50'), 'date_max_temp' => $today('13:40'), 'temp_trend' => 'stable'],
'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, 'min_temp' => 9.2, 'max_temp' => 19.8,
'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, 'min_temp' => 18.1, 'max_temp' => 19.6, 'temp_trend' => 'up']],
'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, 'min_temp' => 19.0, 'max_temp' => 21.0, 'temp_trend' => 'stable']],
'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' => '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]],
],
@@ -56,6 +57,16 @@ final class FixtureSource implements WeatherSource
return $body;
}
public function pressureHistory(string $deviceId, int $begin, int $end): array
{
$body = [];
for ($ts = $begin - ($begin % 1800); $ts <= $end; $ts += 1800) {
$i = ($end - $ts) / 1800;
$body[(string) $ts] = [round(1012 + 3 * sin($i / 8) + ($i % 5) * 0.1, 1)];
}
return $body;
}
public function forecast(float $lat, float $lon, string $timezone): array
{
$dates = [];
+5
View File
@@ -34,6 +34,11 @@ final class LiveSource implements WeatherSource
return $this->netatmo->getMeasure($deviceId, $moduleId, $begin, $end);
}
public function pressureHistory(string $deviceId, int $begin, int $end): array
{
return $this->netatmo->getPressureHistory($deviceId, $begin, $end);
}
public function forecast(float $lat, float $lon, string $timezone): array
{
return $this->openMeteo->dailyForecast($lat, $lon, $timezone);
+18
View File
@@ -57,6 +57,24 @@ final class NetatmoClient
return $json['body'];
}
/** @return array<string|int, mixed> timestamp => [hPa], per 30 minuten, van het basisstation */
public function getPressureHistory(string $deviceId, int $begin, int $end): array
{
$json = $this->apiGet('getmeasure', [
'device_id' => $deviceId,
'scale' => '30min',
'type' => 'Pressure',
'date_begin' => $begin,
'date_end' => $end,
'optimize' => 'false',
'real_time' => 'false',
]);
if (!is_array($json['body'] ?? null)) {
throw new UpstreamException('getmeasure (Pressure): geen body in antwoord');
}
return $json['body'];
}
/** @param array<string, string|int|float> $query */
private function apiGet(string $path, array $query): array
{
+33
View File
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Weerscherm;
/** Zet een getmeasure-body (scale=30min, type=Pressure) om naar een gesorteerde reeks punten. */
final class PressureHistory
{
public const HOURS = 24;
/**
* @param array<string|int, mixed> $body
* @return list<array{at: string, hpa: float}>
*/
public static function map(array $body, \DateTimeZone $tz): array
{
$points = [];
foreach ($body as $timestamp => $values) {
if (!is_numeric($timestamp) || !is_array($values) || !isset($values[0]) || !is_numeric($values[0])) {
continue;
}
$points[(int) $timestamp] = (float) $values[0];
}
ksort($points);
$out = [];
foreach ($points as $ts => $hpa) {
$out[] = ['at' => (new \DateTimeImmutable('@' . $ts))->setTimezone($tz)->format(\DATE_ATOM), 'hpa' => $hpa];
}
return $out;
}
}
+14 -4
View File
@@ -10,14 +10,16 @@ namespace Weerscherm;
*/
final class StationMapper
{
private const EMPTY_OUTDOOR = ['temp' => null, 'trend' => null, 'min' => null, 'min_at' => null, 'max' => null, 'max_at' => null];
private const EMPTY_OUTDOOR = ['temp' => null, 'trend' => null, 'min' => null, 'min_at' => null, 'max' => null, 'max_at' => null, 'humidity' => null];
private const EMPTY_PRESSURE = ['current' => null, 'trend' => null];
/**
* @param array<string, mixed> $body
* @param array<string, string> $indoorModules _id => label, in de gewenste volgorde
* @return array{timezone: ?string, lat: ?float, lon: ?float, device_id: ?string, measured_at: ?int,
* outdoor: array{temp: ?float, trend: ?string, min: ?float, min_at: ?int, max: ?float, max_at: ?int},
* indoor: list<array{id: string, name: string, temp: ?float, min: ?float, max: ?float, reachable: bool}>,
* outdoor: array{temp: ?float, trend: ?string, min: ?float, min_at: ?int, max: ?float, max_at: ?int, humidity: ?float},
* indoor: list<array{id: string, name: string, temp: ?float, min: ?float, max: ?float, humidity: ?float, co2: ?int, reachable: bool}>,
* pressure: array{current: ?float, trend: ?string},
* rain_module_id: ?string, sum_rain_24: ?float}
*/
public static function map(array $body, ?string $deviceId, array $indoorModules): array
@@ -26,7 +28,8 @@ final class StationMapper
if ($device === null) {
return [
'timezone' => null, 'lat' => null, 'lon' => null, 'device_id' => null, 'measured_at' => null,
'outdoor' => self::EMPTY_OUTDOOR, 'indoor' => [], 'rain_module_id' => null, 'sum_rain_24' => null,
'outdoor' => self::EMPTY_OUTDOOR, 'indoor' => [], 'pressure' => self::EMPTY_PRESSURE,
'rain_module_id' => null, 'sum_rain_24' => null,
];
}
@@ -43,6 +46,10 @@ final class StationMapper
'measured_at' => self::intOrNull(self::dashboard($device)['time_utc'] ?? self::dashboard($outdoor)['time_utc'] ?? null),
'outdoor' => self::mapOutdoor($outdoor),
'indoor' => self::mapIndoor($device, $modules, $indoorModules),
'pressure' => [
'current' => self::floatOrNull(self::dashboard($device)['Pressure'] ?? null),
'trend' => self::stringOrNull(self::dashboard($device)['pressure_trend'] ?? null),
],
'rain_module_id' => $rain === null ? null : self::stringOrNull($rain['_id'] ?? null),
'sum_rain_24' => self::floatOrNull(self::dashboard($rain)['sum_rain_24'] ?? null),
];
@@ -92,6 +99,7 @@ final class StationMapper
'min_at' => self::intOrNull($d['date_min_temp'] ?? null),
'max' => self::floatOrNull($d['max_temp'] ?? null),
'max_at' => self::intOrNull($d['date_max_temp'] ?? null),
'humidity' => self::floatOrNull($d['Humidity'] ?? null),
];
}
@@ -118,6 +126,8 @@ final class StationMapper
'temp' => self::floatOrNull($d['Temperature'] ?? null),
'min' => self::floatOrNull($d['min_temp'] ?? null),
'max' => self::floatOrNull($d['max_temp'] ?? null),
'humidity' => self::floatOrNull($d['Humidity'] ?? null),
'co2' => self::intOrNull($d['CO2'] ?? null),
'reachable' => $d !== [],
];
}
+3
View File
@@ -13,6 +13,9 @@ interface WeatherSource
/** @return array<string|int, mixed> de "body" van Netatmo getmeasure (timestamp => [mm]) */
public function measure(string $deviceId, string $moduleId, int $begin, int $end): array;
/** @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, mixed> het "daily"-blok van Open-Meteo */
public function forecast(float $lat, float $lon, string $timezone): array;
}
+2 -2
View File
@@ -28,7 +28,7 @@ final class ConfigTest extends TestCase
self::assertSame('Europe/Berlin', $config->get('timezone'));
self::assertSame('fixture', $config->get('data_source'));
self::assertSame(['stations' => 300, 'measure' => 900, 'forecast' => 3600], $config->get('cache_ttl'));
self::assertSame(['stations' => 300, 'measure' => 900, 'pressure' => 900, 'forecast' => 3600], $config->get('cache_ttl'));
self::assertSame([], $config->get('indoor_modules'));
self::assertNull($config->get('netatmo_device_id'));
}
@@ -39,7 +39,7 @@ final class ConfigTest extends TestCase
$config = Config::load($this->file);
self::assertSame('Europe/Amsterdam', $config->get('timezone'));
self::assertSame(['stations' => 60, 'measure' => 900, 'forecast' => 3600], $config->get('cache_ttl'));
self::assertSame(['stations' => 60, 'measure' => 900, 'pressure' => 900, 'forecast' => 3600], $config->get('cache_ttl'));
}
public function testUnknownKeyReturnsGivenDefault(): void
+36 -4
View File
@@ -32,7 +32,7 @@ final class DashboardServiceTest extends TestCase
return new DashboardService(
$this->source,
new Cache($this->dir),
['stations' => 300, 'measure' => 900, 'forecast' => 3600],
['stations' => 300, 'measure' => 900, 'pressure' => 900, 'forecast' => 3600],
'Europe/Amsterdam',
$lat,
$lon,
@@ -46,12 +46,19 @@ final class DashboardServiceTest extends TestCase
{
$p = $this->service()->build();
self::assertSame(['generated_at', 'timezone', 'station', 'outdoor', 'indoor', 'rain', 'forecast', 'forecast_stale', 'errors'], array_keys($p));
self::assertSame(['generated_at', 'timezone', 'station', 'outdoor', 'indoor', 'rain', 'pressure', 'forecast', 'forecast_stale', 'errors'], array_keys($p));
self::assertSame('2026-09-11T14:32:05+02:00', $p['generated_at']);
self::assertSame('Europe/Berlin', $p['timezone']);
self::assertSame(['measured_at' => '2026-09-11T14:30:12+02:00', 'stale' => false], $p['station']);
self::assertSame(['temp' => 16.4, 'trend' => 'down', 'min' => 9.2, 'min_at' => '2026-09-11T06:10:00+02:00', 'max' => 19.8, 'max_at' => '2026-09-11T13:05:00+02:00'], $p['outdoor']);
self::assertSame(['temp' => 16.4, 'trend' => 'down', 'min' => 9.2, 'min_at' => '2026-09-11T06:10:00+02:00', 'max' => 19.8, 'max_at' => '2026-09-11T13:05:00+02:00', 'humidity' => 71.0], $p['outdoor']);
self::assertCount(3, $p['indoor']);
self::assertSame(612, $p['indoor'][0]['co2']);
self::assertSame(48.0, $p['indoor'][0]['humidity']);
self::assertSame(1014.2, $p['pressure']['current']);
self::assertSame('up', $p['pressure']['trend']);
self::assertCount(49, $p['pressure']['history']);
self::assertSame('2026-09-10T14:30:00+02:00', $p['pressure']['history'][0]['at']);
self::assertFalse($p['pressure']['stale']);
self::assertSame(2.4, $p['rain']['today']);
self::assertSame(['2026-09-08', '2026-09-09', '2026-09-10'], array_column($p['rain']['previous_days'], 'date'));
self::assertEqualsWithDelta(38.5, $p['rain']['month_total'], 0.0001);
@@ -73,6 +80,29 @@ final class DashboardServiceTest extends TestCase
self::assertSame((new \DateTimeImmutable('2026-09-11 12:32:05 UTC'))->getTimestamp(), $end);
}
public function testPressureHistoryIsRequestedForLast24Hours(): void
{
$this->service()->build();
[$deviceId, $begin, $end] = $this->source->lastArgs['pressure'];
$now = (new \DateTimeImmutable('2026-09-11 12:32:05 UTC'))->getTimestamp();
self::assertSame('70:ee:50:00:00:01', $deviceId);
self::assertSame($now - 86400, $begin);
self::assertSame($now, $end);
}
public function testPressureFailureKeepsCurrentValueAndMarksStale(): void
{
$this->source->responses['pressure'] = new UpstreamException('500');
$p = $this->service()->build();
self::assertSame(1014.2, $p['pressure']['current']);
self::assertSame([], $p['pressure']['history']);
self::assertTrue($p['pressure']['stale']);
self::assertSame(['netatmo_unavailable'], $p['errors']);
}
public function testForecastUsesStationLocationUnlessConfigured(): void
{
$this->service()->build();
@@ -96,6 +126,8 @@ final class DashboardServiceTest extends TestCase
self::assertSame([], $p['indoor']);
self::assertNull($p['rain']['today']);
self::assertSame(0, $this->source->calls['measure'], 'zonder regenmeter-id geen getmeasure-call');
self::assertSame(0, $this->source->calls['pressure'], 'zonder device-id geen luchtdruk-call');
self::assertSame(['current' => null, 'trend' => null, 'history' => [], 'stale' => false], $p['pressure']);
self::assertSame('Europe/Amsterdam', $p['timezone'], 'fallback-tijdzone uit config');
self::assertSame(['netatmo_unavailable', 'forecast_unavailable'], $p['errors']);
}
@@ -152,7 +184,7 @@ final class DashboardServiceTest extends TestCase
$this->service()->build();
$this->service('2026-09-11 12:36:05 UTC')->build();
self::assertSame(['stations' => 1, 'measure' => 1, 'forecast' => 1], $this->source->calls);
self::assertSame(['stations' => 1, 'measure' => 1, 'pressure' => 1, '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');
+7 -1
View File
@@ -13,7 +13,7 @@ final class FakeSource implements WeatherSource
public array $responses = [];
/** @var array<string, int> */
public array $calls = ['stations' => 0, 'measure' => 0, 'forecast' => 0];
public array $calls = ['stations' => 0, 'measure' => 0, 'pressure' => 0, 'forecast' => 0];
/** @var array<string, array> */
public array $lastArgs = [];
@@ -24,6 +24,7 @@ final class FakeSource implements WeatherSource
$s->responses = [
'stations' => json_decode((string) file_get_contents(__DIR__ . '/fixtures/getstationsdata.json'), true)['body'],
'measure' => json_decode((string) file_get_contents(__DIR__ . '/fixtures/getmeasure.json'), true)['body'],
'pressure' => json_decode((string) file_get_contents(__DIR__ . '/fixtures/getmeasure-pressure.json'), true)['body'],
'forecast' => json_decode((string) file_get_contents(__DIR__ . '/fixtures/forecast.json'), true)['daily'],
];
return $s;
@@ -39,6 +40,11 @@ final class FakeSource implements WeatherSource
return $this->answer('measure', func_get_args());
}
public function pressureHistory(string $deviceId, int $begin, int $end): array
{
return $this->answer('pressure', func_get_args());
}
public function forecast(float $lat, float $lon, string $timezone): array
{
return $this->answer('forecast', func_get_args());
+14
View File
@@ -198,4 +198,18 @@ final class NetatmoClientTest extends TestCase
'date_begin' => 1000, 'date_end' => 2000, 'optimize' => 'false', 'real_time' => 'false',
], $this->http->requests[0]['params']);
}
public function testGetPressureHistoryQuery(): void
{
$this->store->write(['access_token' => 'A1', 'refresh_token' => 'R1', 'expires_at' => self::NOW + 3600]);
$this->http->queue(200, json_decode((string) file_get_contents(__DIR__ . '/fixtures/getmeasure-pressure.json'), true));
$body = $this->client()->getPressureHistory('dev', 1000, 2000);
self::assertCount(49, $body);
self::assertSame([
'device_id' => 'dev', 'scale' => '30min', 'type' => 'Pressure',
'date_begin' => 1000, 'date_end' => 2000, 'optimize' => 'false', 'real_time' => 'false',
], $this->http->requests[0]['params']);
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Weerscherm\Tests;
use PHPUnit\Framework\TestCase;
use Weerscherm\PressureHistory;
final class PressureHistoryTest extends TestCase
{
public function testMapsAndSortsPoints(): void
{
$body = json_decode((string) file_get_contents(__DIR__ . '/fixtures/getmeasure-pressure.json'), true)['body'];
$shuffled = array_reverse($body, true);
$points = PressureHistory::map($shuffled, new \DateTimeZone('Europe/Berlin'));
self::assertCount(49, $points);
self::assertSame('2026-09-10T14:30:00+02:00', $points[0]['at']);
self::assertSame('2026-09-11T14:30:00+02:00', $points[48]['at']);
self::assertIsFloat($points[0]['hpa']);
self::assertSame(['at', 'hpa'], array_keys($points[0]));
}
public function testSkipsGarbage(): void
{
$points = PressureHistory::map(['abc' => [1000.0], '1789129800' => ['x'], '1789133400' => [1013.5]], new \DateTimeZone('UTC'));
self::assertSame([['at' => '2026-09-11T13:30:00+00:00', 'hpa' => 1013.5]], $points);
}
}
+23 -3
View File
@@ -38,6 +38,7 @@ final class StationMapperTest extends TestCase
'min_at' => 1789099800,
'max' => 19.8,
'max_at' => 1789124700,
'humidity' => 71.0,
], $m['outdoor']);
}
@@ -47,8 +48,8 @@ final class StationMapperTest extends TestCase
self::assertCount(3, $m['indoor']);
self::assertSame(['Woonkamer', 'Slaapkamer', 'Kantoor'], array_column($m['indoor'], 'name'));
self::assertSame(['id' => '70:ee:50:00:00:01', 'name' => 'Woonkamer', 'temp' => 21.3, 'min' => 20.4, 'max' => 22.1, 'reachable' => true], $m['indoor'][0]);
self::assertSame(['id' => '03:00:00:00:00:03', 'name' => 'Kantoor', 'temp' => null, 'min' => null, 'max' => null, 'reachable' => false], $m['indoor'][2]);
self::assertSame(['id' => '70:ee:50:00:00:01', 'name' => 'Woonkamer', 'temp' => 21.3, 'min' => 20.4, 'max' => 22.1, 'humidity' => 48.0, 'co2' => 612, 'reachable' => true], $m['indoor'][0]);
self::assertSame(['id' => '03:00:00:00:00:03', 'name' => 'Kantoor', 'temp' => null, 'min' => null, 'max' => null, 'humidity' => null, 'co2' => null, 'reachable' => false], $m['indoor'][2]);
}
public function testIndoorConfigDeterminesOrderAndLabels(): void
@@ -69,6 +70,24 @@ final class StationMapperTest extends TestCase
self::assertSame(2.4, $m['sum_rain_24']);
}
public function testPressureFromBaseStation(): void
{
$m = StationMapper::map($this->body(), null, []);
self::assertSame(['current' => 1014.2, 'trend' => 'up'], $m['pressure']);
}
public function testPressureNullWhenBaseStationUnreachable(): void
{
$body = $this->body();
$body['devices'][0]['reachable'] = false;
unset($body['devices'][0]['dashboard_data']);
$m = StationMapper::map($body, null, []);
self::assertSame(['current' => null, 'trend' => null], $m['pressure']);
self::assertSame(1789129780, $m['measured_at'], 'valt terug op time_utc van de buitenmodule');
}
public function testWithoutRainModule(): void
{
$body = $this->body();
@@ -89,7 +108,7 @@ final class StationMapperTest extends TestCase
unset($body['devices'][0]['modules'][0]['dashboard_data']);
$m = StationMapper::map($body, null, []);
self::assertSame(['temp' => null, 'trend' => null, 'min' => null, 'min_at' => null, 'max' => null, 'max_at' => null], $m['outdoor']);
self::assertSame(['temp' => null, 'trend' => null, 'min' => null, 'min_at' => null, 'max' => null, 'max_at' => null, 'humidity' => null], $m['outdoor']);
}
public function testUnknownDeviceIdFallsBackToFirstStation(): void
@@ -108,5 +127,6 @@ final class StationMapperTest extends TestCase
self::assertSame([], $m['indoor']);
self::assertNull($m['outdoor']['temp']);
self::assertNull($m['rain_module_id']);
self::assertSame(['current' => null, 'trend' => null], $m['pressure']);
}
}
+152
View File
@@ -0,0 +1,152 @@
{
"body": {
"1789043400": [
1011.5
],
"1789045200": [
1011
],
"1789047000": [
1010.6
],
"1789048800": [
1010.2
],
"1789050600": [
1010.3
],
"1789052400": [
1009.9
],
"1789054200": [
1009.6
],
"1789056000": [
1009.4
],
"1789057800": [
1009.1
],
"1789059600": [
1009.4
],
"1789061400": [
1009.3
],
"1789063200": [
1009.2
],
"1789065000": [
1009.2
],
"1789066800": [
1009.2
],
"1789068600": [
1009.7
],
"1789070400": [
1009.8
],
"1789072200": [
1009.9
],
"1789074000": [
1010.1
],
"1789075800": [
1010.3
],
"1789077600": [
1011
],
"1789079400": [
1011.2
],
"1789081200": [
1011.5
],
"1789083000": [
1011.8
],
"1789084800": [
1012
],
"1789086600": [
1012.8
],
"1789088400": [
1013.1
],
"1789090200": [
1013.3
],
"1789092000": [
1013.6
],
"1789093800": [
1013.8
],
"1789095600": [
1014.5
],
"1789097400": [
1014.6
],
"1789099200": [
1014.8
],
"1789101000": [
1014.8
],
"1789102800": [
1014.9
],
"1789104600": [
1015.4
],
"1789106400": [
1015.3
],
"1789108200": [
1015.2
],
"1789110000": [
1015
],
"1789111800": [
1014.8
],
"1789113600": [
1015.1
],
"1789115400": [
1014.8
],
"1789117200": [
1014.5
],
"1789119000": [
1014.1
],
"1789120800": [
1013.8
],
"1789122600": [
1013.8
],
"1789124400": [
1013.4
],
"1789126200": [
1012.9
],
"1789128000": [
1012.5
],
"1789129800": [
1012
]
},
"status": "ok"
}