From e0d404456b48258c57cd679c8476d10daec5a0c0 Mon Sep 17 00:00:00 2001 From: MizterB <5458030+MizterB@users.noreply.github.com> Date: Thu, 23 Oct 2025 14:03:32 -0400 Subject: [PATCH] Add cavity-aware oven sensors for Whirlpool (#145145) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Abílio Costa --- .../components/whirlpool/config_flow.py | 1 + homeassistant/components/whirlpool/entity.py | 29 + homeassistant/components/whirlpool/icons.json | 9 + homeassistant/components/whirlpool/sensor.py | 119 ++- .../components/whirlpool/strings.json | 81 ++ tests/components/whirlpool/conftest.py | 52 +- .../whirlpool/snapshots/test_diagnostics.ambr | 10 + .../whirlpool/snapshots/test_sensor.ambr | 726 ++++++++++++++++++ .../components/whirlpool/test_config_flow.py | 1 + tests/components/whirlpool/test_init.py | 2 + tests/components/whirlpool/test_sensor.py | 55 ++ 11 files changed, 1082 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/whirlpool/config_flow.py b/homeassistant/components/whirlpool/config_flow.py index 8c216109731..a1e6481da0b 100644 --- a/homeassistant/components/whirlpool/config_flow.py +++ b/homeassistant/components/whirlpool/config_flow.py @@ -74,6 +74,7 @@ async def authenticate( not appliances_manager.aircons and not appliances_manager.washers and not appliances_manager.dryers + and not appliances_manager.ovens ): return "no_appliances" diff --git a/homeassistant/components/whirlpool/entity.py b/homeassistant/components/whirlpool/entity.py index 95a065db2ca..7c3007cba20 100644 --- a/homeassistant/components/whirlpool/entity.py +++ b/homeassistant/components/whirlpool/entity.py @@ -3,6 +3,7 @@ import logging from whirlpool.appliance import Appliance +from whirlpool.oven import Cavity as OvenCavity, Oven from homeassistant.core import callback from homeassistant.exceptions import HomeAssistantError @@ -64,3 +65,31 @@ class WhirlpoolEntity(Entity): translation_domain=DOMAIN, translation_key="request_failed", ) + + +class WhirlpoolOvenEntity(WhirlpoolEntity): + """Base class for Whirlpool oven entities.""" + + _appliance: Oven + + def __init__( + self, + appliance: Oven, + cavity: OvenCavity, + translation_key_base: str | None, + unique_id_suffix: str = "", + ) -> None: + """Initialize the entity.""" + self.cavity = cavity + cavity_suffix = "" + if appliance.get_oven_cavity_exists( + OvenCavity.Upper + ) and appliance.get_oven_cavity_exists(OvenCavity.Lower): + if cavity == OvenCavity.Upper: + cavity_suffix = "_upper" + elif cavity == OvenCavity.Lower: + cavity_suffix = "_lower" + super().__init__( + appliance, unique_id_suffix=f"{unique_id_suffix}{cavity_suffix}" + ) + self._attr_translation_key = f"{translation_key_base}{cavity_suffix}" diff --git a/homeassistant/components/whirlpool/icons.json b/homeassistant/components/whirlpool/icons.json index 574b491090e..0a82ac349df 100644 --- a/homeassistant/components/whirlpool/icons.json +++ b/homeassistant/components/whirlpool/icons.json @@ -6,6 +6,15 @@ }, "dryer_state": { "default": "mdi:tumble-dryer" + }, + "oven_state": { + "default": "mdi:stove" + }, + "oven_state_upper": { + "default": "mdi:stove" + }, + "oven_state_lower": { + "default": "mdi:stove" } } } diff --git a/homeassistant/components/whirlpool/sensor.py b/homeassistant/components/whirlpool/sensor.py index 545ae67eaa1..91705428945 100644 --- a/homeassistant/components/whirlpool/sensor.py +++ b/homeassistant/components/whirlpool/sensor.py @@ -8,6 +8,12 @@ from typing import override from whirlpool.appliance import Appliance from whirlpool.dryer import Dryer, MachineState as DryerMachineState +from whirlpool.oven import ( + Cavity as OvenCavity, + CavityState as OvenCavityState, + CookMode, + Oven, +) from whirlpool.washer import MachineState as WasherMachineState, Washer from homeassistant.components.sensor import ( @@ -15,14 +21,16 @@ from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, SensorEntityDescription, + SensorStateClass, ) +from homeassistant.const import UnitOfTemperature from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.typing import StateType from homeassistant.util.dt import utcnow from . import WhirlpoolConfigEntry -from .entity import WhirlpoolEntity +from .entity import WhirlpoolEntity, WhirlpoolOvenEntity PARALLEL_UPDATES = 1 SCAN_INTERVAL = timedelta(minutes=5) @@ -88,6 +96,23 @@ STATE_CYCLE_SOAKING = "cycle_soaking" STATE_CYCLE_SPINNING = "cycle_spinning" STATE_CYCLE_WASHING = "cycle_washing" +OVEN_CAVITY_STATE = { + OvenCavityState.Standby: "standby", + OvenCavityState.Preheating: "preheating", + OvenCavityState.Cooking: "cooking", +} + +OVEN_COOK_MODE = { + CookMode.Standby: "standby", + CookMode.Bake: "bake", + CookMode.ConvectBake: "convection_bake", + CookMode.Broil: "broil", + CookMode.ConvectBroil: "convection_broil", + CookMode.ConvectRoast: "convection_roast", + CookMode.KeepWarm: "keep_warm", + CookMode.AirFry: "air_fry", +} + def washer_state(washer: Washer) -> str | None: """Determine correct states for a washer.""" @@ -183,6 +208,59 @@ WASHER_DRYER_TIME_SENSORS: tuple[SensorEntityDescription] = ( ) +@dataclass(frozen=True, kw_only=True) +class WhirlpoolOvenCavitySensorEntityDescription(SensorEntityDescription): + """Describes a Whirlpool oven cavity sensor entity.""" + + value_fn: Callable[[Oven, OvenCavity], str | int | float | None] + + +OVEN_CAVITY_SENSORS: tuple[WhirlpoolOvenCavitySensorEntityDescription, ...] = ( + WhirlpoolOvenCavitySensorEntityDescription( + key="oven_state", + translation_key="oven_state", + device_class=SensorDeviceClass.ENUM, + options=list(OVEN_CAVITY_STATE.values()), + value_fn=lambda oven, cavity: ( + OVEN_CAVITY_STATE.get(state) + if (state := oven.get_cavity_state(cavity)) is not None + else None + ), + ), + WhirlpoolOvenCavitySensorEntityDescription( + key="oven_cook_mode", + translation_key="oven_cook_mode", + device_class=SensorDeviceClass.ENUM, + options=list(OVEN_COOK_MODE.values()), + value_fn=lambda oven, cavity: ( + OVEN_COOK_MODE.get(cook_mode) + if (cook_mode := oven.get_cook_mode(cavity)) is not None + else None + ), + ), + WhirlpoolOvenCavitySensorEntityDescription( + key="oven_current_temperature", + translation_key="oven_current_temperature", + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + value_fn=lambda oven, cavity: ( + temp if (temp := oven.get_temp(cavity)) != 0 else None + ), + ), + WhirlpoolOvenCavitySensorEntityDescription( + key="oven_target_temperature", + translation_key="oven_target_temperature", + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + value_fn=lambda oven, cavity: ( + temp if (temp := oven.get_target_temp(cavity)) != 0 else None + ), + ), +) + + async def async_setup_entry( hass: HomeAssistant, config_entry: WhirlpoolConfigEntry, @@ -215,12 +293,28 @@ async def async_setup_entry( for description in WASHER_DRYER_TIME_SENSORS ] + oven_upper_cavity_sensors = [ + WhirlpoolOvenCavitySensor(oven, OvenCavity.Upper, description) + for oven in appliances_manager.ovens + if oven.get_oven_cavity_exists(OvenCavity.Upper) + for description in OVEN_CAVITY_SENSORS + ] + + oven_lower_cavity_sensors = [ + WhirlpoolOvenCavitySensor(oven, OvenCavity.Lower, description) + for oven in appliances_manager.ovens + if oven.get_oven_cavity_exists(OvenCavity.Lower) + for description in OVEN_CAVITY_SENSORS + ] + async_add_entities( [ *washer_sensors, *washer_time_sensors, *dryer_sensors, *dryer_time_sensors, + *oven_upper_cavity_sensors, + *oven_lower_cavity_sensors, ] ) @@ -333,3 +427,26 @@ class DryerTimeSensor(WasherDryerTimeSensorBase): def _is_machine_state_running(self) -> bool: """Return true if the machine is in a running state.""" return self._appliance.get_machine_state() is DryerMachineState.RunningMainCycle + + +class WhirlpoolOvenCavitySensor(WhirlpoolOvenEntity, SensorEntity): + """A class for Whirlpool oven cavity sensors.""" + + def __init__( + self, + oven: Oven, + cavity: OvenCavity, + description: WhirlpoolOvenCavitySensorEntityDescription, + ) -> None: + """Initialize the oven cavity sensor.""" + super().__init__( + oven, cavity, description.translation_key, f"-{description.key}" + ) + self.entity_description: WhirlpoolOvenCavitySensorEntityDescription = ( + description + ) + + @property + def native_value(self) -> StateType: + """Return native value of sensor.""" + return self.entity_description.value_fn(self._appliance, self.cavity) diff --git a/homeassistant/components/whirlpool/strings.json b/homeassistant/components/whirlpool/strings.json index 3ab65d2e3aa..aa0fb6ffe11 100644 --- a/homeassistant/components/whirlpool/strings.json +++ b/homeassistant/components/whirlpool/strings.json @@ -120,6 +120,87 @@ }, "end_time": { "name": "End time" + }, + "oven_state": { + "name": "State", + "state": { + "standby": "[%key:common::state::standby%]", + "preheating": "Preheating", + "cooking": "Cooking" + } + }, + "oven_state_upper": { + "name": "Upper oven state", + "state": { + "standby": "[%key:common::state::standby%]", + "preheating": "[%key:component::whirlpool::entity::sensor::oven_state::state::preheating%]", + "cooking": "[%key:component::whirlpool::entity::sensor::oven_state::state::cooking%]" + } + }, + "oven_state_lower": { + "name": "Lower oven state", + "state": { + "standby": "[%key:common::state::standby%]", + "preheating": "[%key:component::whirlpool::entity::sensor::oven_state::state::preheating%]", + "cooking": "[%key:component::whirlpool::entity::sensor::oven_state::state::cooking%]" + } + }, + "oven_cook_mode": { + "name": "Cook mode", + "state": { + "standby": "[%key:common::state::standby%]", + "bake": "Bake", + "convection_bake": "Convection bake", + "broil": "Broil", + "convection_broil": "Convection broil", + "convection_roast": "Convection roast", + "keep_warm": "Keep warm", + "air_fry": "Air fry" + } + }, + "oven_cook_mode_upper": { + "name": "Upper oven cook mode", + "state": { + "standby": "[%key:common::state::standby%]", + "bake": "[%key:component::whirlpool::entity::sensor::oven_cook_mode::state::bake%]", + "convection_bake": "[%key:component::whirlpool::entity::sensor::oven_cook_mode::state::convection_bake%]", + "broil": "[%key:component::whirlpool::entity::sensor::oven_cook_mode::state::broil%]", + "convection_broil": "[%key:component::whirlpool::entity::sensor::oven_cook_mode::state::convection_broil%]", + "convection_roast": "[%key:component::whirlpool::entity::sensor::oven_cook_mode::state::convection_roast%]", + "keep_warm": "[%key:component::whirlpool::entity::sensor::oven_cook_mode::state::keep_warm%]", + "air_fry": "[%key:component::whirlpool::entity::sensor::oven_cook_mode::state::air_fry%]" + } + }, + "oven_cook_mode_lower": { + "name": "Lower oven cook mode", + "state": { + "standby": "[%key:common::state::standby%]", + "bake": "[%key:component::whirlpool::entity::sensor::oven_cook_mode::state::bake%]", + "convection_bake": "[%key:component::whirlpool::entity::sensor::oven_cook_mode::state::convection_bake%]", + "broil": "[%key:component::whirlpool::entity::sensor::oven_cook_mode::state::broil%]", + "convection_broil": "[%key:component::whirlpool::entity::sensor::oven_cook_mode::state::convection_broil%]", + "convection_roast": "[%key:component::whirlpool::entity::sensor::oven_cook_mode::state::convection_roast%]", + "keep_warm": "[%key:component::whirlpool::entity::sensor::oven_cook_mode::state::keep_warm%]", + "air_fry": "[%key:component::whirlpool::entity::sensor::oven_cook_mode::state::air_fry%]" + } + }, + "oven_current_temperature": { + "name": "Current temperature" + }, + "oven_current_temperature_upper": { + "name": "Upper oven current temperature" + }, + "oven_current_temperature_lower": { + "name": "Lower oven current temperature" + }, + "oven_target_temperature": { + "name": "Target temperature" + }, + "oven_target_temperature_upper": { + "name": "Upper oven target temperature" + }, + "oven_target_temperature_lower": { + "name": "Lower oven target temperature" } } }, diff --git a/tests/components/whirlpool/conftest.py b/tests/components/whirlpool/conftest.py index fb82750924a..75ab793eeec 100644 --- a/tests/components/whirlpool/conftest.py +++ b/tests/components/whirlpool/conftest.py @@ -4,7 +4,7 @@ from unittest import mock from unittest.mock import Mock import pytest -from whirlpool import aircon, appliancesmanager, auth, dryer, washer +from whirlpool import aircon, appliancesmanager, auth, dryer, oven, washer from whirlpool.backendselector import Brand, Region from .const import MOCK_SAID1, MOCK_SAID2 @@ -49,7 +49,12 @@ def fixture_mock_auth_api(): @pytest.fixture(name="mock_appliances_manager_api", autouse=True) def fixture_mock_appliances_manager_api( - mock_aircon1_api, mock_aircon2_api, mock_washer_api, mock_dryer_api + mock_aircon1_api, + mock_aircon2_api, + mock_washer_api, + mock_dryer_api, + mock_oven_single_cavity_api, + mock_oven_dual_cavity_api, ): """Set up AppliancesManager fixture.""" with ( @@ -68,6 +73,10 @@ def fixture_mock_appliances_manager_api( ] mock_appliances_manager.return_value.washers = [mock_washer_api] mock_appliances_manager.return_value.dryers = [mock_dryer_api] + mock_appliances_manager.return_value.ovens = [ + mock_oven_single_cavity_api, + mock_oven_dual_cavity_api, + ] yield mock_appliances_manager @@ -155,3 +164,42 @@ def mock_dryer_api(): mock_dryer.get_time_remaining.return_value = 3540 mock_dryer.get_cycle_status_sensing.return_value = False return mock_dryer + + +@pytest.fixture +def mock_oven_single_cavity_api(): + """Get a mock of a single cavity oven.""" + mock_oven = Mock(spec=oven.Oven, said="said_oven_single") + mock_oven.name = "Single Cavity Oven" + mock_oven.appliance_info = Mock( + data_model="oven", category="oven", model_number="12345" + ) + mock_oven.get_cavity_state.return_value = oven.CavityState.Standby + mock_oven.get_cook_mode.return_value = oven.CookMode.Bake + mock_oven.get_online.return_value = True + mock_oven.get_oven_cavity_exists.side_effect = ( + lambda cavity: cavity == oven.Cavity.Upper + ) + mock_oven.get_temp.return_value = 180 + mock_oven.get_target_temp.return_value = 200 + return mock_oven + + +@pytest.fixture +def mock_oven_dual_cavity_api(): + """Get a mock of a dual cavity oven.""" + mock_oven = Mock(spec=oven.Oven, said="said_oven_dual") + mock_oven.name = "Dual Cavity Oven" + mock_oven.appliance_info = Mock( + data_model="oven", category="oven", model_number="12345" + ) + mock_oven.get_cavity_state.return_value = oven.CavityState.Standby + mock_oven.get_cook_mode.return_value = oven.CookMode.Bake + mock_oven.get_online.return_value = True + mock_oven.get_oven_cavity_exists.side_effect = lambda cavity: cavity in ( + oven.Cavity.Upper, + oven.Cavity.Lower, + ) + mock_oven.get_temp.return_value = 180 + mock_oven.get_target_temp.return_value = 200 + return mock_oven diff --git a/tests/components/whirlpool/snapshots/test_diagnostics.ambr b/tests/components/whirlpool/snapshots/test_diagnostics.ambr index 11aecc93d0d..783e5e980ca 100644 --- a/tests/components/whirlpool/snapshots/test_diagnostics.ambr +++ b/tests/components/whirlpool/snapshots/test_diagnostics.ambr @@ -22,6 +22,16 @@ }), }), 'ovens': dict({ + 'Dual Cavity Oven': dict({ + 'category': 'oven', + 'data_model': 'oven', + 'model_number': '12345', + }), + 'Single Cavity Oven': dict({ + 'category': 'oven', + 'data_model': 'oven', + 'model_number': '12345', + }), }), 'washers': dict({ 'Washer': dict({ diff --git a/tests/components/whirlpool/snapshots/test_sensor.ambr b/tests/components/whirlpool/snapshots/test_sensor.ambr index 64b513abe4e..976adc36025 100644 --- a/tests/components/whirlpool/snapshots/test_sensor.ambr +++ b/tests/components/whirlpool/snapshots/test_sensor.ambr @@ -145,6 +145,732 @@ 'state': 'running_maincycle', }) # --- +# name: test_all_entities[sensor.dual_cavity_oven_lower_oven_cook_mode-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'standby', + 'bake', + 'convection_bake', + 'broil', + 'convection_broil', + 'convection_roast', + 'keep_warm', + 'air_fry', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.dual_cavity_oven_lower_oven_cook_mode', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Lower oven cook mode', + 'platform': 'whirlpool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'oven_cook_mode_lower', + 'unique_id': 'said_oven_dual-oven_cook_mode_lower', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.dual_cavity_oven_lower_oven_cook_mode-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'Dual cavity oven Lower oven cook mode', + 'options': list([ + 'standby', + 'bake', + 'convection_bake', + 'broil', + 'convection_broil', + 'convection_roast', + 'keep_warm', + 'air_fry', + ]), + }), + 'context': , + 'entity_id': 'sensor.dual_cavity_oven_lower_oven_cook_mode', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'bake', + }) +# --- +# name: test_all_entities[sensor.dual_cavity_oven_lower_oven_current_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.dual_cavity_oven_lower_oven_current_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Lower oven current temperature', + 'platform': 'whirlpool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'oven_current_temperature_lower', + 'unique_id': 'said_oven_dual-oven_current_temperature_lower', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[sensor.dual_cavity_oven_lower_oven_current_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'friendly_name': 'Dual cavity oven Lower oven current temperature', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.dual_cavity_oven_lower_oven_current_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '180', + }) +# --- +# name: test_all_entities[sensor.dual_cavity_oven_lower_oven_state-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'standby', + 'preheating', + 'cooking', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.dual_cavity_oven_lower_oven_state', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Lower oven state', + 'platform': 'whirlpool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'oven_state_lower', + 'unique_id': 'said_oven_dual-oven_state_lower', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.dual_cavity_oven_lower_oven_state-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'Dual cavity oven Lower oven state', + 'options': list([ + 'standby', + 'preheating', + 'cooking', + ]), + }), + 'context': , + 'entity_id': 'sensor.dual_cavity_oven_lower_oven_state', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'standby', + }) +# --- +# name: test_all_entities[sensor.dual_cavity_oven_lower_oven_target_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.dual_cavity_oven_lower_oven_target_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Lower oven target temperature', + 'platform': 'whirlpool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'oven_target_temperature_lower', + 'unique_id': 'said_oven_dual-oven_target_temperature_lower', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[sensor.dual_cavity_oven_lower_oven_target_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'friendly_name': 'Dual cavity oven Lower oven target temperature', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.dual_cavity_oven_lower_oven_target_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '200', + }) +# --- +# name: test_all_entities[sensor.dual_cavity_oven_upper_oven_cook_mode-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'standby', + 'bake', + 'convection_bake', + 'broil', + 'convection_broil', + 'convection_roast', + 'keep_warm', + 'air_fry', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.dual_cavity_oven_upper_oven_cook_mode', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Upper oven cook mode', + 'platform': 'whirlpool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'oven_cook_mode_upper', + 'unique_id': 'said_oven_dual-oven_cook_mode_upper', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.dual_cavity_oven_upper_oven_cook_mode-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'Dual cavity oven Upper oven cook mode', + 'options': list([ + 'standby', + 'bake', + 'convection_bake', + 'broil', + 'convection_broil', + 'convection_roast', + 'keep_warm', + 'air_fry', + ]), + }), + 'context': , + 'entity_id': 'sensor.dual_cavity_oven_upper_oven_cook_mode', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'bake', + }) +# --- +# name: test_all_entities[sensor.dual_cavity_oven_upper_oven_current_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.dual_cavity_oven_upper_oven_current_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Upper oven current temperature', + 'platform': 'whirlpool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'oven_current_temperature_upper', + 'unique_id': 'said_oven_dual-oven_current_temperature_upper', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[sensor.dual_cavity_oven_upper_oven_current_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'friendly_name': 'Dual cavity oven Upper oven current temperature', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.dual_cavity_oven_upper_oven_current_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '180', + }) +# --- +# name: test_all_entities[sensor.dual_cavity_oven_upper_oven_state-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'standby', + 'preheating', + 'cooking', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.dual_cavity_oven_upper_oven_state', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Upper oven state', + 'platform': 'whirlpool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'oven_state_upper', + 'unique_id': 'said_oven_dual-oven_state_upper', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.dual_cavity_oven_upper_oven_state-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'Dual cavity oven Upper oven state', + 'options': list([ + 'standby', + 'preheating', + 'cooking', + ]), + }), + 'context': , + 'entity_id': 'sensor.dual_cavity_oven_upper_oven_state', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'standby', + }) +# --- +# name: test_all_entities[sensor.dual_cavity_oven_upper_oven_target_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.dual_cavity_oven_upper_oven_target_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Upper oven target temperature', + 'platform': 'whirlpool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'oven_target_temperature_upper', + 'unique_id': 'said_oven_dual-oven_target_temperature_upper', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[sensor.dual_cavity_oven_upper_oven_target_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'friendly_name': 'Dual cavity oven Upper oven target temperature', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.dual_cavity_oven_upper_oven_target_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '200', + }) +# --- +# name: test_all_entities[sensor.single_cavity_oven_cook_mode-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'standby', + 'bake', + 'convection_bake', + 'broil', + 'convection_broil', + 'convection_roast', + 'keep_warm', + 'air_fry', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.single_cavity_oven_cook_mode', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Cook mode', + 'platform': 'whirlpool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'oven_cook_mode', + 'unique_id': 'said_oven_single-oven_cook_mode', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.single_cavity_oven_cook_mode-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'Single cavity oven Cook mode', + 'options': list([ + 'standby', + 'bake', + 'convection_bake', + 'broil', + 'convection_broil', + 'convection_roast', + 'keep_warm', + 'air_fry', + ]), + }), + 'context': , + 'entity_id': 'sensor.single_cavity_oven_cook_mode', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'bake', + }) +# --- +# name: test_all_entities[sensor.single_cavity_oven_current_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.single_cavity_oven_current_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Current temperature', + 'platform': 'whirlpool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'oven_current_temperature', + 'unique_id': 'said_oven_single-oven_current_temperature', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[sensor.single_cavity_oven_current_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'friendly_name': 'Single cavity oven Current temperature', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.single_cavity_oven_current_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '180', + }) +# --- +# name: test_all_entities[sensor.single_cavity_oven_state-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'standby', + 'preheating', + 'cooking', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.single_cavity_oven_state', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'State', + 'platform': 'whirlpool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'oven_state', + 'unique_id': 'said_oven_single-oven_state', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.single_cavity_oven_state-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'Single cavity oven State', + 'options': list([ + 'standby', + 'preheating', + 'cooking', + ]), + }), + 'context': , + 'entity_id': 'sensor.single_cavity_oven_state', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'standby', + }) +# --- +# name: test_all_entities[sensor.single_cavity_oven_target_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.single_cavity_oven_target_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Target temperature', + 'platform': 'whirlpool', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'oven_target_temperature', + 'unique_id': 'said_oven_single-oven_target_temperature', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[sensor.single_cavity_oven_target_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'friendly_name': 'Single cavity oven Target temperature', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.single_cavity_oven_target_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '200', + }) +# --- # name: test_all_entities[sensor.washer_detergent_level-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/whirlpool/test_config_flow.py b/tests/components/whirlpool/test_config_flow.py index 92546acd773..7fae0348d3f 100644 --- a/tests/components/whirlpool/test_config_flow.py +++ b/tests/components/whirlpool/test_config_flow.py @@ -210,6 +210,7 @@ async def test_no_appliances_flow( mock_appliances_manager_api.return_value.aircons = [] mock_appliances_manager_api.return_value.washers = [] mock_appliances_manager_api.return_value.dryers = [] + mock_appliances_manager_api.return_value.ovens = [] result = await hass.config_entries.flow.async_configure( result["flow_id"], CONFIG_INPUT | {CONF_REGION: region[0], CONF_BRAND: brand[0]} ) diff --git a/tests/components/whirlpool/test_init.py b/tests/components/whirlpool/test_init.py index 463ed305d2e..38367f52455 100644 --- a/tests/components/whirlpool/test_init.py +++ b/tests/components/whirlpool/test_init.py @@ -83,6 +83,8 @@ async def test_setup_no_appliances( mock_appliances_manager_api.return_value.aircons = [] mock_appliances_manager_api.return_value.washers = [] mock_appliances_manager_api.return_value.dryers = [] + mock_appliances_manager_api.return_value.ovens = [] + await init_integration(hass) assert len(hass.states.async_all()) == 0 diff --git a/tests/components/whirlpool/test_sensor.py b/tests/components/whirlpool/test_sensor.py index 85f0940fc4e..578232f4641 100644 --- a/tests/components/whirlpool/test_sensor.py +++ b/tests/components/whirlpool/test_sensor.py @@ -6,6 +6,7 @@ from freezegun.api import FrozenDateTimeFactory import pytest from syrupy.assertion import SnapshotAssertion from whirlpool.dryer import MachineState as DryerMachineState +from whirlpool.oven import CavityState as OvenCavityState, CookMode from whirlpool.washer import MachineState as WasherMachineState from homeassistant.components.whirlpool.sensor import SCAN_INTERVAL @@ -312,6 +313,60 @@ async def test_washer_running_states( (5, "active"), ], ), + ( + "sensor.dual_cavity_oven_upper_oven_state", + "mock_oven_dual_cavity_api", + "get_cavity_state", + [ + (OvenCavityState.Standby, "standby"), + (OvenCavityState.Preheating, "preheating"), + (OvenCavityState.Cooking, "cooking"), + (None, STATE_UNKNOWN), + ], + ), + ( + "sensor.dual_cavity_oven_upper_oven_cook_mode", + "mock_oven_dual_cavity_api", + "get_cook_mode", + [ + (CookMode.Standby, "standby"), + (CookMode.Bake, "bake"), + (CookMode.ConvectBake, "convection_bake"), + (CookMode.Broil, "broil"), + (CookMode.ConvectBroil, "convection_broil"), + (CookMode.ConvectRoast, "convection_roast"), + (CookMode.KeepWarm, "keep_warm"), + (CookMode.AirFry, "air_fry"), + (None, STATE_UNKNOWN), + ], + ), + ( + "sensor.single_cavity_oven_state", + "mock_oven_single_cavity_api", + "get_cavity_state", + [ + (OvenCavityState.Standby, "standby"), + (OvenCavityState.Preheating, "preheating"), + (OvenCavityState.Cooking, "cooking"), + (None, STATE_UNKNOWN), + ], + ), + ( + "sensor.single_cavity_oven_cook_mode", + "mock_oven_single_cavity_api", + "get_cook_mode", + [ + (CookMode.Standby, "standby"), + (CookMode.Bake, "bake"), + (CookMode.ConvectBake, "convection_bake"), + (CookMode.Broil, "broil"), + (CookMode.ConvectBroil, "convection_broil"), + (CookMode.ConvectRoast, "convection_roast"), + (CookMode.KeepWarm, "keep_warm"), + (CookMode.AirFry, "air_fry"), + (None, STATE_UNKNOWN), + ], + ), ], ) @pytest.mark.usefixtures("entity_registry_enabled_by_default")