Migrate lutron caseta occupancygroup unique ids so they are actually unique (#73378)

This commit is contained in:
J. Nick Koston 2022-06-14 10:02:45 -10:00 committed by GitHub
parent 8e6fa54e0a
commit a0ed54465f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 38 additions and 10 deletions

View File

@ -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"])

View File

@ -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):