From c6157d55208017a03cc209e950e51776ac146be8 Mon Sep 17 00:00:00 2001
From: Franck Nijhof <git@frenck.dev>
Date: Fri, 29 Oct 2021 06:08:59 +0200
Subject: [PATCH] Migrate Tuya unique IDs for switches & lights (#58631)

---
 homeassistant/components/tuya/__init__.py | 85 ++++++++++++++++++++++-
 1 file changed, 84 insertions(+), 1 deletion(-)

diff --git a/homeassistant/components/tuya/__init__.py b/homeassistant/components/tuya/__init__.py
index bcc5c9dc79c..4f34d3c31bf 100644
--- a/homeassistant/components/tuya/__init__.py
+++ b/homeassistant/components/tuya/__init__.py
@@ -14,9 +14,11 @@ from tuya_iot import (
     TuyaOpenMQ,
 )
 
+from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
+from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
 from homeassistant.config_entries import ConfigEntry
 from homeassistant.core import HomeAssistant, callback
-from homeassistant.helpers import device_registry as dr
+from homeassistant.helpers import device_registry as dr, entity_registry as er
 from homeassistant.helpers.dispatcher import dispatcher_send
 
 from .const import (
@@ -33,6 +35,7 @@ from .const import (
     PLATFORMS,
     TUYA_DISCOVERY_NEW,
     TUYA_HA_SIGNAL_UPDATE_ENTITY,
+    DPCode,
 )
 
 _LOGGER = logging.getLogger(__name__)
@@ -115,6 +118,9 @@ async def _init_tuya_sdk(hass: HomeAssistant, entry: ConfigEntry) -> bool:
     await hass.async_add_executor_job(home_manager.update_device_cache)
     await cleanup_device_registry(hass, device_manager)
 
+    # Migrate old unique_ids to the new format
+    async_migrate_entities_unique_ids(hass, entry, device_manager)
+
     # Register known device IDs
     device_registry = dr.async_get(hass)
     for device in device_manager.device_map.values():
@@ -143,6 +149,83 @@ async def cleanup_device_registry(
                 break
 
 
+@callback
+def async_migrate_entities_unique_ids(
+    hass: HomeAssistant, config_entry: ConfigEntry, device_manager: TuyaDeviceManager
+) -> None:
+    """Migrate unique_ids in the entity registry to the new format."""
+    entity_registry = er.async_get(hass)
+    registry_entries = er.async_entries_for_config_entry(
+        entity_registry, config_entry.entry_id
+    )
+    light_entries = {
+        entry.unique_id: entry
+        for entry in registry_entries
+        if entry.domain == LIGHT_DOMAIN
+    }
+    switch_entries = {
+        entry.unique_id: entry
+        for entry in registry_entries
+        if entry.domain == SWITCH_DOMAIN
+    }
+
+    for device in device_manager.device_map.values():
+        # Old lights where in `tuya.{device_id}` format, now the DPCode is added.
+        #
+        # If the device is a previously supported light category and still has
+        # the old format for the unique ID, migrate it to the new format.
+        #
+        # Previously only devices providing the SWITCH_LED DPCode were supported,
+        # thus this can be added to those existing IDs.
+        #
+        # `tuya.{device_id}` -> `tuya.{device_id}{SWITCH_LED}`
+        if (
+            device.category in ("dc", "dd", "dj", "fs", "fwl", "jsq", "xdd", "xxj")
+            and (entry := light_entries.get(f"tuya.{device.id}"))
+            and f"tuya.{device.id}{DPCode.SWITCH_LED}" not in light_entries
+        ):
+            entity_registry.async_update_entity(
+                entry.entity_id, new_unique_id=f"tuya.{device.id}{DPCode.SWITCH_LED}"
+            )
+
+        # Old switches has different formats for the unique ID, but is mappable.
+        #
+        # If the device is a previously supported switch category and still has
+        # the old format for the unique ID, migrate it to the new format.
+        #
+        # `tuya.{device_id}` -> `tuya.{device_id}{SWITCH}`
+        # `tuya.{device_id}_1` -> `tuya.{device_id}{SWITCH_1}`
+        # ...
+        # `tuya.{device_id}_6` -> `tuya.{device_id}{SWITCH_6}`
+        # `tuya.{device_id}_usb1` -> `tuya.{device_id}{SWITCH_USB1}`
+        # ...
+        # `tuya.{device_id}_usb6` -> `tuya.{device_id}{SWITCH_USB6}`
+        #
+        # In all other cases, the unique ID is not changed.
+        if device.category in ("bh", "cwysj", "cz", "dlq", "kg", "kj", "pc", "xxj"):
+            for postfix, dpcode in (
+                ("", DPCode.SWITCH),
+                ("_1", DPCode.SWITCH_1),
+                ("_2", DPCode.SWITCH_2),
+                ("_3", DPCode.SWITCH_3),
+                ("_4", DPCode.SWITCH_4),
+                ("_5", DPCode.SWITCH_5),
+                ("_6", DPCode.SWITCH_6),
+                ("_usb1", DPCode.SWITCH_USB1),
+                ("_usb2", DPCode.SWITCH_USB2),
+                ("_usb3", DPCode.SWITCH_USB3),
+                ("_usb4", DPCode.SWITCH_USB4),
+                ("_usb5", DPCode.SWITCH_USB5),
+                ("_usb6", DPCode.SWITCH_USB6),
+            ):
+                if (
+                    entry := switch_entries.get(f"tuya.{device.id}{postfix}")
+                ) and f"tuya.{device.id}{dpcode}" not in switch_entries:
+                    entity_registry.async_update_entity(
+                        entry.entity_id, new_unique_id=f"tuya.{device.id}{dpcode}"
+                    )
+
+
 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
     """Unloading the Tuya platforms."""
     unload = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
-- 
GitLab