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."""
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