Use mac address in Twinkly for unique id (#133717)
parent
a3fad89d0d
commit
dc9133f919
|
@ -1,6 +1,7 @@
|
|||
"""The twinkly component."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import ClientError
|
||||
|
@ -10,12 +11,15 @@ from homeassistant.config_entries import ConfigEntry
|
|||
from homeassistant.const import CONF_HOST, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import ATTR_VERSION
|
||||
from .const import ATTR_VERSION, DOMAIN
|
||||
|
||||
PLATFORMS = [Platform.LIGHT]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class TwinklyData:
|
||||
|
@ -56,3 +60,39 @@ async def async_unload_entry(hass: HomeAssistant, entry: TwinklyConfigEntry) ->
|
|||
"""Remove a twinkly entry."""
|
||||
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
async def async_migrate_entry(hass: HomeAssistant, entry: TwinklyConfigEntry) -> bool:
|
||||
"""Migrate old entry."""
|
||||
if entry.minor_version == 1:
|
||||
client = Twinkly(entry.data[CONF_HOST], async_get_clientsession(hass))
|
||||
try:
|
||||
device_info = await client.get_details()
|
||||
except (TimeoutError, ClientError) as exception:
|
||||
_LOGGER.error("Error while migrating: %s", exception)
|
||||
return False
|
||||
identifier = entry.unique_id
|
||||
assert identifier is not None
|
||||
entity_registry = er.async_get(hass)
|
||||
entity_id = entity_registry.async_get_entity_id("light", DOMAIN, identifier)
|
||||
if entity_id:
|
||||
entity_entry = entity_registry.async_get(entity_id)
|
||||
assert entity_entry is not None
|
||||
entity_registry.async_update_entity(
|
||||
entity_entry.entity_id, new_unique_id=device_info["mac"]
|
||||
)
|
||||
device_registry = dr.async_get(hass)
|
||||
device_entry = device_registry.async_get_device(
|
||||
identifiers={(DOMAIN, identifier)}
|
||||
)
|
||||
if device_entry:
|
||||
device_registry.async_update_device(
|
||||
device_entry.id, new_identifiers={(DOMAIN, device_info["mac"])}
|
||||
)
|
||||
hass.config_entries.async_update_entry(
|
||||
entry,
|
||||
unique_id=device_info["mac"],
|
||||
minor_version=2,
|
||||
)
|
||||
|
||||
return True
|
||||
|
|
|
@ -23,6 +23,7 @@ class TwinklyConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||
"""Handle twinkly config flow."""
|
||||
|
||||
VERSION = 1
|
||||
MINOR_VERSION = 2
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the config flow."""
|
||||
|
@ -46,7 +47,7 @@ class TwinklyConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||
errors[CONF_HOST] = "cannot_connect"
|
||||
else:
|
||||
await self.async_set_unique_id(
|
||||
device_info[DEV_ID], raise_on_progress=False
|
||||
device_info["mac"], raise_on_progress=False
|
||||
)
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
|
@ -64,7 +65,7 @@ class TwinklyConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||
device_info = await Twinkly(
|
||||
discovery_info.ip, async_get_clientsession(self.hass)
|
||||
).get_details()
|
||||
await self.async_set_unique_id(device_info[DEV_ID])
|
||||
await self.async_set_unique_id(device_info["mac"])
|
||||
self._abort_if_unique_id_configured(updates={CONF_HOST: discovery_info.ip})
|
||||
|
||||
self._discovered_device = (device_info, discovery_info.ip)
|
||||
|
|
|
@ -60,8 +60,8 @@ class TwinklyLight(LightEntity):
|
|||
entry: TwinklyConfigEntry,
|
||||
) -> None:
|
||||
"""Initialize a TwinklyLight entity."""
|
||||
self._attr_unique_id: str = entry.data[CONF_ID]
|
||||
device_info = entry.runtime_data.device_info
|
||||
self._attr_unique_id: str = device_info["mac"]
|
||||
self._conf = entry
|
||||
|
||||
if device_info.get(DEV_LED_PROFILE) == DEV_PROFILE_RGBW:
|
||||
|
@ -98,7 +98,7 @@ class TwinklyLight(LightEntity):
|
|||
def device_info(self) -> DeviceInfo | None:
|
||||
"""Get device specific attributes."""
|
||||
return DeviceInfo(
|
||||
identifiers={(DOMAIN, self._attr_unique_id)},
|
||||
identifiers={(DOMAIN, self._mac)},
|
||||
connections={(CONNECTION_NETWORK_MAC, self._mac)},
|
||||
manufacturer="LEDWORKS",
|
||||
model=self._model,
|
||||
|
|
|
@ -32,14 +32,14 @@
|
|||
}),
|
||||
'domain': 'twinkly',
|
||||
'entry_id': '4c8fccf5-e08a-4173-92d5-49bf479252a2',
|
||||
'minor_version': 1,
|
||||
'minor_version': 2,
|
||||
'options': dict({
|
||||
}),
|
||||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'title': 'Twinkly',
|
||||
'unique_id': '4c8fccf5-e08a-4173-92d5-49bf479252a2',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff',
|
||||
'version': 1,
|
||||
}),
|
||||
'sw_version': '2.8.10',
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
"""Tests of the initialization of the twinly integration."""
|
||||
"""Tests of the initialization of the twinkly integration."""
|
||||
|
||||
from unittest.mock import patch
|
||||
from uuid import uuid4
|
||||
|
||||
from homeassistant.components.twinkly.const import DOMAIN as TWINKLY_DOMAIN
|
||||
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
|
||||
from homeassistant.components.twinkly.const import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import CONF_HOST, CONF_ID, CONF_MODEL, CONF_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
|
||||
from . import TEST_HOST, TEST_MODEL, TEST_NAME_ORIGINAL, ClientMock
|
||||
from . import TEST_HOST, TEST_MAC, TEST_MODEL, TEST_NAME_ORIGINAL, ClientMock
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
@ -19,7 +21,7 @@ async def test_load_unload_entry(hass: HomeAssistant) -> None:
|
|||
|
||||
device_id = str(uuid4())
|
||||
config_entry = MockConfigEntry(
|
||||
domain=TWINKLY_DOMAIN,
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_HOST: TEST_HOST,
|
||||
CONF_ID: device_id,
|
||||
|
@ -27,6 +29,8 @@ async def test_load_unload_entry(hass: HomeAssistant) -> None:
|
|||
CONF_MODEL: TEST_MODEL,
|
||||
},
|
||||
entry_id=device_id,
|
||||
unique_id=TEST_MAC,
|
||||
minor_version=2,
|
||||
)
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
|
@ -47,13 +51,15 @@ async def test_config_entry_not_ready(hass: HomeAssistant) -> None:
|
|||
client.is_offline = True
|
||||
|
||||
config_entry = MockConfigEntry(
|
||||
domain=TWINKLY_DOMAIN,
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_HOST: TEST_HOST,
|
||||
CONF_ID: id,
|
||||
CONF_NAME: TEST_NAME_ORIGINAL,
|
||||
CONF_MODEL: TEST_MODEL,
|
||||
},
|
||||
minor_version=2,
|
||||
unique_id=TEST_MAC,
|
||||
)
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
|
@ -62,3 +68,45 @@ async def test_config_entry_not_ready(hass: HomeAssistant) -> None:
|
|||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
|
||||
assert config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
|
||||
|
||||
async def test_mac_migration(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
) -> None:
|
||||
"""Validate that the unique_id is migrated to the MAC address."""
|
||||
client = ClientMock()
|
||||
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
minor_version=1,
|
||||
unique_id="unique_id",
|
||||
data={
|
||||
CONF_HOST: TEST_HOST,
|
||||
CONF_ID: id,
|
||||
CONF_NAME: TEST_NAME_ORIGINAL,
|
||||
CONF_MODEL: TEST_MODEL,
|
||||
},
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
entity_entry = entity_registry.async_get_or_create(
|
||||
LIGHT_DOMAIN,
|
||||
DOMAIN,
|
||||
config_entry.unique_id,
|
||||
)
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
identifiers={(DOMAIN, config_entry.unique_id)},
|
||||
)
|
||||
|
||||
with patch("homeassistant.components.twinkly.Twinkly", return_value=client):
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
assert entity_registry.async_get(entity_entry.entity_id).unique_id == TEST_MAC
|
||||
assert device_registry.async_get_device(
|
||||
identifiers={(DOMAIN, config_entry.unique_id)}
|
||||
).identifiers == {(DOMAIN, TEST_MAC)}
|
||||
assert config_entry.unique_id == TEST_MAC
|
||||
|
|
|
@ -15,7 +15,7 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er
|
|||
from homeassistant.helpers.device_registry import DeviceEntry
|
||||
from homeassistant.helpers.entity_registry import RegistryEntry
|
||||
|
||||
from . import TEST_MODEL, TEST_NAME, TEST_NAME_ORIGINAL, ClientMock
|
||||
from . import TEST_MAC, TEST_MODEL, TEST_NAME, TEST_NAME_ORIGINAL, ClientMock
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
|
@ -301,7 +301,7 @@ async def test_update_name(
|
|||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
dev_entry = device_registry.async_get_device({(TWINKLY_DOMAIN, client.id)})
|
||||
dev_entry = device_registry.async_get_device({(TWINKLY_DOMAIN, TEST_MAC)})
|
||||
|
||||
assert dev_entry.name == "new_device_name"
|
||||
assert config_entry.data[CONF_NAME] == "new_device_name"
|
||||
|
@ -310,10 +310,9 @@ async def test_update_name(
|
|||
async def test_unload(hass: HomeAssistant) -> None:
|
||||
"""Validate that entities can be unloaded from the UI."""
|
||||
|
||||
_, _, client, _ = await _create_entries(hass)
|
||||
entry_id = client.id
|
||||
_, _, _, entry = await _create_entries(hass)
|
||||
|
||||
assert await hass.config_entries.async_unload(entry_id)
|
||||
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||
|
||||
|
||||
async def _create_entries(
|
||||
|
@ -330,18 +329,19 @@ async def _create_entries(
|
|||
CONF_NAME: TEST_NAME_ORIGINAL,
|
||||
CONF_MODEL: TEST_MODEL,
|
||||
},
|
||||
entry_id=client.id,
|
||||
unique_id=TEST_MAC,
|
||||
minor_version=2,
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(client.id)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device_registry = dr.async_get(hass)
|
||||
entity_registry = er.async_get(hass)
|
||||
|
||||
entity_id = entity_registry.async_get_entity_id("light", TWINKLY_DOMAIN, client.id)
|
||||
entity_id = entity_registry.async_get_entity_id("light", TWINKLY_DOMAIN, TEST_MAC)
|
||||
entity_entry = entity_registry.async_get(entity_id)
|
||||
device = device_registry.async_get_device(identifiers={(TWINKLY_DOMAIN, client.id)})
|
||||
device = device_registry.async_get_device(identifiers={(TWINKLY_DOMAIN, TEST_MAC)})
|
||||
|
||||
assert entity_entry is not None
|
||||
assert device is not None
|
||||
|
|
Loading…
Reference in New Issue