Use switch format unique ids for tplink dimmers (#57346)
This commit is contained in:
parent
a58085639e
commit
5b3711ed19
4 changed files with 163 additions and 4 deletions
|
@ -11,6 +11,7 @@ import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components import network
|
from homeassistant.components import network
|
||||||
|
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
|
||||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryNotReady
|
from homeassistant.config_entries import ConfigEntry, ConfigEntryNotReady
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_HOST,
|
CONF_HOST,
|
||||||
|
@ -19,7 +20,11 @@ from homeassistant.const import (
|
||||||
EVENT_HOMEASSISTANT_STARTED,
|
EVENT_HOMEASSISTANT_STARTED,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers import config_validation as cv, device_registry as dr
|
from homeassistant.helpers import (
|
||||||
|
config_validation as cv,
|
||||||
|
device_registry as dr,
|
||||||
|
entity_registry as er,
|
||||||
|
)
|
||||||
from homeassistant.helpers.event import async_track_time_interval
|
from homeassistant.helpers.event import async_track_time_interval
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
|
@ -158,12 +163,52 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
except SmartDeviceException as ex:
|
except SmartDeviceException as ex:
|
||||||
raise ConfigEntryNotReady from ex
|
raise ConfigEntryNotReady from ex
|
||||||
|
|
||||||
|
if device.is_dimmer:
|
||||||
|
async_fix_dimmer_unique_id(hass, entry, device)
|
||||||
|
|
||||||
hass.data[DOMAIN][entry.entry_id] = TPLinkDataUpdateCoordinator(hass, device)
|
hass.data[DOMAIN][entry.entry_id] = TPLinkDataUpdateCoordinator(hass, device)
|
||||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_fix_dimmer_unique_id(
|
||||||
|
hass: HomeAssistant, entry: ConfigEntry, device: SmartDevice
|
||||||
|
) -> None:
|
||||||
|
"""Migrate the unique id of dimmers back to the legacy one.
|
||||||
|
|
||||||
|
Dimmers used to use the switch format since pyHS100 treated them as SmartPlug but
|
||||||
|
the old code created them as lights
|
||||||
|
|
||||||
|
https://github.com/home-assistant/core/blob/2021.9.7/homeassistant/components/tplink/common.py#L86
|
||||||
|
"""
|
||||||
|
|
||||||
|
# This is the unique id before 2021.0/2021.1
|
||||||
|
original_unique_id = legacy_device_id(device)
|
||||||
|
|
||||||
|
# This is the unique id that was used in 2021.0/2021.1 rollout
|
||||||
|
rollout_unique_id = device.mac.replace(":", "").upper()
|
||||||
|
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
|
||||||
|
rollout_entity_id = entity_registry.async_get_entity_id(
|
||||||
|
LIGHT_DOMAIN, DOMAIN, rollout_unique_id
|
||||||
|
)
|
||||||
|
original_entry_id = entity_registry.async_get_entity_id(
|
||||||
|
LIGHT_DOMAIN, DOMAIN, original_unique_id
|
||||||
|
)
|
||||||
|
|
||||||
|
# If they are now using the 2021.0/2021.1 rollout entity id
|
||||||
|
# and have deleted the original entity id, we want to update that entity id
|
||||||
|
# so they don't end up with another _2 entity, but only if they deleted
|
||||||
|
# the original
|
||||||
|
if rollout_entity_id and not original_entry_id:
|
||||||
|
entity_registry.async_update_entity(
|
||||||
|
rollout_entity_id, new_unique_id=original_unique_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
hass_data: dict[str, Any] = hass.data[DOMAIN]
|
hass_data: dict[str, Any] = hass.data[DOMAIN]
|
||||||
|
|
|
@ -26,6 +26,7 @@ from homeassistant.util.color import (
|
||||||
color_temperature_mired_to_kelvin as mired_to_kelvin,
|
color_temperature_mired_to_kelvin as mired_to_kelvin,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from . import legacy_device_id
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .coordinator import TPLinkDataUpdateCoordinator
|
from .coordinator import TPLinkDataUpdateCoordinator
|
||||||
from .entity import CoordinatedTPLinkEntity, async_refresh_after
|
from .entity import CoordinatedTPLinkEntity, async_refresh_after
|
||||||
|
@ -58,7 +59,14 @@ class TPLinkSmartBulb(CoordinatedTPLinkEntity, LightEntity):
|
||||||
"""Initialize the switch."""
|
"""Initialize the switch."""
|
||||||
super().__init__(device, coordinator)
|
super().__init__(device, coordinator)
|
||||||
# For backwards compat with pyHS100
|
# For backwards compat with pyHS100
|
||||||
self._attr_unique_id = self.device.mac.replace(":", "").upper()
|
if self.device.is_dimmer:
|
||||||
|
# Dimmers used to use the switch format since
|
||||||
|
# pyHS100 treated them as SmartPlug but the old code
|
||||||
|
# created them as lights
|
||||||
|
# https://github.com/home-assistant/core/blob/2021.9.7/homeassistant/components/tplink/common.py#L86
|
||||||
|
self._attr_unique_id = legacy_device_id(device)
|
||||||
|
else:
|
||||||
|
self._attr_unique_id = self.device.mac.replace(":", "").upper()
|
||||||
|
|
||||||
@async_refresh_after
|
@async_refresh_after
|
||||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
from kasa import SmartBulb, SmartPlug, SmartStrip
|
from kasa import SmartBulb, SmartDimmer, SmartPlug, SmartStrip
|
||||||
from kasa.exceptions import SmartDeviceException
|
from kasa.exceptions import SmartDeviceException
|
||||||
from kasa.protocol import TPLinkSmartHomeProtocol
|
from kasa.protocol import TPLinkSmartHomeProtocol
|
||||||
|
|
||||||
|
@ -48,6 +48,33 @@ def _mocked_bulb() -> SmartBulb:
|
||||||
return bulb
|
return bulb
|
||||||
|
|
||||||
|
|
||||||
|
def _mocked_dimmer() -> SmartDimmer:
|
||||||
|
dimmer = MagicMock(auto_spec=SmartDimmer)
|
||||||
|
dimmer.update = AsyncMock()
|
||||||
|
dimmer.mac = MAC_ADDRESS
|
||||||
|
dimmer.alias = ALIAS
|
||||||
|
dimmer.model = MODEL
|
||||||
|
dimmer.host = IP_ADDRESS
|
||||||
|
dimmer.brightness = 50
|
||||||
|
dimmer.color_temp = 4000
|
||||||
|
dimmer.is_color = True
|
||||||
|
dimmer.is_strip = False
|
||||||
|
dimmer.is_plug = False
|
||||||
|
dimmer.is_dimmer = True
|
||||||
|
dimmer.hsv = (10, 30, 5)
|
||||||
|
dimmer.device_id = MAC_ADDRESS
|
||||||
|
dimmer.valid_temperature_range.min = 4000
|
||||||
|
dimmer.valid_temperature_range.max = 9000
|
||||||
|
dimmer.hw_info = {"sw_ver": "1.0.0"}
|
||||||
|
dimmer.turn_off = AsyncMock()
|
||||||
|
dimmer.turn_on = AsyncMock()
|
||||||
|
dimmer.set_brightness = AsyncMock()
|
||||||
|
dimmer.set_hsv = AsyncMock()
|
||||||
|
dimmer.set_color_temp = AsyncMock()
|
||||||
|
dimmer.protocol = _mock_protocol()
|
||||||
|
return dimmer
|
||||||
|
|
||||||
|
|
||||||
def _mocked_plug() -> SmartPlug:
|
def _mocked_plug() -> SmartPlug:
|
||||||
plug = MagicMock(auto_spec=SmartPlug)
|
plug = MagicMock(auto_spec=SmartPlug)
|
||||||
plug.update = AsyncMock()
|
plug.update = AsyncMock()
|
||||||
|
|
|
@ -4,14 +4,23 @@ from __future__ import annotations
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
from homeassistant import setup
|
||||||
from homeassistant.components import tplink
|
from homeassistant.components import tplink
|
||||||
from homeassistant.components.tplink.const import DOMAIN
|
from homeassistant.components.tplink.const import DOMAIN
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STARTED
|
from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STARTED
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity_registry import EntityRegistry
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from . import IP_ADDRESS, MAC_ADDRESS, _patch_discovery, _patch_single_discovery
|
from . import (
|
||||||
|
IP_ADDRESS,
|
||||||
|
MAC_ADDRESS,
|
||||||
|
_mocked_dimmer,
|
||||||
|
_patch_discovery,
|
||||||
|
_patch_single_discovery,
|
||||||
|
)
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
|
|
||||||
|
@ -63,3 +72,73 @@ async def test_config_entry_retry(hass):
|
||||||
await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
|
await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert already_migrated_config_entry.state == ConfigEntryState.SETUP_RETRY
|
assert already_migrated_config_entry.state == ConfigEntryState.SETUP_RETRY
|
||||||
|
|
||||||
|
|
||||||
|
async def test_dimmer_switch_unique_id_fix_original_entity_was_deleted(
|
||||||
|
hass: HomeAssistant, entity_reg: EntityRegistry
|
||||||
|
):
|
||||||
|
"""Test that roll out unique id entity id changed to the original unique id."""
|
||||||
|
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=MAC_ADDRESS)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
dimmer = _mocked_dimmer()
|
||||||
|
rollout_unique_id = MAC_ADDRESS.replace(":", "").upper()
|
||||||
|
original_unique_id = tplink.legacy_device_id(dimmer)
|
||||||
|
rollout_dimmer_entity_reg = entity_reg.async_get_or_create(
|
||||||
|
config_entry=config_entry,
|
||||||
|
platform=DOMAIN,
|
||||||
|
domain="light",
|
||||||
|
unique_id=rollout_unique_id,
|
||||||
|
original_name="Rollout dimmer",
|
||||||
|
)
|
||||||
|
|
||||||
|
with _patch_discovery(device=dimmer), _patch_single_discovery(device=dimmer):
|
||||||
|
await setup.async_setup_component(hass, DOMAIN, {})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
migrated_dimmer_entity_reg = entity_reg.async_get_or_create(
|
||||||
|
config_entry=config_entry,
|
||||||
|
platform=DOMAIN,
|
||||||
|
domain="light",
|
||||||
|
unique_id=original_unique_id,
|
||||||
|
original_name="Migrated dimmer",
|
||||||
|
)
|
||||||
|
assert migrated_dimmer_entity_reg.entity_id == rollout_dimmer_entity_reg.entity_id
|
||||||
|
|
||||||
|
|
||||||
|
async def test_dimmer_switch_unique_id_fix_original_entity_still_exists(
|
||||||
|
hass: HomeAssistant, entity_reg: EntityRegistry
|
||||||
|
):
|
||||||
|
"""Test no migration happens if the original entity id still exists."""
|
||||||
|
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=MAC_ADDRESS)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
dimmer = _mocked_dimmer()
|
||||||
|
rollout_unique_id = MAC_ADDRESS.replace(":", "").upper()
|
||||||
|
original_unique_id = tplink.legacy_device_id(dimmer)
|
||||||
|
original_dimmer_entity_reg = entity_reg.async_get_or_create(
|
||||||
|
config_entry=config_entry,
|
||||||
|
platform=DOMAIN,
|
||||||
|
domain="light",
|
||||||
|
unique_id=original_unique_id,
|
||||||
|
original_name="Original dimmer",
|
||||||
|
)
|
||||||
|
rollout_dimmer_entity_reg = entity_reg.async_get_or_create(
|
||||||
|
config_entry=config_entry,
|
||||||
|
platform=DOMAIN,
|
||||||
|
domain="light",
|
||||||
|
unique_id=rollout_unique_id,
|
||||||
|
original_name="Rollout dimmer",
|
||||||
|
)
|
||||||
|
|
||||||
|
with _patch_discovery(device=dimmer), _patch_single_discovery(device=dimmer):
|
||||||
|
await setup.async_setup_component(hass, DOMAIN, {})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
migrated_dimmer_entity_reg = entity_reg.async_get_or_create(
|
||||||
|
config_entry=config_entry,
|
||||||
|
platform=DOMAIN,
|
||||||
|
domain="light",
|
||||||
|
unique_id=original_unique_id,
|
||||||
|
original_name="Migrated dimmer",
|
||||||
|
)
|
||||||
|
assert migrated_dimmer_entity_reg.entity_id == original_dimmer_entity_reg.entity_id
|
||||||
|
assert migrated_dimmer_entity_reg.entity_id != rollout_dimmer_entity_reg.entity_id
|
||||||
|
|
Loading…
Add table
Reference in a new issue