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 comments
pull/109224/head
G Johansson 2024-01-31 16:29:36 +01:00 committed by GitHub
parent 816c2e9500
commit ddb56fe20d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 71 additions and 85 deletions

View File

@ -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

View File

@ -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
)

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()