Add validation to climate hvac mode (#125178)
* Add validation to climate hvac mode * Make softer * Remove string --------- Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
2ef37f01b1
commit
c2d5696b5b
2 changed files with 58 additions and 13 deletions
|
@ -175,7 +175,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_SET_HVAC_MODE,
|
SERVICE_SET_HVAC_MODE,
|
||||||
{vol.Required(ATTR_HVAC_MODE): vol.Coerce(HVACMode)},
|
{vol.Required(ATTR_HVAC_MODE): vol.Coerce(HVACMode)},
|
||||||
"async_set_hvac_mode",
|
"async_handle_set_hvac_mode_service",
|
||||||
)
|
)
|
||||||
component.async_register_entity_service(
|
component.async_register_entity_service(
|
||||||
SERVICE_SET_PRESET_MODE,
|
SERVICE_SET_PRESET_MODE,
|
||||||
|
@ -694,20 +694,35 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||||
@callback
|
@callback
|
||||||
def _valid_mode_or_raise(
|
def _valid_mode_or_raise(
|
||||||
self,
|
self,
|
||||||
mode_type: Literal["preset", "swing", "fan"],
|
mode_type: Literal["preset", "swing", "fan", "hvac"],
|
||||||
mode: str,
|
mode: str | HVACMode,
|
||||||
modes: list[str] | None,
|
modes: list[str] | list[HVACMode] | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Raise ServiceValidationError on invalid modes."""
|
"""Raise ServiceValidationError on invalid modes."""
|
||||||
if modes and mode in modes:
|
if modes and mode in modes:
|
||||||
return
|
return
|
||||||
modes_str: str = ", ".join(modes) if modes else ""
|
modes_str: str = ", ".join(modes) if modes else ""
|
||||||
if mode_type == "preset":
|
translation_key = f"not_valid_{mode_type}_mode"
|
||||||
translation_key = "not_valid_preset_mode"
|
if mode_type == "hvac":
|
||||||
elif mode_type == "swing":
|
report_issue = async_suggest_report_issue(
|
||||||
translation_key = "not_valid_swing_mode"
|
self.hass,
|
||||||
elif mode_type == "fan":
|
integration_domain=self.platform.platform_name,
|
||||||
translation_key = "not_valid_fan_mode"
|
module=type(self).__module__,
|
||||||
|
)
|
||||||
|
_LOGGER.warning(
|
||||||
|
(
|
||||||
|
"%s::%s sets the hvac_mode %s which is not "
|
||||||
|
"valid for this entity with modes: %s. "
|
||||||
|
"This will stop working in 2025.3 and raise an error instead. "
|
||||||
|
"Please %s"
|
||||||
|
),
|
||||||
|
self.platform.platform_name,
|
||||||
|
self.__class__.__name__,
|
||||||
|
mode,
|
||||||
|
modes_str,
|
||||||
|
report_issue,
|
||||||
|
)
|
||||||
|
return
|
||||||
raise ServiceValidationError(
|
raise ServiceValidationError(
|
||||||
translation_domain=DOMAIN,
|
translation_domain=DOMAIN,
|
||||||
translation_key=translation_key,
|
translation_key=translation_key,
|
||||||
|
@ -749,6 +764,12 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||||
"""Set new target fan mode."""
|
"""Set new target fan mode."""
|
||||||
await self.hass.async_add_executor_job(self.set_fan_mode, fan_mode)
|
await self.hass.async_add_executor_job(self.set_fan_mode, fan_mode)
|
||||||
|
|
||||||
|
@final
|
||||||
|
async def async_handle_set_hvac_mode_service(self, hvac_mode: HVACMode) -> None:
|
||||||
|
"""Validate and set new preset mode."""
|
||||||
|
self._valid_mode_or_raise("hvac", hvac_mode, self.hvac_modes)
|
||||||
|
await self.async_set_hvac_mode(hvac_mode)
|
||||||
|
|
||||||
def set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
def set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||||
"""Set new target hvac mode."""
|
"""Set new target hvac mode."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
|
@ -27,6 +27,7 @@ from homeassistant.components.climate.const import (
|
||||||
ATTR_TARGET_TEMP_HIGH,
|
ATTR_TARGET_TEMP_HIGH,
|
||||||
ATTR_TARGET_TEMP_LOW,
|
ATTR_TARGET_TEMP_LOW,
|
||||||
SERVICE_SET_FAN_MODE,
|
SERVICE_SET_FAN_MODE,
|
||||||
|
SERVICE_SET_HVAC_MODE,
|
||||||
SERVICE_SET_PRESET_MODE,
|
SERVICE_SET_PRESET_MODE,
|
||||||
SERVICE_SET_SWING_MODE,
|
SERVICE_SET_SWING_MODE,
|
||||||
SERVICE_SET_TEMPERATURE,
|
SERVICE_SET_TEMPERATURE,
|
||||||
|
@ -138,6 +139,10 @@ class MockClimateEntity(MockEntity, ClimateEntity):
|
||||||
"""Set swing mode."""
|
"""Set swing mode."""
|
||||||
self._attr_swing_mode = swing_mode
|
self._attr_swing_mode = swing_mode
|
||||||
|
|
||||||
|
def set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||||
|
"""Set new target hvac mode."""
|
||||||
|
self._attr_hvac_mode = hvac_mode
|
||||||
|
|
||||||
|
|
||||||
class MockClimateEntityTestMethods(MockClimateEntity):
|
class MockClimateEntityTestMethods(MockClimateEntity):
|
||||||
"""Mock Climate device."""
|
"""Mock Climate device."""
|
||||||
|
@ -237,10 +242,12 @@ def test_deprecated_current_constants(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_preset_mode_validation(
|
async def test_mode_validation(
|
||||||
hass: HomeAssistant, register_test_integration: MockConfigEntry
|
hass: HomeAssistant,
|
||||||
|
register_test_integration: MockConfigEntry,
|
||||||
|
caplog: pytest.LogCaptureFixture,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test mode validation for fan, swing and preset."""
|
"""Test mode validation for hvac_mode, fan, swing and preset."""
|
||||||
climate_entity = MockClimateEntity(name="test", entity_id="climate.test")
|
climate_entity = MockClimateEntity(name="test", entity_id="climate.test")
|
||||||
|
|
||||||
setup_test_component_platform(
|
setup_test_component_platform(
|
||||||
|
@ -250,6 +257,7 @@ async def test_preset_mode_validation(
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get("climate.test")
|
state = hass.states.get("climate.test")
|
||||||
|
assert state.state == "heat"
|
||||||
assert state.attributes.get(ATTR_PRESET_MODE) == "home"
|
assert state.attributes.get(ATTR_PRESET_MODE) == "home"
|
||||||
assert state.attributes.get(ATTR_FAN_MODE) == "auto"
|
assert state.attributes.get(ATTR_FAN_MODE) == "auto"
|
||||||
assert state.attributes.get(ATTR_SWING_MODE) == "auto"
|
assert state.attributes.get(ATTR_SWING_MODE) == "auto"
|
||||||
|
@ -286,6 +294,22 @@ async def test_preset_mode_validation(
|
||||||
assert state.attributes.get(ATTR_FAN_MODE) == "off"
|
assert state.attributes.get(ATTR_FAN_MODE) == "off"
|
||||||
assert state.attributes.get(ATTR_SWING_MODE) == "off"
|
assert state.attributes.get(ATTR_SWING_MODE) == "off"
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_SET_HVAC_MODE,
|
||||||
|
{
|
||||||
|
"entity_id": "climate.test",
|
||||||
|
"hvac_mode": "auto",
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
"MockClimateEntity sets the hvac_mode auto which is not valid "
|
||||||
|
"for this entity with modes: off, heat. This will stop working "
|
||||||
|
"in 2025.3 and raise an error instead. Please" in caplog.text
|
||||||
|
)
|
||||||
|
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
ServiceValidationError,
|
ServiceValidationError,
|
||||||
match="Preset mode invalid is not valid. Valid preset modes are: home, away",
|
match="Preset mode invalid is not valid. Valid preset modes are: home, away",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue