diff --git a/homeassistant/components/smartthings/sensor.py b/homeassistant/components/smartthings/sensor.py index 87ae1488329..1b4ccf1c576 100644 --- a/homeassistant/components/smartthings/sensor.py +++ b/homeassistant/components/smartthings/sensor.py @@ -9,6 +9,8 @@ from typing import Any, cast from pysmartthings import Attribute, Capability, SmartThings, Status +from homeassistant.components.automation import automations_with_entity +from homeassistant.components.script import scripts_with_entity from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, @@ -29,11 +31,17 @@ from homeassistant.const import ( UnitOfVolume, ) from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback +from homeassistant.helpers.issue_registry import ( + IssueSeverity, + async_create_issue, + async_delete_issue, +) from homeassistant.util import dt as dt_util from . import FullDevice, SmartThingsConfigEntry -from .const import MAIN +from .const import DOMAIN, MAIN from .entity import SmartThingsEntity THERMOSTAT_CAPABILITIES = { @@ -1089,3 +1097,55 @@ class SmartThingsSensor(SmartThingsEntity, SensorEntity): return [] return [option.lower() for option in options] return super().options + + async def async_added_to_hass(self) -> None: + """Call when entity is added to hass.""" + await super().async_added_to_hass() + if ( + self.capability + not in {Capability.DRYER_OPERATING_STATE, Capability.WASHER_OPERATING_STATE} + or self._attribute is not Attribute.MACHINE_STATE + ): + return + automations = automations_with_entity(self.hass, self.entity_id) + scripts = scripts_with_entity(self.hass, self.entity_id) + if not automations and not scripts: + return + + entity_reg: er.EntityRegistry = er.async_get(self.hass) + items_list = [ + f"- [{item.original_name}](/config/{integration}/edit/{item.unique_id})" + for integration, entities in ( + ("automation", automations), + ("script", scripts), + ) + for entity_id in entities + if (item := entity_reg.async_get(entity_id)) + ] + + async_create_issue( + self.hass, + DOMAIN, + f"deprecated_machine_state_{self.entity_id}", + breaks_in_ha_version="2025.10.0", + is_fixable=False, + severity=IssueSeverity.WARNING, + translation_key="deprecated_machine_state", + translation_placeholders={ + "entity": self.entity_id, + "items": "\n".join(items_list), + }, + ) + + async def async_will_remove_from_hass(self) -> None: + """Call when entity will be removed from hass.""" + await super().async_will_remove_from_hass() + if ( + self.capability + not in {Capability.DRYER_OPERATING_STATE, Capability.WASHER_OPERATING_STATE} + or self._attribute is not Attribute.MACHINE_STATE + ): + return + async_delete_issue( + self.hass, DOMAIN, f"deprecated_machine_state_{self.entity_id}" + ) diff --git a/homeassistant/components/smartthings/strings.json b/homeassistant/components/smartthings/strings.json index cbea23f6318..0d9fe38dd0a 100644 --- a/homeassistant/components/smartthings/strings.json +++ b/homeassistant/components/smartthings/strings.json @@ -481,6 +481,10 @@ "title": "Deprecated refrigerator door binary sensor detected in some automations or scripts", "description": "The refrigerator door binary sensor `{entity}` is deprecated and is used in the following automations or scripts:\n{items}\n\nSeparate entities for cooler and freezer door are available and should be used going forward. Please use them in the above automations or scripts to fix this issue." }, + "deprecated_machine_state": { + "title": "Deprecated machine state sensor detected in some automations or scripts", + "description": "The machine state sensor `{entity}` is deprecated and is used in the following automations or scripts:\n{items}\n\nA select entity is now available for the machine state and should be used going forward. Please use them in the above automations or scripts to fix this issue." + }, "deprecated_switch_appliance": { "title": "Deprecated switch detected in some automations or scripts", "description": "The switch `{entity}` is deprecated because the actions did not work, so it has been replaced with a binary sensor instead.\n\nThe switch was used in the following automations or scripts:\n{items}\n\nPlease use them in the above automations or scripts to fix this issue." diff --git a/tests/components/smartthings/test_sensor.py b/tests/components/smartthings/test_sensor.py index c83950de9e9..229644e2473 100644 --- a/tests/components/smartthings/test_sensor.py +++ b/tests/components/smartthings/test_sensor.py @@ -6,9 +6,14 @@ from pysmartthings import Attribute, Capability import pytest from syrupy import SnapshotAssertion +from homeassistant.components import automation, script +from homeassistant.components.automation import automations_with_entity +from homeassistant.components.script import scripts_with_entity +from homeassistant.components.smartthings.const import DOMAIN from homeassistant.const import Platform from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry as er +from homeassistant.helpers import entity_registry as er, issue_registry as ir +from homeassistant.setup import async_setup_component from . import setup_integration, snapshot_smartthings_entities, trigger_update @@ -49,3 +54,78 @@ async def test_state_update( ) assert hass.states.get("sensor.ac_office_granit_temperature").state == "20" + + +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +@pytest.mark.parametrize( + ("device_fixture", "entity_id"), + [ + ("da_wm_wm_000001", "sensor.washer_machine_state"), + ("da_wm_wd_000001", "sensor.dryer_machine_state"), + ], +) +async def test_create_issue( + hass: HomeAssistant, + devices: AsyncMock, + mock_config_entry: MockConfigEntry, + issue_registry: ir.IssueRegistry, + entity_id: str, +) -> None: + """Test we create an issue when an automation or script is using a deprecated entity.""" + issue_id = f"deprecated_machine_state_{entity_id}" + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: { + "id": "test", + "alias": "test", + "trigger": {"platform": "state", "entity_id": entity_id}, + "action": { + "action": "automation.turn_on", + "target": { + "entity_id": "automation.test", + }, + }, + } + }, + ) + assert await async_setup_component( + hass, + script.DOMAIN, + { + script.DOMAIN: { + "test": { + "sequence": [ + { + "condition": "state", + "entity_id": entity_id, + "state": "on", + }, + ], + } + } + }, + ) + + await setup_integration(hass, mock_config_entry) + + assert automations_with_entity(hass, entity_id)[0] == "automation.test" + assert scripts_with_entity(hass, entity_id)[0] == "script.test" + + assert len(issue_registry.issues) == 1 + issue = issue_registry.async_get_issue(DOMAIN, issue_id) + assert issue is not None + assert issue.translation_key == "deprecated_machine_state" + assert issue.translation_placeholders == { + "entity": entity_id, + "items": "- [test](/config/automation/edit/test)\n- [test](/config/script/edit/test)", + } + + await hass.config_entries.async_unload(mock_config_entry.entry_id) + await hass.async_block_till_done() + + # Assert the issue is no longer present + assert not issue_registry.async_get_issue(DOMAIN, issue_id) + assert len(issue_registry.issues) == 0