From 881306f6a4f4aa1469c7d5c8ad10e6eff17c17db Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Tue, 7 Oct 2025 00:50:47 +0300 Subject: [PATCH] Migrate Shelly virtual component unique IDs to include roles (#153844) --- homeassistant/components/shelly/__init__.py | 8 ++ .../components/shelly/binary_sensor.py | 13 +- homeassistant/components/shelly/const.py | 2 + homeassistant/components/shelly/entity.py | 7 +- homeassistant/components/shelly/number.py | 65 ++++++++- homeassistant/components/shelly/select.py | 3 +- homeassistant/components/shelly/sensor.py | 47 +------ homeassistant/components/shelly/switch.py | 123 +++++++++++++++++- homeassistant/components/shelly/text.py | 3 +- homeassistant/components/shelly/utils.py | 37 ++++++ tests/components/shelly/test_binary_sensor.py | 10 +- tests/components/shelly/test_number.py | 6 +- tests/components/shelly/test_select.py | 6 +- tests/components/shelly/test_sensor.py | 20 ++- tests/components/shelly/test_switch.py | 10 +- tests/components/shelly/test_text.py | 6 +- 16 files changed, 287 insertions(+), 79 deletions(-) diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index c2df1ed4cb25..fa920e786b00 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations +from functools import partial from typing import Final from aioshelly.ble.const import BLE_SCRIPT_NAME @@ -63,6 +64,7 @@ from .repairs import ( ) from .utils import ( async_create_issue_unsupported_firmware, + async_migrate_rpc_virtual_components_unique_ids, get_coap_context, get_device_entry_gen, get_http_port, @@ -323,6 +325,12 @@ async def _async_setup_rpc_entry(hass: HomeAssistant, entry: ShellyConfigEntry) translation_placeholders={"device": entry.title}, ) from err + await er.async_migrate_entries( + hass, + entry.entry_id, + partial(async_migrate_rpc_virtual_components_unique_ids, device.config), + ) + runtime_data.rpc = ShellyRpcCoordinator(hass, entry, device) runtime_data.rpc.async_setup() runtime_data.rpc_poll = ShellyRpcPollingCoordinator(hass, entry, device) diff --git a/homeassistant/components/shelly/binary_sensor.py b/homeassistant/components/shelly/binary_sensor.py index 3cce2f0183f5..28d8c2de084a 100644 --- a/homeassistant/components/shelly/binary_sensor.py +++ b/homeassistant/components/shelly/binary_sensor.py @@ -18,7 +18,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.restore_state import RestoreEntity -from .const import CONF_SLEEP_PERIOD +from .const import CONF_SLEEP_PERIOD, MODEL_FRANKEVER_WATER_VALVE from .coordinator import ShellyConfigEntry, ShellyRpcCoordinator from .entity import ( BlockEntityDescription, @@ -270,12 +270,21 @@ RPC_SENSORS: Final = { entity_registry_enabled_default=False, entity_category=EntityCategory.DIAGNOSTIC, ), - "boolean": RpcBinarySensorDescription( + "boolean_generic": RpcBinarySensorDescription( key="boolean", sub_key="value", removal_condition=lambda config, _status, key: not is_view_for_platform( config, key, BINARY_SENSOR_PLATFORM ), + role="generic", + ), + "boolean_has_power": RpcBinarySensorDescription( + key="boolean", + sub_key="value", + device_class=BinarySensorDeviceClass.POWER, + entity_category=EntityCategory.DIAGNOSTIC, + role="has_power", + models={MODEL_FRANKEVER_WATER_VALVE}, ), "calibration": RpcBinarySensorDescription( key="blutrv", diff --git a/homeassistant/components/shelly/const.py b/homeassistant/components/shelly/const.py index d99be1b0eb3e..98ccb90d4b97 100644 --- a/homeassistant/components/shelly/const.py +++ b/homeassistant/components/shelly/const.py @@ -308,3 +308,5 @@ MODEL_NEO_WATER_VALVE = "NeoWaterValve" MODEL_FRANKEVER_WATER_VALVE = "WaterValve" MODEL_LINKEDGO_ST802_THERMOSTAT = "ST-802" MODEL_LINKEDGO_ST1820_THERMOSTAT = "ST1820" +MODEL_TOP_EV_CHARGER_EVE01 = "EVE01" +MODEL_FRANKEVER_IRRIGATION_CONTROLLER = "Irrigation" diff --git a/homeassistant/components/shelly/entity.py b/homeassistant/components/shelly/entity.py index 0e4a2b00742c..327d40df4213 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -29,6 +29,7 @@ from .utils import ( get_rpc_device_info, get_rpc_entity_name, get_rpc_key_instances, + get_rpc_role_by_key, ) @@ -189,9 +190,9 @@ def async_setup_rpc_attribute_entities( if description.models and coordinator.model not in description.models: continue - if description.role and description.role != coordinator.device.config[ - key - ].get("role", "generic"): + if description.role and description.role != get_rpc_role_by_key( + coordinator.device.config, key + ): continue if description.sub_key not in coordinator.device.status[ diff --git a/homeassistant/components/shelly/number.py b/homeassistant/components/shelly/number.py index f77db143c850..dfb5fb950383 100644 --- a/homeassistant/components/shelly/number.py +++ b/homeassistant/components/shelly/number.py @@ -24,7 +24,16 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.entity_registry import RegistryEntry -from .const import CONF_SLEEP_PERIOD, DOMAIN, LOGGER, VIRTUAL_NUMBER_MODE_MAP +from .const import ( + CONF_SLEEP_PERIOD, + DOMAIN, + LOGGER, + MODEL_FRANKEVER_WATER_VALVE, + MODEL_LINKEDGO_ST802_THERMOSTAT, + MODEL_LINKEDGO_ST1820_THERMOSTAT, + MODEL_TOP_EV_CHARGER_EVE01, + VIRTUAL_NUMBER_MODE_MAP, +) from .coordinator import ShellyBlockCoordinator, ShellyConfigEntry, ShellyRpcCoordinator from .entity import ( BlockEntityDescription, @@ -183,7 +192,7 @@ RPC_NUMBERS: Final = { method="blu_trv_set_external_temperature", entity_class=RpcBluTrvExtTempNumber, ), - "number": RpcNumberDescription( + "number_generic": RpcNumberDescription( key="number", sub_key="value", removal_condition=lambda config, _status, key: not is_view_for_platform( @@ -197,6 +206,58 @@ RPC_NUMBERS: Final = { step_fn=lambda config: config["meta"]["ui"].get("step"), unit=get_virtual_component_unit, method="number_set", + role="generic", + ), + "number_current_limit": RpcNumberDescription( + key="number", + sub_key="value", + max_fn=lambda config: config["max"], + min_fn=lambda config: config["min"], + mode_fn=lambda config: NumberMode.SLIDER, + step_fn=lambda config: config["meta"]["ui"].get("step"), + unit=get_virtual_component_unit, + method="number_set", + role="current_limit", + models={MODEL_TOP_EV_CHARGER_EVE01}, + ), + "number_position": RpcNumberDescription( + key="number", + sub_key="value", + entity_registry_enabled_default=False, + max_fn=lambda config: config["max"], + min_fn=lambda config: config["min"], + mode_fn=lambda config: NumberMode.SLIDER, + step_fn=lambda config: config["meta"]["ui"].get("step"), + unit=get_virtual_component_unit, + method="number_set", + role="position", + models={MODEL_FRANKEVER_WATER_VALVE}, + ), + "number_target_humidity": RpcNumberDescription( + key="number", + sub_key="value", + entity_registry_enabled_default=False, + max_fn=lambda config: config["max"], + min_fn=lambda config: config["min"], + mode_fn=lambda config: NumberMode.SLIDER, + step_fn=lambda config: config["meta"]["ui"].get("step"), + unit=get_virtual_component_unit, + method="number_set", + role="target_humidity", + models={MODEL_LINKEDGO_ST802_THERMOSTAT, MODEL_LINKEDGO_ST1820_THERMOSTAT}, + ), + "number_target_temperature": RpcNumberDescription( + key="number", + sub_key="value", + entity_registry_enabled_default=False, + max_fn=lambda config: config["max"], + min_fn=lambda config: config["min"], + mode_fn=lambda config: NumberMode.SLIDER, + step_fn=lambda config: config["meta"]["ui"].get("step"), + unit=get_virtual_component_unit, + method="number_set", + role="target_temperature", + models={MODEL_LINKEDGO_ST802_THERMOSTAT, MODEL_LINKEDGO_ST1820_THERMOSTAT}, ), "valve_position": RpcNumberDescription( key="blutrv", diff --git a/homeassistant/components/shelly/select.py b/homeassistant/components/shelly/select.py index c0838482b947..617e2d900097 100644 --- a/homeassistant/components/shelly/select.py +++ b/homeassistant/components/shelly/select.py @@ -38,12 +38,13 @@ class RpcSelectDescription(RpcEntityDescription, SelectEntityDescription): RPC_SELECT_ENTITIES: Final = { - "enum": RpcSelectDescription( + "enum_generic": RpcSelectDescription( key="enum", sub_key="value", removal_condition=lambda config, _status, key: not is_view_for_platform( config, key, SELECT_PLATFORM ), + role="generic", ), } diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index 6bece8f9565e..f48bf28f0b5f 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -3,8 +3,7 @@ from __future__ import annotations from dataclasses import dataclass -from functools import partial -from typing import Any, Final, cast +from typing import Final, cast from aioshelly.block_device import Block from aioshelly.const import RPC_GENERATIONS @@ -37,13 +36,12 @@ from homeassistant.const import ( UnitOfVolume, UnitOfVolumeFlowRate, ) -from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers import entity_registry as er +from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.entity_registry import RegistryEntry from homeassistant.helpers.typing import StateType -from .const import CONF_SLEEP_PERIOD, LOGGER +from .const import CONF_SLEEP_PERIOD from .coordinator import ShellyBlockCoordinator, ShellyConfigEntry, ShellyRpcCoordinator from .entity import ( BlockEntityDescription, @@ -1663,39 +1661,6 @@ RPC_SENSORS: Final = { } -@callback -def async_migrate_unique_ids( - coordinator: ShellyRpcCoordinator, - entity_entry: er.RegistryEntry, -) -> dict[str, Any] | None: - """Migrate sensor unique IDs to include role.""" - if not entity_entry.entity_id.startswith("sensor."): - return None - - for sensor_id in ("text", "number", "enum"): - old_unique_id = entity_entry.unique_id - if old_unique_id.endswith(f"-{sensor_id}"): - if entity_entry.original_device_class == SensorDeviceClass.HUMIDITY: - new_unique_id = f"{old_unique_id}_current_humidity" - elif entity_entry.original_device_class == SensorDeviceClass.TEMPERATURE: - new_unique_id = f"{old_unique_id}_current_temperature" - else: - new_unique_id = f"{old_unique_id}_generic" - LOGGER.debug( - "Migrating unique_id for %s entity from [%s] to [%s]", - entity_entry.entity_id, - old_unique_id, - new_unique_id, - ) - return { - "new_unique_id": entity_entry.unique_id.replace( - old_unique_id, new_unique_id - ) - } - - return None - - async def async_setup_entry( hass: HomeAssistant, config_entry: ShellyConfigEntry, @@ -1715,12 +1680,6 @@ async def async_setup_entry( coordinator = config_entry.runtime_data.rpc assert coordinator - await er.async_migrate_entries( - hass, - config_entry.entry_id, - partial(async_migrate_unique_ids, coordinator), - ) - async_setup_entry_rpc( hass, config_entry, async_add_entities, RPC_SENSORS, RpcSensor ) diff --git a/homeassistant/components/shelly/switch.py b/homeassistant/components/shelly/switch.py index 0518858868df..bc77219f7cb3 100644 --- a/homeassistant/components/shelly/switch.py +++ b/homeassistant/components/shelly/switch.py @@ -21,6 +21,13 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.entity_registry import RegistryEntry from homeassistant.helpers.restore_state import RestoreEntity +from .const import ( + MODEL_FRANKEVER_IRRIGATION_CONTROLLER, + MODEL_LINKEDGO_ST802_THERMOSTAT, + MODEL_LINKEDGO_ST1820_THERMOSTAT, + MODEL_NEO_WATER_VALVE, + MODEL_TOP_EV_CHARGER_EVE01, +) from .coordinator import ShellyBlockCoordinator, ShellyConfigEntry, ShellyRpcCoordinator from .entity import ( BlockEntityDescription, @@ -87,7 +94,7 @@ RPC_RELAY_SWITCHES = { } RPC_SWITCHES = { - "boolean": RpcSwitchDescription( + "boolean_generic": RpcSwitchDescription( key="boolean", sub_key="value", removal_condition=lambda config, _status, key: not is_view_for_platform( @@ -97,6 +104,120 @@ RPC_SWITCHES = { method_on="Boolean.Set", method_off="Boolean.Set", method_params_fn=lambda id, value: {"id": id, "value": value}, + role="generic", + ), + "boolean_anti_freeze": RpcSwitchDescription( + key="boolean", + sub_key="value", + entity_registry_enabled_default=False, + is_on=lambda status: bool(status["value"]), + method_on="Boolean.Set", + method_off="Boolean.Set", + method_params_fn=lambda id, value: {"id": id, "value": value}, + role="anti_freeze", + models={MODEL_LINKEDGO_ST802_THERMOSTAT, MODEL_LINKEDGO_ST1820_THERMOSTAT}, + ), + "boolean_child_lock": RpcSwitchDescription( + key="boolean", + sub_key="value", + is_on=lambda status: bool(status["value"]), + method_on="Boolean.Set", + method_off="Boolean.Set", + method_params_fn=lambda id, value: {"id": id, "value": value}, + role="child_lock", + models={MODEL_LINKEDGO_ST1820_THERMOSTAT}, + ), + "boolean_enable": RpcSwitchDescription( + key="boolean", + sub_key="value", + entity_registry_enabled_default=False, + is_on=lambda status: bool(status["value"]), + method_on="Boolean.Set", + method_off="Boolean.Set", + method_params_fn=lambda id, value: {"id": id, "value": value}, + role="enable", + models={MODEL_LINKEDGO_ST802_THERMOSTAT, MODEL_LINKEDGO_ST1820_THERMOSTAT}, + ), + "boolean_start_charging": RpcSwitchDescription( + key="boolean", + sub_key="value", + is_on=lambda status: bool(status["value"]), + method_on="Boolean.Set", + method_off="Boolean.Set", + method_params_fn=lambda id, value: {"id": id, "value": value}, + role="start_charging", + models={MODEL_TOP_EV_CHARGER_EVE01}, + ), + "boolean_state": RpcSwitchDescription( + key="boolean", + sub_key="value", + entity_registry_enabled_default=False, + is_on=lambda status: bool(status["value"]), + method_on="Boolean.Set", + method_off="Boolean.Set", + method_params_fn=lambda id, value: {"id": id, "value": value}, + role="state", + models={MODEL_NEO_WATER_VALVE}, + ), + "boolean_zone0": RpcSwitchDescription( + key="boolean", + sub_key="value", + is_on=lambda status: bool(status["value"]), + method_on="Boolean.Set", + method_off="Boolean.Set", + method_params_fn=lambda id, value: {"id": id, "value": value}, + role="zone0", + models={MODEL_FRANKEVER_IRRIGATION_CONTROLLER}, + ), + "boolean_zone1": RpcSwitchDescription( + key="boolean", + sub_key="value", + is_on=lambda status: bool(status["value"]), + method_on="Boolean.Set", + method_off="Boolean.Set", + method_params_fn=lambda id, value: {"id": id, "value": value}, + role="zone1", + models={MODEL_FRANKEVER_IRRIGATION_CONTROLLER}, + ), + "boolean_zone2": RpcSwitchDescription( + key="boolean", + sub_key="value", + is_on=lambda status: bool(status["value"]), + method_on="Boolean.Set", + method_off="Boolean.Set", + method_params_fn=lambda id, value: {"id": id, "value": value}, + role="zone2", + models={MODEL_FRANKEVER_IRRIGATION_CONTROLLER}, + ), + "boolean_zone3": RpcSwitchDescription( + key="boolean", + sub_key="value", + is_on=lambda status: bool(status["value"]), + method_on="Boolean.Set", + method_off="Boolean.Set", + method_params_fn=lambda id, value: {"id": id, "value": value}, + role="zone3", + models={MODEL_FRANKEVER_IRRIGATION_CONTROLLER}, + ), + "boolean_zone4": RpcSwitchDescription( + key="boolean", + sub_key="value", + is_on=lambda status: bool(status["value"]), + method_on="Boolean.Set", + method_off="Boolean.Set", + method_params_fn=lambda id, value: {"id": id, "value": value}, + role="zone4", + models={MODEL_FRANKEVER_IRRIGATION_CONTROLLER}, + ), + "boolean_zone5": RpcSwitchDescription( + key="boolean", + sub_key="value", + is_on=lambda status: bool(status["value"]), + method_on="Boolean.Set", + method_off="Boolean.Set", + method_params_fn=lambda id, value: {"id": id, "value": value}, + role="zone5", + models={MODEL_FRANKEVER_IRRIGATION_CONTROLLER}, ), "script": RpcSwitchDescription( key="script", diff --git a/homeassistant/components/shelly/text.py b/homeassistant/components/shelly/text.py index 5a514771a3f1..ef30ec310ed6 100644 --- a/homeassistant/components/shelly/text.py +++ b/homeassistant/components/shelly/text.py @@ -38,12 +38,13 @@ class RpcTextDescription(RpcEntityDescription, TextEntityDescription): RPC_TEXT_ENTITIES: Final = { - "text": RpcTextDescription( + "text_generic": RpcTextDescription( key="text", sub_key="value", removal_condition=lambda config, _status, key: not is_view_for_platform( config, key, TEXT_PLATFORM ), + role="generic", ), } diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index 6cd90f1feb9d..8024fe64446b 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -484,6 +484,11 @@ def get_rpc_key_by_role(keys_dict: dict[str, Any], role: str) -> str | None: return None +def get_rpc_role_by_key(keys_dict: dict[str, Any], key: str) -> str: + """Return role by key for RPC device from a dict.""" + return cast(str, keys_dict[key].get("role", "generic")) + + def id_from_key(key: str) -> int: """Return id from key.""" return int(key.split(":")[-1]) @@ -934,3 +939,35 @@ def remove_empty_sub_devices(hass: HomeAssistant, entry: ConfigEntry) -> None: def format_ble_addr(ble_addr: str) -> str: """Format BLE address to use in unique_id.""" return ble_addr.replace(":", "").upper() + + +@callback +def async_migrate_rpc_virtual_components_unique_ids( + config: dict[str, Any], entity_entry: er.RegistryEntry +) -> dict[str, Any] | None: + """Migrate RPC virtual components unique_ids to include role in the ID. + + This is needed to support multiple components with the same key. + The old unique_id format is: {mac}-{key}-{component} + The new unique_id format is: {mac}-{key}-{component}_{role} + """ + for component in VIRTUAL_COMPONENTS: + if entity_entry.unique_id.endswith(f"-{component!s}"): + key = entity_entry.unique_id.split("-")[-2] + if key not in config: + continue + role = get_rpc_role_by_key(config, key) + new_unique_id = f"{entity_entry.unique_id}_{role}" + LOGGER.debug( + "Migrating unique_id for %s entity from [%s] to [%s]", + entity_entry.entity_id, + entity_entry.unique_id, + new_unique_id, + ) + return { + "new_unique_id": entity_entry.unique_id.replace( + entity_entry.unique_id, new_unique_id + ) + } + + return None diff --git a/tests/components/shelly/test_binary_sensor.py b/tests/components/shelly/test_binary_sensor.py index 090a0b47c3c1..0c42a20d8226 100644 --- a/tests/components/shelly/test_binary_sensor.py +++ b/tests/components/shelly/test_binary_sensor.py @@ -440,7 +440,7 @@ async def test_rpc_device_virtual_binary_sensor( assert state.state == STATE_ON assert (entry := entity_registry.async_get(entity_id)) - assert entry.unique_id == "123456789ABC-boolean:203-boolean" + assert entry.unique_id == "123456789ABC-boolean:203-boolean_generic" monkeypatch.setitem(mock_rpc_device.status["boolean:203"], "value", False) mock_rpc_device.mock_update() @@ -472,7 +472,7 @@ async def test_rpc_remove_virtual_binary_sensor_when_mode_toggle( hass, BINARY_SENSOR_DOMAIN, "test_name_boolean_200", - "boolean:200-boolean", + "boolean:200-boolean_generic", config_entry, device_id=device_entry.id, ) @@ -498,7 +498,7 @@ async def test_rpc_remove_virtual_binary_sensor_when_orphaned( hass, BINARY_SENSOR_DOMAIN, "test_name_boolean_200", - "boolean:200-boolean", + "boolean:200-boolean_generic", config_entry, device_id=device_entry.id, ) @@ -507,13 +507,13 @@ async def test_rpc_remove_virtual_binary_sensor_when_orphaned( sub_device_entry = register_sub_device( device_registry, config_entry, - "boolean:201-boolean", + "boolean:201-boolean_generic", ) entity_id2 = register_entity( hass, BINARY_SENSOR_DOMAIN, "boolean_201", - "boolean:201-boolean", + "boolean:201-boolean_generic", config_entry, device_id=sub_device_entry.id, ) diff --git a/tests/components/shelly/test_number.py b/tests/components/shelly/test_number.py index c72308217727..5f42f9a131c0 100644 --- a/tests/components/shelly/test_number.py +++ b/tests/components/shelly/test_number.py @@ -331,7 +331,7 @@ async def test_rpc_device_virtual_number( assert state.attributes.get(ATTR_MODE) is mode assert (entry := entity_registry.async_get(entity_id)) - assert entry.unique_id == "123456789ABC-number:203-number" + assert entry.unique_id == "123456789ABC-number:203-number_generic" monkeypatch.setitem(mock_rpc_device.status["number:203"], "value", 78.9) mock_rpc_device.mock_update() @@ -380,7 +380,7 @@ async def test_rpc_remove_virtual_number_when_mode_label( hass, NUMBER_DOMAIN, "test_name_number_200", - "number:200-number", + "number:200-number_generic", config_entry, device_id=device_entry.id, ) @@ -404,7 +404,7 @@ async def test_rpc_remove_virtual_number_when_orphaned( hass, NUMBER_DOMAIN, "test_name_number_200", - "number:200-number", + "number:200-number_generic", config_entry, device_id=device_entry.id, ) diff --git a/tests/components/shelly/test_select.py b/tests/components/shelly/test_select.py index eefd84d40eb6..d99fc9bf85cf 100644 --- a/tests/components/shelly/test_select.py +++ b/tests/components/shelly/test_select.py @@ -76,7 +76,7 @@ async def test_rpc_device_virtual_enum( ] assert (entry := entity_registry.async_get(entity_id)) - assert entry.unique_id == "123456789ABC-enum:203-enum" + assert entry.unique_id == "123456789ABC-enum:203-enum_generic" monkeypatch.setitem(mock_rpc_device.status["enum:203"], "value", "option 2") mock_rpc_device.mock_update() @@ -128,7 +128,7 @@ async def test_rpc_remove_virtual_enum_when_mode_label( hass, SELECT_PLATFORM, "test_name_enum_200", - "enum:200-enum", + "enum:200-enum_generic", config_entry, device_id=device_entry.id, ) @@ -152,7 +152,7 @@ async def test_rpc_remove_virtual_enum_when_orphaned( hass, SELECT_PLATFORM, "test_name_enum_200", - "enum:200-enum", + "enum:200-enum_generic", config_entry, device_id=device_entry.id, ) diff --git a/tests/components/shelly/test_sensor.py b/tests/components/shelly/test_sensor.py index f1f41f5c1883..44e13f7c1fba 100644 --- a/tests/components/shelly/test_sensor.py +++ b/tests/components/shelly/test_sensor.py @@ -1080,12 +1080,12 @@ async def test_rpc_device_virtual_text_sensor( @pytest.mark.parametrize( - ("old_id", "new_id", "device_class"), + ("old_id", "new_id", "role"), [ - ("enum", "enum_generic", SensorDeviceClass.ENUM), + ("enum", "enum_generic", None), ("number", "number_generic", None), - ("number", "number_current_humidity", SensorDeviceClass.HUMIDITY), - ("number", "number_current_temperature", SensorDeviceClass.TEMPERATURE), + ("number", "number_current_humidity", "current_humidity"), + ("number", "number_current_temperature", "current_temperature"), ("text", "text_generic", None), ], ) @@ -1094,15 +1094,24 @@ async def test_migrate_unique_id_virtual_components_roles( mock_rpc_device: Mock, entity_registry: EntityRegistry, caplog: pytest.LogCaptureFixture, + monkeypatch: pytest.MonkeyPatch, old_id: str, new_id: str, - device_class: SensorDeviceClass | None, + role: str | None, ) -> None: """Test migration of unique_id for virtual components to include role.""" entry = await init_integration(hass, 3, skip_setup=True) unique_base = f"{MOCK_MAC}-{old_id}:200" old_unique_id = f"{unique_base}-{old_id}" new_unique_id = f"{unique_base}-{new_id}" + config = deepcopy(mock_rpc_device.config) + if role: + config[f"{old_id}:200"] = { + "role": role, + } + else: + config[f"{old_id}:200"] = {} + monkeypatch.setattr(mock_rpc_device, "config", config) entity = entity_registry.async_get_or_create( suggested_object_id="test_name_test_sensor", @@ -1111,7 +1120,6 @@ async def test_migrate_unique_id_virtual_components_roles( platform=DOMAIN, unique_id=old_unique_id, config_entry=entry, - original_device_class=device_class, ) assert entity.unique_id == old_unique_id diff --git a/tests/components/shelly/test_switch.py b/tests/components/shelly/test_switch.py index 39fc001cbed6..82eab4bb12dc 100644 --- a/tests/components/shelly/test_switch.py +++ b/tests/components/shelly/test_switch.py @@ -645,7 +645,7 @@ async def test_rpc_device_virtual_switch( assert state.state == STATE_ON assert (entry := entity_registry.async_get(entity_id)) - assert entry.unique_id == "123456789ABC-boolean:200-boolean" + assert entry.unique_id == "123456789ABC-boolean:200-boolean_generic" monkeypatch.setitem(mock_rpc_device.status["boolean:200"], "value", False) await hass.services.async_call( @@ -715,7 +715,7 @@ async def test_rpc_remove_virtual_switch_when_mode_label( hass, SWITCH_DOMAIN, "test_name_boolean_200", - "boolean:200-boolean", + "boolean:200-boolean_generic", config_entry, device_id=device_entry.id, ) @@ -741,7 +741,7 @@ async def test_rpc_remove_virtual_switch_when_orphaned( hass, SWITCH_DOMAIN, "test_name_boolean_200", - "boolean:200-boolean", + "boolean:200-boolean_generic", config_entry, device_id=device_entry.id, ) @@ -750,13 +750,13 @@ async def test_rpc_remove_virtual_switch_when_orphaned( sub_device_entry = register_sub_device( device_registry, config_entry, - "boolean:201-boolean", + "boolean:201-boolean_generic", ) entity_id2 = register_entity( hass, SWITCH_DOMAIN, "boolean_201", - "boolean:201-boolean", + "boolean:201-boolean_generic", config_entry, device_id=sub_device_entry.id, ) diff --git a/tests/components/shelly/test_text.py b/tests/components/shelly/test_text.py index 59c434213b1e..ad8497a1d038 100644 --- a/tests/components/shelly/test_text.py +++ b/tests/components/shelly/test_text.py @@ -62,7 +62,7 @@ async def test_rpc_device_virtual_text( assert state.state == "lorem ipsum" assert (entry := entity_registry.async_get(entity_id)) - assert entry.unique_id == "123456789ABC-text:203-text" + assert entry.unique_id == "123456789ABC-text:203-text_generic" monkeypatch.setitem(mock_rpc_device.status["text:203"], "value", "dolor sit amet") mock_rpc_device.mock_update() @@ -107,7 +107,7 @@ async def test_rpc_remove_virtual_text_when_mode_label( hass, TEXT_PLATFORM, "test_name_text_200", - "text:200-text", + "text:200-text_generic", config_entry, device_id=device_entry.id, ) @@ -131,7 +131,7 @@ async def test_rpc_remove_virtual_text_when_orphaned( hass, TEXT_PLATFORM, "test_name_text_200", - "text:200-text", + "text:200-text_generic", config_entry, device_id=device_entry.id, )