diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index ca85979f19a..d248c271d83 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -240,6 +240,7 @@ CACHED_PROPERTIES_WITH_ATTR_ = { "preset_mode", "preset_modes", "is_aux_heat", + "is_on", "fan_mode", "fan_modes", "swing_mode", @@ -280,6 +281,7 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): _attr_hvac_mode: HVACMode | None _attr_hvac_modes: list[HVACMode] _attr_is_aux_heat: bool | None + _attr_is_on: bool | None _attr_max_humidity: float = DEFAULT_MAX_HUMIDITY _attr_max_temp: float _attr_min_humidity: float = DEFAULT_MIN_HUMIDITY @@ -357,6 +359,26 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): return HVACMode(hvac_mode).value # type: ignore[unreachable] return hvac_mode.value + @property + def is_on(self) -> bool | None: + """Return True if the climate is turned on. + + The climate's on/off state can be be controlled independently + from the hvac_action and hvac_mode if the _attr_is_on attribute is set. + + If the _attr_is_on attrubiute is set, then return that value. + Otherwise, return True if hvac_action is not None and not HVACAction.OFF. + Return None if hvac_action is None, + otherwise return True if hvac_mode is not HVACMode.OFF. + """ + if hasattr(self, "_attr_is_on"): + return self._attr_is_on + if self.hvac_action is not None: + return self.hvac_action != HVACAction.OFF + if self.hvac_mode is None: + return None + return self.hvac_mode != HVACMode.OFF + @property def precision(self) -> float: """Return the precision of the system.""" diff --git a/tests/components/climate/test_init.py b/tests/components/climate/test_init.py index 45570c63008..218069e4f21 100644 --- a/tests/components/climate/test_init.py +++ b/tests/components/climate/test_init.py @@ -36,6 +36,7 @@ from homeassistant.components.climate.const import ( SWING_HORIZONTAL_OFF, SWING_HORIZONTAL_ON, ClimateEntityFeature, + HVACAction, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, UnitOfTemperature @@ -425,7 +426,7 @@ async def test_mode_validation( async def test_turn_on_off_toggle(hass: HomeAssistant) -> None: - """Test turn_on/turn_off/toggle methods.""" + """Test turn_on/turn_off/toggle methods and the is_on property.""" class MockClimateEntityTest(MockClimateEntity): """Mock Climate device.""" @@ -440,20 +441,54 @@ async def test_turn_on_off_toggle(hass: HomeAssistant) -> None: async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: """Set new target hvac mode.""" self._attr_hvac_mode = hvac_mode + self._attr_is_on = hvac_mode != HVACMode.OFF climate = MockClimateEntityTest() climate.hass = hass + assert climate.is_on is False + await climate.async_turn_on() assert climate.hvac_mode == HVACMode.HEAT + assert climate.is_on is True await climate.async_turn_off() assert climate.hvac_mode == HVACMode.OFF + assert climate.is_on is False await climate.async_toggle() assert climate.hvac_mode == HVACMode.HEAT + assert climate.is_on is True await climate.async_toggle() assert climate.hvac_mode == HVACMode.OFF + assert climate.is_on is False + + # Test is_on property in case _attr_is_on is not set + delattr(climate, "_attr_is_on") + + # When hvac_action is set, is_on should depend on it + climate._attr_hvac_mode = HVACMode.AUTO + climate._attr_hvac_action = HVACAction.HEATING + assert climate.hvac_mode == HVACMode.AUTO + assert climate.hvac_action == HVACAction.HEATING + assert climate.is_on is True + climate._attr_hvac_action = HVACAction.OFF + assert climate.hvac_action == HVACAction.OFF + assert climate.is_on is False + + # When hvac_action is not used and hvac_mode is set, is_on should depend on it + climate._attr_hvac_action = None + assert climate.hvac_action is None + assert climate.hvac_mode == HVACMode.AUTO + assert climate.is_on is True + climate._attr_hvac_mode = HVACMode.OFF + assert climate.hvac_mode == HVACMode.OFF + assert climate.is_on is False + + # If no mode or action is set and _attr_is_on is not set, is_on should be None + climate._attr_hvac_action = None + climate._attr_hvac_mode = None + assert climate.is_on is None async def test_sync_toggle(hass: HomeAssistant) -> None: