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 from itertools import chain
import logging import logging
import ssl import ssl
from typing import Any
import async_timeout import async_timeout
from pylutron_caseta import BUTTON_STATUS_PRESSED 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.const import ATTR_DEVICE_ID, ATTR_SUGGESTED_AREA, CONF_HOST, Platform
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady 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 import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import DeviceInfo, Entity from homeassistant.helpers.entity import DeviceInfo, Entity
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
@ -106,6 +107,33 @@ async def async_setup(hass: HomeAssistant, base_config: ConfigType) -> bool:
return True 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( async def async_setup_entry(
hass: HomeAssistant, config_entry: config_entries.ConfigEntry hass: HomeAssistant, config_entry: config_entries.ConfigEntry
) -> bool: ) -> bool:
@ -117,6 +145,8 @@ async def async_setup_entry(
ca_certs = hass.config.path(config_entry.data[CONF_CA_CERTS]) ca_certs = hass.config.path(config_entry.data[CONF_CA_CERTS])
bridge = None bridge = None
await _async_migrate_unique_ids(hass, config_entry)
try: try:
bridge = Smartbridge.create_tls( bridge = Smartbridge.create_tls(
hostname=host, keyfile=keyfile, certfile=certfile, ca_certs=ca_certs 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] bridge_device = devices[BRIDGE_DEVICE_ID]
if not config_entry.unique_id: if not config_entry.unique_id:
hass.config_entries.async_update_entry( 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 buttons = bridge.buttons
@ -312,6 +342,7 @@ class LutronCasetaDevice(Entity):
self._device = device self._device = device
self._smartbridge = bridge self._smartbridge = bridge
self._bridge_device = bridge_device self._bridge_device = bridge_device
self._bridge_unique_id = serial_to_unique_id(bridge_device["serial"])
if "serial" not in self._device: if "serial" not in self._device:
return return
area, name = _area_and_name_from_name(device["name"]) area, name = _area_and_name_from_name(device["name"])

View File

@ -6,14 +6,13 @@ from homeassistant.components.binary_sensor import (
BinarySensorEntity, BinarySensorEntity,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_SUGGESTED_AREA
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import DOMAIN as CASETA_DOMAIN, LutronCasetaDevice, _area_and_name_from_name 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( async def async_setup_entry(
@ -44,7 +43,9 @@ class LutronOccupancySensor(LutronCasetaDevice, BinarySensorEntity):
def __init__(self, device, bridge, bridge_device): def __init__(self, device, bridge, bridge_device):
"""Init an occupancy sensor.""" """Init an occupancy sensor."""
super().__init__(device, bridge, bridge_device) 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)}, identifiers={(CASETA_DOMAIN, self.unique_id)},
manufacturer=MANUFACTURER, manufacturer=MANUFACTURER,
model="Lutron Occupancy", model="Lutron Occupancy",
@ -53,10 +54,6 @@ class LutronOccupancySensor(LutronCasetaDevice, BinarySensorEntity):
configuration_url=CONFIG_URL, configuration_url=CONFIG_URL,
entry_type=DeviceEntryType.SERVICE, 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 @property
def is_on(self): def is_on(self):
@ -77,7 +74,7 @@ class LutronOccupancySensor(LutronCasetaDevice, BinarySensorEntity):
@property @property
def unique_id(self): def unique_id(self):
"""Return a unique identifier.""" """Return a unique identifier."""
return f"occupancygroup_{self.device_id}" return f"occupancygroup_{self._bridge_unique_id}_{self.device_id}"
@property @property
def extra_state_attributes(self): def extra_state_attributes(self):