From b0b8c3d45d9cd5d16bd606118fafb23c1c935d29 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Sat, 29 Aug 2026 01:54:02 +0200 Subject: [PATCH] Poll the myStrom motion sensor readings (#180416) --- homeassistant/components/mystrom/sensor.py | 25 +++++++++++++- tests/components/mystrom/test_sensor.py | 39 ++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 tests/components/mystrom/test_sensor.py diff --git a/homeassistant/components/mystrom/sensor.py b/homeassistant/components/mystrom/sensor.py index d935c407efdb..16af8da0c4d5 100644 --- a/homeassistant/components/mystrom/sensor.py +++ b/homeassistant/components/mystrom/sensor.py @@ -1,10 +1,12 @@ """Support for myStrom sensors of switches/plugs.""" -from collections.abc import Callable +from collections.abc import Callable, Coroutine from dataclasses import dataclass from datetime import datetime, timedelta +import logging from typing import Any, override +from pymystrom.exceptions import MyStromConnectionError from pymystrom.pir import MyStromPir from pymystrom.switch import MyStromSwitch @@ -29,12 +31,17 @@ from homeassistant.util.dt import utcnow from .const import DOMAIN, MANUFACTURER from .models import MyStromConfigEntry +_LOGGER = logging.getLogger(__name__) + @dataclass(frozen=True, kw_only=True) class MyStromSensorEntityDescription[_DeviceT](SensorEntityDescription): """Class describing mystrom sensor entities.""" value_fn: Callable[[_DeviceT], float | None] + # Only needed where nothing else on the device polls; a switch is kept + # fresh by its own entity refreshing the shared device. + update_fn: Callable[[_DeviceT], Coroutine[Any, Any, None]] | None = None SENSOR_TYPES_PIR: tuple[MyStromSensorEntityDescription[MyStromPir], ...] = ( @@ -50,6 +57,7 @@ SENSOR_TYPES_PIR: tuple[MyStromSensorEntityDescription[MyStromPir], ...] = ( else None ) ), + update_fn=lambda device: device.get_temperatures(), ), MyStromSensorEntityDescription( key="illuminance", @@ -61,6 +69,7 @@ SENSOR_TYPES_PIR: tuple[MyStromSensorEntityDescription[MyStromPir], ...] = ( float(device.intensity) if device.intensity is not None else None ) ), + update_fn=lambda device: device.get_light(), ), ) @@ -180,6 +189,20 @@ class MyStromSensor[_DeviceT](MyStromSensorBase): """Return the value of the sensor.""" return self.entity_description.value_fn(self.device) + async def async_update(self) -> None: + """Get the latest reading from the device.""" + if (update_fn := self.entity_description.update_fn) is None: + return + + try: + await update_fn(self.device) + except MyStromConnectionError: + if self.available: + self._attr_available = False + _LOGGER.error("No route to myStrom device") + else: + self._attr_available = True + class MyStromSwitchUptimeSensor(MyStromSensorBase): """Representation of a MyStrom Switch uptime sensor.""" diff --git a/tests/components/mystrom/test_sensor.py b/tests/components/mystrom/test_sensor.py new file mode 100644 index 000000000000..f165b1c78dcc --- /dev/null +++ b/tests/components/mystrom/test_sensor.py @@ -0,0 +1,39 @@ +"""Test the myStrom sensors.""" + +from datetime import timedelta + +from freezegun.api import FrozenDateTimeFactory + +from homeassistant.core import HomeAssistant + +from .test_init import init_integration + +from tests.common import MockConfigEntry, async_fire_time_changed + + +async def test_pir_sensors_are_polled( + hass: HomeAssistant, + config_entry: MockConfigEntry, + freezer: FrozenDateTimeFactory, +) -> None: + """Test the motion sensor readings are refreshed while polling.""" + await init_integration(hass, config_entry, 110) + + device = config_entry.runtime_data.device + assert hass.states.get("sensor.mystrom_device_temperature").state == "24.87" + assert hass.states.get("sensor.mystrom_device_illuminance").state == "16.0" + + # The mock only reports readings once they have been fetched, and nothing + # else talks to a motion sensor, so this stays cleared unless the sensors + # fetch them themselves. + device._requested_state = False + device._state["temperature_compensated"] = 21.5 + device._state["intensity"] = 42 + + freezer.tick(timedelta(minutes=5)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + assert device._requested_state is True + assert hass.states.get("sensor.mystrom_device_temperature").state == "21.5" + assert hass.states.get("sensor.mystrom_device_illuminance").state == "42.0"