diff --git a/homeassistant/components/knx/binary_sensor.py b/homeassistant/components/knx/binary_sensor.py index 455fd877c0e..d81a86970a4 100644 --- a/homeassistant/components/knx/binary_sensor.py +++ b/homeassistant/components/knx/binary_sensor.py @@ -37,6 +37,7 @@ class KNXBinarySensor(KnxEntity, BinarySensorEntity): """Initialize of KNX binary sensor.""" self._device: XknxBinarySensor super().__init__(device) + self._unique_id = f"{self._device.remote_value.group_address_state}" @property def device_class(self) -> str | None: diff --git a/homeassistant/components/knx/climate.py b/homeassistant/components/knx/climate.py index 63a8bd634d5..b059c86e8a0 100644 --- a/homeassistant/components/knx/climate.py +++ b/homeassistant/components/knx/climate.py @@ -6,6 +6,7 @@ from typing import Any, Callable from xknx.devices import Climate as XknxClimate from xknx.dpt.dpt_hvac_mode import HVACControllerMode, HVACOperationMode +from xknx.telegram.address import parse_device_group_address from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( @@ -16,12 +17,14 @@ from homeassistant.components.climate.const import ( SUPPORT_TARGET_TEMPERATURE, ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from .const import CONTROLLER_MODES, DOMAIN, PRESET_MODES from .knx_entity import KnxEntity +from .schema import ClimateSchema CONTROLLER_MODES_INV = {value: key for key, value in CONTROLLER_MODES.items()} PRESET_MODES_INV = {value: key for key, value in PRESET_MODES.items()} @@ -34,6 +37,7 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up climate(s) for KNX platform.""" + _async_migrate_unique_id(hass, discovery_info) entities = [] for device in hass.data[DOMAIN].xknx.devices: if isinstance(device, XknxClimate): @@ -41,6 +45,33 @@ async def async_setup_platform( async_add_entities(entities) +@callback +def _async_migrate_unique_id( + hass: HomeAssistant, discovery_info: DiscoveryInfoType | None +) -> None: + """Change unique_ids used in 2021.4 to include target_temperature GA.""" + entity_registry = er.async_get(hass) + if not discovery_info or not discovery_info["platform_config"]: + return + + platform_config = discovery_info["platform_config"] + for entity_config in platform_config: + # normalize group address strings - ga_temperature_state was the old uid + ga_temperature_state = parse_device_group_address( + entity_config[ClimateSchema.CONF_TEMPERATURE_ADDRESS][0] + ) + old_uid = str(ga_temperature_state) + + entity_id = entity_registry.async_get_entity_id("climate", DOMAIN, old_uid) + if entity_id is None: + continue + ga_target_temperature_state = parse_device_group_address( + entity_config[ClimateSchema.CONF_TARGET_TEMPERATURE_STATE_ADDRESS][0] + ) + new_uid = f"{ga_temperature_state}_{ga_target_temperature_state}" + entity_registry.async_update_entity(entity_id, new_unique_id=new_uid) + + class KNXClimate(KnxEntity, ClimateEntity): """Representation of a KNX climate device.""" @@ -48,7 +79,10 @@ class KNXClimate(KnxEntity, ClimateEntity): """Initialize of a KNX climate device.""" self._device: XknxClimate super().__init__(device) - + self._unique_id = ( + f"{device.temperature.group_address_state}_" + f"{device.target_temperature.group_address_state}" + ) self._unit_of_measurement = TEMP_CELSIUS @property diff --git a/homeassistant/components/knx/cover.py b/homeassistant/components/knx/cover.py index 10f1be57be4..d8983089a93 100644 --- a/homeassistant/components/knx/cover.py +++ b/homeassistant/components/knx/cover.py @@ -6,6 +6,7 @@ from datetime import datetime from typing import Any, Callable from xknx.devices import Cover as XknxCover, Device as XknxDevice +from xknx.telegram.address import parse_device_group_address from homeassistant.components.cover import ( ATTR_POSITION, @@ -23,12 +24,14 @@ from homeassistant.components.cover import ( CoverEntity, ) from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_utc_time_change from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from .const import DOMAIN from .knx_entity import KnxEntity +from .schema import CoverSchema async def async_setup_platform( @@ -38,6 +41,7 @@ async def async_setup_platform( discovery_info: DiscoveryInfoType | None = None, ) -> None: """Set up cover(s) for KNX platform.""" + _async_migrate_unique_id(hass, discovery_info) entities = [] for device in hass.data[DOMAIN].xknx.devices: if isinstance(device, XknxCover): @@ -45,6 +49,37 @@ async def async_setup_platform( async_add_entities(entities) +@callback +def _async_migrate_unique_id( + hass: HomeAssistant, discovery_info: DiscoveryInfoType | None +) -> None: + """Change unique_ids used in 2021.4 to include position_target GA.""" + entity_registry = er.async_get(hass) + if not discovery_info or not discovery_info["platform_config"]: + return + + platform_config = discovery_info["platform_config"] + for entity_config in platform_config: + # normalize group address strings - ga_updown was the old uid but is optional + updown_addresses = entity_config.get(CoverSchema.CONF_MOVE_LONG_ADDRESS) + if updown_addresses is None: + continue + ga_updown = parse_device_group_address(updown_addresses[0]) + old_uid = str(ga_updown) + + entity_id = entity_registry.async_get_entity_id("cover", DOMAIN, old_uid) + if entity_id is None: + continue + position_target_addresses = entity_config.get(CoverSchema.CONF_POSITION_ADDRESS) + ga_position_target = ( + parse_device_group_address(position_target_addresses[0]) + if position_target_addresses is not None + else None + ) + new_uid = f"{ga_updown}_{ga_position_target}" + entity_registry.async_update_entity(entity_id, new_unique_id=new_uid) + + class KNXCover(KnxEntity, CoverEntity): """Representation of a KNX cover.""" @@ -52,7 +87,9 @@ class KNXCover(KnxEntity, CoverEntity): """Initialize the cover.""" self._device: XknxCover super().__init__(device) - + self._unique_id = ( + f"{device.updown.group_address}_{device.position_target.group_address}" + ) self._unsubscribe_auto_updater: Callable[[], None] | None = None @callback diff --git a/homeassistant/components/knx/fan.py b/homeassistant/components/knx/fan.py index ca8ce74a52a..b526f727f7a 100644 --- a/homeassistant/components/knx/fan.py +++ b/homeassistant/components/knx/fan.py @@ -44,7 +44,7 @@ class KNXFan(KnxEntity, FanEntity): """Initialize of KNX fan.""" self._device: XknxFan super().__init__(device) - + self._unique_id = f"{self._device.speed.group_address}" self._step_range: tuple[int, int] | None = None if device.max_step: # FanSpeedMode.STEP: diff --git a/homeassistant/components/knx/knx_entity.py b/homeassistant/components/knx/knx_entity.py index 670f1ddf44d..1e374250bba 100644 --- a/homeassistant/components/knx/knx_entity.py +++ b/homeassistant/components/knx/knx_entity.py @@ -17,6 +17,7 @@ class KnxEntity(Entity): def __init__(self, device: XknxDevice) -> None: """Set up device.""" self._device = device + self._unique_id: str | None = None @property def name(self) -> str: @@ -37,7 +38,7 @@ class KnxEntity(Entity): @property def unique_id(self) -> str | None: """Return the unique id of the device.""" - return self._device.unique_id + return self._unique_id async def async_update(self) -> None: """Request a state update from KNX bus.""" diff --git a/homeassistant/components/knx/light.py b/homeassistant/components/knx/light.py index 26bd27baa75..63d7d40b7fc 100644 --- a/homeassistant/components/knx/light.py +++ b/homeassistant/components/knx/light.py @@ -52,7 +52,7 @@ class KNXLight(KnxEntity, LightEntity): """Initialize of KNX light.""" self._device: XknxLight super().__init__(device) - + self._unique_id = self._device_unique_id() self._min_kelvin = device.min_kelvin or LightSchema.DEFAULT_MIN_KELVIN self._max_kelvin = device.max_kelvin or LightSchema.DEFAULT_MAX_KELVIN self._min_mireds = color_util.color_temperature_kelvin_to_mired( @@ -62,6 +62,17 @@ class KNXLight(KnxEntity, LightEntity): self._min_kelvin ) + def _device_unique_id(self) -> str: + """Return unique id for this device.""" + if self._device.switch.group_address is not None: + return f"{self._device.switch.group_address}" + return ( + f"{self._device.red.switch.group_address}_" + f"{self._device.green.switch.group_address}_" + f"{self._device.blue.switch.group_address}_" + f"{self._device.white.switch.group_address}" + ) + @property def brightness(self) -> int | None: """Return the brightness of this light between 0..255.""" diff --git a/homeassistant/components/knx/scene.py b/homeassistant/components/knx/scene.py index d845cb94676..8aa55917973 100644 --- a/homeassistant/components/knx/scene.py +++ b/homeassistant/components/knx/scene.py @@ -36,6 +36,9 @@ class KNXScene(KnxEntity, Scene): """Init KNX scene.""" self._device: XknxScene super().__init__(device) + self._unique_id = ( + f"{self._device.scene_value.group_address}_{self._device.scene_number}" + ) async def async_activate(self, **kwargs: Any) -> None: """Activate the scene.""" diff --git a/homeassistant/components/knx/sensor.py b/homeassistant/components/knx/sensor.py index 51951304f93..190e0feb4b3 100644 --- a/homeassistant/components/knx/sensor.py +++ b/homeassistant/components/knx/sensor.py @@ -37,6 +37,7 @@ class KNXSensor(KnxEntity, SensorEntity): """Initialize of a KNX sensor.""" self._device: XknxSensor super().__init__(device) + self._unique_id = f"{self._device.sensor_value.group_address_state}" @property def state(self) -> StateType: diff --git a/homeassistant/components/knx/switch.py b/homeassistant/components/knx/switch.py index fa8a33cc5bb..6006b45c60c 100644 --- a/homeassistant/components/knx/switch.py +++ b/homeassistant/components/knx/switch.py @@ -53,6 +53,7 @@ class KNXSwitch(KnxEntity, SwitchEntity): invert=config[SwitchSchema.CONF_INVERT], ) ) + self._unique_id = f"{self._device.switch.group_address}" @property def is_on(self) -> bool: diff --git a/homeassistant/components/knx/weather.py b/homeassistant/components/knx/weather.py index 1a29e7d8e12..21cb6ddf55c 100644 --- a/homeassistant/components/knx/weather.py +++ b/homeassistant/components/knx/weather.py @@ -37,6 +37,7 @@ class KNXWeather(KnxEntity, WeatherEntity): """Initialize of a KNX sensor.""" self._device: XknxWeather super().__init__(device) + self._unique_id = f"{self._device._temperature.group_address_state}" @property def temperature(self) -> float | None: