Migrate unique IDs of Rituals Perfume Genie (#92342)

* Migrate unique IDs of Rituals Perfume Genie

* Fix doc string
This commit is contained in:
Franck Nijhof 2023-05-01 22:46:38 +02:00 committed by GitHub
parent 40896514eb
commit a7088e767e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 126 additions and 22 deletions

View File

@ -2,12 +2,13 @@
import asyncio import asyncio
import aiohttp import aiohttp
from pyrituals import Account from pyrituals import Account, Diffuser
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import ACCOUNT_HASH, DOMAIN from .const import ACCOUNT_HASH, DOMAIN
@ -32,6 +33,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
except aiohttp.ClientError as err: except aiohttp.ClientError as err:
raise ConfigEntryNotReady from err raise ConfigEntryNotReady from err
# Migrate old unique_ids to the new format
async_migrate_entities_unique_ids(hass, entry, account_devices)
# Create a coordinator for each diffuser # Create a coordinator for each diffuser
coordinators = { coordinators = {
diffuser.hublot: RitualsDataUpdateCoordinator(hass, diffuser) diffuser.hublot: RitualsDataUpdateCoordinator(hass, diffuser)
@ -59,3 +63,38 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data[DOMAIN].pop(entry.entry_id) hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok return unload_ok
@callback
def async_migrate_entities_unique_ids(
hass: HomeAssistant, config_entry: ConfigEntry, diffusers: list[Diffuser]
) -> 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
)
conversion: dict[tuple[str, str], str] = {
(Platform.BINARY_SENSOR, " Battery Charging"): "charging",
(Platform.NUMBER, " Perfume Amount"): "perfume_amount",
(Platform.SELECT, " Room Size"): "room_size_square_meter",
(Platform.SENSOR, " Battery"): "battery_percentage",
(Platform.SENSOR, " Fill"): "fill",
(Platform.SENSOR, " Perfume"): "perfume",
(Platform.SENSOR, " Wifi"): "wifi_percentage",
(Platform.SWITCH, ""): "is_on",
}
for diffuser in diffusers:
for registry_entry in registry_entries:
if new_unique_id := conversion.get(
(
registry_entry.domain,
registry_entry.unique_id.removeprefix(diffuser.hublot),
)
):
entity_registry.async_update_entity(
registry_entry.entity_id,
new_unique_id=f"{diffuser.hublot}-{new_unique_id}",
)

View File

@ -43,6 +43,7 @@ class DiffuserBatteryChargingBinarySensor(DiffuserEntity, BinarySensorEntity):
def __init__(self, coordinator: RitualsDataUpdateCoordinator) -> None: def __init__(self, coordinator: RitualsDataUpdateCoordinator) -> None:
"""Initialize the battery charging binary sensor.""" """Initialize the battery charging binary sensor."""
super().__init__(coordinator, CHARGING_SUFFIX) super().__init__(coordinator, CHARGING_SUFFIX)
self._attr_unique_id = f"{coordinator.diffuser.hublot}-charging"
@property @property
def is_on(self) -> bool: def is_on(self) -> bool:

View File

@ -26,7 +26,6 @@ class DiffuserEntity(CoordinatorEntity[RitualsDataUpdateCoordinator]):
hubname = coordinator.diffuser.name hubname = coordinator.diffuser.name
self._attr_name = f"{hubname}{entity_suffix}" self._attr_name = f"{hubname}{entity_suffix}"
self._attr_unique_id = f"{hublot}{entity_suffix}"
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, hublot)}, identifiers={(DOMAIN, hublot)},
manufacturer=MANUFACTURER, manufacturer=MANUFACTURER,

View File

@ -40,6 +40,7 @@ class DiffuserPerfumeAmount(DiffuserEntity, NumberEntity):
def __init__(self, coordinator: RitualsDataUpdateCoordinator) -> None: def __init__(self, coordinator: RitualsDataUpdateCoordinator) -> None:
"""Initialize the diffuser perfume amount number.""" """Initialize the diffuser perfume amount number."""
super().__init__(coordinator, PERFUME_AMOUNT_SUFFIX) super().__init__(coordinator, PERFUME_AMOUNT_SUFFIX)
self._attr_unique_id = f"{coordinator.diffuser.hublot}-perfume_amount"
@property @property
def native_value(self) -> int: def native_value(self) -> int:

View File

@ -43,6 +43,7 @@ class DiffuserRoomSize(DiffuserEntity, SelectEntity):
self._attr_entity_registry_enabled_default = ( self._attr_entity_registry_enabled_default = (
self.coordinator.diffuser.has_battery self.coordinator.diffuser.has_battery
) )
self._attr_unique_id = f"{coordinator.diffuser.hublot}-room_size_square_meter"
@property @property
def current_option(self) -> str: def current_option(self) -> str:

View File

@ -48,6 +48,7 @@ class DiffuserPerfumeSensor(DiffuserEntity, SensorEntity):
def __init__(self, coordinator: RitualsDataUpdateCoordinator) -> None: def __init__(self, coordinator: RitualsDataUpdateCoordinator) -> None:
"""Initialize the perfume sensor.""" """Initialize the perfume sensor."""
super().__init__(coordinator, PERFUME_SUFFIX) super().__init__(coordinator, PERFUME_SUFFIX)
self._attr_unique_id = f"{coordinator.diffuser.hublot}-perfume"
@property @property
def icon(self) -> str: def icon(self) -> str:
@ -68,6 +69,7 @@ class DiffuserFillSensor(DiffuserEntity, SensorEntity):
def __init__(self, coordinator: RitualsDataUpdateCoordinator) -> None: def __init__(self, coordinator: RitualsDataUpdateCoordinator) -> None:
"""Initialize the fill sensor.""" """Initialize the fill sensor."""
super().__init__(coordinator, FILL_SUFFIX) super().__init__(coordinator, FILL_SUFFIX)
self._attr_unique_id = f"{coordinator.diffuser.hublot}-fill"
@property @property
def icon(self) -> str: def icon(self) -> str:
@ -92,6 +94,7 @@ class DiffuserBatterySensor(DiffuserEntity, SensorEntity):
def __init__(self, coordinator: RitualsDataUpdateCoordinator) -> None: def __init__(self, coordinator: RitualsDataUpdateCoordinator) -> None:
"""Initialize the battery sensor.""" """Initialize the battery sensor."""
super().__init__(coordinator, BATTERY_SUFFIX) super().__init__(coordinator, BATTERY_SUFFIX)
self._attr_unique_id = f"{coordinator.diffuser.hublot}-battery_percentage"
@property @property
def native_value(self) -> int: def native_value(self) -> int:
@ -108,6 +111,7 @@ class DiffuserWifiSensor(DiffuserEntity, SensorEntity):
def __init__(self, coordinator: RitualsDataUpdateCoordinator) -> None: def __init__(self, coordinator: RitualsDataUpdateCoordinator) -> None:
"""Initialize the wifi sensor.""" """Initialize the wifi sensor."""
super().__init__(coordinator, WIFI_SUFFIX) super().__init__(coordinator, WIFI_SUFFIX)
self._attr_unique_id = f"{coordinator.diffuser.hublot}-wifi_percentage"
@property @property
def native_value(self) -> int: def native_value(self) -> int:

View File

@ -37,6 +37,7 @@ class DiffuserSwitch(DiffuserEntity, SwitchEntity):
"""Initialize the diffuser switch.""" """Initialize the diffuser switch."""
super().__init__(coordinator, "") super().__init__(coordinator, "")
self._attr_is_on = self.coordinator.diffuser.is_on self._attr_is_on = self.coordinator.diffuser.is_on
self._attr_unique_id = f"{coordinator.diffuser.hublot}-is_on"
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the device on.""" """Turn the device on."""

View File

@ -1,6 +1,5 @@
"""Tests for the Rituals Perfume Genie binary sensor platform.""" """Tests for the Rituals Perfume Genie binary sensor platform."""
from homeassistant.components.binary_sensor import BinarySensorDeviceClass from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from homeassistant.components.rituals_perfume_genie.binary_sensor import CHARGING_SUFFIX
from homeassistant.const import ATTR_DEVICE_CLASS, STATE_ON, EntityCategory from homeassistant.const import ATTR_DEVICE_CLASS, STATE_ON, EntityCategory
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
@ -30,5 +29,5 @@ async def test_binary_sensors(
entry = entity_registry.async_get("binary_sensor.genie_battery_charging") entry = entity_registry.async_get("binary_sensor.genie_battery_charging")
assert entry assert entry
assert entry.unique_id == f"{hublot}{CHARGING_SUFFIX}" assert entry.unique_id == f"{hublot}-charging"
assert entry.entity_category == EntityCategory.DIAGNOSTIC assert entry.entity_category == EntityCategory.DIAGNOSTIC

View File

@ -6,8 +6,13 @@ import aiohttp
from homeassistant.components.rituals_perfume_genie.const import DOMAIN from homeassistant.components.rituals_perfume_genie.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from .common import init_integration, mock_config_entry from .common import (
init_integration,
mock_config_entry,
mock_diffuser_v1_battery_cartridge,
)
async def test_config_entry_not_ready(hass: HomeAssistant) -> None: async def test_config_entry_not_ready(hass: HomeAssistant) -> None:
@ -32,3 +37,65 @@ async def test_config_entry_unload(hass: HomeAssistant) -> None:
assert config_entry.state is ConfigEntryState.NOT_LOADED assert config_entry.state is ConfigEntryState.NOT_LOADED
assert config_entry.entry_id not in hass.data[DOMAIN] assert config_entry.entry_id not in hass.data[DOMAIN]
async def test_entity_id_migration(
hass: HomeAssistant, entity_registry: er.RegistryEntry
) -> None:
"""Test the migration of unique IDs on config entry setup."""
config_entry = mock_config_entry(unique_id="binary_sensor_test_diffuser_v1")
# Pre-create old style unique IDs
charging = entity_registry.async_get_or_create(
"binary_sensor", DOMAIN, "lot123v1 Battery Charging", config_entry=config_entry
)
perfume_amount = entity_registry.async_get_or_create(
"number", DOMAIN, "lot123v1 Perfume Amount", config_entry=config_entry
)
room_size = entity_registry.async_get_or_create(
"select", DOMAIN, "lot123v1 Room Size", config_entry=config_entry
)
battery = entity_registry.async_get_or_create(
"sensor", DOMAIN, "lot123v1 Battery", config_entry=config_entry
)
fill = entity_registry.async_get_or_create(
"sensor", DOMAIN, "lot123v1 Fill", config_entry=config_entry
)
perfume = entity_registry.async_get_or_create(
"sensor", DOMAIN, "lot123v1 Perfume", config_entry=config_entry
)
wifi = entity_registry.async_get_or_create(
"sensor", DOMAIN, "lot123v1 Wifi", config_entry=config_entry
)
switch = entity_registry.async_get_or_create(
"switch", DOMAIN, "lot123v1", config_entry=config_entry
)
# Set up integration
diffuser = mock_diffuser_v1_battery_cartridge()
await init_integration(hass, config_entry, [diffuser])
# Check that old style unique IDs have been migrated
entry = entity_registry.async_get(charging.entity_id)
assert entry.unique_id == "lot123v1-charging"
entry = entity_registry.async_get(perfume_amount.entity_id)
assert entry.unique_id == "lot123v1-perfume_amount"
entry = entity_registry.async_get(room_size.entity_id)
assert entry.unique_id == "lot123v1-room_size_square_meter"
entry = entity_registry.async_get(battery.entity_id)
assert entry.unique_id == "lot123v1-battery_percentage"
entry = entity_registry.async_get(fill.entity_id)
assert entry.unique_id == "lot123v1-fill"
entry = entity_registry.async_get(perfume.entity_id)
assert entry.unique_id == "lot123v1-perfume"
entry = entity_registry.async_get(wifi.entity_id)
assert entry.unique_id == "lot123v1-wifi_percentage"
entry = entity_registry.async_get(switch.entity_id)
assert entry.unique_id == "lot123v1-is_on"

View File

@ -14,7 +14,6 @@ from homeassistant.components.number import (
from homeassistant.components.rituals_perfume_genie.number import ( from homeassistant.components.rituals_perfume_genie.number import (
MAX_PERFUME_AMOUNT, MAX_PERFUME_AMOUNT,
MIN_PERFUME_AMOUNT, MIN_PERFUME_AMOUNT,
PERFUME_AMOUNT_SUFFIX,
) )
from homeassistant.const import ATTR_ENTITY_ID, ATTR_ICON from homeassistant.const import ATTR_ENTITY_ID, ATTR_ICON
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -46,7 +45,7 @@ async def test_number_entity(
entry = entity_registry.async_get("number.genie_perfume_amount") entry = entity_registry.async_get("number.genie_perfume_amount")
assert entry assert entry
assert entry.unique_id == f"{diffuser.hublot}{PERFUME_AMOUNT_SUFFIX}" assert entry.unique_id == f"{diffuser.hublot}-perfume_amount"
async def test_set_number_value(hass: HomeAssistant) -> None: async def test_set_number_value(hass: HomeAssistant) -> None:

View File

@ -2,7 +2,6 @@
import pytest import pytest
from homeassistant.components.homeassistant import SERVICE_UPDATE_ENTITY from homeassistant.components.homeassistant import SERVICE_UPDATE_ENTITY
from homeassistant.components.rituals_perfume_genie.select import ROOM_SIZE_SUFFIX
from homeassistant.components.select import ( from homeassistant.components.select import (
ATTR_OPTION, ATTR_OPTION,
ATTR_OPTIONS, ATTR_OPTIONS,
@ -38,7 +37,7 @@ async def test_select_entity(
entry = entity_registry.async_get("select.genie_room_size") entry = entity_registry.async_get("select.genie_room_size")
assert entry assert entry
assert entry.unique_id == f"{diffuser.hublot}{ROOM_SIZE_SUFFIX}" assert entry.unique_id == f"{diffuser.hublot}-room_size_square_meter"
assert entry.unit_of_measurement == AREA_SQUARE_METERS assert entry.unit_of_measurement == AREA_SQUARE_METERS
assert entry.entity_category == EntityCategory.CONFIG assert entry.entity_category == EntityCategory.CONFIG

View File

@ -1,11 +1,5 @@
"""Tests for the Rituals Perfume Genie sensor platform.""" """Tests for the Rituals Perfume Genie sensor platform."""
from homeassistant.components.rituals_perfume_genie.sensor import ( from homeassistant.components.rituals_perfume_genie.sensor import SensorDeviceClass
BATTERY_SUFFIX,
FILL_SUFFIX,
PERFUME_SUFFIX,
WIFI_SUFFIX,
SensorDeviceClass,
)
from homeassistant.const import ( from homeassistant.const import (
ATTR_DEVICE_CLASS, ATTR_DEVICE_CLASS,
ATTR_ICON, ATTR_ICON,
@ -40,7 +34,7 @@ async def test_sensors_diffuser_v1_battery_cartridge(
entry = entity_registry.async_get("sensor.genie_perfume") entry = entity_registry.async_get("sensor.genie_perfume")
assert entry assert entry
assert entry.unique_id == f"{hublot}{PERFUME_SUFFIX}" assert entry.unique_id == f"{hublot}-perfume"
state = hass.states.get("sensor.genie_fill") state = hass.states.get("sensor.genie_fill")
assert state assert state
@ -49,7 +43,7 @@ async def test_sensors_diffuser_v1_battery_cartridge(
entry = entity_registry.async_get("sensor.genie_fill") entry = entity_registry.async_get("sensor.genie_fill")
assert entry assert entry
assert entry.unique_id == f"{hublot}{FILL_SUFFIX}" assert entry.unique_id == f"{hublot}-fill"
state = hass.states.get("sensor.genie_battery") state = hass.states.get("sensor.genie_battery")
assert state assert state
@ -59,7 +53,7 @@ async def test_sensors_diffuser_v1_battery_cartridge(
entry = entity_registry.async_get("sensor.genie_battery") entry = entity_registry.async_get("sensor.genie_battery")
assert entry assert entry
assert entry.unique_id == f"{hublot}{BATTERY_SUFFIX}" assert entry.unique_id == f"{hublot}-battery_percentage"
assert entry.entity_category == EntityCategory.DIAGNOSTIC assert entry.entity_category == EntityCategory.DIAGNOSTIC
state = hass.states.get("sensor.genie_wifi") state = hass.states.get("sensor.genie_wifi")
@ -70,7 +64,7 @@ async def test_sensors_diffuser_v1_battery_cartridge(
entry = entity_registry.async_get("sensor.genie_wifi") entry = entity_registry.async_get("sensor.genie_wifi")
assert entry assert entry
assert entry.unique_id == f"{hublot}{WIFI_SUFFIX}" assert entry.unique_id == f"{hublot}-wifi_percentage"
assert entry.entity_category == EntityCategory.DIAGNOSTIC assert entry.entity_category == EntityCategory.DIAGNOSTIC

View File

@ -38,7 +38,7 @@ async def test_switch_entity(
entry = entity_registry.async_get("switch.genie") entry = entity_registry.async_get("switch.genie")
assert entry assert entry
assert entry.unique_id == diffuser.hublot assert entry.unique_id == f"{diffuser.hublot}-is_on"
async def test_switch_handle_coordinator_update(hass: HomeAssistant) -> None: async def test_switch_handle_coordinator_update(hass: HomeAssistant) -> None: