core/homeassistant/components/home_connect/sensor.py

704 lines
26 KiB
Python

"""Provides a sensor for Home Connect."""
from collections import defaultdict
from collections.abc import Callable
from dataclasses import dataclass
from datetime import timedelta
from functools import partial
import logging
from typing import cast
from aiohomeconnect.model import EventKey, StatusKey
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfVolume
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util import dt as dt_util, slugify
from .common import setup_home_connect_entry
from .const import (
APPLIANCES_WITH_PROGRAMS,
BSH_OPERATION_STATE_FINISHED,
BSH_OPERATION_STATE_PAUSE,
BSH_OPERATION_STATE_RUN,
UNIT_MAP,
)
from .coordinator import HomeConnectApplianceData, HomeConnectConfigEntry
from .entity import HomeConnectEntity, constraint_fetcher
_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 0
EVENT_OPTIONS = ["confirmed", "off", "present"]
@dataclass(frozen=True, kw_only=True)
class HomeConnectSensorEntityDescription(
SensorEntityDescription,
):
"""Entity Description class for sensors."""
appliance_types: tuple[str, ...] | None = None
fetch_unit: bool = False
BSH_PROGRAM_SENSORS = (
HomeConnectSensorEntityDescription(
key=EventKey.BSH_COMMON_OPTION_REMAINING_PROGRAM_TIME,
device_class=SensorDeviceClass.TIMESTAMP,
translation_key="program_finish_time",
appliance_types=(
"CoffeeMaker",
"CookProcessor",
"Dishwasher",
"Dryer",
"Hood",
"Oven",
"Washer",
"WasherDryer",
),
),
HomeConnectSensorEntityDescription(
key=EventKey.BSH_COMMON_OPTION_PROGRAM_PROGRESS,
native_unit_of_measurement=PERCENTAGE,
translation_key="program_progress",
appliance_types=APPLIANCES_WITH_PROGRAMS,
),
)
SENSORS = (
HomeConnectSensorEntityDescription(
key=StatusKey.BSH_COMMON_OPERATION_STATE,
device_class=SensorDeviceClass.ENUM,
options=[
"inactive",
"ready",
"delayedstart",
"run",
"pause",
"actionrequired",
"finished",
"error",
"aborting",
],
translation_key="operation_state",
),
HomeConnectSensorEntityDescription(
key=StatusKey.BSH_COMMON_DOOR_STATE,
device_class=SensorDeviceClass.ENUM,
options=[
"closed",
"locked",
"open",
],
translation_key="door",
),
HomeConnectSensorEntityDescription(
key=StatusKey.CONSUMER_PRODUCTS_COFFEE_MAKER_BEVERAGE_COUNTER_COFFEE,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL_INCREASING,
translation_key="coffee_counter",
),
HomeConnectSensorEntityDescription(
key=StatusKey.CONSUMER_PRODUCTS_COFFEE_MAKER_BEVERAGE_COUNTER_POWDER_COFFEE,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL_INCREASING,
translation_key="powder_coffee_counter",
),
HomeConnectSensorEntityDescription(
key=StatusKey.CONSUMER_PRODUCTS_COFFEE_MAKER_BEVERAGE_COUNTER_HOT_WATER,
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=UnitOfVolume.MILLILITERS,
device_class=SensorDeviceClass.VOLUME,
state_class=SensorStateClass.TOTAL_INCREASING,
translation_key="hot_water_counter",
),
HomeConnectSensorEntityDescription(
key=StatusKey.CONSUMER_PRODUCTS_COFFEE_MAKER_BEVERAGE_COUNTER_HOT_WATER_CUPS,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL_INCREASING,
translation_key="hot_water_cups_counter",
),
HomeConnectSensorEntityDescription(
key=StatusKey.CONSUMER_PRODUCTS_COFFEE_MAKER_BEVERAGE_COUNTER_HOT_MILK,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL_INCREASING,
translation_key="hot_milk_counter",
),
HomeConnectSensorEntityDescription(
key=StatusKey.CONSUMER_PRODUCTS_COFFEE_MAKER_BEVERAGE_COUNTER_FROTHY_MILK,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL_INCREASING,
translation_key="frothy_milk_counter",
),
HomeConnectSensorEntityDescription(
key=StatusKey.CONSUMER_PRODUCTS_COFFEE_MAKER_BEVERAGE_COUNTER_MILK,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL_INCREASING,
translation_key="milk_counter",
),
HomeConnectSensorEntityDescription(
key=StatusKey.CONSUMER_PRODUCTS_COFFEE_MAKER_BEVERAGE_COUNTER_COFFEE_AND_MILK,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL_INCREASING,
translation_key="coffee_and_milk_counter",
),
HomeConnectSensorEntityDescription(
key=StatusKey.CONSUMER_PRODUCTS_COFFEE_MAKER_BEVERAGE_COUNTER_RISTRETTO_ESPRESSO,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL_INCREASING,
translation_key="ristretto_espresso_counter",
),
HomeConnectSensorEntityDescription(
key=StatusKey.BSH_COMMON_BATTERY_LEVEL,
device_class=SensorDeviceClass.BATTERY,
),
HomeConnectSensorEntityDescription(
key=StatusKey.BSH_COMMON_VIDEO_CAMERA_STATE,
device_class=SensorDeviceClass.ENUM,
options=[
"disabled",
"sleeping",
"ready",
"streaminglocal",
"streamingcloud",
"streaminglocalancloud",
"error",
],
translation_key="camera_state",
),
HomeConnectSensorEntityDescription(
key=StatusKey.CONSUMER_PRODUCTS_CLEANING_ROBOT_LAST_SELECTED_MAP,
device_class=SensorDeviceClass.ENUM,
options=[
"tempmap",
"map1",
"map2",
"map3",
],
translation_key="last_selected_map",
),
HomeConnectSensorEntityDescription(
key=StatusKey.COOKING_OVEN_CURRENT_CAVITY_TEMPERATURE,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
translation_key="oven_current_cavity_temperature",
fetch_unit=True,
),
)
EVENT_SENSORS = (
HomeConnectSensorEntityDescription(
key=EventKey.BSH_COMMON_EVENT_PROGRAM_ABORTED,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="program_aborted",
appliance_types=("Dishwasher", "CleaningRobot", "CookProcessor"),
),
HomeConnectSensorEntityDescription(
key=EventKey.BSH_COMMON_EVENT_PROGRAM_FINISHED,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="program_finished",
appliance_types=(
"Oven",
"Dishwasher",
"Washer",
"Dryer",
"WasherDryer",
"CleaningRobot",
"CookProcessor",
),
),
HomeConnectSensorEntityDescription(
key=EventKey.BSH_COMMON_EVENT_ALARM_CLOCK_ELAPSED,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="alarm_clock_elapsed",
appliance_types=("Oven", "Cooktop"),
),
HomeConnectSensorEntityDescription(
key=EventKey.COOKING_OVEN_EVENT_PREHEAT_FINISHED,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="preheat_finished",
appliance_types=("Oven", "Cooktop"),
),
HomeConnectSensorEntityDescription(
key=EventKey.COOKING_OVEN_EVENT_REGULAR_PREHEAT_FINISHED,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="regular_preheat_finished",
appliance_types=("Oven",),
),
HomeConnectSensorEntityDescription(
key=EventKey.LAUNDRY_CARE_DRYER_EVENT_DRYING_PROCESS_FINISHED,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="drying_process_finished",
appliance_types=("Dryer",),
),
HomeConnectSensorEntityDescription(
key=EventKey.DISHCARE_DISHWASHER_EVENT_SALT_NEARLY_EMPTY,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="salt_nearly_empty",
appliance_types=("Dishwasher",),
),
HomeConnectSensorEntityDescription(
key=EventKey.DISHCARE_DISHWASHER_EVENT_RINSE_AID_NEARLY_EMPTY,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="rinse_aid_nearly_empty",
appliance_types=("Dishwasher",),
),
HomeConnectSensorEntityDescription(
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_BEAN_CONTAINER_EMPTY,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="bean_container_empty",
appliance_types=("CoffeeMaker",),
),
HomeConnectSensorEntityDescription(
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_WATER_TANK_EMPTY,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="water_tank_empty",
appliance_types=("CoffeeMaker",),
),
HomeConnectSensorEntityDescription(
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DRIP_TRAY_FULL,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="drip_tray_full",
appliance_types=("CoffeeMaker",),
),
HomeConnectSensorEntityDescription(
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_KEEP_MILK_TANK_COOL,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="keep_milk_tank_cool",
appliance_types=("CoffeeMaker",),
),
HomeConnectSensorEntityDescription(
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DESCALING_IN_20_CUPS,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="descaling_in_20_cups",
appliance_types=("CoffeeMaker",),
),
HomeConnectSensorEntityDescription(
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DESCALING_IN_15_CUPS,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="descaling_in_15_cups",
appliance_types=("CoffeeMaker",),
),
HomeConnectSensorEntityDescription(
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DESCALING_IN_10_CUPS,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="descaling_in_10_cups",
appliance_types=("CoffeeMaker",),
),
HomeConnectSensorEntityDescription(
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DESCALING_IN_5_CUPS,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="descaling_in_5_cups",
appliance_types=("CoffeeMaker",),
),
HomeConnectSensorEntityDescription(
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DEVICE_SHOULD_BE_DESCALED,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="device_should_be_descaled",
appliance_types=("CoffeeMaker",),
),
HomeConnectSensorEntityDescription(
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DEVICE_DESCALING_OVERDUE,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="device_descaling_overdue",
appliance_types=("CoffeeMaker",),
),
HomeConnectSensorEntityDescription(
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DEVICE_DESCALING_BLOCKAGE,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="device_descaling_blockage",
appliance_types=("CoffeeMaker",),
),
HomeConnectSensorEntityDescription(
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DEVICE_SHOULD_BE_CLEANED,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="device_should_be_cleaned",
appliance_types=("CoffeeMaker",),
),
HomeConnectSensorEntityDescription(
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DEVICE_CLEANING_OVERDUE,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="device_cleaning_overdue",
appliance_types=("CoffeeMaker",),
),
HomeConnectSensorEntityDescription(
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_CALC_N_CLEAN_IN20CUPS,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="calc_n_clean_in20cups",
appliance_types=("CoffeeMaker",),
),
HomeConnectSensorEntityDescription(
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_CALC_N_CLEAN_IN15CUPS,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="calc_n_clean_in15cups",
appliance_types=("CoffeeMaker",),
),
HomeConnectSensorEntityDescription(
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_CALC_N_CLEAN_IN10CUPS,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="calc_n_clean_in10cups",
appliance_types=("CoffeeMaker",),
),
HomeConnectSensorEntityDescription(
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_CALC_N_CLEAN_IN5CUPS,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="calc_n_clean_in5cups",
appliance_types=("CoffeeMaker",),
),
HomeConnectSensorEntityDescription(
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DEVICE_SHOULD_BE_CALC_N_CLEANED,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="device_should_be_calc_n_cleaned",
appliance_types=("CoffeeMaker",),
),
HomeConnectSensorEntityDescription(
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DEVICE_CALC_N_CLEAN_OVERDUE,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="device_calc_n_clean_overdue",
appliance_types=("CoffeeMaker",),
),
HomeConnectSensorEntityDescription(
key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DEVICE_CALC_N_CLEAN_BLOCKAGE,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="device_calc_n_clean_blockage",
appliance_types=("CoffeeMaker",),
),
HomeConnectSensorEntityDescription(
key=EventKey.REFRIGERATION_FRIDGE_FREEZER_EVENT_DOOR_ALARM_FREEZER,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="freezer_door_alarm",
appliance_types=("FridgeFreezer", "Freezer"),
),
HomeConnectSensorEntityDescription(
key=EventKey.REFRIGERATION_FRIDGE_FREEZER_EVENT_DOOR_ALARM_REFRIGERATOR,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="refrigerator_door_alarm",
appliance_types=("FridgeFreezer", "Refrigerator"),
),
HomeConnectSensorEntityDescription(
key=EventKey.REFRIGERATION_FRIDGE_FREEZER_EVENT_TEMPERATURE_ALARM_FREEZER,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="freezer_temperature_alarm",
appliance_types=("FridgeFreezer", "Freezer"),
),
HomeConnectSensorEntityDescription(
key=EventKey.CONSUMER_PRODUCTS_CLEANING_ROBOT_EVENT_EMPTY_DUST_BOX_AND_CLEAN_FILTER,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="empty_dust_box_and_clean_filter",
appliance_types=("CleaningRobot",),
),
HomeConnectSensorEntityDescription(
key=EventKey.CONSUMER_PRODUCTS_CLEANING_ROBOT_EVENT_ROBOT_IS_STUCK,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="robot_is_stuck",
appliance_types=("CleaningRobot",),
),
HomeConnectSensorEntityDescription(
key=EventKey.CONSUMER_PRODUCTS_CLEANING_ROBOT_EVENT_DOCKING_STATION_NOT_FOUND,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="docking_station_not_found",
appliance_types=("CleaningRobot",),
),
HomeConnectSensorEntityDescription(
key=EventKey.LAUNDRY_CARE_WASHER_EVENT_I_DOS_1_FILL_LEVEL_POOR,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="poor_i_dos_1_fill_level",
appliance_types=("Washer", "WasherDryer"),
),
HomeConnectSensorEntityDescription(
key=EventKey.LAUNDRY_CARE_WASHER_EVENT_I_DOS_2_FILL_LEVEL_POOR,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="poor_i_dos_2_fill_level",
appliance_types=("Washer", "WasherDryer"),
),
HomeConnectSensorEntityDescription(
key=EventKey.COOKING_COMMON_EVENT_HOOD_GREASE_FILTER_MAX_SATURATION_NEARLY_REACHED,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="grease_filter_max_saturation_nearly_reached",
appliance_types=("Hood",),
),
HomeConnectSensorEntityDescription(
key=EventKey.COOKING_COMMON_EVENT_HOOD_GREASE_FILTER_MAX_SATURATION_REACHED,
device_class=SensorDeviceClass.ENUM,
options=EVENT_OPTIONS,
translation_key="grease_filter_max_saturation_reached",
appliance_types=("Hood",),
),
)
def _get_entities_for_appliance(
entry: HomeConnectConfigEntry,
appliance: HomeConnectApplianceData,
) -> list[HomeConnectEntity]:
"""Get a list of entities."""
return [
*[
HomeConnectProgramSensor(entry.runtime_data, appliance, desc)
for desc in BSH_PROGRAM_SENSORS
if desc.appliance_types and appliance.info.type in desc.appliance_types
],
*[
HomeConnectSensor(entry.runtime_data, appliance, description)
for description in SENSORS
if description.key in appliance.status
],
]
def _add_event_sensor_entity(
entry: HomeConnectConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
appliance: HomeConnectApplianceData,
description: HomeConnectSensorEntityDescription,
remove_event_sensor_listener_list: list[Callable[[], None]],
) -> None:
"""Add an event sensor entity."""
if (
(appliance_data := entry.runtime_data.data.get(appliance.info.ha_id)) is None
) or description.key not in appliance_data.events:
return
for remove_listener in remove_event_sensor_listener_list:
remove_listener()
async_add_entities(
[
HomeConnectEventSensor(entry.runtime_data, appliance, description),
]
)
def _add_event_sensor_listeners(
entry: HomeConnectConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
remove_event_sensor_listener_dict: dict[str, list[CALLBACK_TYPE]],
) -> None:
for appliance in entry.runtime_data.data.values():
if appliance.info.ha_id in remove_event_sensor_listener_dict:
continue
for event_sensor_description in EVENT_SENSORS:
if appliance.info.type not in cast(
tuple[str, ...], event_sensor_description.appliance_types
):
continue
# We use a list as a kind of lazy initializer, as we can use the
# remove_listener while we are initializing it.
remove_event_sensor_listener_list = remove_event_sensor_listener_dict[
appliance.info.ha_id
]
remove_listener = entry.runtime_data.async_add_listener(
partial(
_add_event_sensor_entity,
entry,
async_add_entities,
appliance,
event_sensor_description,
remove_event_sensor_listener_list,
),
(appliance.info.ha_id, event_sensor_description.key),
)
remove_event_sensor_listener_list.append(remove_listener)
entry.async_on_unload(remove_listener)
def _remove_event_sensor_listeners_on_depaired(
entry: HomeConnectConfigEntry,
remove_event_sensor_listener_dict: dict[str, list[CALLBACK_TYPE]],
) -> None:
registered_listeners_ha_id = set(remove_event_sensor_listener_dict)
actual_appliances = set(entry.runtime_data.data)
for appliance_ha_id in registered_listeners_ha_id - actual_appliances:
for listener in remove_event_sensor_listener_dict.pop(appliance_ha_id):
listener()
async def async_setup_entry(
hass: HomeAssistant,
entry: HomeConnectConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Home Connect sensor."""
setup_home_connect_entry(
entry,
_get_entities_for_appliance,
async_add_entities,
)
remove_event_sensor_listener_dict: dict[str, list[CALLBACK_TYPE]] = defaultdict(
list
)
entry.async_on_unload(
entry.runtime_data.async_add_special_listener(
partial(
_add_event_sensor_listeners,
entry,
async_add_entities,
remove_event_sensor_listener_dict,
),
(EventKey.BSH_COMMON_APPLIANCE_PAIRED,),
)
)
entry.async_on_unload(
entry.runtime_data.async_add_special_listener(
partial(
_remove_event_sensor_listeners_on_depaired,
entry,
remove_event_sensor_listener_dict,
),
(EventKey.BSH_COMMON_APPLIANCE_DEPAIRED,),
)
)
class HomeConnectSensor(HomeConnectEntity, SensorEntity):
"""Sensor class for Home Connect."""
entity_description: HomeConnectSensorEntityDescription
def update_native_value(self) -> None:
"""Set the value of the sensor."""
status = self.appliance.status[cast(StatusKey, self.bsh_key)].value
self._update_native_value(status)
def _update_native_value(self, status: str | float) -> None:
"""Set the value of the sensor based on the given value."""
match self.device_class:
case SensorDeviceClass.TIMESTAMP:
self._attr_native_value = dt_util.utcnow() + timedelta(
seconds=cast(float, status)
)
case SensorDeviceClass.ENUM:
# Value comes back as an enum, we only really care about the
# last part, so split it off
# https://developer.home-connect.com/docs/status/operation_state
self._attr_native_value = slugify(cast(str, status).split(".")[-1])
case _:
self._attr_native_value = status
async def async_added_to_hass(self) -> None:
"""When entity is added to hass."""
await super().async_added_to_hass()
if self.entity_description.fetch_unit:
data = self.appliance.status[cast(StatusKey, self.bsh_key)]
if data.unit:
self._attr_native_unit_of_measurement = UNIT_MAP.get(
data.unit, data.unit
)
else:
await self.fetch_unit()
@constraint_fetcher
async def fetch_unit(self) -> None:
"""Fetch the unit of measurement."""
data = await self.coordinator.client.get_status_value(
self.appliance.info.ha_id, status_key=cast(StatusKey, self.bsh_key)
)
if data.unit:
self._attr_native_unit_of_measurement = UNIT_MAP.get(data.unit, data.unit)
class HomeConnectProgramSensor(HomeConnectSensor):
"""Sensor class for Home Connect sensors that reports information related to the running program."""
program_running: bool = False
async def async_added_to_hass(self) -> None:
"""Register listener."""
await super().async_added_to_hass()
self.async_on_remove(
self.coordinator.async_add_listener(
self._handle_operation_state_event,
(self.appliance.info.ha_id, EventKey.BSH_COMMON_STATUS_OPERATION_STATE),
)
)
@callback
def _handle_operation_state_event(self) -> None:
"""Update status when an event for the entity is received."""
self.program_running = (
status := self.appliance.status.get(StatusKey.BSH_COMMON_OPERATION_STATE)
) is not None and status.value in [
BSH_OPERATION_STATE_RUN,
BSH_OPERATION_STATE_PAUSE,
BSH_OPERATION_STATE_FINISHED,
]
if not self.program_running:
# reset the value when the program is not running, paused or finished
self._attr_native_value = None
self.async_write_ha_state()
@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
def update_native_value(self) -> None:
"""Update the program sensor's status."""
self.program_running = (
status := self.appliance.status.get(StatusKey.BSH_COMMON_OPERATION_STATE)
) is not None and status.value in [
BSH_OPERATION_STATE_RUN,
BSH_OPERATION_STATE_PAUSE,
BSH_OPERATION_STATE_FINISHED,
]
event = self.appliance.events.get(cast(EventKey, self.bsh_key))
if event:
self._update_native_value(event.value)
class HomeConnectEventSensor(HomeConnectSensor):
"""Sensor class for Home Connect events."""
def update_native_value(self) -> None:
"""Update the sensor's status."""
event = self.appliance.events[cast(EventKey, self.bsh_key)]
self._update_native_value(event.value)