Use switch format unique ids for tplink dimmers (#57346)
parent
a58085639e
commit
5b3711ed19
|
@ -11,6 +11,7 @@ import voluptuous as vol
|
|||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import network
|
||||
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryNotReady
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
|
@ -19,7 +20,11 @@ from homeassistant.const import (
|
|||
EVENT_HOMEASSISTANT_STARTED,
|
||||
)
|
||||
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.typing import ConfigType
|
||||
|
||||
|
@ -158,12 +163,52 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
except SmartDeviceException as 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.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
|
||||
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:
|
||||
"""Unload a config entry."""
|
||||
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,
|
||||
)
|
||||
|
||||
from . import legacy_device_id
|
||||
from .const import DOMAIN
|
||||
from .coordinator import TPLinkDataUpdateCoordinator
|
||||
from .entity import CoordinatedTPLinkEntity, async_refresh_after
|
||||
|
@ -58,7 +59,14 @@ class TPLinkSmartBulb(CoordinatedTPLinkEntity, LightEntity):
|
|||
"""Initialize the switch."""
|
||||
super().__init__(device, coordinator)
|
||||
# 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 def async_turn_on(self, **kwargs: Any) -> None:
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
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.protocol import TPLinkSmartHomeProtocol
|
||||
|
||||
|
@ -48,6 +48,33 @@ def _mocked_bulb() -> SmartBulb:
|
|||
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:
|
||||
plug = MagicMock(auto_spec=SmartPlug)
|
||||
plug.update = AsyncMock()
|
||||
|
|
|
@ -4,14 +4,23 @@ from __future__ import annotations
|
|||
from datetime import timedelta
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from homeassistant import setup
|
||||
from homeassistant.components import tplink
|
||||
from homeassistant.components.tplink.const import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
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.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
|
||||
|
||||
|
@ -63,3 +72,73 @@ async def test_config_entry_retry(hass):
|
|||
await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
|
||||
await hass.async_block_till_done()
|
||||
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…
Reference in New Issue