diff --git a/homeassistant/components/vicare/__init__.py b/homeassistant/components/vicare/__init__.py index 0c87cd6f4fe..ead210e2816 100644 --- a/homeassistant/components/vicare/__init__.py +++ b/homeassistant/components/vicare/__init__.py @@ -15,10 +15,12 @@ from PyViCare.PyViCareUtils import ( PyViCareInvalidCredentialsError, ) +from homeassistant.components.climate import DOMAIN as DOMAIN_CLIMATE from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_CLIENT_ID, CONF_PASSWORD, CONF_USERNAME -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryAuthFailed +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.storage import STORAGE_DIR from .const import ( @@ -47,6 +49,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: except (PyViCareInvalidConfigurationError, PyViCareInvalidCredentialsError) as err: raise ConfigEntryAuthFailed("Authentication failed") from err + for device in hass.data[DOMAIN][entry.entry_id][DEVICE_LIST]: + # Migration can be removed in 2025.4.0 + await async_migrate_devices(hass, entry, device) + # Migration can be removed in 2025.4.0 + await async_migrate_entities(hass, entry, device) + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True @@ -109,6 +117,72 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok +async def async_migrate_devices( + hass: HomeAssistant, entry: ConfigEntry, device: ViCareDevice +) -> None: + """Migrate old entry.""" + registry = dr.async_get(hass) + + gateway_serial: str = device.config.getConfig().serial + device_serial: str = device.api.getSerial() + + old_identifier = gateway_serial + new_identifier = f"{gateway_serial}_{device_serial}" + + # Migrate devices + for device_entry in dr.async_entries_for_config_entry(registry, entry.entry_id): + if device_entry.identifiers == {(DOMAIN, old_identifier)}: + _LOGGER.debug("Migrating device %s", device_entry.name) + registry.async_update_device( + device_entry.id, + serial_number=device_serial, + new_identifiers={(DOMAIN, new_identifier)}, + ) + + +async def async_migrate_entities( + hass: HomeAssistant, entry: ConfigEntry, device: ViCareDevice +) -> None: + """Migrate old entry.""" + gateway_serial: str = device.config.getConfig().serial + device_serial: str = device.api.getSerial() + new_identifier = f"{gateway_serial}_{device_serial}" + + @callback + def _update_unique_id( + entity_entry: er.RegistryEntry, + ) -> dict[str, str] | None: + """Update unique ID of entity entry.""" + if not entity_entry.unique_id.startswith(gateway_serial): + # belongs to other device/gateway + return None + if entity_entry.unique_id.startswith(f"{gateway_serial}_"): + # Already correct, nothing to do + return None + + unique_id_parts = entity_entry.unique_id.split("-") + unique_id_parts[0] = new_identifier + + # convert climate entity unique id from `-` to `-heating-` + if entity_entry.domain == DOMAIN_CLIMATE: + unique_id_parts[len(unique_id_parts) - 1] = ( + f"{entity_entry.translation_key}-{unique_id_parts[len(unique_id_parts)-1]}" + ) + + entity_new_unique_id = "-".join(unique_id_parts) + + _LOGGER.debug( + "Migrating entity %s from %s to new id %s", + entity_entry.entity_id, + entity_entry.unique_id, + entity_new_unique_id, + ) + return {"new_unique_id": entity_new_unique_id} + + # Migrate entities + await er.async_migrate_entries(hass, entry.entry_id, _update_unique_id) + + def get_supported_devices( devices: list[PyViCareDeviceConfig], ) -> list[PyViCareDeviceConfig]: diff --git a/homeassistant/components/vicare/climate.py b/homeassistant/components/vicare/climate.py index 4968e565d0b..410395760ea 100644 --- a/homeassistant/components/vicare/climate.py +++ b/homeassistant/components/vicare/climate.py @@ -148,10 +148,10 @@ class ViCareClimate(ViCareEntity, ClimateEntity): circuit: PyViCareHeatingCircuit, ) -> None: """Initialize the climate device.""" - super().__init__(circuit.id, device_config, device) - self._circuit = circuit + super().__init__(self._attr_translation_key, device_config, device, circuit) + self._device = device self._attributes: dict[str, Any] = {} - self._attributes["vicare_programs"] = self._circuit.getPrograms() + self._attributes["vicare_programs"] = self._api.getPrograms() self._attr_preset_modes = [ preset for heating_program in self._attributes["vicare_programs"] @@ -163,11 +163,11 @@ class ViCareClimate(ViCareEntity, ClimateEntity): try: _room_temperature = None with suppress(PyViCareNotSupportedFeatureError): - _room_temperature = self._circuit.getRoomTemperature() + _room_temperature = self._api.getRoomTemperature() _supply_temperature = None with suppress(PyViCareNotSupportedFeatureError): - _supply_temperature = self._circuit.getSupplyTemperature() + _supply_temperature = self._api.getSupplyTemperature() if _room_temperature is not None: self._attr_current_temperature = _room_temperature @@ -177,15 +177,13 @@ class ViCareClimate(ViCareEntity, ClimateEntity): self._attr_current_temperature = None with suppress(PyViCareNotSupportedFeatureError): - self._current_program = self._circuit.getActiveProgram() + self._current_program = self._api.getActiveProgram() with suppress(PyViCareNotSupportedFeatureError): - self._attr_target_temperature = ( - self._circuit.getCurrentDesiredTemperature() - ) + self._attr_target_temperature = self._api.getCurrentDesiredTemperature() with suppress(PyViCareNotSupportedFeatureError): - self._current_mode = self._circuit.getActiveMode() + self._current_mode = self._api.getActiveMode() # Update the generic device attributes self._attributes = { @@ -196,25 +194,25 @@ class ViCareClimate(ViCareEntity, ClimateEntity): with suppress(PyViCareNotSupportedFeatureError): self._attributes["heating_curve_slope"] = ( - self._circuit.getHeatingCurveSlope() + self._api.getHeatingCurveSlope() ) with suppress(PyViCareNotSupportedFeatureError): self._attributes["heating_curve_shift"] = ( - self._circuit.getHeatingCurveShift() + self._api.getHeatingCurveShift() ) with suppress(PyViCareNotSupportedFeatureError): - self._attributes["vicare_modes"] = self._circuit.getModes() + self._attributes["vicare_modes"] = self._api.getModes() self._current_action = False # Update the specific device attributes with suppress(PyViCareNotSupportedFeatureError): - for burner in get_burners(self._api): + for burner in get_burners(self._device): self._current_action = self._current_action or burner.getActive() with suppress(PyViCareNotSupportedFeatureError): - for compressor in get_compressors(self._api): + for compressor in get_compressors(self._device): self._current_action = ( self._current_action or compressor.getActive() ) @@ -245,9 +243,9 @@ class ViCareClimate(ViCareEntity, ClimateEntity): raise ValueError(f"Cannot set invalid hvac mode: {hvac_mode}") _LOGGER.debug("Setting hvac mode to %s / %s", hvac_mode, vicare_mode) - self._circuit.setMode(vicare_mode) + self._api.setMode(vicare_mode) - def vicare_mode_from_hvac_mode(self, hvac_mode): + def vicare_mode_from_hvac_mode(self, hvac_mode) -> str | None: """Return the corresponding vicare mode for an hvac_mode.""" if "vicare_modes" not in self._attributes: return None @@ -283,7 +281,7 @@ class ViCareClimate(ViCareEntity, ClimateEntity): def set_temperature(self, **kwargs: Any) -> None: """Set new target temperatures.""" if (temp := kwargs.get(ATTR_TEMPERATURE)) is not None: - self._circuit.setProgramTemperature(self._current_program, temp) + self._api.setProgramTemperature(self._current_program, temp) self._attr_target_temperature = temp @property @@ -312,7 +310,7 @@ class ViCareClimate(ViCareEntity, ClimateEntity): ): _LOGGER.debug("deactivating %s", self._current_program) try: - self._circuit.deactivateProgram(self._current_program) + self._api.deactivateProgram(self._current_program) except PyViCareCommandError as err: raise ServiceValidationError( translation_domain=DOMAIN, @@ -326,7 +324,7 @@ class ViCareClimate(ViCareEntity, ClimateEntity): if target_program in CHANGABLE_HEATING_PROGRAMS: _LOGGER.debug("activating %s", target_program) try: - self._circuit.activateProgram(target_program) + self._api.activateProgram(target_program) except PyViCareCommandError as err: raise ServiceValidationError( translation_domain=DOMAIN, @@ -341,9 +339,9 @@ class ViCareClimate(ViCareEntity, ClimateEntity): """Show Device Attributes.""" return self._attributes - def set_vicare_mode(self, vicare_mode): + def set_vicare_mode(self, vicare_mode) -> None: """Service function to set vicare modes directly.""" if vicare_mode not in self._attributes["vicare_modes"]: raise ValueError(f"Cannot set invalid vicare mode: {vicare_mode}.") - self._circuit.setMode(vicare_mode) + self._api.setMode(vicare_mode) diff --git a/homeassistant/components/vicare/entity.py b/homeassistant/components/vicare/entity.py index eef114b4039..f48243e83e1 100644 --- a/homeassistant/components/vicare/entity.py +++ b/homeassistant/components/vicare/entity.py @@ -25,18 +25,20 @@ class ViCareEntity(Entity): component: PyViCareHeatingDeviceComponent | None = None, ) -> None: """Initialize the entity.""" + gateway_serial = device_config.getConfig().serial + device_serial = device.getSerial() + identifier = f"{gateway_serial}_{device_serial}" + self._api: PyViCareDevice | PyViCareHeatingDeviceComponent = ( component if component else device ) - - self._attr_unique_id = f"{device_config.getConfig().serial}-{unique_id_suffix}" - # valid for compressors, circuits, burners (HeatingDeviceWithComponent) + self._attr_unique_id = f"{identifier}-{unique_id_suffix}" if component: self._attr_unique_id += f"-{component.id}" self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, device_config.getConfig().serial)}, - serial_number=device.getSerial(), + identifiers={(DOMAIN, identifier)}, + serial_number=device_serial, name=device_config.getModel(), manufacturer="Viessmann", model=device_config.getModel(), diff --git a/tests/components/vicare/fixtures/ViAir300F.json b/tests/components/vicare/fixtures/ViAir300F.json index b1ec747e127..090c7a81ddf 100644 --- a/tests/components/vicare/fixtures/ViAir300F.json +++ b/tests/components/vicare/fixtures/ViAir300F.json @@ -50,7 +50,7 @@ "properties": { "value": { "type": "string", - "value": "################" + "value": "deviceSerialViAir300F" } }, "timestamp": "2024-03-20T01:29:35.549Z", diff --git a/tests/components/vicare/fixtures/Vitodens300W.json b/tests/components/vicare/fixtures/Vitodens300W.json index bb86bda981b..d183146e94d 100644 --- a/tests/components/vicare/fixtures/Vitodens300W.json +++ b/tests/components/vicare/fixtures/Vitodens300W.json @@ -11,10 +11,10 @@ "properties": { "value": { "type": "string", - "value": "################" + "value": "deviceSerialVitodens300W" } }, - "timestamp": "2024-03-20T01:29:35.549Z", + "timestamp": "2024-07-30T20:03:40.073Z", "uri": "https://api.viessmann.com/iot/v1/features/installations/#######/gateways/################/devices/0/features/device.serial" }, { diff --git a/tests/components/vicare/snapshots/test_binary_sensor.ambr b/tests/components/vicare/snapshots/test_binary_sensor.ambr index a03a6150c45..f3e4d4e1c84 100644 --- a/tests/components/vicare/snapshots/test_binary_sensor.ambr +++ b/tests/components/vicare/snapshots/test_binary_sensor.ambr @@ -28,7 +28,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'burner', - 'unique_id': 'gateway0-burner_active-0', + 'unique_id': 'gateway0_deviceSerialVitodens300W-burner_active-0', 'unit_of_measurement': None, }) # --- @@ -75,7 +75,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'circulation_pump', - 'unique_id': 'gateway0-circulationpump_active-0', + 'unique_id': 'gateway0_deviceSerialVitodens300W-circulationpump_active-0', 'unit_of_measurement': None, }) # --- @@ -122,7 +122,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'circulation_pump', - 'unique_id': 'gateway0-circulationpump_active-1', + 'unique_id': 'gateway0_deviceSerialVitodens300W-circulationpump_active-1', 'unit_of_measurement': None, }) # --- @@ -169,7 +169,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'domestic_hot_water_charging', - 'unique_id': 'gateway0-charging_active', + 'unique_id': 'gateway0_deviceSerialVitodens300W-charging_active', 'unit_of_measurement': None, }) # --- @@ -216,7 +216,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'domestic_hot_water_circulation_pump', - 'unique_id': 'gateway0-dhw_circulationpump_active', + 'unique_id': 'gateway0_deviceSerialVitodens300W-dhw_circulationpump_active', 'unit_of_measurement': None, }) # --- @@ -263,7 +263,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'domestic_hot_water_pump', - 'unique_id': 'gateway0-dhw_pump_active', + 'unique_id': 'gateway0_deviceSerialVitodens300W-dhw_pump_active', 'unit_of_measurement': None, }) # --- @@ -310,7 +310,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'frost_protection', - 'unique_id': 'gateway0-frost_protection_active-0', + 'unique_id': 'gateway0_deviceSerialVitodens300W-frost_protection_active-0', 'unit_of_measurement': None, }) # --- @@ -356,7 +356,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'frost_protection', - 'unique_id': 'gateway0-frost_protection_active-1', + 'unique_id': 'gateway0_deviceSerialVitodens300W-frost_protection_active-1', 'unit_of_measurement': None, }) # --- diff --git a/tests/components/vicare/snapshots/test_button.ambr b/tests/components/vicare/snapshots/test_button.ambr index 01120b8b0d6..9fadc6a983f 100644 --- a/tests/components/vicare/snapshots/test_button.ambr +++ b/tests/components/vicare/snapshots/test_button.ambr @@ -28,7 +28,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'activate_onetimecharge', - 'unique_id': 'gateway0-activate_onetimecharge', + 'unique_id': 'gateway0_deviceSerialVitodens300W-activate_onetimecharge', 'unit_of_measurement': None, }) # --- diff --git a/tests/components/vicare/snapshots/test_climate.ambr b/tests/components/vicare/snapshots/test_climate.ambr index a01d1c43bea..aea0ea879c2 100644 --- a/tests/components/vicare/snapshots/test_climate.ambr +++ b/tests/components/vicare/snapshots/test_climate.ambr @@ -40,7 +40,7 @@ 'previous_unique_id': None, 'supported_features': , 'translation_key': 'heating', - 'unique_id': 'gateway0-0', + 'unique_id': 'gateway0_deviceSerialVitodens300W-heating-0', 'unit_of_measurement': None, }) # --- @@ -123,7 +123,7 @@ 'previous_unique_id': None, 'supported_features': , 'translation_key': 'heating', - 'unique_id': 'gateway0-1', + 'unique_id': 'gateway0_deviceSerialVitodens300W-heating-1', 'unit_of_measurement': None, }) # --- diff --git a/tests/components/vicare/snapshots/test_diagnostics.ambr b/tests/components/vicare/snapshots/test_diagnostics.ambr index 430b2de35ad..120bdf7a333 100644 --- a/tests/components/vicare/snapshots/test_diagnostics.ambr +++ b/tests/components/vicare/snapshots/test_diagnostics.ambr @@ -16,10 +16,10 @@ 'properties': dict({ 'value': dict({ 'type': 'string', - 'value': '################', + 'value': 'deviceSerialVitodens300W', }), }), - 'timestamp': '2024-03-20T01:29:35.549Z', + 'timestamp': '2024-07-30T20:03:40.073Z', 'uri': 'https://api.viessmann.com/iot/v1/features/installations/#######/gateways/################/devices/0/features/device.serial', }), dict({ diff --git a/tests/components/vicare/snapshots/test_fan.ambr b/tests/components/vicare/snapshots/test_fan.ambr index 48c8d728569..8ec4bc41d8d 100644 --- a/tests/components/vicare/snapshots/test_fan.ambr +++ b/tests/components/vicare/snapshots/test_fan.ambr @@ -35,7 +35,7 @@ 'previous_unique_id': None, 'supported_features': , 'translation_key': 'ventilation', - 'unique_id': 'gateway0-ventilation', + 'unique_id': 'gateway0_deviceSerialViAir300F-ventilation', 'unit_of_measurement': None, }) # --- diff --git a/tests/components/vicare/snapshots/test_number.ambr b/tests/components/vicare/snapshots/test_number.ambr index e6e87ce5dc7..5a030fc0213 100644 --- a/tests/components/vicare/snapshots/test_number.ambr +++ b/tests/components/vicare/snapshots/test_number.ambr @@ -33,7 +33,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'comfort_temperature', - 'unique_id': 'gateway0-comfort_temperature-0', + 'unique_id': 'gateway0_deviceSerialVitodens300W-comfort_temperature-0', 'unit_of_measurement': , }) # --- @@ -90,7 +90,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'comfort_temperature', - 'unique_id': 'gateway0-comfort_temperature-1', + 'unique_id': 'gateway0_deviceSerialVitodens300W-comfort_temperature-1', 'unit_of_measurement': , }) # --- @@ -147,7 +147,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'heating_curve_shift', - 'unique_id': 'gateway0-heating curve shift-0', + 'unique_id': 'gateway0_deviceSerialVitodens300W-heating curve shift-0', 'unit_of_measurement': , }) # --- @@ -204,7 +204,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'heating_curve_shift', - 'unique_id': 'gateway0-heating curve shift-1', + 'unique_id': 'gateway0_deviceSerialVitodens300W-heating curve shift-1', 'unit_of_measurement': , }) # --- @@ -261,7 +261,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'heating_curve_slope', - 'unique_id': 'gateway0-heating curve slope-0', + 'unique_id': 'gateway0_deviceSerialVitodens300W-heating curve slope-0', 'unit_of_measurement': None, }) # --- @@ -316,7 +316,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'heating_curve_slope', - 'unique_id': 'gateway0-heating curve slope-1', + 'unique_id': 'gateway0_deviceSerialVitodens300W-heating curve slope-1', 'unit_of_measurement': None, }) # --- @@ -371,7 +371,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'normal_temperature', - 'unique_id': 'gateway0-normal_temperature-0', + 'unique_id': 'gateway0_deviceSerialVitodens300W-normal_temperature-0', 'unit_of_measurement': , }) # --- @@ -428,7 +428,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'normal_temperature', - 'unique_id': 'gateway0-normal_temperature-1', + 'unique_id': 'gateway0_deviceSerialVitodens300W-normal_temperature-1', 'unit_of_measurement': , }) # --- @@ -485,7 +485,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'reduced_temperature', - 'unique_id': 'gateway0-reduced_temperature-0', + 'unique_id': 'gateway0_deviceSerialVitodens300W-reduced_temperature-0', 'unit_of_measurement': , }) # --- @@ -542,7 +542,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'reduced_temperature', - 'unique_id': 'gateway0-reduced_temperature-1', + 'unique_id': 'gateway0_deviceSerialVitodens300W-reduced_temperature-1', 'unit_of_measurement': , }) # --- @@ -599,7 +599,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'dhw_temperature', - 'unique_id': 'gateway0-dhw_temperature', + 'unique_id': 'gateway0_deviceSerialVitodens300W-dhw_temperature', 'unit_of_measurement': , }) # --- diff --git a/tests/components/vicare/snapshots/test_sensor.ambr b/tests/components/vicare/snapshots/test_sensor.ambr index 7bbac75bedc..43e5b713293 100644 --- a/tests/components/vicare/snapshots/test_sensor.ambr +++ b/tests/components/vicare/snapshots/test_sensor.ambr @@ -30,7 +30,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'boiler_temperature', - 'unique_id': 'gateway0-boiler_temperature', + 'unique_id': 'gateway0_deviceSerialVitodens300W-boiler_temperature', 'unit_of_measurement': , }) # --- @@ -81,7 +81,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'burner_hours', - 'unique_id': 'gateway0-burner_hours-0', + 'unique_id': 'gateway0_deviceSerialVitodens300W-burner_hours-0', 'unit_of_measurement': , }) # --- @@ -131,7 +131,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'burner_modulation', - 'unique_id': 'gateway0-burner_modulation-0', + 'unique_id': 'gateway0_deviceSerialVitodens300W-burner_modulation-0', 'unit_of_measurement': '%', }) # --- @@ -181,7 +181,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'burner_starts', - 'unique_id': 'gateway0-burner_starts-0', + 'unique_id': 'gateway0_deviceSerialVitodens300W-burner_starts-0', 'unit_of_measurement': None, }) # --- @@ -230,7 +230,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'hotwater_gas_consumption_heating_this_month', - 'unique_id': 'gateway0-hotwater_gas_consumption_heating_this_month', + 'unique_id': 'gateway0_deviceSerialVitodens300W-hotwater_gas_consumption_heating_this_month', 'unit_of_measurement': None, }) # --- @@ -279,7 +279,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'hotwater_gas_consumption_heating_this_week', - 'unique_id': 'gateway0-hotwater_gas_consumption_heating_this_week', + 'unique_id': 'gateway0_deviceSerialVitodens300W-hotwater_gas_consumption_heating_this_week', 'unit_of_measurement': None, }) # --- @@ -328,7 +328,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'hotwater_gas_consumption_heating_this_year', - 'unique_id': 'gateway0-hotwater_gas_consumption_heating_this_year', + 'unique_id': 'gateway0_deviceSerialVitodens300W-hotwater_gas_consumption_heating_this_year', 'unit_of_measurement': None, }) # --- @@ -377,7 +377,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'hotwater_gas_consumption_today', - 'unique_id': 'gateway0-hotwater_gas_consumption_today', + 'unique_id': 'gateway0_deviceSerialVitodens300W-hotwater_gas_consumption_today', 'unit_of_measurement': None, }) # --- @@ -426,7 +426,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'hotwater_max_temperature', - 'unique_id': 'gateway0-hotwater_max_temperature', + 'unique_id': 'gateway0_deviceSerialVitodens300W-hotwater_max_temperature', 'unit_of_measurement': , }) # --- @@ -477,7 +477,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'hotwater_min_temperature', - 'unique_id': 'gateway0-hotwater_min_temperature', + 'unique_id': 'gateway0_deviceSerialVitodens300W-hotwater_min_temperature', 'unit_of_measurement': , }) # --- @@ -528,7 +528,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'power consumption this month', - 'unique_id': 'gateway0-power consumption this month', + 'unique_id': 'gateway0_deviceSerialVitodens300W-power consumption this month', 'unit_of_measurement': , }) # --- @@ -579,7 +579,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'power_consumption_this_year', - 'unique_id': 'gateway0-power consumption this year', + 'unique_id': 'gateway0_deviceSerialVitodens300W-power consumption this year', 'unit_of_measurement': , }) # --- @@ -630,7 +630,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'power_consumption_today', - 'unique_id': 'gateway0-power consumption today', + 'unique_id': 'gateway0_deviceSerialVitodens300W-power consumption today', 'unit_of_measurement': , }) # --- @@ -681,7 +681,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'gas_consumption_heating_this_month', - 'unique_id': 'gateway0-gas_consumption_heating_this_month', + 'unique_id': 'gateway0_deviceSerialVitodens300W-gas_consumption_heating_this_month', 'unit_of_measurement': None, }) # --- @@ -730,7 +730,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'gas_consumption_heating_this_week', - 'unique_id': 'gateway0-gas_consumption_heating_this_week', + 'unique_id': 'gateway0_deviceSerialVitodens300W-gas_consumption_heating_this_week', 'unit_of_measurement': None, }) # --- @@ -779,7 +779,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'gas_consumption_heating_this_year', - 'unique_id': 'gateway0-gas_consumption_heating_this_year', + 'unique_id': 'gateway0_deviceSerialVitodens300W-gas_consumption_heating_this_year', 'unit_of_measurement': None, }) # --- @@ -828,7 +828,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'gas_consumption_heating_today', - 'unique_id': 'gateway0-gas_consumption_heating_today', + 'unique_id': 'gateway0_deviceSerialVitodens300W-gas_consumption_heating_today', 'unit_of_measurement': None, }) # --- @@ -877,7 +877,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'outside_temperature', - 'unique_id': 'gateway0-outside_temperature', + 'unique_id': 'gateway0_deviceSerialVitodens300W-outside_temperature', 'unit_of_measurement': , }) # --- @@ -928,7 +928,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'power_consumption_this_week', - 'unique_id': 'gateway0-power consumption this week', + 'unique_id': 'gateway0_deviceSerialVitodens300W-power consumption this week', 'unit_of_measurement': , }) # --- @@ -979,7 +979,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'supply_temperature', - 'unique_id': 'gateway0-supply_temperature-0', + 'unique_id': 'gateway0_deviceSerialVitodens300W-supply_temperature-0', 'unit_of_measurement': , }) # --- @@ -1030,7 +1030,7 @@ 'previous_unique_id': None, 'supported_features': 0, 'translation_key': 'supply_temperature', - 'unique_id': 'gateway0-supply_temperature-1', + 'unique_id': 'gateway0_deviceSerialVitodens300W-supply_temperature-1', 'unit_of_measurement': , }) # --- diff --git a/tests/components/vicare/snapshots/test_water_heater.ambr b/tests/components/vicare/snapshots/test_water_heater.ambr index 5ab4fcc78bd..bca04b1bbfa 100644 --- a/tests/components/vicare/snapshots/test_water_heater.ambr +++ b/tests/components/vicare/snapshots/test_water_heater.ambr @@ -31,7 +31,7 @@ 'previous_unique_id': None, 'supported_features': , 'translation_key': 'domestic_hot_water', - 'unique_id': 'gateway0-0', + 'unique_id': 'gateway0_deviceSerialVitodens300W-0', 'unit_of_measurement': None, }) # --- @@ -87,7 +87,7 @@ 'previous_unique_id': None, 'supported_features': , 'translation_key': 'domestic_hot_water', - 'unique_id': 'gateway0-1', + 'unique_id': 'gateway0_deviceSerialVitodens300W-1', 'unit_of_measurement': None, }) # --- diff --git a/tests/components/vicare/test_init.py b/tests/components/vicare/test_init.py new file mode 100644 index 00000000000..fea7b5985f1 --- /dev/null +++ b/tests/components/vicare/test_init.py @@ -0,0 +1,99 @@ +"""Test ViCare migration.""" + +from unittest.mock import patch + +from homeassistant.components.vicare.const import DOMAIN +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr, entity_registry as er + +from . import MODULE +from .conftest import Fixture, MockPyViCare + +from tests.common import MockConfigEntry + + +# Device migration test can be removed in 2025.4.0 +async def test_device_migration( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + mock_config_entry: MockConfigEntry, +) -> None: + """Test that the device registry is updated correctly.""" + fixtures: list[Fixture] = [Fixture({"type:boiler"}, "vicare/Vitodens300W.json")] + with ( + patch(f"{MODULE}.vicare_login", return_value=MockPyViCare(fixtures)), + patch(f"{MODULE}.PLATFORMS", [Platform.CLIMATE]), + ): + mock_config_entry.add_to_hass(hass) + + device_registry.async_get_or_create( + config_entry_id=mock_config_entry.entry_id, + identifiers={ + (DOMAIN, "gateway0"), + }, + ) + + await hass.config_entries.async_setup(mock_config_entry.entry_id) + + await hass.async_block_till_done() + + assert device_registry.async_get_device(identifiers={(DOMAIN, "gateway0")}) is None + + assert ( + device_registry.async_get_device( + identifiers={(DOMAIN, "gateway0_deviceSerialVitodens300W")} + ) + is not None + ) + + +# Entity migration test can be removed in 2025.4.0 +async def test_climate_entity_migration( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + mock_config_entry: MockConfigEntry, +) -> None: + """Test that the climate entity unique_id gets migrated correctly.""" + fixtures: list[Fixture] = [Fixture({"type:boiler"}, "vicare/Vitodens300W.json")] + with ( + patch(f"{MODULE}.vicare_login", return_value=MockPyViCare(fixtures)), + patch(f"{MODULE}.PLATFORMS", [Platform.CLIMATE]), + ): + mock_config_entry.add_to_hass(hass) + + entry1 = entity_registry.async_get_or_create( + domain=Platform.CLIMATE, + platform=DOMAIN, + config_entry=mock_config_entry, + unique_id="gateway0-0", + translation_key="heating", + ) + entry2 = entity_registry.async_get_or_create( + domain=Platform.CLIMATE, + platform=DOMAIN, + config_entry=mock_config_entry, + unique_id="gateway0_deviceSerialVitodens300W-heating-1", + translation_key="heating", + ) + entry3 = entity_registry.async_get_or_create( + domain=Platform.CLIMATE, + platform=DOMAIN, + config_entry=mock_config_entry, + unique_id="gateway1-0", + translation_key="heating", + ) + + await hass.config_entries.async_setup(mock_config_entry.entry_id) + + await hass.async_block_till_done() + + assert ( + entity_registry.async_get(entry1.entity_id).unique_id + == "gateway0_deviceSerialVitodens300W-heating-0" + ) + assert ( + entity_registry.async_get(entry2.entity_id).unique_id + == "gateway0_deviceSerialVitodens300W-heating-1" + ) + assert entity_registry.async_get(entry3.entity_id).unique_id == "gateway1-0"