Deprecate SmartThings switch entity (#141360)

* Deprecate SmartThings switch entity

* Apply suggestions from code review

Co-authored-by: Robert Resch <robert@resch.dev>

* Fix

* Revert "Apply suggestions from code review"

This reverts commit c6d39d38de1c8b8cc1a95d79a62b6658776375cc.

* Revert "Revert "Apply suggestions from code review""

This reverts commit d92411c1560b031eb44679c3f24f3a6835279570.

* Fix

* Fix

---------

Co-authored-by: Robert Resch <robert@resch.dev>
This commit is contained in:
Joost Lekkerkerker 2025-03-26 11:05:31 +01:00 committed by GitHub
parent 02f8322ac1
commit 208e8ae451
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 151 additions and 4 deletions

View File

@ -480,6 +480,10 @@
"deprecated_binary_fridge_door": { "deprecated_binary_fridge_door": {
"title": "Deprecated refrigerator door binary sensor detected in some automations or scripts", "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." "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_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."
} }
} }
} }

View File

@ -5,14 +5,22 @@ from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any from typing import Any
from pysmartthings import Attribute, Capability, Command, SmartThings from pysmartthings import Attribute, Capability, Category, Command, SmartThings
from homeassistant.components.automation import automations_with_entity
from homeassistant.components.script import scripts_with_entity
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.issue_registry import (
IssueSeverity,
async_create_issue,
async_delete_issue,
)
from . import FullDevice, SmartThingsConfigEntry from . import FullDevice, SmartThingsConfigEntry
from .const import MAIN from .const import DOMAIN, MAIN
from .entity import SmartThingsEntity from .entity import SmartThingsEntity
CAPABILITIES = ( CAPABILITIES = (
@ -149,6 +157,62 @@ class SmartThingsSwitch(SmartThingsEntity, SwitchEntity):
== "on" == "on"
) )
async def async_added_to_hass(self) -> None:
"""Call when entity is added to hass."""
await super().async_added_to_hass()
if self.entity_description != SWITCH or self.device.device.components[
MAIN
].manufacturer_category not in {
Category.DRYER,
Category.WASHER,
Category.MICROWAVE,
Category.DISHWASHER,
}:
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_switch_{self.entity_id}",
breaks_in_ha_version="2025.10.0",
is_fixable=False,
severity=IssueSeverity.WARNING,
translation_key="deprecated_switch_appliance",
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.entity_description != SWITCH or self.device.device.components[
MAIN
].manufacturer_category not in {
Category.DRYER,
Category.WASHER,
Category.MICROWAVE,
Category.DISHWASHER,
}:
return
async_delete_issue(self.hass, DOMAIN, f"deprecated_switch_{self.entity_id}")
class SmartThingsCommandSwitch(SmartThingsSwitch): class SmartThingsCommandSwitch(SmartThingsSwitch):
"""Define a SmartThings command switch.""" """Define a SmartThings command switch."""

View File

@ -6,7 +6,10 @@ from pysmartthings import Attribute, Capability, Command
import pytest import pytest
from syrupy import SnapshotAssertion from syrupy import SnapshotAssertion
from homeassistant.components.smartthings.const import MAIN 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, MAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
@ -17,7 +20,8 @@ from homeassistant.const import (
Platform, Platform,
) )
from homeassistant.core import HomeAssistant 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 from . import setup_integration, snapshot_smartthings_entities, trigger_update
@ -120,3 +124,78 @@ async def test_state_update(
) )
assert hass.states.get("switch.2nd_floor_hallway").state == STATE_OFF assert hass.states.get("switch.2nd_floor_hallway").state == STATE_OFF
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
@pytest.mark.parametrize(
("device_fixture", "entity_id"),
[
("da_wm_wm_000001", "switch.washer"),
("da_wm_wd_000001", "switch.dryer"),
],
)
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_switch_{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_switch_appliance"
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