diff --git a/homeassistant/components/shelly/event.py b/homeassistant/components/shelly/event.py index 40af3505968c72..649cd8f15fcc02 100644 --- a/homeassistant/components/shelly/event.py +++ b/homeassistant/components/shelly/event.py @@ -32,11 +32,16 @@ async_remove_shelly_entity, get_block_channel, get_block_custom_name, + get_block_number_of_channels, get_device_entry_gen, - get_rpc_component_name, + get_rpc_custom_name, get_rpc_entity_name, + get_rpc_key, + get_rpc_key_id, get_rpc_key_instances, + get_rpc_number_of_channels, is_block_momentary_input, + is_block_single_device, is_rpc_momentary_input, ) @@ -158,8 +163,7 @@ def _async_setup_rpc_entry( if script_name == BLE_SCRIPT_NAME: continue - script_id = int(script.split(":")[-1]) - if script_events and (event_types := script_events[script_id]): + if script_events and (event_types := script_events[get_rpc_key_id(script)]): entities.append(ShellyRpcScriptEvent(coordinator, script, event_types)) # If a script is removed, from the device configuration, we need to remove orphaned entities @@ -197,13 +201,15 @@ def __init__( self._attr_event_types = list(BASIC_INPUTS_EVENTS_TYPES) self.entity_description = description - if ( - hasattr(self, "_attr_name") - and self._attr_name - and not get_block_custom_name(coordinator.device, block) + if hasattr(self, "_attr_name") and not ( + (single := is_block_single_device(coordinator.device, block)) + and get_block_custom_name(coordinator.device, block) ): self._attr_translation_placeholders = { "input_number": get_block_channel(block) + if single + and get_block_number_of_channels(coordinator.device, block) > 1 + else "" } delattr(self, "_attr_name") @@ -237,22 +243,24 @@ def __init__( ) -> None: """Initialize Shelly entity.""" super().__init__(coordinator) - self.event_id = int(key.split(":")[-1]) self._attr_device_info = get_entity_rpc_device_info(coordinator, key) self._attr_unique_id = f"{coordinator.mac}-{key}" self.entity_description = description if description.key == "input": - component = key.split(":")[0] - component_id = key.split(":")[-1] - if not get_rpc_component_name(coordinator.device, key) and ( - component.lower() == "input" and component_id.isnumeric() - ): - self._attr_translation_placeholders = {"input_number": component_id} + _, component, component_id = get_rpc_key(key) + if not get_rpc_custom_name(coordinator.device, key): + self._attr_translation_placeholders = { + "input_number": component_id + if get_rpc_number_of_channels(coordinator.device, component) > 1 + else "" + } else: self._attr_name = get_rpc_entity_name(coordinator.device, key) + self.event_id = int(component_id) elif description.key == "script": self._attr_name = get_rpc_entity_name(coordinator.device, key) + self.event_id = get_rpc_key_id(key) async def async_added_to_hass(self) -> None: """When entity is added to hass.""" diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index e2483d18059e39..c74eb44ab86c58 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -93,8 +93,8 @@ def async_remove_shelly_entity( entity_reg.async_remove(entity_id) -def get_number_of_channels(device: BlockDevice, block: Block) -> int: - """Get number of channels for block type.""" +def get_block_number_of_channels(device: BlockDevice, block: Block) -> int: + """Get number of channels.""" channels = None if block.type == "input": @@ -154,7 +154,7 @@ def get_block_channel_name(device: BlockDevice, block: Block | None) -> str | No if ( not block or block.type in ("device", "light", "relay", "emeter") - or get_number_of_channels(device, block) == 1 + or get_block_number_of_channels(device, block) == 1 ): return None @@ -253,7 +253,7 @@ def get_block_input_triggers( if not is_block_momentary_input(device.settings, block, True): return [] - if block.type == "device" or get_number_of_channels(device, block) == 1: + if block.type == "device" or get_block_number_of_channels(device, block) == 1: subtype = "button" else: assert block.channel @@ -397,8 +397,13 @@ def get_rpc_key(value: str) -> tuple[bool, str, str]: return len(parts) > 1, parts[0], parts[-1] +def get_rpc_key_id(value: str) -> int: + """Get id from device key.""" + return int(get_rpc_key(value)[-1]) + + def get_rpc_custom_name(device: RpcDevice, key: str) -> str | None: - """Get component name from device config.""" + """Get custom name from device config.""" if ( key in device.config and key != "em:0" # workaround for Pro 3EM, we don't want to get name for em:0 @@ -409,9 +414,9 @@ def get_rpc_custom_name(device: RpcDevice, key: str) -> str | None: return None -def get_rpc_component_name(device: RpcDevice, key: str) -> str | None: - """Get component name from device config.""" - return get_rpc_custom_name(device, key) +def get_rpc_number_of_channels(device: RpcDevice, component: str) -> int: + """Get number of channels.""" + return len(get_rpc_key_instances(device.status, component, all_lights=True)) def get_rpc_channel_name(device: RpcDevice, key: str) -> str | None: @@ -419,9 +424,6 @@ def get_rpc_channel_name(device: RpcDevice, key: str) -> str | None: if BLU_TRV_IDENTIFIER in key: return None - instances = len( - get_rpc_key_instances(device.status, key.split(":")[0], all_lights=True) - ) component = key.split(":")[0] component_id = key.split(":")[-1] @@ -429,7 +431,9 @@ def get_rpc_channel_name(device: RpcDevice, key: str) -> str | None: if component in (*VIRTUAL_COMPONENTS, "input", "presencezone", "script"): return custom_name - return custom_name if instances == 1 else None + channels = get_rpc_number_of_channels(device, component) + + return custom_name if channels == 1 else None if component in (*VIRTUAL_COMPONENTS, "input"): return f"{component.title()} {component_id}" @@ -890,7 +894,7 @@ def get_rpc_device_info( and get_irrigation_zone_id(device, key) is None ) or idx is None - or len(get_rpc_key_instances(device.status, component, all_lights=True)) < 2 + or get_rpc_number_of_channels(device, component) < 2 ): return DeviceInfo(connections={(CONNECTION_NETWORK_MAC, mac)}) @@ -923,6 +927,15 @@ def get_blu_trv_device_info( ) +def is_block_single_device(device: BlockDevice, block: Block | None = None) -> bool: + """Return true if block is single device.""" + return ( + block is None + or block.type not in ("light", "relay", "emeter") + or device.settings.get("mode") == "roller" + ) + + def get_block_device_info( device: BlockDevice, mac: str, @@ -933,14 +946,14 @@ def get_block_device_info( suggested_area: str | None = None, ) -> DeviceInfo: """Return device info for Block device.""" - if ( - block is None - or block.type not in ("light", "relay", "emeter") - or device.settings.get("mode") == "roller" - or get_number_of_channels(device, block) < 2 + if is_block_single_device(device, block) or ( + block is not None and get_block_number_of_channels(device, block) < 2 ): return DeviceInfo(connections={(CONNECTION_NETWORK_MAC, mac)}) + if TYPE_CHECKING: + assert block + return DeviceInfo( identifiers={(DOMAIN, f"{mac}-{block.description}")}, name=get_block_sub_device_name(device, block), diff --git a/tests/components/shelly/test_event.py b/tests/components/shelly/test_event.py index ba92b8508eb4f8..3591c093f046ab 100644 --- a/tests/components/shelly/test_event.py +++ b/tests/components/shelly/test_event.py @@ -195,7 +195,7 @@ async def test_block_event( """Test block device event.""" await init_integration(hass, 1) # num_outputs is 2, device name and channel name is used - entity_id = "event.test_name_channel_1" + entity_id = "event.test_name_channel_1_input" assert (state := hass.states.get(entity_id)) assert state.state == STATE_UNKNOWN @@ -226,7 +226,38 @@ async def test_block_event_single_output( monkeypatch.setitem(mock_block_device.shelly, "num_outputs", 1) await init_integration(hass, 1) - assert hass.states.get("event.test_name") + assert hass.states.get("event.test_name_input") + + +async def test_block_event_custom_name( + hass: HomeAssistant, + monkeypatch: pytest.MonkeyPatch, + mock_block_device: Mock, +) -> None: + """Test block device event with custom name.""" + monkeypatch.setitem( + mock_block_device.settings, + "relays", + [{"name": "test channel", "btn_type": "momentary"}, {"btn_type": "toggle"}], + ) + await init_integration(hass, 1) + # num_outputs is 2, device name and custom name is used + assert hass.states.get("event.test_channel_input") + + +async def test_block_event_custom_name_single_output( + hass: HomeAssistant, mock_block_device: Mock, monkeypatch: pytest.MonkeyPatch +) -> None: + """Test block device event with custom name when num_outputs is 1.""" + monkeypatch.setitem(mock_block_device.shelly, "num_outputs", 1) + monkeypatch.setitem( + mock_block_device.settings, + "relays", + [{"name": "test channel", "btn_type": "momentary"}, {"btn_type": "toggle"}], + ) + await init_integration(hass, 1) + + assert hass.states.get("event.test_name_input") async def test_block_event_shix3_1( diff --git a/tests/components/shelly/test_utils.py b/tests/components/shelly/test_utils.py index ec5bd411ac3843..13c306fdb22334 100644 --- a/tests/components/shelly/test_utils.py +++ b/tests/components/shelly/test_utils.py @@ -27,9 +27,9 @@ get_block_channel_name, get_block_device_sleep_period, get_block_input_triggers, + get_block_number_of_channels, get_device_uptime, get_host, - get_number_of_channels, get_release_url, get_rpc_channel_name, get_rpc_input_triggers, @@ -41,15 +41,15 @@ DEVICE_BLOCK_ID = 4 -async def test_block_get_number_of_channels( +async def test_block_get_block_number_of_channels( mock_block_device: Mock, monkeypatch: pytest.MonkeyPatch ) -> None: - """Test block get number of channels.""" + """Test block get block number of channels.""" monkeypatch.setattr(mock_block_device.blocks[DEVICE_BLOCK_ID], "type", "emeter") monkeypatch.setitem(mock_block_device.shelly, "num_emeters", 3) assert ( - get_number_of_channels( + get_block_number_of_channels( mock_block_device, mock_block_device.blocks[DEVICE_BLOCK_ID], ) @@ -59,7 +59,7 @@ async def test_block_get_number_of_channels( monkeypatch.setitem(mock_block_device.shelly, "num_inputs", 4) monkeypatch.setattr(mock_block_device.blocks[DEVICE_BLOCK_ID], "type", "input") assert ( - get_number_of_channels( + get_block_number_of_channels( mock_block_device, mock_block_device.blocks[DEVICE_BLOCK_ID], ) @@ -68,7 +68,7 @@ async def test_block_get_number_of_channels( monkeypatch.setitem(mock_block_device.settings["device"], "type", MODEL_DIMMER_2) assert ( - get_number_of_channels( + get_block_number_of_channels( mock_block_device, mock_block_device.blocks[DEVICE_BLOCK_ID], )