From eac5a4dfcfc4283f2201f15d6497d06f88ef3236 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 30 Jun 2026 18:57:51 +0200 Subject: [PATCH 01/12] Correct climate unique_id --- custom_components/plugwise/climate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/plugwise/climate.py b/custom_components/plugwise/climate.py index 4dc857e04..1c16707a7 100644 --- a/custom_components/plugwise/climate.py +++ b/custom_components/plugwise/climate.py @@ -143,7 +143,7 @@ def __init__( self._attr_target_temperature_step = max( self.device.get(THERMOSTAT, {}).get(RESOLUTION, 0.5), 0.1 ) - self._attr_unique_id = f"{device_id}-climate" + self._attr_unique_id = f"{device_id}-{self.device[ATTR_NAME]}" # Determine supported features self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE From ffb8e9860f7eead12b8bcce55a0a759312d48c16 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 30 Jun 2026 19:08:20 +0200 Subject: [PATCH 02/12] Migrate to the updated climate unique_id --- custom_components/plugwise/__init__.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/custom_components/plugwise/__init__.py b/custom_components/plugwise/__init__.py index 989c23a09..3b3b2a617 100644 --- a/custom_components/plugwise/__init__.py +++ b/custom_components/plugwise/__init__.py @@ -2,7 +2,7 @@ from typing import Any -from homeassistant.const import CONF_TIMEOUT, Platform +from homeassistant.const import ATTR_NAME, CONF_TIMEOUT, Platform from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.typing import ConfigType @@ -41,7 +41,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: PlugwiseConfigEntry) -> ) # pw-beta - cooldown, update_interval as extra await coordinator.async_config_entry_first_refresh() - await async_migrate_sensor_entities(hass, coordinator) + await async_migrate_entities(hass, coordinator) entry.runtime_data = coordinator @@ -98,19 +98,19 @@ def async_migrate_entity_entry(entry: er.RegistryEntry) -> dict[str, Any] | None # No migration needed return None -async def async_migrate_sensor_entities( +async def async_migrate_entities( hass: HomeAssistant, coordinator: PlugwiseDataUpdateCoordinator, ) -> None: - """Migrate Sensors if needed.""" + """Migrate entites if needed.""" ent_reg = er.async_get(hass) - # Migrate opentherm_outdoor_temperature - # to opentherm_outdoor_air_temperature sensor for device_id, device in coordinator.data.items(): - if device["dev_class"] != "heater_central": + if device["dev_class"] not in ("climate", "heater_central") : continue + # Migrate opentherm_outdoor_temperature + # to opentherm_outdoor_air_temperature sensor old_unique_id = f"{device_id}-outdoor_temperature" if entity_id := ent_reg.async_get_entity_id( Platform.SENSOR, DOMAIN, old_unique_id @@ -119,6 +119,16 @@ async def async_migrate_sensor_entities( # Upstream remove LOGGER debug ent_reg.async_update_entity(entity_id, new_unique_id=new_unique_id) + # Migrate *id*_climate to *id*_device-name + old_unique_id = f"{device_id}-climate" + if entity_id := ent_reg.async_get_entity_id( + Platform.CLIMATE, DOMAIN, old_unique_id + ): + new_unique_id = f"{device_id}-{device[ATT_NAME]}" + # Upstream remove LOGGER debug + ent_reg.async_update_entity(entity_id, new_unique_id=new_unique_id) + + # pw-beta only - revert adding CONF_TIMEOUT to config_entry in v0.53.3 async def async_migrate_entry(hass: HomeAssistant, entry: PlugwiseConfigEntry) -> bool: """Migrate back to v1.1 config entry.""" From cc7b287a88bbb14ca85b3a0c4bfb52dd0c613bd9 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 30 Jun 2026 19:19:59 +0200 Subject: [PATCH 03/12] Add climate unique_id migration test --- tests/components/plugwise/test_init.py | 31 ++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/components/plugwise/test_init.py b/tests/components/plugwise/test_init.py index bd5dbf89f..b28f930de 100644 --- a/tests/components/plugwise/test_init.py +++ b/tests/components/plugwise/test_init.py @@ -37,6 +37,7 @@ HA_PLUGWISE_SMILE_ASYNC_UPDATE = ( "homeassistant.components.plugwise.coordinator.Smile.async_update" ) +CLIMATE_ID = "06aecb3d00354375924f50c47af36bd2" # ThermoZone device_id for migration HEATER_ID = "1cbf783bb11e4a7c8a6843dee3a86927" # Opentherm device_id for migration PLUG_ID = "cd0ddb54ef694e11ac18ed1cbce5dbbd" # VCR device_id for migration SECONDARY_ID = ( @@ -268,6 +269,36 @@ async def test_migrate_unique_id_relay( ) +@pytest.mark.parametrize( + ("entitydata", "old_unique_id", "new_unique_id"), + [ + ( + { + "domain": Platform.CLIMATE, + "platform": DOMAIN, + "unique_id": f"{CLIMATE_ID}-climate", + "suggested_object_id": f"{CLIMATE_ID}-climate", + "disabled_by": None, + }, + f"{CLIMATE_ID}-climate", + f"{CLIMATE_ID}-Slaapkamer", + ) + ], +) +async def test_migrate_unique_id_relay( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + entitydata: dict, + old_unique_id: str, + new_unique_id: str, + mock_smile_adam_jip: MagicMock, +) -> None: + """Test migration of unique_id.""" + await check_migration( + hass, mock_config_entry, entitydata, old_unique_id, new_unique_id + ) + + @pytest.mark.parametrize("chosen_env", ["m_adam_heating"], indirect=True) @pytest.mark.parametrize("cooling_present", [False], indirect=True) async def test_update_device( From b8f0eadeb27d97ff5c12cf3f7f0bfae85d402750 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 30 Jun 2026 19:23:39 +0200 Subject: [PATCH 04/12] Fix typo --- custom_components/plugwise/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/plugwise/__init__.py b/custom_components/plugwise/__init__.py index 3b3b2a617..c413b2fb8 100644 --- a/custom_components/plugwise/__init__.py +++ b/custom_components/plugwise/__init__.py @@ -124,7 +124,7 @@ async def async_migrate_entities( if entity_id := ent_reg.async_get_entity_id( Platform.CLIMATE, DOMAIN, old_unique_id ): - new_unique_id = f"{device_id}-{device[ATT_NAME]}" + new_unique_id = f"{device_id}-{device[ATTR_NAME]}" # Upstream remove LOGGER debug ent_reg.async_update_entity(entity_id, new_unique_id=new_unique_id) From ac3ba460482f002da9b48de8da9e1e5307eb692e Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 30 Jun 2026 19:25:49 +0200 Subject: [PATCH 05/12] Avoid capitals --- custom_components/plugwise/climate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/plugwise/climate.py b/custom_components/plugwise/climate.py index 1c16707a7..a3325f354 100644 --- a/custom_components/plugwise/climate.py +++ b/custom_components/plugwise/climate.py @@ -143,7 +143,7 @@ def __init__( self._attr_target_temperature_step = max( self.device.get(THERMOSTAT, {}).get(RESOLUTION, 0.5), 0.1 ) - self._attr_unique_id = f"{device_id}-{self.device[ATTR_NAME]}" + self._attr_unique_id = f"{device_id}-{lower(self.device[ATTR_NAME])}" # Determine supported features self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE From 9bb7036446b70b43c68912c0d4b48d0f035c5588 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 30 Jun 2026 19:28:59 +0200 Subject: [PATCH 06/12] Update/improve test code --- tests/components/plugwise/test_init.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/components/plugwise/test_init.py b/tests/components/plugwise/test_init.py index b28f930de..5bee97c70 100644 --- a/tests/components/plugwise/test_init.py +++ b/tests/components/plugwise/test_init.py @@ -214,7 +214,7 @@ async def check_migration( ), ], ) -async def test_migrate_unique_id_temperature( +async def test_migrate_temperature_unique_id( hass: HomeAssistant, mock_config_entry: MockConfigEntry, entitydata: dict, @@ -222,7 +222,7 @@ async def test_migrate_unique_id_temperature( new_unique_id: str, mock_smile_anna: MagicMock, ) -> None: - """Test migration of unique_id.""" + """Test migration of sensor unique_id.""" await check_migration( hass, mock_config_entry, entitydata, old_unique_id, new_unique_id ) @@ -255,7 +255,7 @@ async def test_migrate_unique_id_temperature( ), ], ) -async def test_migrate_unique_id_relay( +async def test_migrate_relay_unique_id( hass: HomeAssistant, mock_config_entry: MockConfigEntry, entitydata: dict, @@ -263,7 +263,7 @@ async def test_migrate_unique_id_relay( new_unique_id: str, mock_smile_adam: MagicMock, ) -> None: - """Test migration of unique_id.""" + """Test migration of binary_sensor and switch unique_ids.""" await check_migration( hass, mock_config_entry, entitydata, old_unique_id, new_unique_id ) @@ -281,11 +281,11 @@ async def test_migrate_unique_id_relay( "disabled_by": None, }, f"{CLIMATE_ID}-climate", - f"{CLIMATE_ID}-Slaapkamer", + f"{CLIMATE_ID}-slaapkamer", ) ], ) -async def test_migrate_unique_id_relay( +async def test_migrate_climate_unique_id( hass: HomeAssistant, mock_config_entry: MockConfigEntry, entitydata: dict, @@ -293,7 +293,7 @@ async def test_migrate_unique_id_relay( new_unique_id: str, mock_smile_adam_jip: MagicMock, ) -> None: - """Test migration of unique_id.""" + """Test migration of climate unique_id.""" await check_migration( hass, mock_config_entry, entitydata, old_unique_id, new_unique_id ) From 9e9898a1621d1239c40d13dbbbf59865867597ab Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 30 Jun 2026 19:34:38 +0200 Subject: [PATCH 07/12] Correct use of lower() --- custom_components/plugwise/__init__.py | 2 +- custom_components/plugwise/climate.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/custom_components/plugwise/__init__.py b/custom_components/plugwise/__init__.py index c413b2fb8..77f107276 100644 --- a/custom_components/plugwise/__init__.py +++ b/custom_components/plugwise/__init__.py @@ -124,7 +124,7 @@ async def async_migrate_entities( if entity_id := ent_reg.async_get_entity_id( Platform.CLIMATE, DOMAIN, old_unique_id ): - new_unique_id = f"{device_id}-{device[ATTR_NAME]}" + new_unique_id = f"{device_id}-{device[ATTR_NAME]}".lower() # Upstream remove LOGGER debug ent_reg.async_update_entity(entity_id, new_unique_id=new_unique_id) diff --git a/custom_components/plugwise/climate.py b/custom_components/plugwise/climate.py index a3325f354..a7395a662 100644 --- a/custom_components/plugwise/climate.py +++ b/custom_components/plugwise/climate.py @@ -143,7 +143,8 @@ def __init__( self._attr_target_temperature_step = max( self.device.get(THERMOSTAT, {}).get(RESOLUTION, 0.5), 0.1 ) - self._attr_unique_id = f"{device_id}-{lower(self.device[ATTR_NAME])}" + entity_name = f"{self.device[ATTR_NAME]}".lower() + self._attr_unique_id = f"{device_id}-{entity_name}" # Determine supported features self._attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE From a132bed354a03c5d2b28e32846b70fd07ab9dca0 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 30 Jun 2026 20:07:29 +0200 Subject: [PATCH 08/12] Correct water_heater unique_id too --- custom_components/plugwise/water_heater.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index 8b3beade2..d46419403 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -72,7 +72,9 @@ def __init__( ) -> None: """Initialise the water_heater.""" super().__init__(coordinator, device_id) - self._attr_unique_id = f"{device_id}-water_heater" + + entity_name = f"{self.device[ATTR_NAME]}".lower() + self._attr_unique_id = f"{device_id}-{entity_name}" max_dhw_temp_bounds = self.device.get(MAX_DHW_TEMP, {}) if max_dhw_temp_bounds: From edea89bf8dee034162aba5e1efef30f575f5788f Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 30 Jun 2026 20:11:11 +0200 Subject: [PATCH 09/12] Save updated snapshots --- .../plugwise/snapshots/test_climate.ambr | 20 +++++++++---------- .../plugwise/snapshots/test_water_heater.ambr | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/components/plugwise/snapshots/test_climate.ambr b/tests/components/plugwise/snapshots/test_climate.ambr index 60b6db166..ffcda5cee 100644 --- a/tests/components/plugwise/snapshots/test_climate.ambr +++ b/tests/components/plugwise/snapshots/test_climate.ambr @@ -48,7 +48,7 @@ 'suggested_object_id': None, 'supported_features': , 'translation_key': 'plugwise', - 'unique_id': 'f871b8c4d63549319221e294e4f88074-climate', + 'unique_id': 'f871b8c4d63549319221e294e4f88074-bathroom', 'unit_of_measurement': None, }) # --- @@ -134,7 +134,7 @@ 'suggested_object_id': None, 'supported_features': , 'translation_key': 'plugwise', - 'unique_id': 'f2bf9048bef64cc5b6d5110154e33c81-climate', + 'unique_id': 'f2bf9048bef64cc5b6d5110154e33c81-living room', 'unit_of_measurement': None, }) # --- @@ -219,7 +219,7 @@ 'suggested_object_id': None, 'supported_features': , 'translation_key': 'plugwise', - 'unique_id': '08963fec7c53423ca5680aa4cb502c63-climate', + 'unique_id': '08963fec7c53423ca5680aa4cb502c63-badkamer', 'unit_of_measurement': None, }) # --- @@ -303,7 +303,7 @@ 'suggested_object_id': None, 'supported_features': , 'translation_key': 'plugwise', - 'unique_id': '12493538af164a409c6a1c79e38afe1c-climate', + 'unique_id': '12493538af164a409c6a1c79e38afe1c-bios', 'unit_of_measurement': None, }) # --- @@ -386,7 +386,7 @@ 'suggested_object_id': None, 'supported_features': , 'translation_key': 'plugwise', - 'unique_id': '446ac08dd04d4eff8ac57489757b7314-climate', + 'unique_id': '446ac08dd04d4eff8ac57489757b7314-garage', 'unit_of_measurement': None, }) # --- @@ -469,7 +469,7 @@ 'suggested_object_id': None, 'supported_features': , 'translation_key': 'plugwise', - 'unique_id': '82fa13f017d240daa0d0ea1775420f24-climate', + 'unique_id': '82fa13f017d240daa0d0ea1775420f24-jessie', 'unit_of_measurement': None, }) # --- @@ -553,7 +553,7 @@ 'suggested_object_id': None, 'supported_features': , 'translation_key': 'plugwise', - 'unique_id': 'c50f167537524366a5af7aa3942feb1e-climate', + 'unique_id': 'c50f167537524366a5af7aa3942feb1e-woonkamer', 'unit_of_measurement': None, }) # --- @@ -637,7 +637,7 @@ 'suggested_object_id': None, 'supported_features': , 'translation_key': 'plugwise', - 'unique_id': '3cb70739631c4d17a86b8b12e8a5161b-climate', + 'unique_id': '3cb70739631c4d17a86b8b12e8a5161b-anna', 'unit_of_measurement': None, }) # --- @@ -722,7 +722,7 @@ 'suggested_object_id': None, 'supported_features': , 'translation_key': 'plugwise', - 'unique_id': '1e5e55b958ac445583602f767cb45942-climate', + 'unique_id': '1e5e55b958ac445583602f767cb45942-anna', 'unit_of_measurement': None, }) # --- @@ -806,7 +806,7 @@ 'suggested_object_id': None, 'supported_features': , 'translation_key': 'plugwise', - 'unique_id': '3cb70739631c4d17a86b8b12e8a5161b-climate', + 'unique_id': '3cb70739631c4d17a86b8b12e8a5161b-anna', 'unit_of_measurement': None, }) # --- diff --git a/tests/components/plugwise/snapshots/test_water_heater.ambr b/tests/components/plugwise/snapshots/test_water_heater.ambr index 634cca64c..2f3ef30d5 100644 --- a/tests/components/plugwise/snapshots/test_water_heater.ambr +++ b/tests/components/plugwise/snapshots/test_water_heater.ambr @@ -39,7 +39,7 @@ 'suggested_object_id': None, 'supported_features': , 'translation_key': None, - 'unique_id': 'e4684553153b44afbef2200885f379dc-water_heater', + 'unique_id': 'e4684553153b44afbef2200885f379dc-opentherm', 'unit_of_measurement': None, }) # --- @@ -108,7 +108,7 @@ 'suggested_object_id': None, 'supported_features': , 'translation_key': None, - 'unique_id': 'cd0e6156b1f04d5f952349ffbe397481-water_heater', + 'unique_id': 'cd0e6156b1f04d5f952349ffbe397481-opentherm', 'unit_of_measurement': None, }) # --- From 340f79c8051a2935fb77e6a74e77ac9d6f2ed1ef Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 30 Jun 2026 20:11:38 +0200 Subject: [PATCH 10/12] Ruffed --- custom_components/plugwise/water_heater.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/plugwise/water_heater.py b/custom_components/plugwise/water_heater.py index d46419403..d7758a242 100644 --- a/custom_components/plugwise/water_heater.py +++ b/custom_components/plugwise/water_heater.py @@ -72,7 +72,7 @@ def __init__( ) -> None: """Initialise the water_heater.""" super().__init__(coordinator, device_id) - + entity_name = f"{self.device[ATTR_NAME]}".lower() self._attr_unique_id = f"{device_id}-{entity_name}" From 7c8308b3486112ffd3531fde481de65263ea2258 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 30 Jun 2026 20:12:36 +0200 Subject: [PATCH 11/12] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8c8430f6..ff064e564 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Versions from 0.40 and up ## Ongoing +- Correct climate and water_heater unique_id's via PR [#1098](https://github.com/plugwise/plugwise-beta/pull/1098) - Implement @overload and improved snapshot-formatting as required for the Next HA version, via PR [#1096](https://github.com/plugwise/plugwise-beta/pull/1096) ## v0.65.0 From d082123af1331a53e9bacd471f3748a0ad0ee0e0 Mon Sep 17 00:00:00 2001 From: Bouwe Westerdijk Date: Tue, 30 Jun 2026 20:19:26 +0200 Subject: [PATCH 12/12] Fix typos --- custom_components/plugwise/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/custom_components/plugwise/__init__.py b/custom_components/plugwise/__init__.py index 77f107276..050088521 100644 --- a/custom_components/plugwise/__init__.py +++ b/custom_components/plugwise/__init__.py @@ -102,11 +102,11 @@ async def async_migrate_entities( hass: HomeAssistant, coordinator: PlugwiseDataUpdateCoordinator, ) -> None: - """Migrate entites if needed.""" + """Migrate entities if needed.""" ent_reg = er.async_get(hass) for device_id, device in coordinator.data.items(): - if device["dev_class"] not in ("climate", "heater_central") : + if device["dev_class"] not in ("climate", "heater_central"): continue # Migrate opentherm_outdoor_temperature @@ -119,7 +119,7 @@ async def async_migrate_entities( # Upstream remove LOGGER debug ent_reg.async_update_entity(entity_id, new_unique_id=new_unique_id) - # Migrate *id*_climate to *id*_device-name + # Migrate id-climate to id-device-name old_unique_id = f"{device_id}-climate" if entity_id := ent_reg.async_get_entity_id( Platform.CLIMATE, DOMAIN, old_unique_id