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