From a938001805efd3958eac3355460b20189999cbc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20Diego=20Rodr=C3=ADguez=20Royo?= Date: Thu, 22 May 2025 12:55:11 +0200 Subject: [PATCH] Don't add dynamically Home Connect event sensors and disable them by default (#144757) * Don't add dynamically Home Connect sensors and disable them by default * Fix test * Check for None --------- Co-authored-by: Martin Hjelmare --- .../components/home_connect/coordinator.py | 8 +- .../components/home_connect/sensor.py | 151 ++++------ .../home_connect/test_coordinator.py | 6 +- tests/components/home_connect/test_sensor.py | 273 ++++++++---------- 4 files changed, 171 insertions(+), 267 deletions(-) diff --git a/homeassistant/components/home_connect/coordinator.py b/homeassistant/components/home_connect/coordinator.py index 9e40de86e24..3c9d33424a8 100644 --- a/homeassistant/components/home_connect/coordinator.py +++ b/homeassistant/components/home_connect/coordinator.py @@ -5,7 +5,6 @@ from __future__ import annotations from asyncio import sleep as asyncio_sleep from collections import defaultdict from collections.abc import Callable -from contextlib import suppress from dataclasses import dataclass import logging from typing import Any, cast @@ -137,11 +136,8 @@ class HomeConnectCoordinator( self.__dict__.pop("context_listeners", None) def remove_listener_and_invalidate_context_listeners() -> None: - # There are cases where the remove_listener will be called - # although it has been already removed somewhere else - with suppress(KeyError): - remove_listener() - self.__dict__.pop("context_listeners", None) + remove_listener() + self.__dict__.pop("context_listeners", None) return remove_listener_and_invalidate_context_listeners diff --git a/homeassistant/components/home_connect/sensor.py b/homeassistant/components/home_connect/sensor.py index 2872c4a95d3..d8fda46385d 100644 --- a/homeassistant/components/home_connect/sensor.py +++ b/homeassistant/components/home_connect/sensor.py @@ -1,10 +1,7 @@ """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 @@ -17,7 +14,7 @@ from homeassistant.components.sensor import ( SensorStateClass, ) from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfVolume -from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.util import dt as dt_util, slugify @@ -45,6 +42,7 @@ class HomeConnectSensorEntityDescription( ): """Entity Description class for sensors.""" + default_value: str | None = None appliance_types: tuple[str, ...] | None = None fetch_unit: bool = False @@ -199,6 +197,7 @@ EVENT_SENSORS = ( key=EventKey.BSH_COMMON_EVENT_PROGRAM_ABORTED, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="program_aborted", appliance_types=("Dishwasher", "CleaningRobot", "CookProcessor"), ), @@ -206,6 +205,7 @@ EVENT_SENSORS = ( key=EventKey.BSH_COMMON_EVENT_PROGRAM_FINISHED, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="program_finished", appliance_types=( "Oven", @@ -221,6 +221,7 @@ EVENT_SENSORS = ( key=EventKey.BSH_COMMON_EVENT_ALARM_CLOCK_ELAPSED, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="alarm_clock_elapsed", appliance_types=("Oven", "Cooktop"), ), @@ -228,6 +229,7 @@ EVENT_SENSORS = ( key=EventKey.COOKING_OVEN_EVENT_PREHEAT_FINISHED, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="preheat_finished", appliance_types=("Oven", "Cooktop"), ), @@ -235,6 +237,7 @@ EVENT_SENSORS = ( key=EventKey.COOKING_OVEN_EVENT_REGULAR_PREHEAT_FINISHED, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="regular_preheat_finished", appliance_types=("Oven",), ), @@ -242,6 +245,7 @@ EVENT_SENSORS = ( key=EventKey.LAUNDRY_CARE_DRYER_EVENT_DRYING_PROCESS_FINISHED, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="drying_process_finished", appliance_types=("Dryer",), ), @@ -249,6 +253,7 @@ EVENT_SENSORS = ( key=EventKey.DISHCARE_DISHWASHER_EVENT_SALT_NEARLY_EMPTY, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="salt_nearly_empty", appliance_types=("Dishwasher",), ), @@ -256,6 +261,7 @@ EVENT_SENSORS = ( key=EventKey.DISHCARE_DISHWASHER_EVENT_RINSE_AID_NEARLY_EMPTY, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="rinse_aid_nearly_empty", appliance_types=("Dishwasher",), ), @@ -263,6 +269,7 @@ EVENT_SENSORS = ( key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_BEAN_CONTAINER_EMPTY, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="bean_container_empty", appliance_types=("CoffeeMaker",), ), @@ -270,6 +277,7 @@ EVENT_SENSORS = ( key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_WATER_TANK_EMPTY, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="water_tank_empty", appliance_types=("CoffeeMaker",), ), @@ -277,6 +285,7 @@ EVENT_SENSORS = ( key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DRIP_TRAY_FULL, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="drip_tray_full", appliance_types=("CoffeeMaker",), ), @@ -284,6 +293,7 @@ EVENT_SENSORS = ( key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_KEEP_MILK_TANK_COOL, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="keep_milk_tank_cool", appliance_types=("CoffeeMaker",), ), @@ -291,6 +301,7 @@ EVENT_SENSORS = ( key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DESCALING_IN_20_CUPS, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="descaling_in_20_cups", appliance_types=("CoffeeMaker",), ), @@ -298,6 +309,7 @@ EVENT_SENSORS = ( key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DESCALING_IN_15_CUPS, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="descaling_in_15_cups", appliance_types=("CoffeeMaker",), ), @@ -305,6 +317,7 @@ EVENT_SENSORS = ( key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DESCALING_IN_10_CUPS, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="descaling_in_10_cups", appliance_types=("CoffeeMaker",), ), @@ -312,6 +325,7 @@ EVENT_SENSORS = ( key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DESCALING_IN_5_CUPS, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="descaling_in_5_cups", appliance_types=("CoffeeMaker",), ), @@ -319,6 +333,7 @@ EVENT_SENSORS = ( key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DEVICE_SHOULD_BE_DESCALED, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="device_should_be_descaled", appliance_types=("CoffeeMaker",), ), @@ -326,6 +341,7 @@ EVENT_SENSORS = ( key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DEVICE_DESCALING_OVERDUE, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="device_descaling_overdue", appliance_types=("CoffeeMaker",), ), @@ -333,6 +349,7 @@ EVENT_SENSORS = ( key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DEVICE_DESCALING_BLOCKAGE, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="device_descaling_blockage", appliance_types=("CoffeeMaker",), ), @@ -340,6 +357,7 @@ EVENT_SENSORS = ( key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DEVICE_SHOULD_BE_CLEANED, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="device_should_be_cleaned", appliance_types=("CoffeeMaker",), ), @@ -347,6 +365,7 @@ EVENT_SENSORS = ( key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DEVICE_CLEANING_OVERDUE, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="device_cleaning_overdue", appliance_types=("CoffeeMaker",), ), @@ -354,6 +373,7 @@ EVENT_SENSORS = ( key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_CALC_N_CLEAN_IN20CUPS, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="calc_n_clean_in20cups", appliance_types=("CoffeeMaker",), ), @@ -361,6 +381,7 @@ EVENT_SENSORS = ( key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_CALC_N_CLEAN_IN15CUPS, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="calc_n_clean_in15cups", appliance_types=("CoffeeMaker",), ), @@ -368,6 +389,7 @@ EVENT_SENSORS = ( key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_CALC_N_CLEAN_IN10CUPS, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="calc_n_clean_in10cups", appliance_types=("CoffeeMaker",), ), @@ -375,6 +397,7 @@ EVENT_SENSORS = ( key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_CALC_N_CLEAN_IN5CUPS, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="calc_n_clean_in5cups", appliance_types=("CoffeeMaker",), ), @@ -382,6 +405,7 @@ EVENT_SENSORS = ( key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DEVICE_SHOULD_BE_CALC_N_CLEANED, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="device_should_be_calc_n_cleaned", appliance_types=("CoffeeMaker",), ), @@ -389,6 +413,7 @@ EVENT_SENSORS = ( key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DEVICE_CALC_N_CLEAN_OVERDUE, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="device_calc_n_clean_overdue", appliance_types=("CoffeeMaker",), ), @@ -396,6 +421,7 @@ EVENT_SENSORS = ( key=EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_DEVICE_CALC_N_CLEAN_BLOCKAGE, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="device_calc_n_clean_blockage", appliance_types=("CoffeeMaker",), ), @@ -403,6 +429,7 @@ EVENT_SENSORS = ( key=EventKey.REFRIGERATION_FRIDGE_FREEZER_EVENT_DOOR_ALARM_FREEZER, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="freezer_door_alarm", appliance_types=("FridgeFreezer", "Freezer"), ), @@ -410,6 +437,7 @@ EVENT_SENSORS = ( key=EventKey.REFRIGERATION_FRIDGE_FREEZER_EVENT_DOOR_ALARM_REFRIGERATOR, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="refrigerator_door_alarm", appliance_types=("FridgeFreezer", "Refrigerator"), ), @@ -417,6 +445,7 @@ EVENT_SENSORS = ( key=EventKey.REFRIGERATION_FRIDGE_FREEZER_EVENT_TEMPERATURE_ALARM_FREEZER, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="freezer_temperature_alarm", appliance_types=("FridgeFreezer", "Freezer"), ), @@ -424,6 +453,7 @@ EVENT_SENSORS = ( key=EventKey.CONSUMER_PRODUCTS_CLEANING_ROBOT_EVENT_EMPTY_DUST_BOX_AND_CLEAN_FILTER, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="empty_dust_box_and_clean_filter", appliance_types=("CleaningRobot",), ), @@ -431,6 +461,7 @@ EVENT_SENSORS = ( key=EventKey.CONSUMER_PRODUCTS_CLEANING_ROBOT_EVENT_ROBOT_IS_STUCK, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="robot_is_stuck", appliance_types=("CleaningRobot",), ), @@ -438,6 +469,7 @@ EVENT_SENSORS = ( key=EventKey.CONSUMER_PRODUCTS_CLEANING_ROBOT_EVENT_DOCKING_STATION_NOT_FOUND, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="docking_station_not_found", appliance_types=("CleaningRobot",), ), @@ -445,6 +477,7 @@ EVENT_SENSORS = ( key=EventKey.LAUNDRY_CARE_WASHER_EVENT_I_DOS_1_FILL_LEVEL_POOR, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="poor_i_dos_1_fill_level", appliance_types=("Washer", "WasherDryer"), ), @@ -452,6 +485,7 @@ EVENT_SENSORS = ( key=EventKey.LAUNDRY_CARE_WASHER_EVENT_I_DOS_2_FILL_LEVEL_POOR, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="poor_i_dos_2_fill_level", appliance_types=("Washer", "WasherDryer"), ), @@ -459,6 +493,7 @@ EVENT_SENSORS = ( key=EventKey.COOKING_COMMON_EVENT_HOOD_GREASE_FILTER_MAX_SATURATION_NEARLY_REACHED, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="grease_filter_max_saturation_nearly_reached", appliance_types=("Hood",), ), @@ -466,6 +501,7 @@ EVENT_SENSORS = ( key=EventKey.COOKING_COMMON_EVENT_HOOD_GREASE_FILTER_MAX_SATURATION_REACHED, device_class=SensorDeviceClass.ENUM, options=EVENT_OPTIONS, + default_value="off", translation_key="grease_filter_max_saturation_reached", appliance_types=("Hood",), ), @@ -478,6 +514,12 @@ def _get_entities_for_appliance( ) -> list[HomeConnectEntity]: """Get a list of entities.""" return [ + *[ + HomeConnectEventSensor(entry.runtime_data, appliance, description) + for description in EVENT_SENSORS + if description.appliance_types + and appliance.info.type in description.appliance_types + ], *[ HomeConnectProgramSensor(entry.runtime_data, appliance, desc) for desc in BSH_PROGRAM_SENSORS @@ -491,72 +533,6 @@ def _get_entities_for_appliance( ] -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, @@ -569,32 +545,6 @@ async def async_setup_entry( 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.""" @@ -697,7 +647,12 @@ class HomeConnectProgramSensor(HomeConnectSensor): class HomeConnectEventSensor(HomeConnectSensor): """Sensor class for Home Connect events.""" + _attr_entity_registry_enabled_default = False + 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) + event = self.appliance.events.get(cast(EventKey, self.bsh_key)) + if event: + self._update_native_value(event.value) + elif self._attr_native_value is None: + self._attr_native_value = self.entity_description.default_value diff --git a/tests/components/home_connect/test_coordinator.py b/tests/components/home_connect/test_coordinator.py index 40af64f9042..f9fed995b89 100644 --- a/tests/components/home_connect/test_coordinator.py +++ b/tests/components/home_connect/test_coordinator.py @@ -247,6 +247,7 @@ async def test_coordinator_update_failing( getattr(client, mock_method).assert_called() +@pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.parametrize("appliance", ["Dishwasher"], indirect=True) @pytest.mark.parametrize( ("event_type", "event_key", "event_value", ATTR_ENTITY_ID), @@ -288,7 +289,7 @@ async def test_event_listener( assert config_entry.state is ConfigEntryState.LOADED state = hass.states.get(entity_id) - + assert state event_message = EventMessage( appliance.ha_id, event_type, @@ -310,8 +311,7 @@ async def test_event_listener( new_state = hass.states.get(entity_id) assert new_state - if state is not None: - assert new_state.state != state.state + assert new_state.state != state.state # Following, we are gonna check that the listeners are clean up correctly new_entity_id = entity_id + "_new" diff --git a/tests/components/home_connect/test_sensor.py b/tests/components/home_connect/test_sensor.py index 47badd8d06d..fe8a3ab4be0 100644 --- a/tests/components/home_connect/test_sensor.py +++ b/tests/components/home_connect/test_sensor.py @@ -1,7 +1,6 @@ """Tests for home_connect sensor entities.""" from collections.abc import Awaitable, Callable -import logging from unittest.mock import AsyncMock, MagicMock from aiohomeconnect.model import ( @@ -140,29 +139,6 @@ async def test_paired_depaired_devices_flow( for entity_entry in entity_entries: assert entity_registry.async_get(entity_entry.entity_id) - await client.add_events( - [ - EventMessage( - appliance.ha_id, - EventType.EVENT, - ArrayOfEvents( - [ - Event( - key=EventKey.LAUNDRY_CARE_WASHER_EVENT_I_DOS_1_FILL_LEVEL_POOR, - raw_key=EventKey.LAUNDRY_CARE_WASHER_EVENT_I_DOS_1_FILL_LEVEL_POOR.value, - timestamp=0, - level="", - handling="", - value=BSH_EVENT_PRESENT_STATE_PRESENT, - ) - ], - ), - ), - ] - ) - await hass.async_block_till_done() - assert hass.states.is_state("sensor.washer_poor_i_dos_1_fill_level", "present") - @pytest.mark.parametrize( ("appliance", "keys_to_check"), @@ -231,6 +207,7 @@ async def test_connected_devices( ) +@pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.parametrize("appliance", [TEST_HC_APP], indirect=True) async def test_sensor_entity_availability( hass: HomeAssistant, @@ -247,28 +224,6 @@ async def test_sensor_entity_availability( assert await integration_setup(client) assert config_entry.state is ConfigEntryState.LOADED - await client.add_events( - [ - EventMessage( - appliance.ha_id, - EventType.EVENT, - ArrayOfEvents( - [ - Event( - key=EventKey.DISHCARE_DISHWASHER_EVENT_SALT_NEARLY_EMPTY, - raw_key=EventKey.DISHCARE_DISHWASHER_EVENT_SALT_NEARLY_EMPTY.value, - timestamp=0, - level="", - handling="", - value=BSH_EVENT_PRESENT_STATE_OFF, - ) - ], - ), - ), - ] - ) - await hass.async_block_till_done() - for entity_id in entity_ids: state = hass.states.get(entity_id) assert state @@ -545,33 +500,105 @@ async def test_remaining_prog_time_edge_cases( assert hass.states.is_state(entity_id, expected_state) +@pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.parametrize( ( "entity_id", "event_key", - "value_expected_state", + "event_type", + "event_value_update", + "expected", "appliance", ), [ ( "sensor.dishwasher_door", EventKey.BSH_COMMON_STATUS_DOOR_STATE, - [ - ( - BSH_DOOR_STATE_LOCKED, - "locked", - ), - ( - BSH_DOOR_STATE_CLOSED, - "closed", - ), - ( - BSH_DOOR_STATE_OPEN, - "open", - ), - ], + EventType.STATUS, + BSH_DOOR_STATE_LOCKED, + "locked", "Dishwasher", ), + ( + "sensor.dishwasher_door", + EventKey.BSH_COMMON_STATUS_DOOR_STATE, + EventType.STATUS, + BSH_DOOR_STATE_CLOSED, + "closed", + "Dishwasher", + ), + ( + "sensor.dishwasher_door", + EventKey.BSH_COMMON_STATUS_DOOR_STATE, + EventType.STATUS, + BSH_DOOR_STATE_OPEN, + "open", + "Dishwasher", + ), + ( + "sensor.fridgefreezer_freezer_door_alarm", + "EVENT_NOT_IN_STATUS_YET_SO_SET_TO_OFF", + EventType.EVENT, + "", + "off", + "FridgeFreezer", + ), + ( + "sensor.fridgefreezer_freezer_door_alarm", + EventKey.REFRIGERATION_FRIDGE_FREEZER_EVENT_DOOR_ALARM_FREEZER, + EventType.EVENT, + BSH_EVENT_PRESENT_STATE_OFF, + "off", + "FridgeFreezer", + ), + ( + "sensor.fridgefreezer_freezer_door_alarm", + EventKey.REFRIGERATION_FRIDGE_FREEZER_EVENT_DOOR_ALARM_FREEZER, + EventType.EVENT, + BSH_EVENT_PRESENT_STATE_PRESENT, + "present", + "FridgeFreezer", + ), + ( + "sensor.fridgefreezer_freezer_door_alarm", + EventKey.REFRIGERATION_FRIDGE_FREEZER_EVENT_DOOR_ALARM_FREEZER, + EventType.EVENT, + BSH_EVENT_PRESENT_STATE_CONFIRMED, + "confirmed", + "FridgeFreezer", + ), + ( + "sensor.coffeemaker_bean_container_empty", + EventType.EVENT, + "EVENT_NOT_IN_STATUS_YET_SO_SET_TO_OFF", + "", + "off", + "CoffeeMaker", + ), + ( + "sensor.coffeemaker_bean_container_empty", + EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_BEAN_CONTAINER_EMPTY, + EventType.EVENT, + BSH_EVENT_PRESENT_STATE_OFF, + "off", + "CoffeeMaker", + ), + ( + "sensor.coffeemaker_bean_container_empty", + EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_BEAN_CONTAINER_EMPTY, + EventType.EVENT, + BSH_EVENT_PRESENT_STATE_PRESENT, + "present", + "CoffeeMaker", + ), + ( + "sensor.coffeemaker_bean_container_empty", + EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_BEAN_CONTAINER_EMPTY, + EventType.EVENT, + BSH_EVENT_PRESENT_STATE_CONFIRMED, + "confirmed", + "CoffeeMaker", + ), ], indirect=["appliance"], ) @@ -582,111 +609,37 @@ async def test_sensors_states( integration_setup: Callable[[MagicMock], Awaitable[bool]], entity_id: str, event_key: EventKey, - value_expected_state: list[tuple[str, str]], + event_type: EventType, + event_value_update: str, appliance: HomeAppliance, + expected: str, ) -> None: - """Tests for appliance sensors.""" + """Tests for appliance alarm sensors.""" assert await integration_setup(client) assert config_entry.state is ConfigEntryState.LOADED - for value, expected_state in value_expected_state: - await client.add_events( - [ - EventMessage( - appliance.ha_id, - EventType.STATUS, - ArrayOfEvents( - [ - Event( - key=event_key, - raw_key=str(event_key), - timestamp=0, - level="", - handling="", - value=value, - ) - ], - ), + await client.add_events( + [ + EventMessage( + appliance.ha_id, + event_type, + ArrayOfEvents( + [ + Event( + key=event_key, + raw_key=str(event_key), + timestamp=0, + level="", + handling="", + value=event_value_update, + ) + ], ), - ] - ) - await hass.async_block_till_done() - assert hass.states.is_state(entity_id, expected_state) - - -@pytest.mark.parametrize( - ( - "entity_id", - "event_key", - "appliance", - ), - [ - ( - "sensor.fridgefreezer_freezer_door_alarm", - EventKey.REFRIGERATION_FRIDGE_FREEZER_EVENT_DOOR_ALARM_FREEZER, - "FridgeFreezer", - ), - ( - "sensor.coffeemaker_bean_container_empty", - EventKey.CONSUMER_PRODUCTS_COFFEE_MAKER_EVENT_BEAN_CONTAINER_EMPTY, - "CoffeeMaker", - ), - ], - indirect=["appliance"], -) -async def test_event_sensors_states( - hass: HomeAssistant, - caplog: pytest.LogCaptureFixture, - entity_registry: er.EntityRegistry, - client: MagicMock, - config_entry: MockConfigEntry, - integration_setup: Callable[[MagicMock], Awaitable[bool]], - entity_id: str, - event_key: EventKey, - appliance: HomeAppliance, -) -> None: - """Tests for appliance event sensors.""" - caplog.set_level(logging.ERROR) - assert await integration_setup(client) - assert config_entry.state is ConfigEntryState.LOADED - - assert not hass.states.get(entity_id) - - for value, expected_state in ( - (BSH_EVENT_PRESENT_STATE_OFF, "off"), - (BSH_EVENT_PRESENT_STATE_PRESENT, "present"), - (BSH_EVENT_PRESENT_STATE_CONFIRMED, "confirmed"), - ): - await client.add_events( - [ - EventMessage( - appliance.ha_id, - EventType.EVENT, - ArrayOfEvents( - [ - Event( - key=event_key, - raw_key=str(event_key), - timestamp=0, - level="", - handling="", - value=value, - ) - ], - ), - ), - ] - ) - await hass.async_block_till_done() - assert hass.states.is_state(entity_id, expected_state) - - # Verify that the integration doesn't attempt to add the event sensors more than once - # If that happens, the EntityPlatform logs an error with the entity's unique ID. - assert "exists" not in caplog.text - assert entity_id not in caplog.text - entity_entry = entity_registry.async_get(entity_id) - assert entity_entry - assert entity_entry.unique_id not in caplog.text + ), + ] + ) + await hass.async_block_till_done() + assert hass.states.is_state(entity_id, expected) @pytest.mark.parametrize(