From ac58494b5538be7c1e5ddfb71eac3f0c2c670c48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20Diego=20Rodr=C3=ADguez=20Royo?= Date: Sun, 19 Jan 2025 12:02:23 +0100 Subject: [PATCH] Improve program related sensors at Home Connect (#135929) --- .../components/home_connect/sensor.py | 84 ++++++++++++------- 1 file changed, 52 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/home_connect/sensor.py b/homeassistant/components/home_connect/sensor.py index 3ccf55bac6e..7b82ef8b676 100644 --- a/homeassistant/components/home_connect/sensor.py +++ b/homeassistant/components/home_connect/sensor.py @@ -1,13 +1,10 @@ """Provides a sensor for Home Connect.""" -import contextlib from dataclasses import dataclass from datetime import datetime, timedelta import logging from typing import cast -from homeconnect.api import HomeConnectError - from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, @@ -22,6 +19,7 @@ import homeassistant.util.dt as dt_util from . import HomeConnectConfigEntry from .const import ( + APPLIANCES_WITH_PROGRAMS, ATTR_VALUE, BSH_DOOR_STATE, BSH_OPERATION_STATE, @@ -51,27 +49,35 @@ class HomeConnectSensorEntityDescription(SensorEntityDescription): default_value: str | None = None appliance_types: tuple[str, ...] | None = None - sign: int = 1 BSH_PROGRAM_SENSORS = ( HomeConnectSensorEntityDescription( key="BSH.Common.Option.RemainingProgramTime", device_class=SensorDeviceClass.TIMESTAMP, - sign=1, translation_key="program_finish_time", + appliance_types=( + "CoffeMaker", + "CookProcessor", + "Dishwasher", + "Dryer", + "Hood", + "Oven", + "Washer", + "WasherDryer", + ), ), HomeConnectSensorEntityDescription( key="BSH.Common.Option.Duration", device_class=SensorDeviceClass.DURATION, native_unit_of_measurement=UnitOfTime.SECONDS, - sign=1, + appliance_types=("Oven",), ), HomeConnectSensorEntityDescription( key="BSH.Common.Option.ProgramProgress", native_unit_of_measurement=PERCENTAGE, - sign=1, translation_key="program_progress", + appliance_types=APPLIANCES_WITH_PROGRAMS, ), ) @@ -269,11 +275,12 @@ async def async_setup_entry( if description.appliance_types and device.appliance.type in description.appliance_types ) - with contextlib.suppress(HomeConnectError): - if device.appliance.get_programs_available(): - entities.extend( - HomeConnectSensor(device, desc) for desc in BSH_PROGRAM_SENSORS - ) + entities.extend( + HomeConnectProgramSensor(device, desc) + for desc in BSH_PROGRAM_SENSORS + if desc.appliance_types + and device.appliance.type in desc.appliance_types + ) entities.extend( HomeConnectSensor(device, description) for description in SENSORS @@ -289,11 +296,6 @@ class HomeConnectSensor(HomeConnectEntity, SensorEntity): entity_description: HomeConnectSensorEntityDescription - @property - def available(self) -> bool: - """Return true if the sensor is available.""" - return self._attr_native_value is not None - async def async_update(self) -> None: """Update the sensor's status.""" appliance_status = self.device.appliance.status @@ -311,30 +313,17 @@ class HomeConnectSensor(HomeConnectEntity, SensorEntity): self._attr_native_value = None elif ( self._attr_native_value is not None - and self.entity_description.sign == 1 and isinstance(self._attr_native_value, datetime) and self._attr_native_value < dt_util.utcnow() ): # if the date is supposed to be in the future but we're # already past it, set state to None. self._attr_native_value = None - elif ( - BSH_OPERATION_STATE - in (appliance_status := self.device.appliance.status) - and ATTR_VALUE in appliance_status[BSH_OPERATION_STATE] - and appliance_status[BSH_OPERATION_STATE][ATTR_VALUE] - in [ - BSH_OPERATION_STATE_RUN, - BSH_OPERATION_STATE_PAUSE, - BSH_OPERATION_STATE_FINISHED, - ] - ): - seconds = self.entity_description.sign * float(status[ATTR_VALUE]) + else: + seconds = float(status[ATTR_VALUE]) self._attr_native_value = dt_util.utcnow() + timedelta( seconds=seconds ) - else: - self._attr_native_value = None case SensorDeviceClass.ENUM: # Value comes back as an enum, we only really care about the # last part, so split it off @@ -345,3 +334,34 @@ class HomeConnectSensor(HomeConnectEntity, SensorEntity): case _: self._attr_native_value = status.get(ATTR_VALUE) _LOGGER.debug("Updated, new state: %s", self._attr_native_value) + + +class HomeConnectProgramSensor(HomeConnectSensor): + """Sensor class for Home Connect sensors that reports information related to the running program.""" + + program_running: bool = False + + @property + def available(self) -> bool: + """Return true if the sensor is available.""" + # These sensors are only available if the program is running, paused or finished. + # Otherwise, some sensors report erroneous values. + return super().available and self.program_running + + async def async_update(self) -> None: + """Update the sensor's status.""" + self.program_running = ( + BSH_OPERATION_STATE in (appliance_status := self.device.appliance.status) + and ATTR_VALUE in appliance_status[BSH_OPERATION_STATE] + and appliance_status[BSH_OPERATION_STATE][ATTR_VALUE] + in [ + BSH_OPERATION_STATE_RUN, + BSH_OPERATION_STATE_PAUSE, + BSH_OPERATION_STATE_FINISHED, + ] + ) + if self.program_running: + await super().async_update() + else: + # reset the value when the program is not running, paused or finished + self._attr_native_value = None