mirror of
https://github.com/home-assistant/core.git
synced 2026-09-25 17:04:04 -04:00
Migrate Shelly virtual component unique IDs to include roles (#153844)
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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[
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user