Modify climate turn_on/off backwards compatibility check (#109195)
* Modify climate turn_on/off backwards compatibility check * Fix logger message * Comments * Fix demo * devolo * ecobee * Some more * Fix missing feature flag * some more * and some more * Remove demo change * Add back demo change * Fix demo * Update commentspull/109224/head
parent
816c2e9500
commit
ddb56fe20d
|
@ -301,6 +301,9 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||
_attr_temperature_unit: str
|
||||
|
||||
__mod_supported_features: ClimateEntityFeature = ClimateEntityFeature(0)
|
||||
# Integrations should set `_enable_turn_on_off_backwards_compatibility` to False
|
||||
# once migrated and set the feature flags TURN_ON/TURN_OFF as needed.
|
||||
_enable_turn_on_off_backwards_compatibility: bool = True
|
||||
|
||||
def __getattribute__(self, __name: str) -> Any:
|
||||
"""Get attribute.
|
||||
|
@ -345,7 +348,7 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||
else:
|
||||
message = (
|
||||
"Entity %s (%s) implements HVACMode(s): %s and therefore implicitly"
|
||||
" supports the %s service without setting the proper"
|
||||
" supports the %s methods without setting the proper"
|
||||
" ClimateEntityFeature. Please %s"
|
||||
)
|
||||
_LOGGER.warning(
|
||||
|
@ -353,48 +356,44 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||
self.entity_id,
|
||||
type(self),
|
||||
feature,
|
||||
feature.lower(),
|
||||
method,
|
||||
report_issue,
|
||||
)
|
||||
|
||||
# Adds ClimateEntityFeature.TURN_OFF/TURN_ON depending on service calls implemented
|
||||
# This should be removed in 2025.1.
|
||||
if not self.supported_features & ClimateEntityFeature.TURN_OFF:
|
||||
if (
|
||||
type(self).async_turn_off is not ClimateEntity.async_turn_off
|
||||
or type(self).turn_off is not ClimateEntity.turn_off
|
||||
):
|
||||
# turn_off implicitly supported by implementing turn_off method
|
||||
_report_turn_on_off("TURN_OFF", "turn_off")
|
||||
self.__mod_supported_features |= ( # pylint: disable=unused-private-member
|
||||
ClimateEntityFeature.TURN_OFF
|
||||
)
|
||||
elif self.hvac_modes and HVACMode.OFF in self.hvac_modes:
|
||||
# turn_off implicitly supported by including HVACMode.OFF
|
||||
_report_turn_on_off("off", "turn_off")
|
||||
self.__mod_supported_features |= ( # pylint: disable=unused-private-member
|
||||
ClimateEntityFeature.TURN_OFF
|
||||
)
|
||||
if self._enable_turn_on_off_backwards_compatibility is False:
|
||||
# Return if integration has migrated already
|
||||
return
|
||||
|
||||
if not self.supported_features & ClimateEntityFeature.TURN_ON:
|
||||
if (
|
||||
type(self).async_turn_on is not ClimateEntity.async_turn_on
|
||||
or type(self).turn_on is not ClimateEntity.turn_on
|
||||
):
|
||||
# turn_on implicitly supported by implementing turn_on method
|
||||
_report_turn_on_off("TURN_ON", "turn_on")
|
||||
self.__mod_supported_features |= ( # pylint: disable=unused-private-member
|
||||
ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
elif self.hvac_modes and any(
|
||||
_mode != HVACMode.OFF and _mode is not None for _mode in self.hvac_modes
|
||||
):
|
||||
# turn_on implicitly supported by including any other HVACMode than HVACMode.OFF
|
||||
_modes = [_mode for _mode in self.hvac_modes if _mode != HVACMode.OFF]
|
||||
_report_turn_on_off(", ".join(_modes or []), "turn_on")
|
||||
self.__mod_supported_features |= ( # pylint: disable=unused-private-member
|
||||
ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
if not self.supported_features & ClimateEntityFeature.TURN_OFF and (
|
||||
type(self).async_turn_off is not ClimateEntity.async_turn_off
|
||||
or type(self).turn_off is not ClimateEntity.turn_off
|
||||
):
|
||||
# turn_off implicitly supported by implementing turn_off method
|
||||
_report_turn_on_off("TURN_OFF", "turn_off")
|
||||
self.__mod_supported_features |= ( # pylint: disable=unused-private-member
|
||||
ClimateEntityFeature.TURN_OFF
|
||||
)
|
||||
|
||||
if not self.supported_features & ClimateEntityFeature.TURN_ON and (
|
||||
type(self).async_turn_on is not ClimateEntity.async_turn_on
|
||||
or type(self).turn_on is not ClimateEntity.turn_on
|
||||
):
|
||||
# turn_on implicitly supported by implementing turn_on method
|
||||
_report_turn_on_off("TURN_ON", "turn_on")
|
||||
self.__mod_supported_features |= ( # pylint: disable=unused-private-member
|
||||
ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
|
||||
if (modes := self.hvac_modes) and len(modes) >= 2 and HVACMode.OFF in modes:
|
||||
# turn_on/off implicitly supported by including more modes than 1 and one of these
|
||||
# are HVACMode.OFF
|
||||
_modes = [_mode for _mode in self.hvac_modes if _mode is not None]
|
||||
_report_turn_on_off(", ".join(_modes or []), "turn_on/turn_off")
|
||||
self.__mod_supported_features |= ( # pylint: disable=unused-private-member
|
||||
ClimateEntityFeature.TURN_ON | ClimateEntityFeature.TURN_OFF
|
||||
)
|
||||
|
||||
@final
|
||||
@property
|
||||
|
|
|
@ -531,16 +531,9 @@ async def test_implicit_warning_not_implemented_turn_on_off_feature(
|
|||
assert (
|
||||
"Entity climate.test (<class 'tests.components.climate.test_init."
|
||||
"test_implicit_warning_not_implemented_turn_on_off_feature.<locals>.MockClimateEntityTest'>)"
|
||||
" implements HVACMode(s): off and therefore implicitly supports the off service without setting"
|
||||
" the proper ClimateEntityFeature. Please report it to the author of the 'test' custom integration"
|
||||
in caplog.text
|
||||
)
|
||||
assert (
|
||||
"Entity climate.test (<class 'tests.components.climate.test_init."
|
||||
"test_implicit_warning_not_implemented_turn_on_off_feature.<locals>.MockClimateEntityTest'>)"
|
||||
" implements HVACMode(s): heat and therefore implicitly supports the heat service without setting"
|
||||
" the proper ClimateEntityFeature. Please report it to the author of the 'test' custom integration"
|
||||
in caplog.text
|
||||
" implements HVACMode(s): off, heat and therefore implicitly supports the turn_on/turn_off"
|
||||
" methods without setting the proper ClimateEntityFeature. Please report it to the author"
|
||||
" of the 'test' custom integration" in caplog.text
|
||||
)
|
||||
|
||||
|
||||
|
@ -608,10 +601,6 @@ async def test_no_warning_implemented_turn_on_off_feature(
|
|||
not in caplog.text
|
||||
)
|
||||
assert (
|
||||
"implements HVACMode.off and therefore implicitly implements the off method without setting"
|
||||
not in caplog.text
|
||||
)
|
||||
assert (
|
||||
"implements HVACMode.heat and therefore implicitly implements the heat method without setting"
|
||||
" implements HVACMode(s): off, heat and therefore implicitly supports the off, heat methods"
|
||||
not in caplog.text
|
||||
)
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
]),
|
||||
'max_temp': 24,
|
||||
'min_temp': 4,
|
||||
'supported_features': <ClimateEntityFeature: 257>,
|
||||
'supported_features': <ClimateEntityFeature: 1>,
|
||||
'target_temp_step': 0.5,
|
||||
'temperature': 20,
|
||||
}),
|
||||
|
@ -52,7 +52,7 @@
|
|||
'original_name': None,
|
||||
'platform': 'devolo_home_control',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': <ClimateEntityFeature: 257>,
|
||||
'supported_features': <ClimateEntityFeature: 1>,
|
||||
'translation_key': None,
|
||||
'unique_id': 'Test',
|
||||
'unit_of_measurement': None,
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
]),
|
||||
'max_temp': 35.0,
|
||||
'min_temp': 5.0,
|
||||
'supported_features': <ClimateEntityFeature: 259>,
|
||||
'supported_features': <ClimateEntityFeature: 3>,
|
||||
'target_temp_high': 30.0,
|
||||
'target_temp_low': 21.0,
|
||||
'target_temp_step': 0.5,
|
||||
|
@ -36,7 +36,7 @@
|
|||
]),
|
||||
'max_temp': 35.0,
|
||||
'min_temp': 5.0,
|
||||
'supported_features': <ClimateEntityFeature: 259>,
|
||||
'supported_features': <ClimateEntityFeature: 3>,
|
||||
'target_temp_step': 0.5,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
|
@ -59,7 +59,7 @@
|
|||
]),
|
||||
'max_temp': 35.0,
|
||||
'min_temp': 5.0,
|
||||
'supported_features': <ClimateEntityFeature: 259>,
|
||||
'supported_features': <ClimateEntityFeature: 3>,
|
||||
'target_temp_high': 30.0,
|
||||
'target_temp_low': 21.0,
|
||||
'target_temp_step': 0.5,
|
||||
|
@ -83,7 +83,7 @@
|
|||
]),
|
||||
'max_temp': 35.0,
|
||||
'min_temp': 5.0,
|
||||
'supported_features': <ClimateEntityFeature: 259>,
|
||||
'supported_features': <ClimateEntityFeature: 3>,
|
||||
'target_temp_step': 0.5,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
|
@ -112,7 +112,7 @@
|
|||
]),
|
||||
'max_temp': 35.0,
|
||||
'min_temp': 5.0,
|
||||
'supported_features': <ClimateEntityFeature: 259>,
|
||||
'supported_features': <ClimateEntityFeature: 3>,
|
||||
'target_temp_high': 30.0,
|
||||
'target_temp_low': 21.0,
|
||||
'target_temp_step': 0.5,
|
||||
|
@ -138,7 +138,7 @@
|
|||
]),
|
||||
'max_temp': 35.0,
|
||||
'min_temp': 5.0,
|
||||
'supported_features': <ClimateEntityFeature: 259>,
|
||||
'supported_features': <ClimateEntityFeature: 3>,
|
||||
'target_temp_high': None,
|
||||
'target_temp_low': None,
|
||||
'target_temp_step': 0.5,
|
||||
|
@ -164,7 +164,7 @@
|
|||
]),
|
||||
'max_temp': 35.0,
|
||||
'min_temp': 5.0,
|
||||
'supported_features': <ClimateEntityFeature: 259>,
|
||||
'supported_features': <ClimateEntityFeature: 3>,
|
||||
'target_temp_high': None,
|
||||
'target_temp_low': None,
|
||||
'target_temp_step': 0.5,
|
||||
|
@ -190,7 +190,7 @@
|
|||
]),
|
||||
'max_temp': 35.0,
|
||||
'min_temp': 5.0,
|
||||
'supported_features': <ClimateEntityFeature: 259>,
|
||||
'supported_features': <ClimateEntityFeature: 3>,
|
||||
'target_temp_high': 30.0,
|
||||
'target_temp_low': 21.0,
|
||||
'target_temp_step': 0.5,
|
||||
|
@ -216,7 +216,7 @@
|
|||
]),
|
||||
'max_temp': 35.0,
|
||||
'min_temp': 5.0,
|
||||
'supported_features': <ClimateEntityFeature: 259>,
|
||||
'supported_features': <ClimateEntityFeature: 3>,
|
||||
'target_temp_high': 30.0,
|
||||
'target_temp_low': 21.0,
|
||||
'target_temp_step': 0.5,
|
||||
|
@ -242,7 +242,7 @@
|
|||
]),
|
||||
'max_temp': 35.0,
|
||||
'min_temp': 5.0,
|
||||
'supported_features': <ClimateEntityFeature: 259>,
|
||||
'supported_features': <ClimateEntityFeature: 3>,
|
||||
'target_temp_high': 30.0,
|
||||
'target_temp_low': 21.0,
|
||||
'target_temp_step': 0.5,
|
||||
|
@ -268,7 +268,7 @@
|
|||
]),
|
||||
'max_temp': 35.0,
|
||||
'min_temp': 5.0,
|
||||
'supported_features': <ClimateEntityFeature: 259>,
|
||||
'supported_features': <ClimateEntityFeature: 3>,
|
||||
'target_temp_high': None,
|
||||
'target_temp_low': None,
|
||||
'target_temp_step': 0.5,
|
||||
|
@ -294,7 +294,7 @@
|
|||
]),
|
||||
'max_temp': 35.0,
|
||||
'min_temp': 5.0,
|
||||
'supported_features': <ClimateEntityFeature: 259>,
|
||||
'supported_features': <ClimateEntityFeature: 3>,
|
||||
'target_temp_high': None,
|
||||
'target_temp_low': None,
|
||||
'target_temp_step': 0.5,
|
||||
|
@ -320,7 +320,7 @@
|
|||
]),
|
||||
'max_temp': 35.0,
|
||||
'min_temp': 5.0,
|
||||
'supported_features': <ClimateEntityFeature: 259>,
|
||||
'supported_features': <ClimateEntityFeature: 3>,
|
||||
'target_temp_high': 30.0,
|
||||
'target_temp_low': 21.0,
|
||||
'target_temp_step': 0.5,
|
||||
|
@ -346,7 +346,7 @@
|
|||
]),
|
||||
'max_temp': 35.0,
|
||||
'min_temp': 5.0,
|
||||
'supported_features': <ClimateEntityFeature: 259>,
|
||||
'supported_features': <ClimateEntityFeature: 3>,
|
||||
'target_temp_high': None,
|
||||
'target_temp_low': None,
|
||||
'target_temp_step': 0.5,
|
||||
|
@ -372,7 +372,7 @@
|
|||
]),
|
||||
'max_temp': 35.0,
|
||||
'min_temp': 5.0,
|
||||
'supported_features': <ClimateEntityFeature: 259>,
|
||||
'supported_features': <ClimateEntityFeature: 3>,
|
||||
'target_temp_high': None,
|
||||
'target_temp_low': None,
|
||||
'target_temp_step': 0.5,
|
||||
|
@ -398,7 +398,7 @@
|
|||
]),
|
||||
'max_temp': 35.0,
|
||||
'min_temp': 5.0,
|
||||
'supported_features': <ClimateEntityFeature: 259>,
|
||||
'supported_features': <ClimateEntityFeature: 3>,
|
||||
'target_temp_high': 30.0,
|
||||
'target_temp_low': 21.0,
|
||||
'target_temp_step': 0.5,
|
||||
|
@ -424,7 +424,7 @@
|
|||
]),
|
||||
'max_temp': 35.0,
|
||||
'min_temp': 5.0,
|
||||
'supported_features': <ClimateEntityFeature: 259>,
|
||||
'supported_features': <ClimateEntityFeature: 3>,
|
||||
'target_temp_high': 30.0,
|
||||
'target_temp_low': 21.0,
|
||||
'target_temp_step': 0.5,
|
||||
|
@ -450,7 +450,7 @@
|
|||
]),
|
||||
'max_temp': 35.0,
|
||||
'min_temp': 5.0,
|
||||
'supported_features': <ClimateEntityFeature: 259>,
|
||||
'supported_features': <ClimateEntityFeature: 3>,
|
||||
'target_temp_high': 30.0,
|
||||
'target_temp_low': 21.0,
|
||||
'target_temp_step': 0.5,
|
||||
|
@ -476,7 +476,7 @@
|
|||
]),
|
||||
'max_temp': 35.0,
|
||||
'min_temp': 5.0,
|
||||
'supported_features': <ClimateEntityFeature: 259>,
|
||||
'supported_features': <ClimateEntityFeature: 3>,
|
||||
'target_temp_high': None,
|
||||
'target_temp_low': None,
|
||||
'target_temp_step': 0.5,
|
||||
|
@ -502,7 +502,7 @@
|
|||
]),
|
||||
'max_temp': 35.0,
|
||||
'min_temp': 5.0,
|
||||
'supported_features': <ClimateEntityFeature: 259>,
|
||||
'supported_features': <ClimateEntityFeature: 3>,
|
||||
'target_temp_high': None,
|
||||
'target_temp_low': None,
|
||||
'target_temp_step': 0.5,
|
||||
|
|
|
@ -44,7 +44,7 @@ async def test_climate_thermostat_run(hass: HomeAssistant) -> None:
|
|||
"min_temp": 5.0,
|
||||
"preset_mode": "Run Schedule",
|
||||
"preset_modes": ["Run Schedule", "Temporary Hold", "Permanent Hold"],
|
||||
"supported_features": 273,
|
||||
"supported_features": 17,
|
||||
"temperature": 22.2,
|
||||
}
|
||||
# Only test for a subset of attributes in case
|
||||
|
@ -77,7 +77,7 @@ async def test_climate_thermostat_schedule_hold_unavailable(
|
|||
"max_temp": 180.6,
|
||||
"min_temp": -6.1,
|
||||
"preset_modes": ["Run Schedule", "Temporary Hold", "Permanent Hold"],
|
||||
"supported_features": 273,
|
||||
"supported_features": 17,
|
||||
}
|
||||
# Only test for a subset of attributes in case
|
||||
# HA changes the implementation and a new one appears
|
||||
|
@ -110,7 +110,7 @@ async def test_climate_thermostat_schedule_hold_available(hass: HomeAssistant) -
|
|||
"min_temp": -6.1,
|
||||
"preset_mode": "Run Schedule",
|
||||
"preset_modes": ["Run Schedule", "Temporary Hold", "Permanent Hold"],
|
||||
"supported_features": 273,
|
||||
"supported_features": 17,
|
||||
"temperature": 26.1,
|
||||
}
|
||||
# Only test for a subset of attributes in case
|
||||
|
@ -144,7 +144,7 @@ async def test_climate_thermostat_schedule_temporary_hold(hass: HomeAssistant) -
|
|||
"min_temp": -0.6,
|
||||
"preset_mode": "Run Schedule",
|
||||
"preset_modes": ["Run Schedule", "Temporary Hold", "Permanent Hold"],
|
||||
"supported_features": 273,
|
||||
"supported_features": 17,
|
||||
"temperature": 37.2,
|
||||
}
|
||||
# Only test for a subset of attributes in case
|
||||
|
|
|
@ -34,7 +34,7 @@ async def test_adam_climate_entity_attributes(
|
|||
|
||||
assert state.attributes["current_temperature"] == 20.9
|
||||
assert state.attributes["preset_mode"] == "home"
|
||||
assert state.attributes["supported_features"] == 273
|
||||
assert state.attributes["supported_features"] == 17
|
||||
assert state.attributes["temperature"] == 21.5
|
||||
assert state.attributes["min_temp"] == 0.0
|
||||
assert state.attributes["max_temp"] == 35.0
|
||||
|
@ -303,7 +303,7 @@ async def test_anna_climate_entity_attributes(
|
|||
|
||||
assert state.attributes["current_temperature"] == 19.3
|
||||
assert state.attributes["preset_mode"] == "home"
|
||||
assert state.attributes["supported_features"] == 274
|
||||
assert state.attributes["supported_features"] == 18
|
||||
assert state.attributes["target_temp_high"] == 30
|
||||
assert state.attributes["target_temp_low"] == 20.5
|
||||
assert state.attributes["min_temp"] == 4
|
||||
|
@ -325,7 +325,7 @@ async def test_anna_2_climate_entity_attributes(
|
|||
HVACMode.AUTO,
|
||||
HVACMode.HEAT_COOL,
|
||||
]
|
||||
assert state.attributes["supported_features"] == 274
|
||||
assert state.attributes["supported_features"] == 18
|
||||
assert state.attributes["target_temp_high"] == 30
|
||||
assert state.attributes["target_temp_low"] == 20.5
|
||||
|
||||
|
|
|
@ -52,9 +52,7 @@ async def test_thermostat_update(
|
|||
assert state.state == HVACMode.HEAT
|
||||
assert (
|
||||
state.attributes[ATTR_SUPPORTED_FEATURES]
|
||||
== ClimateEntityFeature.PRESET_MODE
|
||||
| ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
== ClimateEntityFeature.PRESET_MODE | ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
)
|
||||
assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 38
|
||||
assert state.attributes[ATTR_TEMPERATURE] == 39
|
||||
|
|
|
@ -332,7 +332,7 @@ async def test_setpoint_thermostat(
|
|||
assert state.attributes[ATTR_HVAC_MODES] == [HVACMode.HEAT]
|
||||
assert (
|
||||
state.attributes[ATTR_SUPPORTED_FEATURES]
|
||||
== ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.TURN_ON
|
||||
== ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
)
|
||||
|
||||
client.async_send_command_no_wait.reset_mock()
|
||||
|
|
Loading…
Reference in New Issue