diff --git a/homeassistant/components/plugwise/const.py b/homeassistant/components/plugwise/const.py index d8d0a040d5f..62eabd8b0de 100644 --- a/homeassistant/components/plugwise/const.py +++ b/homeassistant/components/plugwise/const.py @@ -62,6 +62,5 @@ FLAME_ICON = "mdi:fire" FLOW_OFF_ICON = "mdi:water-pump-off" FLOW_ON_ICON = "mdi:water-pump" IDLE_ICON = "mdi:circle-off-outline" -SWITCH_ICON = "mdi:electric-switch" NO_NOTIFICATION_ICON = "mdi:mailbox-outline" NOTIFICATION_ICON = "mdi:mailbox-up-outline" diff --git a/homeassistant/components/plugwise/gateway.py b/homeassistant/components/plugwise/gateway.py index 6e518aad490..05ef937c6fd 100644 --- a/homeassistant/components/plugwise/gateway.py +++ b/homeassistant/components/plugwise/gateway.py @@ -2,16 +2,19 @@ from __future__ import annotations import asyncio +from typing import Any from plugwise.exceptions import InvalidAuthentication, PlugwiseException from plugwise.smile import Smile +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry as dr from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.entity_registry import RegistryEntry, async_migrate_entries from .const import ( DEFAULT_PORT, @@ -26,6 +29,8 @@ from .coordinator import PlugwiseDataUpdateCoordinator async def async_setup_entry_gw(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Plugwise Smiles from a config entry.""" + await async_migrate_entries(hass, entry.entry_id, async_migrate_entity_entry) + websession = async_get_clientsession(hass, verify_ssl=False) api = Smile( host=entry.data[CONF_HOST], @@ -88,3 +93,16 @@ async def async_unload_entry_gw(hass: HomeAssistant, entry: ConfigEntry): ): hass.data[DOMAIN].pop(entry.entry_id) return unload_ok + + +@callback +def async_migrate_entity_entry(entry: RegistryEntry) -> dict[str, Any] | None: + """Migrate Plugwise entity entries. + + - Migrates unique ID from old relay switches to the new unique ID + """ + if entry.domain == SWITCH_DOMAIN and entry.unique_id.endswith("-plug"): + return {"new_unique_id": entry.unique_id.replace("-plug", "-relay")} + + # No migration needed + return None diff --git a/homeassistant/components/plugwise/switch.py b/homeassistant/components/plugwise/switch.py index 98ba1cd8183..c95474a2b5a 100644 --- a/homeassistant/components/plugwise/switch.py +++ b/homeassistant/components/plugwise/switch.py @@ -5,15 +5,23 @@ from typing import Any from plugwise.exceptions import PlugwiseException -from homeassistant.components.switch import SwitchEntity +from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DOMAIN, LOGGER, SWITCH_ICON +from .const import DOMAIN, LOGGER from .coordinator import PlugwiseDataUpdateCoordinator from .entity import PlugwiseEntity +SWITCHES: tuple[SwitchEntityDescription, ...] = ( + SwitchEntityDescription( + key="relay", + name="Relay", + icon="mdi:electric-switch", + ), +) + async def async_setup_entry( hass: HomeAssistant, @@ -22,35 +30,38 @@ async def async_setup_entry( ) -> None: """Set up the Smile switches from a config entry.""" coordinator = hass.data[DOMAIN][config_entry.entry_id] - async_add_entities( - PlugwiseSwitchEntity(coordinator, device_id) - for device_id, device in coordinator.data.devices.items() - if "switches" in device and "relay" in device["switches"] - ) + entities: list[PlugwiseSwitchEntity] = [] + for device_id, device in coordinator.data.devices.items(): + for description in SWITCHES: + if "switches" not in device or description.key not in device["switches"]: + continue + entities.append(PlugwiseSwitchEntity(coordinator, device_id, description)) + async_add_entities(entities) class PlugwiseSwitchEntity(PlugwiseEntity, SwitchEntity): """Representation of a Plugwise plug.""" - _attr_icon = SWITCH_ICON - def __init__( self, coordinator: PlugwiseDataUpdateCoordinator, device_id: str, + description: SwitchEntityDescription, ) -> None: """Set up the Plugwise API.""" super().__init__(coordinator, device_id) - self._attr_unique_id = f"{device_id}-plug" - self._members = coordinator.data.devices[device_id].get("members") - self._attr_is_on = False + self.entity_description = description + self._attr_unique_id = f"{device_id}-{description.key}" self._attr_name = coordinator.data.devices[device_id].get("name") async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" try: state_on = await self.coordinator.api.set_switch_state( - self._dev_id, self._members, "relay", "on" + self._dev_id, + self.coordinator.data.devices[self._dev_id].get("members"), + self.entity_description.key, + "on", ) except PlugwiseException: LOGGER.error("Error while communicating to device") @@ -63,7 +74,10 @@ class PlugwiseSwitchEntity(PlugwiseEntity, SwitchEntity): """Turn the device off.""" try: state_off = await self.coordinator.api.set_switch_state( - self._dev_id, self._members, "relay", "off" + self._dev_id, + self.coordinator.data.devices[self._dev_id].get("members"), + self.entity_description.key, + "off", ) except PlugwiseException: LOGGER.error("Error while communicating to device") @@ -80,5 +94,5 @@ class PlugwiseSwitchEntity(PlugwiseEntity, SwitchEntity): super()._handle_coordinator_update() return - self._attr_is_on = data["switches"].get("relay") + self._attr_is_on = data["switches"].get(self.entity_description.key) super()._handle_coordinator_update() diff --git a/tests/components/plugwise/test_switch.py b/tests/components/plugwise/test_switch.py index 1e1fb4a0679..4d09489944d 100644 --- a/tests/components/plugwise/test_switch.py +++ b/tests/components/plugwise/test_switch.py @@ -2,8 +2,12 @@ from plugwise.exceptions import PlugwiseException +from homeassistant.components.plugwise.const import DOMAIN +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.config_entries import ConfigEntryState +from homeassistant.helpers import entity_registry as er +from tests.common import MockConfigEntry from tests.components.plugwise.common import async_init_integration @@ -121,3 +125,45 @@ async def test_stretch_switch_changes(hass, mock_stretch): ) state = hass.states.get("switch.droger_52559") assert str(state.state) == "on" + + +async def test_unique_id_migration_plug_relay(hass, mock_smile_adam): + """Test unique ID migration of -plugs to -relay.""" + entry = MockConfigEntry( + domain=DOMAIN, data={"host": "1.1.1.1", "password": "test-password"} + ) + entry.add_to_hass(hass) + + registry = er.async_get(hass) + # Entry to migrate + registry.async_get_or_create( + SWITCH_DOMAIN, + DOMAIN, + "21f2b542c49845e6bb416884c55778d6-plug", + config_entry=entry, + suggested_object_id="playstation_smart_plug", + disabled_by=None, + ) + # Entry not needing migration + registry.async_get_or_create( + SWITCH_DOMAIN, + DOMAIN, + "675416a629f343c495449970e2ca37b5-relay", + config_entry=entry, + suggested_object_id="router", + disabled_by=None, + ) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert hass.states.get("switch.playstation_smart_plug") is not None + assert hass.states.get("switch.router") is not None + + entity_entry = registry.async_get("switch.playstation_smart_plug") + assert entity_entry + assert entity_entry.unique_id == "21f2b542c49845e6bb416884c55778d6-relay" + + entity_entry = registry.async_get("switch.router") + assert entity_entry + assert entity_entry.unique_id == "675416a629f343c495449970e2ca37b5-relay"