From a0ed54465f25b4c6a4b78495174f8b18128a43d9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 14 Jun 2022 10:02:45 -1000 Subject: [PATCH] Migrate lutron caseta occupancygroup unique ids so they are actually unique (#73378) --- .../components/lutron_caseta/__init__.py | 35 +++++++++++++++++-- .../components/lutron_caseta/binary_sensor.py | 13 +++---- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/lutron_caseta/__init__.py b/homeassistant/components/lutron_caseta/__init__.py index d915c2a45cb..27d8ad87861 100644 --- a/homeassistant/components/lutron_caseta/__init__.py +++ b/homeassistant/components/lutron_caseta/__init__.py @@ -6,6 +6,7 @@ import contextlib from itertools import chain import logging import ssl +from typing import Any import async_timeout from pylutron_caseta import BUTTON_STATUS_PRESSED @@ -16,7 +17,7 @@ from homeassistant import config_entries from homeassistant.const import ATTR_DEVICE_ID, ATTR_SUGGESTED_AREA, CONF_HOST, Platform from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers import device_registry as dr +from homeassistant.helpers import device_registry as dr, entity_registry as er import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import DeviceInfo, Entity from homeassistant.helpers.typing import ConfigType @@ -106,6 +107,33 @@ async def async_setup(hass: HomeAssistant, base_config: ConfigType) -> bool: return True +async def _async_migrate_unique_ids( + hass: HomeAssistant, entry: config_entries.ConfigEntry +) -> None: + """Migrate entities since the occupancygroup were not actually unique.""" + + dev_reg = dr.async_get(hass) + bridge_unique_id = entry.unique_id + + @callback + def _async_migrator(entity_entry: er.RegistryEntry) -> dict[str, Any] | None: + if not (unique_id := entity_entry.unique_id): + return None + if not unique_id.startswith("occupancygroup_") or unique_id.startswith( + f"occupancygroup_{bridge_unique_id}" + ): + return None + sensor_id = unique_id.split("_")[1] + new_unique_id = f"occupancygroup_{bridge_unique_id}_{sensor_id}" + if dev_entry := dev_reg.async_get_device({(DOMAIN, unique_id)}): + dev_reg.async_update_device( + dev_entry.id, new_identifiers={(DOMAIN, new_unique_id)} + ) + return {"new_unique_id": f"occupancygroup_{bridge_unique_id}_{sensor_id}"} + + await er.async_migrate_entries(hass, entry.entry_id, _async_migrator) + + async def async_setup_entry( hass: HomeAssistant, config_entry: config_entries.ConfigEntry ) -> bool: @@ -117,6 +145,8 @@ async def async_setup_entry( ca_certs = hass.config.path(config_entry.data[CONF_CA_CERTS]) bridge = None + await _async_migrate_unique_ids(hass, config_entry) + try: bridge = Smartbridge.create_tls( hostname=host, keyfile=keyfile, certfile=certfile, ca_certs=ca_certs @@ -144,7 +174,7 @@ async def async_setup_entry( bridge_device = devices[BRIDGE_DEVICE_ID] if not config_entry.unique_id: hass.config_entries.async_update_entry( - config_entry, unique_id=hex(bridge_device["serial"])[2:].zfill(8) + config_entry, unique_id=serial_to_unique_id(bridge_device["serial"]) ) buttons = bridge.buttons @@ -312,6 +342,7 @@ class LutronCasetaDevice(Entity): self._device = device self._smartbridge = bridge self._bridge_device = bridge_device + self._bridge_unique_id = serial_to_unique_id(bridge_device["serial"]) if "serial" not in self._device: return area, name = _area_and_name_from_name(device["name"]) diff --git a/homeassistant/components/lutron_caseta/binary_sensor.py b/homeassistant/components/lutron_caseta/binary_sensor.py index 6a6e3853280..56a770c0b2e 100644 --- a/homeassistant/components/lutron_caseta/binary_sensor.py +++ b/homeassistant/components/lutron_caseta/binary_sensor.py @@ -6,14 +6,13 @@ from homeassistant.components.binary_sensor import ( BinarySensorEntity, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ATTR_SUGGESTED_AREA from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import DOMAIN as CASETA_DOMAIN, LutronCasetaDevice, _area_and_name_from_name -from .const import BRIDGE_DEVICE, BRIDGE_LEAP, CONFIG_URL, MANUFACTURER, UNASSIGNED_AREA +from .const import BRIDGE_DEVICE, BRIDGE_LEAP, CONFIG_URL, MANUFACTURER async def async_setup_entry( @@ -44,7 +43,9 @@ class LutronOccupancySensor(LutronCasetaDevice, BinarySensorEntity): def __init__(self, device, bridge, bridge_device): """Init an occupancy sensor.""" super().__init__(device, bridge, bridge_device) - info = DeviceInfo( + _, name = _area_and_name_from_name(device["name"]) + self._attr_name = name + self._attr_device_info = DeviceInfo( identifiers={(CASETA_DOMAIN, self.unique_id)}, manufacturer=MANUFACTURER, model="Lutron Occupancy", @@ -53,10 +54,6 @@ class LutronOccupancySensor(LutronCasetaDevice, BinarySensorEntity): configuration_url=CONFIG_URL, entry_type=DeviceEntryType.SERVICE, ) - area, _ = _area_and_name_from_name(device["name"]) - if area != UNASSIGNED_AREA: - info[ATTR_SUGGESTED_AREA] = area - self._attr_device_info = info @property def is_on(self): @@ -77,7 +74,7 @@ class LutronOccupancySensor(LutronCasetaDevice, BinarySensorEntity): @property def unique_id(self): """Return a unique identifier.""" - return f"occupancygroup_{self.device_id}" + return f"occupancygroup_{self._bridge_unique_id}_{self.device_id}" @property def extra_state_attributes(self):