From 793ea1991a4760fd1beb9f452e34bc129c798dc1 Mon Sep 17 00:00:00 2001 From: SchiZzA Date: Sun, 21 Jun 2026 23:37:11 +0200 Subject: [PATCH] feat(device): merge all entities into one shared device with station-type model All entities (weather, battery, health, native Ecowitt) now report through a single device under the existing {(DOMAIN,)} identifier, removing the separate "Ecowitt {model}" device that appeared whenever Ecowitt was enabled. This collapses the previous two-device layout (and the duplicate/untranslated native Ecowitt sensors living on the second device) into one. The device model reflects the running station type via _station_model(): "Ecowitt " when Ecowitt is active (model learned from the payload's `model` field and stored on runtime_data.ecowitt_model), else "WSLink", else "PWS". Device identity is centralised in data.build_device_info() and reused by every platform. No device-registry migration is required: the identifier is unchanged; only the model/grouping change. Full device unification under a renamed hub is deferred to the rename round. Tests: device-info assertions updated to the shared device across the entity suites; new _station_model/build_device_info branch coverage in test_data.py; received_ecowitt now asserts the learned model. 320 passed, 100% coverage. Co-Authored-By: Claude Opus 4.8 (1M context) --- custom_components/sws12500/battery_sensors.py | 14 +--- custom_components/sws12500/coordinator.py | 5 ++ custom_components/sws12500/data.py | 38 ++++++++++ custom_components/sws12500/ecowitt.py | 26 +++---- custom_components/sws12500/health_sensor.py | 15 +--- custom_components/sws12500/sensor.py | 15 +--- tests/test_binary_battery.py | 4 +- tests/test_data.py | 70 ++++++++++++++++++- tests/test_ecowitt_bridge.py | 69 +++++++++++------- tests/test_health.py | 9 ++- tests/test_received_ecowitt.py | 5 +- tests/test_weather_sensor_entity.py | 2 +- 12 files changed, 187 insertions(+), 85 deletions(-) diff --git a/custom_components/sws12500/battery_sensors.py b/custom_components/sws12500/battery_sensors.py index 0e4048d..90fd57f 100644 --- a/custom_components/sws12500/battery_sensors.py +++ b/custom_components/sws12500/battery_sensors.py @@ -11,11 +11,10 @@ from typing import Any from py_typecheck import checked_or from homeassistant.components.binary_sensor import BinarySensorEntity, BinarySensorEntityDescription -from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN +from .data import build_device_info class BatteryBinarySensor( # pyright: ignore[reportIncompatibleVariableOverride] @@ -62,12 +61,5 @@ class BatteryBinarySensor( # pyright: ignore[reportIncompatibleVariableOverride @cached_property def device_info(self) -> DeviceInfo: - """Device info.""" - return DeviceInfo( - connections=set(), - name="Weather Station SWS 12500", - entry_type=DeviceEntryType.SERVICE, - identifiers={(DOMAIN,)}, # type: ignore[arg-type] - manufacturer="Schizza", - model="Weather Station SWS 12500", - ) + """Device info (single shared device for the whole integration).""" + return build_device_info(self.coordinator.config) diff --git a/custom_components/sws12500/coordinator.py b/custom_components/sws12500/coordinator.py index f71b218..d069fb7 100644 --- a/custom_components/sws12500/coordinator.py +++ b/custom_components/sws12500/coordinator.py @@ -198,6 +198,11 @@ class WeatherDataUpdateCoordinator(DataUpdateCoordinator): post_data = await webdata.post() data: dict[str, Any] = dict(post_data) + # Record the Ecowitt station model (used as the device model) before parsing, + # so native entities created during process_payload report it. + if model := checked(data.get("model"), str): + self.config.runtime_data.ecowitt_model = model + mapped_data = await self.ecowitt_bridge.process_payload(data) if mapped_data: diff --git a/custom_components/sws12500/data.py b/custom_components/sws12500/data.py index 316c97a..7528359 100644 --- a/custom_components/sws12500/data.py +++ b/custom_components/sws12500/data.py @@ -11,10 +11,16 @@ from dataclasses import dataclass, field from datetime import datetime from typing import TYPE_CHECKING, Any +from py_typecheck import checked_or + from homeassistant.config_entries import ConfigEntry +from homeassistant.helpers.device_registry import DeviceEntryType +from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util import dt as dt_util +from .const import DOMAIN, ECOWITT_ENABLED, WSLINK + if TYPE_CHECKING: from homeassistant.components.binary_sensor import BinarySensorEntityDescription @@ -51,6 +57,38 @@ class SWSRuntimeData: started_at: datetime = field(default_factory=dt_util.utcnow) last_seen: dict[str, datetime] = field(default_factory=dict) + # Ecowitt station model (e.g. "GW1000"), learned from the first Ecowitt payload. + ecowitt_model: str | None = None + # Type alias for typed ConfigEntry type SWSConfigEntry = ConfigEntry[SWSRuntimeData] + + +def _station_model(entry: SWSConfigEntry) -> str: + """Return the device model label reflecting the running station type. + + Ecowitt (with the learned model when available), else WSLink, else PWS. + """ + if checked_or(entry.options.get(ECOWITT_ENABLED), bool, False): + runtime = getattr(entry, "runtime_data", None) + model = getattr(runtime, "ecowitt_model", None) if runtime is not None else None + return f"Ecowitt {model}" if model else "Ecowitt" + if checked_or(entry.options.get(WSLINK), bool, False): + return "WSLink" + return "PWS" + + +def build_device_info(entry: SWSConfigEntry) -> DeviceInfo: + """Single device shared by all entities (SWS, battery, health, native Ecowitt). + + Keeps the existing ``{(DOMAIN,)}`` identifier so no device-registry migration is + needed; the model reflects the active station type. + """ + return DeviceInfo( + identifiers={(DOMAIN,)}, # type: ignore[arg-type] + name="Weather Station SWS 12500", + entry_type=DeviceEntryType.SERVICE, + manufacturer="Schizza", + model=_station_model(entry), + ) diff --git a/custom_components/sws12500/ecowitt.py b/custom_components/sws12500/ecowitt.py index ae16952..1427765 100644 --- a/custom_components/sws12500/ecowitt.py +++ b/custom_components/sws12500/ecowitt.py @@ -19,14 +19,12 @@ from aioecowitt import EcoWittListener, EcoWittSensor, EcoWittSensorTypes from aioecowitt.sensor import SENSOR_MAP from homeassistant.components.sensor import SensorDeviceClass, SensorEntity, SensorStateClass -from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers.device_registry import DeviceEntryType -from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType -from .const import DOMAIN, REMAP_ECOWITT_COMPAT +from .const import REMAP_ECOWITT_COMPAT +from .data import SWSConfigEntry, build_device_info _LOGGER = logging.getLogger(__name__) @@ -216,7 +214,7 @@ class EcowittBridge: and we are just using parsing/discovery logic. """ - def __init__(self, hass: HomeAssistant, config: ConfigEntry) -> None: + def __init__(self, hass: HomeAssistant, config: SWSConfigEntry) -> None: """Initialize bridge.""" self.hass = hass @@ -308,7 +306,7 @@ class EcowittBridge: return self._know_native_keys.add(sensor.key) - entity = EcoWittNativeSensor(sensor) + entity = EcoWittNativeSensor(sensor, self.config) self._add_entities_cb([entity]) _LOGGER.info("New native Ecowitt sensor %s (type=%s)", sensor.name, sensor.stype.name) @@ -336,7 +334,7 @@ class EcoWittNativeSensor(SensorEntity): _attr_has_entity_name = True _attr_should_poll = False - def __init__(self, sensor: EcoWittSensor) -> None: + def __init__(self, sensor: EcoWittSensor, config: SWSConfigEntry) -> None: """Initialize native EcoWittSensor.""" self._ecowitt_sensor = sensor @@ -358,17 +356,9 @@ class EcoWittNativeSensor(SensorEntity): self._attr_native_unit_of_measurement = unit self._attr_state_class = state_class - # Always attach device info so the entity is grouped under the Ecowitt - # station device even when its sensor type has no HA mapping. - station = sensor.station - self._attr_device_info = DeviceInfo( - connections=set(), - name=f"Ecowitt {station.model}" if station else "Ecowitt station", - entry_type=DeviceEntryType.SERVICE, - identifiers={(DOMAIN, f"ecowitt_{station.key}" if station else "ecowitt")}, - manufacturer="Ecowitt impl. from Schizza for SWS12500", - model=station.model if station else None, - ) + # Share the single integration device (see data.build_device_info); the station + # type is reflected in the device model rather than a separate Ecowitt device. + self._attr_device_info = build_device_info(config) @property def native_value(self) -> StateType | datetime: # pyright: ignore[reportIncompatibleVariableOverride] diff --git a/custom_components/sws12500/health_sensor.py b/custom_components/sws12500/health_sensor.py index d1a3449..05720f7 100644 --- a/custom_components/sws12500/health_sensor.py +++ b/custom_components/sws12500/health_sensor.py @@ -12,14 +12,12 @@ from py_typecheck import checked, checked_or from homeassistant.components.sensor import SensorDeviceClass, SensorEntity, SensorEntityDescription from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant -from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.util import dt as dt_util -from .const import DOMAIN -from .data import SWSConfigEntry +from .data import SWSConfigEntry, build_device_info from .health_coordinator import HealthCoordinator, public_health_snapshot if TYPE_CHECKING: @@ -265,12 +263,5 @@ class HealthDiagnosticSensor( # pyright: ignore[reportIncompatibleVariableOverr @cached_property def device_info(self) -> DeviceInfo: - """Device info.""" - return DeviceInfo( - connections=set(), - name="Weather Station SWS 12500", - entry_type=DeviceEntryType.SERVICE, - identifiers={(DOMAIN,)}, # type: ignore[arg-type] - manufacturer="Schizza", - model="Weather Station SWS 12500", - ) + """Device info (single shared device for the whole integration).""" + return build_device_info(self.coordinator.config) diff --git a/custom_components/sws12500/sensor.py b/custom_components/sws12500/sensor.py index cf9110e..5858276 100644 --- a/custom_components/sws12500/sensor.py +++ b/custom_components/sws12500/sensor.py @@ -24,7 +24,6 @@ from py_typecheck import checked_or from homeassistant.components.sensor import SensorEntity from homeassistant.core import HomeAssistant -from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.entity import DeviceInfo, generate_entity_id from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -33,7 +32,6 @@ from . import health_sensor from .const import ( CHILL_INDEX, DEV_DBG, - DOMAIN, HEAT_INDEX, OUTSIDE_HUMIDITY, OUTSIDE_TEMP, @@ -43,7 +41,7 @@ from .const import ( WIND_SPEED, WSLINK, ) -from .data import SWSConfigEntry +from .data import SWSConfigEntry, build_device_info from .sensors_common import WeatherSensorEntityDescription from .sensors_weather import SENSOR_TYPES_WEATHER_API from .sensors_wslink import SENSOR_TYPES_WSLINK @@ -225,12 +223,5 @@ class WeatherSensor( # pyright: ignore[reportIncompatibleVariableOverride] @cached_property def device_info(self) -> DeviceInfo: - """Device info.""" - return DeviceInfo( - connections=set(), - name="Weather Station SWS 12500", - entry_type=DeviceEntryType.SERVICE, - identifiers={(DOMAIN,)}, # type: ignore[arg-type] - manufacturer="Schizza", - model="Weather Station SWS 12500", - ) + """Device info (single shared device for the whole integration).""" + return build_device_info(self.coordinator.config) diff --git a/tests/test_binary_battery.py b/tests/test_binary_battery.py index a06ed5a..e8acf95 100644 --- a/tests/test_binary_battery.py +++ b/tests/test_binary_battery.py @@ -25,6 +25,8 @@ class _CoordinatorStub: def __init__(self, data: dict[str, Any] | None = None) -> None: self.data: dict[str, Any] = data if data is not None else {} + # device_info reads coordinator.config for the shared device model. + self.config = SimpleNamespace(options={}) def _make_entry( @@ -213,7 +215,7 @@ def test_device_info(): info = sensor.device_info assert info["name"] == "Weather Station SWS 12500" assert info["manufacturer"] == "Schizza" - assert info["model"] == "Weather Station SWS 12500" + assert info["model"] == "PWS" # no ecowitt/wslink in stub options -> PWS assert info["identifiers"] == {(DOMAIN,)} diff --git a/tests/test_data.py b/tests/test_data.py index 7f373af..d346d18 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -2,7 +2,14 @@ from __future__ import annotations -from custom_components.sws12500.data import SWSRuntimeData +from types import SimpleNamespace + +from custom_components.sws12500.const import DOMAIN, ECOWITT_ENABLED, WSLINK +from custom_components.sws12500.data import ( + SWSRuntimeData, + _station_model, + build_device_info, +) def test_runtime_data_defaults(): @@ -44,3 +51,64 @@ def test_runtime_data_collections_are_independent_per_instance(): assert b.sensor_descriptions == {} assert b.added_binary_keys == set() assert b.last_seen == {} + + +# --------------------------------------------------------------------------- # +# _station_model / build_device_info (single shared device) +# --------------------------------------------------------------------------- # + + +def test_station_model_pws_when_no_flags(): + """No ecowitt / wslink flags -> the legacy PWS push station.""" + entry = SimpleNamespace(options={}) + assert _station_model(entry) == "PWS" # type: ignore[arg-type] + + +def test_station_model_wslink_when_flag_set(): + """The WSLINK flag (without ecowitt) yields the WSLink model.""" + entry = SimpleNamespace(options={WSLINK: True}) + assert _station_model(entry) == "WSLink" # type: ignore[arg-type] + + +def test_station_model_ecowitt_without_learned_model(): + """Ecowitt enabled but no model learned yet -> bare "Ecowitt". + + Covers both the missing-runtime_data and the model-is-None paths. + """ + no_runtime = SimpleNamespace(options={ECOWITT_ENABLED: True}) + assert _station_model(no_runtime) == "Ecowitt" # type: ignore[arg-type] + + runtime_no_model = SimpleNamespace( + options={ECOWITT_ENABLED: True}, + runtime_data=SimpleNamespace(ecowitt_model=None), + ) + assert _station_model(runtime_no_model) == "Ecowitt" # type: ignore[arg-type] + + +def test_station_model_ecowitt_with_learned_model(): + """Ecowitt enabled with a learned model -> "Ecowitt ".""" + entry = SimpleNamespace( + options={ECOWITT_ENABLED: True}, + runtime_data=SimpleNamespace(ecowitt_model="GW1000"), + ) + assert _station_model(entry) == "Ecowitt GW1000" # type: ignore[arg-type] + + +def test_station_model_ecowitt_takes_precedence_over_wslink(): + """When both flags are set the ecowitt model wins.""" + entry = SimpleNamespace( + options={ECOWITT_ENABLED: True, WSLINK: True}, + runtime_data=SimpleNamespace(ecowitt_model="WS3900"), + ) + assert _station_model(entry) == "Ecowitt WS3900" # type: ignore[arg-type] + + +def test_build_device_info_shared_identity(): + """All entities share one device under the legacy ``{(DOMAIN,)}`` identifier.""" + entry = SimpleNamespace(options={}) + info = build_device_info(entry) # type: ignore[arg-type] + + assert info["identifiers"] == {(DOMAIN,)} + assert info["name"] == "Weather Station SWS 12500" + assert info["manufacturer"] == "Schizza" + assert info["model"] == "PWS" diff --git a/tests/test_ecowitt_bridge.py b/tests/test_ecowitt_bridge.py index baef99e..f5e0e95 100644 --- a/tests/test_ecowitt_bridge.py +++ b/tests/test_ecowitt_bridge.py @@ -20,9 +20,24 @@ from aioecowitt import EcoWittSensor, EcoWittSensorTypes from aioecowitt.station import EcoWittStation import pytest -from custom_components.sws12500.const import DOMAIN, REMAP_ECOWITT_COMPAT +from custom_components.sws12500.const import DOMAIN, ECOWITT_ENABLED, REMAP_ECOWITT_COMPAT from custom_components.sws12500.ecowitt import STYPE_TO_HA, EcowittBridge, EcoWittNativeSensor +# Default config stub for native entities: the integration shares a single device, +# so EcoWittNativeSensor only needs the entry to resolve the device model. A bare +# PWS config (no ecowitt/wslink flags) is enough for the non-device assertions. +_PWS_CONFIG = SimpleNamespace(options={}) +# An ecowitt config that yields model "Ecowitt GW1000" for device-info assertions. +_ECOWITT_CONFIG = SimpleNamespace( + options={ECOWITT_ENABLED: True}, + runtime_data=SimpleNamespace(ecowitt_model="GW1000"), +) + + +def _native(sensor: Any, config: Any = _PWS_CONFIG) -> EcoWittNativeSensor: + """Build a native sensor with a default (PWS) config stub.""" + return EcoWittNativeSensor(sensor, config) + # A realistic Ecowitt POST payload. `model` is required by aioecowitt's # station extraction. Contains both internally mapped fields (tempf, humidity, # windspeedmph, baromrelin, dewpointf) and unmapped fields (pm25_ch1, co2). @@ -238,7 +253,7 @@ def test_native_sensor_init_with_mapped_stype() -> None: ) sensor = _make_sensor(key="pm25_ch1", stype=EcoWittSensorTypes.PM25, station=station) - entity = EcoWittNativeSensor(sensor) + entity = _native(sensor, _ECOWITT_CONFIG) assert entity._attr_unique_id == "ecowitt_pm25_ch1" assert entity._attr_name == "PM2.5 CH1" @@ -249,12 +264,13 @@ def test_native_sensor_init_with_mapped_stype() -> None: assert entity._attr_native_unit_of_measurement == unit assert entity._attr_state_class == state_class - # Device info groups the entity under the station device. + # Native sensors join the single shared integration device; the running station + # type is reflected in the model, not in a separate Ecowitt device. info = entity._attr_device_info - assert info["name"] == "Ecowitt GW1000" - assert info["model"] == "GW1000" - assert (DOMAIN, "ecowitt_ABC123") in info["identifiers"] - assert info["manufacturer"] == "Ecowitt impl. from Schizza for SWS12500" + assert info["name"] == "Weather Station SWS 12500" + assert info["model"] == "Ecowitt GW1000" + assert info["identifiers"] == {(DOMAIN,)} + assert info["manufacturer"] == "Schizza" def test_native_sensor_init_with_unmapped_stype() -> None: @@ -274,37 +290,40 @@ def test_native_sensor_init_with_unmapped_stype() -> None: value="1000", ) - entity = EcoWittNativeSensor(sensor) + entity = _native(sensor, _ECOWITT_CONFIG) # No HA metadata set for unknown types. assert getattr(entity, "_attr_device_class", None) is None assert getattr(entity, "_attr_native_unit_of_measurement", None) is None assert getattr(entity, "_attr_state_class", None) is None - # Device info is still present. + # Device info is still present and points at the shared device. info = entity._attr_device_info - assert info["name"] == "Ecowitt GW1000" - assert (DOMAIN, "ecowitt_ABC123") in info["identifiers"] + assert info["name"] == "Weather Station SWS 12500" + assert info["identifiers"] == {(DOMAIN,)} -def test_native_sensor_init_without_station() -> None: - """A sensor with no station falls back to generic device info.""" +def test_native_sensor_device_model_follows_config_not_station() -> None: + """Device model comes from the entry config, independent of the sensor station. + + A native sensor whose ``station`` is missing still lands on the shared device, + and the model reflects the configured station type (here a bare PWS config). + """ sensor = _make_sensor(key="pm25_ch1", stype=EcoWittSensorTypes.PM25) - # Force the no-station branch. sensor.station = None # type: ignore[assignment] - entity = EcoWittNativeSensor(sensor) + entity = _native(sensor) # default PWS config info = entity._attr_device_info - assert info["name"] == "Ecowitt station" - assert info["model"] is None - assert (DOMAIN, "ecowitt") in info["identifiers"] + assert info["name"] == "Weather Station SWS 12500" + assert info["model"] == "PWS" + assert info["identifiers"] == {(DOMAIN,)} def test_native_value_returns_value() -> None: """native_value returns the underlying sensor value.""" sensor = _make_sensor(value=42.0) - entity = EcoWittNativeSensor(sensor) + entity = _native(sensor) assert entity.native_value == 42.0 @@ -312,7 +331,7 @@ def test_native_value_returns_value() -> None: def test_native_value_none_or_empty(empty: Any) -> None: """native_value maps None and "" to None.""" sensor = _make_sensor(value=empty) - entity = EcoWittNativeSensor(sensor) + entity = _native(sensor) assert entity.native_value is None @@ -320,7 +339,7 @@ def test_native_value_none_or_empty(empty: Any) -> None: async def test_added_and_removed_callback_lifecycle() -> None: """async_added/async_will_remove register and unregister the update cb.""" sensor = _make_sensor() - entity = EcoWittNativeSensor(sensor) + entity = _native(sensor) assert entity._handle_update not in sensor.update_cb @@ -335,7 +354,7 @@ async def test_added_and_removed_callback_lifecycle() -> None: async def test_will_remove_when_callback_absent() -> None: """async_will_remove is safe when the callback was never registered.""" sensor = _make_sensor() - entity = EcoWittNativeSensor(sensor) + entity = _native(sensor) # Not added; removal must not raise and must not touch the list. assert entity._handle_update not in sensor.update_cb @@ -346,7 +365,7 @@ async def test_will_remove_when_callback_absent() -> None: def test_handle_update_writes_ha_state() -> None: """_handle_update forwards to async_write_ha_state.""" sensor = _make_sensor() - entity = EcoWittNativeSensor(sensor) + entity = _native(sensor) entity.async_write_ha_state = MagicMock() # type: ignore[method-assign] entity._handle_update() @@ -416,14 +435,14 @@ def test_on_new_sensor_skips_already_created_twin() -> None: def test_native_sensor_translation_key_for_curated() -> None: - ent = EcoWittNativeSensor( + ent = _native( _make_sensor(key="baromabsin", name="Absolute Pressure", stype=EcoWittSensorTypes.PRESSURE_INHG) ) assert ent._attr_translation_key == "ecowitt_absolute_pressure" def test_native_sensor_name_fallback_for_unknown() -> None: - ent = EcoWittNativeSensor( + ent = _native( _make_sensor(key="air_ch1", name="Air Gap 1", stype=EcoWittSensorTypes.INTERNAL) ) assert ent._attr_name == "Air Gap 1" diff --git a/tests/test_health.py b/tests/test_health.py index 5debcc0..38736ea 100644 --- a/tests/test_health.py +++ b/tests/test_health.py @@ -675,8 +675,11 @@ def _description(key: str) -> hs.HealthSensorEntityDescription: def _stub_coordinator(data: dict[str, Any]) -> Any: - """CoordinatorEntity.__init__ only stores the coordinator; a stub suffices.""" - return SimpleNamespace(data=data) + """CoordinatorEntity.__init__ only stores the coordinator; a stub suffices. + + device_info reads coordinator.config for the shared device model. + """ + return SimpleNamespace(data=data, config=SimpleNamespace(options={})) def test_sensor_native_value_without_value_fn() -> None: @@ -745,7 +748,7 @@ def test_sensor_device_info() -> None: info = sensor.device_info assert info["name"] == "Weather Station SWS 12500" assert info["manufacturer"] == "Schizza" - assert info["model"] == "Weather Station SWS 12500" + assert info["model"] == "PWS" # no ecowitt/wslink in stub options -> PWS def test_sensor_unique_id_and_category() -> None: diff --git a/tests/test_received_ecowitt.py b/tests/test_received_ecowitt.py index b1288fe..15a2808 100644 --- a/tests/test_received_ecowitt.py +++ b/tests/test_received_ecowitt.py @@ -240,7 +240,7 @@ async def test_received_ecowitt_success_full_pipeline_with_health_autodiscovery_ coordinator.async_set_updated_data = MagicMock() request = _EcowittRequestStub( - match_info={"webhook_id": "hook"}, post_data={"tempf": "68"} + match_info={"webhook_id": "hook"}, post_data={"tempf": "68", "model": "GW2000A"} ) resp = await coordinator.received_ecowitt_data(request) # type: ignore[arg-type] @@ -248,6 +248,9 @@ async def test_received_ecowitt_success_full_pipeline_with_health_autodiscovery_ coordinator.ecowitt_bridge.process_payload.assert_awaited_once() + # Station model is learned from the payload and stored for the shared device model. + assert entry.runtime_data.ecowitt_model == "GW2000A" + # Autodiscovery side-effects. update_options.assert_awaited_once() args, _kwargs = update_options.await_args diff --git a/tests/test_weather_sensor_entity.py b/tests/test_weather_sensor_entity.py index 45f55eb..8d920a7 100644 --- a/tests/test_weather_sensor_entity.py +++ b/tests/test_weather_sensor_entity.py @@ -236,7 +236,7 @@ def test_device_info_contains_expected_identifiers_and_domain(): # DeviceInfo is mapping-like; access defensively. assert info.get("name") == "Weather Station SWS 12500" assert info.get("manufacturer") == "Schizza" - assert info.get("model") == "Weather Station SWS 12500" + assert info.get("model") == "PWS" # no ecowitt/wslink in stub options -> PWS identifiers = info.get("identifiers") assert isinstance(identifiers, set)