diff --git a/homeassistant/components/conversation/default_agent.py b/homeassistant/components/conversation/default_agent.py index 6c238ff0c5216..16a1cbdb4b65d 100644 --- a/homeassistant/components/conversation/default_agent.py +++ b/homeassistant/components/conversation/default_agent.py @@ -45,13 +45,18 @@ ) import yaml -from homeassistant import core from homeassistant.components.homeassistant.exposed_entities import ( async_listen_entity_updates, async_should_expose, ) from homeassistant.const import EVENT_STATE_CHANGED, MATCH_ALL -from homeassistant.core import Event, callback +from homeassistant.core import ( + Event, + EventStateChangedData, + HomeAssistant, + State, + callback, +) from homeassistant.helpers import ( area_registry as ar, device_registry as dr, @@ -192,7 +197,7 @@ def clear(self) -> None: async def async_setup_default_agent( - hass: core.HomeAssistant, + hass: HomeAssistant, entity_component: EntityComponent[ConversationEntity], config_intents: dict[str, Any], ) -> None: @@ -201,15 +206,13 @@ async def async_setup_default_agent( await entity_component.async_add_entities([agent]) await get_agent_manager(hass).async_setup_default_agent(agent) - @core.callback - def async_entity_state_listener( - event: core.Event[core.EventStateChangedData], - ) -> None: + @callback + def async_entity_state_listener(event: Event[EventStateChangedData]) -> None: """Set expose flag on new entities.""" async_should_expose(hass, DOMAIN, event.data["entity_id"]) - @core.callback - def async_hass_started(hass: core.HomeAssistant) -> None: + @callback + def async_hass_started(hass: HomeAssistant) -> None: """Set expose flag on all entities.""" for state in hass.states.async_all(): async_should_expose(hass, DOMAIN, state.entity_id) @@ -224,9 +227,7 @@ class DefaultAgent(ConversationEntity): _attr_name = "Home Assistant" _attr_supported_features = ConversationEntityFeature.CONTROL - def __init__( - self, hass: core.HomeAssistant, config_intents: dict[str, Any] - ) -> None: + def __init__(self, hass: HomeAssistant, config_intents: dict[str, Any]) -> None: """Initialize the default agent.""" self.hass = hass self._lang_intents: dict[str, LanguageIntents | object] = {} @@ -259,7 +260,7 @@ def supported_languages(self) -> list[str]: """Return a list of supported languages.""" return get_languages() - @core.callback + @callback def _filter_entity_registry_changes( self, event_data: er.EventEntityRegistryUpdatedData ) -> bool: @@ -268,12 +269,12 @@ def _filter_entity_registry_changes( field in event_data["changes"] for field in _ENTITY_REGISTRY_UPDATE_FIELDS ) - @core.callback - def _filter_state_changes(self, event_data: core.EventStateChangedData) -> bool: + @callback + def _filter_state_changes(self, event_data: EventStateChangedData) -> bool: """Filter state changed events.""" return not event_data["old_state"] or not event_data["new_state"] - @core.callback + @callback def _listen_clear_slot_list(self) -> None: """Listen for changes that can invalidate slot list.""" assert self._unsub_clear_slot_list is None @@ -890,7 +891,7 @@ async def _build_speech( ) -> str: # Get first matched or unmatched state. # This is available in the response template as "state". - state1: core.State | None = None + state1: State | None = None if intent_response.matched_states: state1 = intent_response.matched_states[0] elif intent_response.unmatched_states: @@ -1589,7 +1590,7 @@ def _get_unmatched_response(result: RecognizeResult) -> tuple[ErrorKey, dict[str def _get_match_error_response( - hass: core.HomeAssistant, + hass: HomeAssistant, match_error: intent.MatchFailedError, ) -> tuple[ErrorKey, dict[str, Any]]: """Return key and template arguments for error when target matching fails.""" diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index c2df1ed4cb259..fa920e786b004 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 .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 3cce2f0183f55..28d8c2de084a0 100644 --- a/homeassistant/components/shelly/binary_sensor.py +++ b/homeassistant/components/shelly/binary_sensor.py @@ -18,7 +18,7 @@ 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 @@ def __init__( 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 d99be1b0eb3e5..98ccb90d4b976 100644 --- a/homeassistant/components/shelly/const.py +++ b/homeassistant/components/shelly/const.py @@ -308,3 +308,5 @@ class BLEScannerMode(StrEnum): 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 0e4a2b00742c9..327d40df42139 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -29,6 +29,7 @@ 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 f77db143c8506..dfb5fb9503831 100644 --- a/homeassistant/components/shelly/number.py +++ b/homeassistant/components/shelly/number.py @@ -24,7 +24,16 @@ 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 @@ async def async_set_native_value(self, value: float) -> None: 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 @@ async def async_set_native_value(self, value: float) -> None: 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 c0838482b9474..617e2d900097c 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 6bece8f9565ea..fb399fd80d4e8 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 @@ 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, @@ -551,7 +549,7 @@ def __init__( "a_act_power": RpcSensorDescription( key="em", sub_key="a_act_power", - name="Active power", + name="Power", native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, @@ -561,7 +559,7 @@ def __init__( "b_act_power": RpcSensorDescription( key="em", sub_key="b_act_power", - name="Active power", + name="Power", native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, @@ -571,7 +569,7 @@ def __init__( "c_act_power": RpcSensorDescription( key="em", sub_key="c_act_power", - name="Active power", + name="Power", native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, @@ -581,7 +579,7 @@ def __init__( "total_act_power": RpcSensorDescription( key="em", sub_key="total_act_power", - name="Total active power", + name="Power", native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, @@ -627,7 +625,7 @@ def __init__( "total_aprt_power": RpcSensorDescription( key="em", sub_key="total_aprt_power", - name="Total apparent power", + name="Apparent power", native_unit_of_measurement=UnitOfApparentPower.VOLT_AMPERE, device_class=SensorDeviceClass.APPARENT_POWER, state_class=SensorStateClass.MEASUREMENT, @@ -882,7 +880,7 @@ def __init__( "n_current": RpcSensorDescription( key="em", sub_key="n_current", - name="Phase N current", + name="Neutral current", native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, device_class=SensorDeviceClass.CURRENT, state_class=SensorStateClass.MEASUREMENT, @@ -892,7 +890,7 @@ def __init__( "total_current": RpcSensorDescription( key="em", sub_key="total_current", - name="Total current", + name="Current", native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, device_class=SensorDeviceClass.CURRENT, state_class=SensorStateClass.MEASUREMENT, @@ -1384,7 +1382,7 @@ def __init__( native_unit_of_measurement="pulse", state_class=SensorStateClass.TOTAL, value=lambda status, _: status["total"], - removal_condition=lambda config, _status, key: ( + removal_condition=lambda config, _, key: ( config[key]["type"] != "count" or config[key]["enable"] is False ), ), @@ -1424,7 +1422,7 @@ def __init__( "text_generic": RpcSensorDescription( key="text", sub_key="value", - removal_condition=lambda config, _status, key: not is_view_for_platform( + removal_condition=lambda config, _, key: not is_view_for_platform( config, key, SENSOR_PLATFORM ), role="generic", @@ -1432,7 +1430,7 @@ def __init__( "number_generic": RpcSensorDescription( key="number", sub_key="value", - removal_condition=lambda config, _status, key: not is_view_for_platform( + removal_condition=lambda config, _, key: not is_view_for_platform( config, key, SENSOR_PLATFORM ), unit=get_virtual_component_unit, @@ -1441,7 +1439,7 @@ def __init__( "enum_generic": RpcSensorDescription( key="enum", sub_key="value", - removal_condition=lambda config, _status, key: not is_view_for_platform( + removal_condition=lambda config, _, key: not is_view_for_platform( config, key, SENSOR_PLATFORM ), options_fn=lambda config: config["options"], @@ -1456,7 +1454,7 @@ def __init__( native_unit_of_measurement=PERCENTAGE, state_class=SensorStateClass.MEASUREMENT, entity_category=EntityCategory.DIAGNOSTIC, - removal_condition=lambda config, _status, key: config[key].get("enable", False) + removal_condition=lambda config, _, key: config[key].get("enable", False) is False, entity_class=RpcBluTrvSensor, ), @@ -1606,7 +1604,7 @@ def __init__( "object_total_act_energy": RpcSensorDescription( key="object", sub_key="value", - name="Total Active Energy", + name="Energy", value=lambda status, _: float(status["total_act_energy"]), native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, @@ -1618,7 +1616,7 @@ def __init__( "object_total_power": RpcSensorDescription( key="object", sub_key="value", - name="Total Power", + name="Power", value=lambda status, _: float(status["total_power"]), native_unit_of_measurement=UnitOfPower.WATT, suggested_unit_of_measurement=UnitOfPower.KILO_WATT, @@ -1663,39 +1661,6 @@ def __init__( } -@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 0518858868df1..bc77219f7cb3e 100644 --- a/homeassistant/components/shelly/switch.py +++ b/homeassistant/components/shelly/switch.py @@ -21,6 +21,13 @@ 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 @@ class RpcSwitchDescription(RpcEntityDescription, SwitchEntityDescription): } 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 @@ class RpcSwitchDescription(RpcEntityDescription, SwitchEntityDescription): 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 5a514771a3f10..ef30ec310ed6a 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 6cd90f1feb9d9..8024fe64446b6 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/homeassistant/const.py b/homeassistant/const.py index 4ae1a73df6bee..1f0bc7bdc30f2 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -8,7 +8,6 @@ from .generated.entity_platforms import EntityPlatforms from .helpers.deprecation import ( - DeprecatedConstant, DeprecatedConstantEnum, EnumWithDeprecatedMembers, all_with_deprecated_constants, @@ -316,60 +315,6 @@ STATE_PROBLEM: Final = "problem" -# #### ALARM CONTROL PANEL STATES #### -# STATE_ALARM_* below are deprecated as of 2024.11 -# use the AlarmControlPanelState enum instead. -_DEPRECATED_STATE_ALARM_DISARMED: Final = DeprecatedConstant( - "disarmed", - "AlarmControlPanelState.DISARMED", - "2025.11", -) -_DEPRECATED_STATE_ALARM_ARMED_HOME: Final = DeprecatedConstant( - "armed_home", - "AlarmControlPanelState.ARMED_HOME", - "2025.11", -) -_DEPRECATED_STATE_ALARM_ARMED_AWAY: Final = DeprecatedConstant( - "armed_away", - "AlarmControlPanelState.ARMED_AWAY", - "2025.11", -) -_DEPRECATED_STATE_ALARM_ARMED_NIGHT: Final = DeprecatedConstant( - "armed_night", - "AlarmControlPanelState.ARMED_NIGHT", - "2025.11", -) -_DEPRECATED_STATE_ALARM_ARMED_VACATION: Final = DeprecatedConstant( - "armed_vacation", - "AlarmControlPanelState.ARMED_VACATION", - "2025.11", -) -_DEPRECATED_STATE_ALARM_ARMED_CUSTOM_BYPASS: Final = DeprecatedConstant( - "armed_custom_bypass", - "AlarmControlPanelState.ARMED_CUSTOM_BYPASS", - "2025.11", -) -_DEPRECATED_STATE_ALARM_PENDING: Final = DeprecatedConstant( - "pending", - "AlarmControlPanelState.PENDING", - "2025.11", -) -_DEPRECATED_STATE_ALARM_ARMING: Final = DeprecatedConstant( - "arming", - "AlarmControlPanelState.ARMING", - "2025.11", -) -_DEPRECATED_STATE_ALARM_DISARMING: Final = DeprecatedConstant( - "disarming", - "AlarmControlPanelState.DISARMING", - "2025.11", -) -_DEPRECATED_STATE_ALARM_TRIGGERED: Final = DeprecatedConstant( - "triggered", - "AlarmControlPanelState.TRIGGERED", - "2025.11", -) - # #### STATE AND EVENT ATTRIBUTES #### # Attribution ATTR_ATTRIBUTION: Final = "attribution" diff --git a/tests/components/shelly/snapshots/test_devices.ambr b/tests/components/shelly/snapshots/test_devices.ambr index 06b9acedf03e6..4a8efa560b36c 100644 --- a/tests/components/shelly/snapshots/test_devices.ambr +++ b/tests/components/shelly/snapshots/test_devices.ambr @@ -3071,7 +3071,7 @@ 'state': 'unknown', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_a_active_power-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_a_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -3086,7 +3086,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_a_active_power', + 'entity_id': 'sensor.test_name_phase_a_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -3101,7 +3101,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Active power', + 'original_name': 'Power', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, @@ -3111,16 +3111,16 @@ 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_a_active_power-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_a_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power', - 'friendly_name': 'Test name Phase A Active power', + 'friendly_name': 'Test name Phase A Power', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_a_active_power', + 'entity_id': 'sensor.test_name_phase_a_power', 'last_changed': , 'last_reported': , 'last_updated': , @@ -3521,7 +3521,7 @@ 'state': '227.0', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_b_active_power-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_b_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -3536,7 +3536,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_b_active_power', + 'entity_id': 'sensor.test_name_phase_b_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -3551,7 +3551,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Active power', + 'original_name': 'Power', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, @@ -3561,16 +3561,16 @@ 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_b_active_power-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_b_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power', - 'friendly_name': 'Test name Phase B Active power', + 'friendly_name': 'Test name Phase B Power', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_b_active_power', + 'entity_id': 'sensor.test_name_phase_b_power', 'last_changed': , 'last_reported': , 'last_updated': , @@ -3971,7 +3971,7 @@ 'state': '230.0', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_c_active_power-entry] +# name: test_shelly_pro_3em[sensor.test_name_phase_c_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -3986,7 +3986,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_c_active_power', + 'entity_id': 'sensor.test_name_phase_c_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -4001,7 +4001,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Active power', + 'original_name': 'Power', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, @@ -4011,16 +4011,16 @@ 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_c_active_power-state] +# name: test_shelly_pro_3em[sensor.test_name_phase_c_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power', - 'friendly_name': 'Test name Phase C Active power', + 'friendly_name': 'Test name Phase C Power', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_c_active_power', + 'entity_id': 'sensor.test_name_phase_c_power', 'last_changed': , 'last_reported': , 'last_updated': , @@ -4421,7 +4421,7 @@ 'state': '230.2', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_n_current-entry] +# name: test_shelly_pro_3em[sensor.test_name_neutral_current-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -4436,7 +4436,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_phase_n_current', + 'entity_id': 'sensor.test_name_neutral_current', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -4451,7 +4451,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Phase N current', + 'original_name': 'Neutral current', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, @@ -4461,16 +4461,16 @@ 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_phase_n_current-state] +# name: test_shelly_pro_3em[sensor.test_name_neutral_current-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'current', - 'friendly_name': 'Test name Phase N current', + 'friendly_name': 'Test name Neutral current', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_phase_n_current', + 'entity_id': 'sensor.test_name_neutral_current', 'last_changed': , 'last_reported': , 'last_updated': , @@ -4645,7 +4645,7 @@ 'state': '5415.41419', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_total_active_power-entry] +# name: test_shelly_pro_3em[sensor.test_name_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -4660,7 +4660,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_total_active_power', + 'entity_id': 'sensor.test_name_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -4675,7 +4675,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Total active power', + 'original_name': 'Power', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, @@ -4685,16 +4685,16 @@ 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_total_active_power-state] +# name: test_shelly_pro_3em[sensor.test_name_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power', - 'friendly_name': 'Test name Total active power', + 'friendly_name': 'Test name Power', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_total_active_power', + 'entity_id': 'sensor.test_name_power', 'last_changed': , 'last_reported': , 'last_updated': , @@ -4760,7 +4760,7 @@ 'state': '0.0', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_total_apparent_power-entry] +# name: test_shelly_pro_3em[sensor.test_name_apparent_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -4775,7 +4775,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_total_apparent_power', + 'entity_id': 'sensor.test_name_apparent_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -4790,7 +4790,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Total apparent power', + 'original_name': 'Apparent power', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, @@ -4800,23 +4800,23 @@ 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_total_apparent_power-state] +# name: test_shelly_pro_3em[sensor.test_name_apparent_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'apparent_power', - 'friendly_name': 'Test name Total apparent power', + 'friendly_name': 'Test name Apparent power', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_total_apparent_power', + 'entity_id': 'sensor.test_name_apparent_power', 'last_changed': , 'last_reported': , 'last_updated': , 'state': '2525.779', }) # --- -# name: test_shelly_pro_3em[sensor.test_name_total_current-entry] +# name: test_shelly_pro_3em[sensor.test_name_current-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -4831,7 +4831,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.test_name_total_current', + 'entity_id': 'sensor.test_name_current', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -4846,7 +4846,7 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Total current', + 'original_name': 'Current', 'platform': 'shelly', 'previous_unique_id': None, 'suggested_object_id': None, @@ -4856,16 +4856,16 @@ 'unit_of_measurement': , }) # --- -# name: test_shelly_pro_3em[sensor.test_name_total_current-state] +# name: test_shelly_pro_3em[sensor.test_name_current-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'current', - 'friendly_name': 'Test name Total current', + 'friendly_name': 'Test name Current', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.test_name_total_current', + 'entity_id': 'sensor.test_name_current', 'last_changed': , 'last_reported': , 'last_updated': , diff --git a/tests/components/shelly/test_binary_sensor.py b/tests/components/shelly/test_binary_sensor.py index 090a0b47c3c19..0c42a20d8226a 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_devices.py b/tests/components/shelly/test_devices.py index 1e2f8088618bb..bee8cd7c2c57e 100644 --- a/tests/components/shelly/test_devices.py +++ b/tests/components/shelly/test_devices.py @@ -347,7 +347,7 @@ async def test_shelly_pro_3em( config_entry = await init_integration(hass, gen=2, model=MODEL_PRO_EM3) # Main device - entity_id = "sensor.test_name_total_active_power" + entity_id = "sensor.test_name_power" state = hass.states.get(entity_id) assert state @@ -360,7 +360,7 @@ async def test_shelly_pro_3em( assert device_entry.name == "Test name" # Phase A sub-device - entity_id = "sensor.test_name_phase_a_active_power" + entity_id = "sensor.test_name_phase_a_power" state = hass.states.get(entity_id) assert state @@ -373,7 +373,7 @@ async def test_shelly_pro_3em( assert device_entry.name == "Test name Phase A" # Phase B sub-device - entity_id = "sensor.test_name_phase_b_active_power" + entity_id = "sensor.test_name_phase_b_power" state = hass.states.get(entity_id) assert state @@ -386,7 +386,7 @@ async def test_shelly_pro_3em( assert device_entry.name == "Test name Phase B" # Phase C sub-device - entity_id = "sensor.test_name_phase_c_active_power" + entity_id = "sensor.test_name_phase_c_power" state = hass.states.get(entity_id) assert state @@ -423,7 +423,7 @@ async def test_shelly_pro_3em_with_emeter_name( await init_integration(hass, gen=2, model=MODEL_PRO_EM3) # Main device - entity_id = "sensor.test_name_total_active_power" + entity_id = "sensor.test_name_power" state = hass.states.get(entity_id) assert state @@ -436,7 +436,7 @@ async def test_shelly_pro_3em_with_emeter_name( assert device_entry.name == "Test name" # Phase A sub-device - entity_id = "sensor.test_name_phase_a_active_power" + entity_id = "sensor.test_name_phase_a_power" state = hass.states.get(entity_id) assert state @@ -449,7 +449,7 @@ async def test_shelly_pro_3em_with_emeter_name( assert device_entry.name == "Test name Phase A" # Phase B sub-device - entity_id = "sensor.test_name_phase_b_active_power" + entity_id = "sensor.test_name_phase_b_power" state = hass.states.get(entity_id) assert state @@ -462,7 +462,7 @@ async def test_shelly_pro_3em_with_emeter_name( assert device_entry.name == "Test name Phase B" # Phase C sub-device - entity_id = "sensor.test_name_phase_c_active_power" + entity_id = "sensor.test_name_phase_c_power" state = hass.states.get(entity_id) assert state diff --git a/tests/components/shelly/test_number.py b/tests/components/shelly/test_number.py index c723082177279..5f42f9a131c0e 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 eefd84d40eb6a..d99fc9bf85cf2 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 f1f41f5c18835..44e13f7c1fbab 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 39fc001cbed60..82eab4bb12dc5 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 59c434213b1ee..ad8497a1d038c 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, ) diff --git a/tests/test_const.py b/tests/test_const.py index 4413e8efe9618..426e3c598a6c1 100644 --- a/tests/test_const.py +++ b/tests/test_const.py @@ -8,13 +8,11 @@ import pytest from homeassistant import const -from homeassistant.components import alarm_control_panel from .common import ( extract_stack_to_frame, help_test_all, import_and_test_deprecated_constant, - import_and_test_deprecated_constant_enum, ) @@ -52,30 +50,6 @@ def test_deprecated_constant_name_changes( ) -def _create_tuples_alarm_states( - enum: type[Enum], constant_prefix: str, remove_in_version: str -) -> list[tuple[Enum, str]]: - return [(enum_field, constant_prefix, remove_in_version) for enum_field in enum] - - -@pytest.mark.parametrize( - ("enum", "constant_prefix", "remove_in_version"), - _create_tuples_alarm_states( - alarm_control_panel.AlarmControlPanelState, "STATE_ALARM_", "2025.11" - ), -) -def test_deprecated_constants_alarm( - caplog: pytest.LogCaptureFixture, - enum: Enum, - constant_prefix: str, - remove_in_version: str, -) -> None: - """Test deprecated constants.""" - import_and_test_deprecated_constant_enum( - caplog, const, enum, constant_prefix, remove_in_version - ) - - def test_deprecated_unit_of_conductivity_alias() -> None: """Test UnitOfConductivity deprecation."""