Improve program related sensors at Home Connect (#135929)

This commit is contained in:
J. Diego Rodríguez Royo 2025-01-19 12:02:23 +01:00 committed by GitHub
parent 33d552e3f7
commit ac58494b55
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,13 +1,10 @@
"""Provides a sensor for Home Connect.""" """Provides a sensor for Home Connect."""
import contextlib
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime, timedelta from datetime import datetime, timedelta
import logging import logging
from typing import cast from typing import cast
from homeconnect.api import HomeConnectError
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
SensorDeviceClass, SensorDeviceClass,
SensorEntity, SensorEntity,
@ -22,6 +19,7 @@ import homeassistant.util.dt as dt_util
from . import HomeConnectConfigEntry from . import HomeConnectConfigEntry
from .const import ( from .const import (
APPLIANCES_WITH_PROGRAMS,
ATTR_VALUE, ATTR_VALUE,
BSH_DOOR_STATE, BSH_DOOR_STATE,
BSH_OPERATION_STATE, BSH_OPERATION_STATE,
@ -51,27 +49,35 @@ class HomeConnectSensorEntityDescription(SensorEntityDescription):
default_value: str | None = None default_value: str | None = None
appliance_types: tuple[str, ...] | None = None appliance_types: tuple[str, ...] | None = None
sign: int = 1
BSH_PROGRAM_SENSORS = ( BSH_PROGRAM_SENSORS = (
HomeConnectSensorEntityDescription( HomeConnectSensorEntityDescription(
key="BSH.Common.Option.RemainingProgramTime", key="BSH.Common.Option.RemainingProgramTime",
device_class=SensorDeviceClass.TIMESTAMP, device_class=SensorDeviceClass.TIMESTAMP,
sign=1,
translation_key="program_finish_time", translation_key="program_finish_time",
appliance_types=(
"CoffeMaker",
"CookProcessor",
"Dishwasher",
"Dryer",
"Hood",
"Oven",
"Washer",
"WasherDryer",
),
), ),
HomeConnectSensorEntityDescription( HomeConnectSensorEntityDescription(
key="BSH.Common.Option.Duration", key="BSH.Common.Option.Duration",
device_class=SensorDeviceClass.DURATION, device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.SECONDS, native_unit_of_measurement=UnitOfTime.SECONDS,
sign=1, appliance_types=("Oven",),
), ),
HomeConnectSensorEntityDescription( HomeConnectSensorEntityDescription(
key="BSH.Common.Option.ProgramProgress", key="BSH.Common.Option.ProgramProgress",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
sign=1,
translation_key="program_progress", translation_key="program_progress",
appliance_types=APPLIANCES_WITH_PROGRAMS,
), ),
) )
@ -269,11 +275,12 @@ async def async_setup_entry(
if description.appliance_types if description.appliance_types
and device.appliance.type in description.appliance_types and device.appliance.type in description.appliance_types
) )
with contextlib.suppress(HomeConnectError): entities.extend(
if device.appliance.get_programs_available(): HomeConnectProgramSensor(device, desc)
entities.extend( for desc in BSH_PROGRAM_SENSORS
HomeConnectSensor(device, desc) for desc in BSH_PROGRAM_SENSORS if desc.appliance_types
) and device.appliance.type in desc.appliance_types
)
entities.extend( entities.extend(
HomeConnectSensor(device, description) HomeConnectSensor(device, description)
for description in SENSORS for description in SENSORS
@ -289,11 +296,6 @@ class HomeConnectSensor(HomeConnectEntity, SensorEntity):
entity_description: HomeConnectSensorEntityDescription 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: async def async_update(self) -> None:
"""Update the sensor's status.""" """Update the sensor's status."""
appliance_status = self.device.appliance.status appliance_status = self.device.appliance.status
@ -311,30 +313,17 @@ class HomeConnectSensor(HomeConnectEntity, SensorEntity):
self._attr_native_value = None self._attr_native_value = None
elif ( elif (
self._attr_native_value is not None self._attr_native_value is not None
and self.entity_description.sign == 1
and isinstance(self._attr_native_value, datetime) and isinstance(self._attr_native_value, datetime)
and self._attr_native_value < dt_util.utcnow() and self._attr_native_value < dt_util.utcnow()
): ):
# if the date is supposed to be in the future but we're # if the date is supposed to be in the future but we're
# already past it, set state to None. # already past it, set state to None.
self._attr_native_value = None self._attr_native_value = None
elif ( else:
BSH_OPERATION_STATE seconds = float(status[ATTR_VALUE])
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])
self._attr_native_value = dt_util.utcnow() + timedelta( self._attr_native_value = dt_util.utcnow() + timedelta(
seconds=seconds seconds=seconds
) )
else:
self._attr_native_value = None
case SensorDeviceClass.ENUM: case SensorDeviceClass.ENUM:
# Value comes back as an enum, we only really care about the # Value comes back as an enum, we only really care about the
# last part, so split it off # last part, so split it off
@ -345,3 +334,34 @@ class HomeConnectSensor(HomeConnectEntity, SensorEntity):
case _: case _:
self._attr_native_value = status.get(ATTR_VALUE) self._attr_native_value = status.get(ATTR_VALUE)
_LOGGER.debug("Updated, new state: %s", self._attr_native_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