Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 22 additions & 14 deletions homeassistant/components/shelly/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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."""
Expand Down
49 changes: 31 additions & 18 deletions homeassistant/components/shelly/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -409,27 +414,26 @@ 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:
"""Get name based on device and channel name."""
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]

if custom_name := get_rpc_custom_name(device, key):
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}"
Expand Down Expand Up @@ -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)})

Expand Down Expand Up @@ -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,
Expand All @@ -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),
Expand Down
35 changes: 33 additions & 2 deletions tests/components/shelly/test_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down
12 changes: 6 additions & 6 deletions tests/components/shelly/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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],
)
Expand All @@ -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],
)
Expand All @@ -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],
)
Expand Down
Loading