1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-24 21:06:19 +00:00

Align Shelly event naming paradigm (#156774)

Signed-off-by: David Rapan <david@rapan.cz>
This commit is contained in:
David Rapan
2025-11-21 09:15:26 +01:00
committed by GitHub
parent e572f8d48f
commit bf76c1601d
4 changed files with 92 additions and 40 deletions

View File

@@ -32,11 +32,16 @@ from .utils import (
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 @@ class ShellyBlockEvent(ShellyBlockEntity, EventEntity):
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 @@ class ShellyRpcEvent(CoordinatorEntity[ShellyRpcCoordinator], EventEntity):
) -> 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."""

View File

@@ -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),

View File

@@ -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(

View File

@@ -27,9 +27,9 @@ from homeassistant.components.shelly.utils import (
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 @@ from homeassistant.util import dt as dt_util
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],
)