feat: StationMapper maps getstationsdata to outdoor/indoor/rain

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-09-11 14:36:00 +02:00
co-authored by Claude Fable 5.1
parent b38b7a3ac4
commit 12f5f97a53
4 changed files with 378 additions and 1 deletions
+112
View File
@@ -0,0 +1,112 @@
<?php
declare(strict_types=1);
namespace Weerscherm\Tests;
use PHPUnit\Framework\TestCase;
use Weerscherm\StationMapper;
final class StationMapperTest extends TestCase
{
/** @return array<string, mixed> */
private function body(): array
{
$json = json_decode((string) file_get_contents(__DIR__ . '/fixtures/getstationsdata.json'), true);
return $json['body'];
}
public function testStationMetadata(): void
{
$m = StationMapper::map($this->body(), null, []);
self::assertSame('Europe/Berlin', $m['timezone']);
self::assertSame(51.82, $m['lat']);
self::assertSame(6.55, $m['lon']);
self::assertSame('70:ee:50:00:00:01', $m['device_id']);
self::assertSame(1789129812, $m['measured_at']);
}
public function testOutdoor(): void
{
$m = StationMapper::map($this->body(), null, []);
self::assertSame([
'temp' => 16.4,
'trend' => 'down',
'min' => 9.2,
'min_at' => 1789099800,
'max' => 19.8,
'max_at' => 1789124700,
], $m['outdoor']);
}
public function testIndoorDefaultOrderAndUnreachableModule(): void
{
$m = StationMapper::map($this->body(), null, []);
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]);
}
public function testIndoorConfigDeterminesOrderAndLabels(): void
{
$m = StationMapper::map($this->body(), null, [
'03:00:00:00:00:03' => 'Studeerkamer',
'70:ee:50:00:00:01' => 'Living',
]);
self::assertSame(['Studeerkamer', 'Living', 'Slaapkamer'], array_column($m['indoor'], 'name'));
}
public function testRainModule(): void
{
$m = StationMapper::map($this->body(), null, []);
self::assertSame('05:00:00:00:00:04', $m['rain_module_id']);
self::assertSame(2.4, $m['sum_rain_24']);
}
public function testWithoutRainModule(): void
{
$body = $this->body();
$body['devices'][0]['modules'] = array_values(array_filter(
$body['devices'][0]['modules'],
fn(array $mod) => $mod['type'] !== 'NAModule3',
));
$m = StationMapper::map($body, null, []);
self::assertNull($m['rain_module_id']);
self::assertNull($m['sum_rain_24']);
}
public function testUnreachableOutdoorModuleGivesNulls(): void
{
$body = $this->body();
$body['devices'][0]['modules'][0]['reachable'] = false;
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']);
}
public function testUnknownDeviceIdFallsBackToFirstStation(): void
{
$m = StationMapper::map($this->body(), 'ff:ff:ff:ff:ff:ff', []);
self::assertSame('70:ee:50:00:00:01', $m['device_id']);
}
public function testEmptyDevicesGivesNullsWithoutCrashing(): void
{
$m = StationMapper::map(['devices' => []], null, []);
self::assertNull($m['device_id']);
self::assertNull($m['timezone']);
self::assertNull($m['measured_at']);
self::assertSame([], $m['indoor']);
self::assertNull($m['outdoor']['temp']);
self::assertNull($m['rain_module_id']);
}
}
+116
View File
@@ -0,0 +1,116 @@
{
"body": {
"devices": [
{
"_id": "70:ee:50:00:00:01",
"type": "NAMain",
"station_name": "Thuis",
"module_name": "Woonkamer",
"reachable": true,
"firmware": 204,
"wifi_status": 50,
"co2_calibrating": false,
"place": {
"altitude": 20,
"city": "Voorbeeldstad",
"country": "DE",
"timezone": "Europe/Berlin",
"location": [
6.55,
51.82
]
},
"dashboard_data": {
"time_utc": 1789129812,
"Temperature": 21.3,
"CO2": 612,
"Humidity": 48,
"Noise": 38,
"Pressure": 1014.2,
"AbsolutePressure": 1011.8,
"min_temp": 20.4,
"max_temp": 22.1,
"date_max_temp": 1789126800,
"date_min_temp": 1789098600,
"temp_trend": "stable",
"pressure_trend": "up"
},
"modules": [
{
"_id": "02:00:00:00:00:01",
"type": "NAModule1",
"module_name": "Buiten",
"reachable": true,
"battery_percent": 71,
"rf_status": 70,
"dashboard_data": {
"time_utc": 1789129780,
"Temperature": 16.4,
"Humidity": 71,
"min_temp": 9.2,
"max_temp": 19.8,
"date_min_temp": 1789099800,
"date_max_temp": 1789124700,
"temp_trend": "down"
}
},
{
"_id": "03:00:00:00:00:02",
"type": "NAModule4",
"module_name": "Slaapkamer",
"reachable": true,
"battery_percent": 64,
"rf_status": 66,
"dashboard_data": {
"time_utc": 1789129680,
"Temperature": 18.9,
"CO2": 540,
"Humidity": 52,
"min_temp": 18.1,
"max_temp": 19.6,
"date_min_temp": 1789101000,
"date_max_temp": 1789120800,
"temp_trend": "up"
}
},
{
"_id": "03:00:00:00:00:03",
"type": "NAModule4",
"module_name": "Kantoor",
"reachable": false,
"battery_percent": 5,
"rf_status": 90
},
{
"_id": "05:00:00:00:00:04",
"type": "NAModule3",
"module_name": "Regenmeter",
"reachable": true,
"battery_percent": 80,
"rf_status": 68,
"dashboard_data": {
"time_utc": 1789129780,
"Rain": 0,
"sum_rain_1": 0,
"sum_rain_24": 2.4
}
}
]
}
],
"user": {
"mail": "x@example.org",
"administrative": {
"lang": "nl-NL",
"reg_locale": "nl-NL",
"unit": 0,
"windunit": 0,
"pressureunit": 0,
"feel_like_algo": 0
}
}
},
"status": "ok",
"time_exec": 0.05,
"time_server": 1789129925
}