From 91fff6c2dd707dd00ef4272bdebfa9855293ba1a Mon Sep 17 00:00:00 2001 From: Tony Roberts Date: Tue, 14 Nov 2023 15:19:58 +0000 Subject: [PATCH] Add sensors for external probes (#8) Expose the external probe temperature for any thermostats with one enabled. Sensors are disabled/hidden by default if the external probe is not enabled. Fixes #7 --- custom_components/wundasmart/sensor.py | 75 +++++++++++++++++++------- tests/fixtures/test_get_devices1.json | 6 +-- tests/test_sensor.py | 4 ++ 3 files changed, 63 insertions(+), 22 deletions(-) diff --git a/custom_components/wundasmart/sensor.py b/custom_components/wundasmart/sensor.py index f022e06..252b36c 100644 --- a/custom_components/wundasmart/sensor.py +++ b/custom_components/wundasmart/sensor.py @@ -1,5 +1,6 @@ """Support for WundaSmart sensors.""" from __future__ import annotations +from dataclasses import dataclass, asdict import itertools from homeassistant.core import HomeAssistant, callback @@ -29,8 +30,14 @@ def _number_or_none(x): return None -ROOM_SENSORS: list[SensorEntityDescription] = [ - SensorEntityDescription( +@dataclass +class WundaSensorDescription(SensorEntityDescription): + available: bool | callable = True + default: float | None = None + + +ROOM_SENSORS: list[WundaSensorDescription] = [ + WundaSensorDescription( key="temp", name="Temperature", icon="mdi:thermometer", @@ -38,7 +45,7 @@ def _number_or_none(x): device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, ), - SensorEntityDescription( + WundaSensorDescription( key="rh", name="Humidity", icon="mdi:water-percent", @@ -46,7 +53,17 @@ def _number_or_none(x): device_class=SensorDeviceClass.HUMIDITY, state_class=SensorStateClass.MEASUREMENT, ), - SensorEntityDescription( + WundaSensorDescription( + key="temp_ext", + name="External Probe Temperature", + icon="mdi:thermometer", + available=lambda state: bool(int(state.get("ext", 0))), + default=0.0, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + ), + WundaSensorDescription( key="bat", name="Battery Level", icon=lambda x: icon_for_battery_level(_number_or_none(x)), @@ -54,7 +71,7 @@ def _number_or_none(x): device_class=SensorDeviceClass.HUMIDITY, state_class=SensorStateClass.MEASUREMENT, ), - SensorEntityDescription( + WundaSensorDescription( key="sig", name="Signal Level", icon=lambda x: icon_for_signal_level(_number_or_none(x)), @@ -64,8 +81,8 @@ def _number_or_none(x): ) ] -TRV_SENSORS: list[SensorEntityDescription] = [ - SensorEntityDescription( +TRV_SENSORS: list[WundaSensorDescription] = [ + WundaSensorDescription( key="vtemp", name="Temperature", icon="mdi:thermometer", @@ -73,7 +90,7 @@ def _number_or_none(x): device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, ), - SensorEntityDescription( + WundaSensorDescription( key="bat", name="Battery Level", icon=lambda x: icon_for_battery_level(_number_or_none(x)), @@ -81,7 +98,7 @@ def _number_or_none(x): device_class=SensorDeviceClass.HUMIDITY, state_class=SensorStateClass.MEASUREMENT, ), - SensorEntityDescription( + WundaSensorDescription( key="sig", name="Signal Level", icon=lambda x: icon_for_signal_level(_number_or_none(x)), @@ -89,27 +106,27 @@ def _number_or_none(x): device_class=SensorDeviceClass.HUMIDITY, state_class=SensorStateClass.MEASUREMENT, ), - SensorEntityDescription( + WundaSensorDescription( key="vpos", name="Position", state_class=SensorStateClass.MEASUREMENT, ), - SensorEntityDescription( + WundaSensorDescription( key="vpos_min", name="Position Min", state_class=SensorStateClass.MEASUREMENT, ), - SensorEntityDescription( + WundaSensorDescription( key="vpos_range", name="Position Range", state_class=SensorStateClass.MEASUREMENT, ), - SensorEntityDescription( + WundaSensorDescription( key="downforce", name="Downforce", state_class=SensorStateClass.MEASUREMENT, ), - SensorEntityDescription( + WundaSensorDescription( key="trv_range", name="TRV Range", state_class=SensorStateClass.MEASUREMENT, @@ -185,28 +202,48 @@ def __init__( wunda_id: str, name: str, coordinator: WundasmartDataUpdateCoordinator, - description: SensorEntityDescription, + description: WundaSensorDescription, ) -> None: """Initialize the sensor.""" super().__init__(coordinator) self._wunda_id = wunda_id + self._coordinator = coordinator self._attr_name = name if (device_sn := coordinator.device_sn) is not None: self._attr_unique_id = f"{device_sn}.{wunda_id}.{description.key}" self._attr_device_info = coordinator.device_info - self.entity_description = description - self._coordinator = coordinator + self.entity_description = self.__update_description_defaults(description) # Update with initial state self.__update_state() + @property + def available(self): + return self.__is_available(self.entity_description) + + def __is_available(self, description: WundaSensorDescription): + if callable(description.available): + device = self.coordinator.data.get(self._wunda_id, {}) + state = device.get("state", {}) + return description.available(state) + return description.available + + def __update_description_defaults(self, description: WundaSensorDescription): + kwargs = asdict(description) + available = self.__is_available(description) + kwargs["entity_registry_enabled_default"] = available + kwargs["entity_registry_visible_default"] = available + return WundaSensorDescription(**kwargs) + def __update_state(self): device = self.coordinator.data.get(self._wunda_id, {}) state = device.get("state", {}) - self._attr_available = True - self._attr_native_value = state.get(self.entity_description.key) + value = state.get(self.entity_description.key) + if not value and self.entity_description.default is not None: + value = self.entity_description.default + self._attr_native_value = value @callback def _handle_coordinator_update(self) -> None: diff --git a/tests/fixtures/test_get_devices1.json b/tests/fixtures/test_get_devices1.json index 4923652..c89c694 100644 --- a/tests/fixtures/test_get_devices1.json +++ b/tests/fixtures/test_get_devices1.json @@ -23,8 +23,8 @@ "v": "1.8", "temp": "17.8", "rh": "66.57", - "temp_ext": "", - "ext": "0", + "temp_ext": "18.0", + "ext": "1", "bat": "100", "sig": "88", "alarm": "0" @@ -55,7 +55,7 @@ "t_norm": "19.00", "t_hi": "21.00", "heat": "4", - "temp_pre": "0", + "temp_pre": "4", "prg": "1", "lock": "0", "temp": "0.00", diff --git a/tests/test_sensor.py b/tests/test_sensor.py index abefff4..ca07441 100644 --- a/tests/test_sensor.py +++ b/tests/test_sensor.py @@ -37,6 +37,10 @@ async def test_sensors(hass: HomeAssistant, config): assert trv_signal_state.state == "88" assert trv_signal_state.attributes["icon"] == "mdi:signal-cellular-3" + ext_temp_state = hass.states.get("sensor.test_room_external_probe_temperature") + assert ext_temp_state + assert ext_temp_state.state == "18.0" + coordinator: DataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] assert coordinator