diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 287a2397121..03acaa08294 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -18,23 +18,20 @@ from homeassistant.const import ( SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, - STATE_OFF, - STATE_ON, UnitOfTemperature, ) from homeassistant.core import HomeAssistant, ServiceCall, callback from homeassistant.exceptions import ServiceValidationError -from homeassistant.helpers import config_validation as cv, issue_registry as ir +from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity import Entity, EntityDescription from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.temperature import display_temp as show_temp from homeassistant.helpers.typing import ConfigType -from homeassistant.loader import async_get_issue_tracker, async_suggest_report_issue +from homeassistant.loader import async_suggest_report_issue from homeassistant.util.hass_dict import HassKey from homeassistant.util.unit_conversion import TemperatureConverter from .const import ( # noqa: F401 - ATTR_AUX_HEAT, ATTR_CURRENT_HUMIDITY, ATTR_CURRENT_TEMPERATURE, ATTR_FAN_MODE, @@ -77,7 +74,6 @@ from .const import ( # noqa: F401 PRESET_HOME, PRESET_NONE, PRESET_SLEEP, - SERVICE_SET_AUX_HEAT, SERVICE_SET_FAN_MODE, SERVICE_SET_HUMIDITY, SERVICE_SET_HVAC_MODE, @@ -168,12 +164,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: "async_handle_set_preset_mode_service", [ClimateEntityFeature.PRESET_MODE], ) - component.async_register_entity_service( - SERVICE_SET_AUX_HEAT, - {vol.Required(ATTR_AUX_HEAT): cv.boolean}, - async_service_aux_heat, - [ClimateEntityFeature.AUX_HEAT], - ) component.async_register_entity_service( SERVICE_SET_TEMPERATURE, SET_TEMPERATURE_SCHEMA, @@ -239,7 +229,6 @@ CACHED_PROPERTIES_WITH_ATTR_ = { "target_temperature_low", "preset_mode", "preset_modes", - "is_aux_heat", "fan_mode", "fan_modes", "swing_mode", @@ -279,7 +268,6 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): _attr_hvac_action: HVACAction | None = None _attr_hvac_mode: HVACMode | None _attr_hvac_modes: list[HVACMode] - _attr_is_aux_heat: bool | None _attr_max_humidity: float = DEFAULT_MAX_HUMIDITY _attr_max_temp: float _attr_min_humidity: float = DEFAULT_MIN_HUMIDITY @@ -299,52 +287,6 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): _attr_target_temperature: float | None = None _attr_temperature_unit: str - __climate_reported_legacy_aux = False - - def _report_legacy_aux(self) -> None: - """Log warning and create an issue if the entity implements legacy auxiliary heater.""" - - report_issue = async_suggest_report_issue( - self.hass, - integration_domain=self.platform.platform_name, - module=type(self).__module__, - ) - _LOGGER.warning( - ( - "%s::%s implements the `is_aux_heat` property or uses the auxiliary " - "heater methods in a subclass of ClimateEntity which is " - "deprecated and will be unsupported from Home Assistant 2025.4." - " Please %s" - ), - self.platform.platform_name, - self.__class__.__name__, - report_issue, - ) - - translation_placeholders = {"platform": self.platform.platform_name} - translation_key = "deprecated_climate_aux_no_url" - issue_tracker = async_get_issue_tracker( - self.hass, - integration_domain=self.platform.platform_name, - module=type(self).__module__, - ) - if issue_tracker: - translation_placeholders["issue_tracker"] = issue_tracker - translation_key = "deprecated_climate_aux_url_custom" - ir.async_create_issue( - self.hass, - DOMAIN, - f"deprecated_climate_aux_{self.platform.platform_name}", - breaks_in_ha_version="2025.4.0", - is_fixable=False, - is_persistent=False, - issue_domain=self.platform.platform_name, - severity=ir.IssueSeverity.WARNING, - translation_key=translation_key, - translation_placeholders=translation_placeholders, - ) - self.__climate_reported_legacy_aux = True - @final @property def state(self) -> str | None: @@ -453,14 +395,6 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): if ClimateEntityFeature.SWING_HORIZONTAL_MODE in supported_features: data[ATTR_SWING_HORIZONTAL_MODE] = self.swing_horizontal_mode - if ClimateEntityFeature.AUX_HEAT in supported_features: - data[ATTR_AUX_HEAT] = STATE_ON if self.is_aux_heat else STATE_OFF - if ( - self.__climate_reported_legacy_aux is False - and "custom_components" in type(self).__module__ - ): - self._report_legacy_aux() - return data @cached_property @@ -540,14 +474,6 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): """ return self._attr_preset_modes - @cached_property - def is_aux_heat(self) -> bool | None: - """Return true if aux heater. - - Requires ClimateEntityFeature.AUX_HEAT. - """ - return self._attr_is_aux_heat - @cached_property def fan_mode(self) -> str | None: """Return the fan setting. @@ -732,22 +658,6 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): """Set new preset mode.""" await self.hass.async_add_executor_job(self.set_preset_mode, preset_mode) - def turn_aux_heat_on(self) -> None: - """Turn auxiliary heater on.""" - raise NotImplementedError - - async def async_turn_aux_heat_on(self) -> None: - """Turn auxiliary heater on.""" - await self.hass.async_add_executor_job(self.turn_aux_heat_on) - - def turn_aux_heat_off(self) -> None: - """Turn auxiliary heater off.""" - raise NotImplementedError - - async def async_turn_aux_heat_off(self) -> None: - """Turn auxiliary heater off.""" - await self.hass.async_add_executor_job(self.turn_aux_heat_off) - def turn_on(self) -> None: """Turn the entity on.""" raise NotImplementedError @@ -845,16 +755,6 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): return self._attr_max_humidity -async def async_service_aux_heat( - entity: ClimateEntity, service_call: ServiceCall -) -> None: - """Handle aux heat service.""" - if service_call.data[ATTR_AUX_HEAT]: - await entity.async_turn_aux_heat_on() - else: - await entity.async_turn_aux_heat_off() - - async def async_service_humidity_set( entity: ClimateEntity, service_call: ServiceCall ) -> None: diff --git a/homeassistant/components/climate/const.py b/homeassistant/components/climate/const.py index ecc0066cd93..7db80281635 100644 --- a/homeassistant/components/climate/const.py +++ b/homeassistant/components/climate/const.py @@ -96,7 +96,6 @@ class HVACAction(StrEnum): CURRENT_HVAC_ACTIONS = [cls.value for cls in HVACAction] -ATTR_AUX_HEAT = "aux_heat" ATTR_CURRENT_HUMIDITY = "current_humidity" ATTR_CURRENT_TEMPERATURE = "current_temperature" ATTR_FAN_MODES = "fan_modes" @@ -128,7 +127,6 @@ DOMAIN = "climate" INTENT_SET_TEMPERATURE = "HassClimateSetTemperature" -SERVICE_SET_AUX_HEAT = "set_aux_heat" SERVICE_SET_FAN_MODE = "set_fan_mode" SERVICE_SET_PRESET_MODE = "set_preset_mode" SERVICE_SET_HUMIDITY = "set_humidity" @@ -147,7 +145,6 @@ class ClimateEntityFeature(IntFlag): FAN_MODE = 8 PRESET_MODE = 16 SWING_MODE = 32 - AUX_HEAT = 64 TURN_OFF = 128 TURN_ON = 256 SWING_HORIZONTAL_MODE = 512 diff --git a/homeassistant/components/climate/services.yaml b/homeassistant/components/climate/services.yaml index 68421bf2386..fb5ba4f1796 100644 --- a/homeassistant/components/climate/services.yaml +++ b/homeassistant/components/climate/services.yaml @@ -1,17 +1,5 @@ # Describes the format for available climate services -set_aux_heat: - target: - entity: - domain: climate - supported_features: - - climate.ClimateEntityFeature.AUX_HEAT - fields: - aux_heat: - required: true - selector: - boolean: - set_preset_mode: target: entity: diff --git a/homeassistant/components/climate/significant_change.py b/homeassistant/components/climate/significant_change.py index 2b7e2c5d8b1..7bc42d5dbd5 100644 --- a/homeassistant/components/climate/significant_change.py +++ b/homeassistant/components/climate/significant_change.py @@ -12,7 +12,6 @@ from homeassistant.helpers.significant_change import ( ) from . import ( - ATTR_AUX_HEAT, ATTR_CURRENT_HUMIDITY, ATTR_CURRENT_TEMPERATURE, ATTR_FAN_MODE, @@ -27,7 +26,6 @@ from . import ( ) SIGNIFICANT_ATTRIBUTES: set[str] = { - ATTR_AUX_HEAT, ATTR_CURRENT_HUMIDITY, ATTR_CURRENT_TEMPERATURE, ATTR_FAN_MODE, @@ -67,7 +65,6 @@ def async_check_significant_change( for attr_name in changed_attrs: if attr_name in [ - ATTR_AUX_HEAT, ATTR_FAN_MODE, ATTR_HVAC_ACTION, ATTR_PRESET_MODE, diff --git a/homeassistant/components/climate/strings.json b/homeassistant/components/climate/strings.json index 250b2a67efe..bd6ed083650 100644 --- a/homeassistant/components/climate/strings.json +++ b/homeassistant/components/climate/strings.json @@ -36,9 +36,6 @@ "fan_only": "Fan only" }, "state_attributes": { - "aux_heat": { - "name": "Aux heat" - }, "current_humidity": { "name": "Current humidity" }, @@ -149,16 +146,6 @@ } }, "services": { - "set_aux_heat": { - "name": "Turn on/off auxiliary heater", - "description": "Turns auxiliary heater on/off.", - "fields": { - "aux_heat": { - "name": "Auxiliary heating", - "description": "New value of auxiliary heater." - } - } - }, "set_preset_mode": { "name": "Set preset mode", "description": "Sets preset mode.", @@ -267,16 +254,6 @@ } } }, - "issues": { - "deprecated_climate_aux_url_custom": { - "title": "The {platform} custom integration is using deprecated climate auxiliary heater", - "description": "The custom integration `{platform}` implements the `is_aux_heat` property or uses the auxiliary heater methods in a subclass of ClimateEntity.\n\nPlease create a bug report at {issue_tracker}.\n\nOnce an updated version of `{platform}` is available, install it and restart Home Assistant to fix this issue." - }, - "deprecated_climate_aux_no_url": { - "title": "[%key:component::climate::issues::deprecated_climate_aux_url_custom::title%]", - "description": "The custom integration `{platform}` implements the `is_aux_heat` property or uses the auxiliary heater methods in a subclass of ClimateEntity.\n\nPlease report it to the author of the {platform} integration.\n\nOnce an updated version of `{platform}` is available, install it and restart Home Assistant to fix this issue." - } - }, "exceptions": { "not_valid_preset_mode": { "message": "Preset mode {mode} is not valid. Valid preset modes are: {modes}." diff --git a/homeassistant/components/econet/climate.py b/homeassistant/components/econet/climate.py index c5d45d75dcf..81fc7ceb298 100644 --- a/homeassistant/components/econet/climate.py +++ b/homeassistant/components/econet/climate.py @@ -53,7 +53,6 @@ SUPPORT_FLAGS_THERMOSTAT = ( ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE | ClimateEntityFeature.FAN_MODE - | ClimateEntityFeature.AUX_HEAT ) diff --git a/tests/components/climate/common.py b/tests/components/climate/common.py index 8f5834d9180..ca214ec2d70 100644 --- a/tests/components/climate/common.py +++ b/tests/components/climate/common.py @@ -6,7 +6,6 @@ components. Instead call the service directly. from homeassistant.components.climate import ( _LOGGER, - ATTR_AUX_HEAT, ATTR_FAN_MODE, ATTR_HUMIDITY, ATTR_HVAC_MODE, @@ -16,7 +15,6 @@ from homeassistant.components.climate import ( ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, DOMAIN, - SERVICE_SET_AUX_HEAT, SERVICE_SET_FAN_MODE, SERVICE_SET_HUMIDITY, SERVICE_SET_HVAC_MODE, @@ -62,31 +60,6 @@ def set_preset_mode( hass.services.call(DOMAIN, SERVICE_SET_PRESET_MODE, data) -async def async_set_aux_heat( - hass: HomeAssistant, aux_heat: bool, entity_id: str = ENTITY_MATCH_ALL -) -> None: - """Turn all or specified climate devices auxiliary heater on.""" - data = {ATTR_AUX_HEAT: aux_heat} - - if entity_id: - data[ATTR_ENTITY_ID] = entity_id - - await hass.services.async_call(DOMAIN, SERVICE_SET_AUX_HEAT, data, blocking=True) - - -@bind_hass -def set_aux_heat( - hass: HomeAssistant, aux_heat: bool, entity_id: str = ENTITY_MATCH_ALL -) -> None: - """Turn all or specified climate devices auxiliary heater on.""" - data = {ATTR_AUX_HEAT: aux_heat} - - if entity_id: - data[ATTR_ENTITY_ID] = entity_id - - hass.services.call(DOMAIN, SERVICE_SET_AUX_HEAT, data) - - async def async_set_temperature( hass: HomeAssistant, temperature: float | None = None, diff --git a/tests/components/climate/test_init.py b/tests/components/climate/test_init.py index 8900a9faefa..a81efa1640c 100644 --- a/tests/components/climate/test_init.py +++ b/tests/components/climate/test_init.py @@ -37,21 +37,14 @@ from homeassistant.components.climate.const import ( SWING_HORIZONTAL_ON, ClimateEntityFeature, ) -from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, UnitOfTemperature from homeassistant.core import HomeAssistant from homeassistant.exceptions import ServiceValidationError -from homeassistant.helpers import issue_registry as ir -from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from tests.common import ( MockConfigEntry, MockEntity, - MockModule, - MockPlatform, async_mock_service, - mock_integration, - mock_platform, setup_test_component_platform, ) @@ -500,255 +493,6 @@ async def test_sync_toggle(hass: HomeAssistant) -> None: assert climate.toggle.called -ISSUE_TRACKER = "https://blablabla.com" - - -@pytest.mark.parametrize( - ( - "manifest_extra", - "translation_key", - "translation_placeholders_extra", - "report", - "module", - ), - [ - ( - {}, - "deprecated_climate_aux_no_url", - {}, - "report it to the author of the 'test' custom integration", - "custom_components.test.climate", - ), - ( - {"issue_tracker": ISSUE_TRACKER}, - "deprecated_climate_aux_url_custom", - {"issue_tracker": ISSUE_TRACKER}, - "create a bug report at https://blablabla.com", - "custom_components.test.climate", - ), - ], -) -async def test_issue_aux_property_deprecated( - hass: HomeAssistant, - caplog: pytest.LogCaptureFixture, - config_flow_fixture: None, - manifest_extra: dict[str, str], - translation_key: str, - translation_placeholders_extra: dict[str, str], - report: str, - module: str, - issue_registry: ir.IssueRegistry, -) -> None: - """Test the issue is raised on deprecated auxiliary heater attributes.""" - - class MockClimateEntityWithAux(MockClimateEntity): - """Mock climate class with mocked aux heater.""" - - _attr_supported_features = ( - ClimateEntityFeature.AUX_HEAT | ClimateEntityFeature.TARGET_TEMPERATURE - ) - - @property - def is_aux_heat(self) -> bool | None: - """Return true if aux heater. - - Requires ClimateEntityFeature.AUX_HEAT. - """ - return True - - async def async_turn_aux_heat_on(self) -> None: - """Turn auxiliary heater on.""" - await self.hass.async_add_executor_job(self.turn_aux_heat_on) - - async def async_turn_aux_heat_off(self) -> None: - """Turn auxiliary heater off.""" - await self.hass.async_add_executor_job(self.turn_aux_heat_off) - - # Fake the module is custom component or built in - MockClimateEntityWithAux.__module__ = module - - climate_entity = MockClimateEntityWithAux( - name="Testing", - entity_id="climate.testing", - ) - - async def async_setup_entry_init( - hass: HomeAssistant, config_entry: ConfigEntry - ) -> bool: - """Set up test config entry.""" - await hass.config_entries.async_forward_entry_setups(config_entry, [DOMAIN]) - return True - - async def async_setup_entry_climate_platform( - hass: HomeAssistant, - config_entry: ConfigEntry, - async_add_entities: AddConfigEntryEntitiesCallback, - ) -> None: - """Set up test weather platform via config entry.""" - async_add_entities([climate_entity]) - - mock_integration( - hass, - MockModule( - "test", - async_setup_entry=async_setup_entry_init, - partial_manifest=manifest_extra, - ), - built_in=False, - ) - mock_platform( - hass, - "test.climate", - MockPlatform(async_setup_entry=async_setup_entry_climate_platform), - ) - - config_entry = MockConfigEntry(domain="test") - config_entry.add_to_hass(hass) - assert await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - - assert climate_entity.state == HVACMode.HEAT - - issue = issue_registry.async_get_issue("climate", "deprecated_climate_aux_test") - assert issue - assert issue.issue_domain == "test" - assert issue.issue_id == "deprecated_climate_aux_test" - assert issue.translation_key == translation_key - assert ( - issue.translation_placeholders - == {"platform": "test"} | translation_placeholders_extra - ) - - assert ( - "test::MockClimateEntityWithAux implements the `is_aux_heat` property or uses " - "the auxiliary heater methods in a subclass of ClimateEntity which is deprecated " - f"and will be unsupported from Home Assistant 2025.4. Please {report}" - ) in caplog.text - - # Assert we only log warning once - caplog.clear() - await hass.services.async_call( - DOMAIN, - SERVICE_SET_TEMPERATURE, - { - "entity_id": "climate.test", - "temperature": "25", - }, - blocking=True, - ) - await hass.async_block_till_done() - - assert ("implements the `is_aux_heat` property") not in caplog.text - - -@pytest.mark.parametrize( - ( - "manifest_extra", - "translation_key", - "translation_placeholders_extra", - "report", - "module", - ), - [ - ( - {"issue_tracker": ISSUE_TRACKER}, - "deprecated_climate_aux_url", - {"issue_tracker": ISSUE_TRACKER}, - "create a bug report at https://blablabla.com", - "homeassistant.components.test.climate", - ), - ], -) -async def test_no_issue_aux_property_deprecated_for_core( - hass: HomeAssistant, - caplog: pytest.LogCaptureFixture, - register_test_integration: MockConfigEntry, - manifest_extra: dict[str, str], - translation_key: str, - translation_placeholders_extra: dict[str, str], - report: str, - module: str, - issue_registry: ir.IssueRegistry, -) -> None: - """Test the no issue on deprecated auxiliary heater attributes for core integrations.""" - - class MockClimateEntityWithAux(MockClimateEntity): - """Mock climate class with mocked aux heater.""" - - _attr_supported_features = ClimateEntityFeature.AUX_HEAT - - @property - def is_aux_heat(self) -> bool | None: - """Return true if aux heater. - - Requires ClimateEntityFeature.AUX_HEAT. - """ - return True - - async def async_turn_aux_heat_on(self) -> None: - """Turn auxiliary heater on.""" - await self.hass.async_add_executor_job(self.turn_aux_heat_on) - - async def async_turn_aux_heat_off(self) -> None: - """Turn auxiliary heater off.""" - await self.hass.async_add_executor_job(self.turn_aux_heat_off) - - # Fake the module is custom component or built in - MockClimateEntityWithAux.__module__ = module - - climate_entity = MockClimateEntityWithAux( - name="Testing", - entity_id="climate.testing", - ) - - setup_test_component_platform( - hass, DOMAIN, entities=[climate_entity], from_config_entry=True - ) - await hass.config_entries.async_setup(register_test_integration.entry_id) - await hass.async_block_till_done() - - assert climate_entity.state == HVACMode.HEAT - - issue = issue_registry.async_get_issue("climate", "deprecated_climate_aux_test") - assert not issue - - assert ( - "test::MockClimateEntityWithAux implements the `is_aux_heat` property or uses " - "the auxiliary heater methods in a subclass of ClimateEntity which is deprecated " - f"and will be unsupported from Home Assistant 2024.10. Please {report}" - ) not in caplog.text - - -async def test_no_issue_no_aux_property( - hass: HomeAssistant, - caplog: pytest.LogCaptureFixture, - register_test_integration: MockConfigEntry, - issue_registry: ir.IssueRegistry, -) -> None: - """Test the issue is raised on deprecated auxiliary heater attributes.""" - - climate_entity = MockClimateEntity( - name="Testing", - entity_id="climate.testing", - ) - - setup_test_component_platform( - hass, DOMAIN, entities=[climate_entity], from_config_entry=True - ) - assert await hass.config_entries.async_setup(register_test_integration.entry_id) - await hass.async_block_till_done() - - assert climate_entity.state == HVACMode.HEAT - - assert len(issue_registry.issues) == 0 - - assert ( - "test::MockClimateEntityWithAux implements the `is_aux_heat` property or uses " - "the auxiliary heater methods in a subclass of ClimateEntity which is deprecated " - "and will be unsupported from Home Assistant 2024.10." - ) not in caplog.text - - async def test_humidity_validation( hass: HomeAssistant, register_test_integration: MockConfigEntry, diff --git a/tests/components/climate/test_significant_change.py b/tests/components/climate/test_significant_change.py index 7d709090357..6fa53c306db 100644 --- a/tests/components/climate/test_significant_change.py +++ b/tests/components/climate/test_significant_change.py @@ -3,7 +3,6 @@ import pytest from homeassistant.components.climate import ( - ATTR_AUX_HEAT, ATTR_CURRENT_HUMIDITY, ATTR_CURRENT_TEMPERATURE, ATTR_FAN_MODE, @@ -37,8 +36,6 @@ async def test_significant_state_change(hass: HomeAssistant) -> None: @pytest.mark.parametrize( ("unit_system", "old_attrs", "new_attrs", "expected_result"), [ - (METRIC, {ATTR_AUX_HEAT: "old_value"}, {ATTR_AUX_HEAT: "old_value"}, False), - (METRIC, {ATTR_AUX_HEAT: "old_value"}, {ATTR_AUX_HEAT: "new_value"}, True), (METRIC, {ATTR_FAN_MODE: "old_value"}, {ATTR_FAN_MODE: "old_value"}, False), (METRIC, {ATTR_FAN_MODE: "old_value"}, {ATTR_FAN_MODE: "new_value"}, True), (