diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index 7440013940c..a7ee1c029df 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -4,6 +4,7 @@ from __future__ import annotations from typing import Final +from aioshelly.ble.const import BLE_SCRIPT_NAME from aioshelly.block_device import BlockDevice from aioshelly.common import ConnectionOptions from aioshelly.const import DEFAULT_COAP_PORT, RPC_GENERATIONS @@ -11,6 +12,7 @@ from aioshelly.exceptions import ( DeviceConnectionError, InvalidAuthError, MacAddressMismatchError, + RpcCallError, ) from aioshelly.rpc_device import RpcDevice, bluetooth_mac_from_primary_mac import voluptuous as vol @@ -59,6 +61,7 @@ from .utils import ( get_coap_context, get_device_entry_gen, get_http_port, + get_rpc_scripts_event_types, get_ws_context, ) @@ -270,7 +273,10 @@ async def _async_setup_rpc_entry(hass: HomeAssistant, entry: ShellyConfigEntry) async_create_issue_unsupported_firmware(hass, entry) await device.shutdown() raise ConfigEntryNotReady - except (DeviceConnectionError, MacAddressMismatchError) as err: + runtime_data.rpc_script_events = await get_rpc_scripts_event_types( + device, ignore_scripts=[BLE_SCRIPT_NAME] + ) + except (DeviceConnectionError, MacAddressMismatchError, RpcCallError) as err: await device.shutdown() raise ConfigEntryNotReady(repr(err)) from err except InvalidAuthError as err: diff --git a/homeassistant/components/shelly/coordinator.py b/homeassistant/components/shelly/coordinator.py index 95812c12e10..85cf430bc5d 100644 --- a/homeassistant/components/shelly/coordinator.py +++ b/homeassistant/components/shelly/coordinator.py @@ -88,6 +88,7 @@ class ShellyEntryData: rest: ShellyRestCoordinator | None = None rpc: ShellyRpcCoordinator | None = None rpc_poll: ShellyRpcPollingCoordinator | None = None + rpc_script_events: dict[int, list[str]] | None = None type ShellyConfigEntry = ConfigEntry[ShellyEntryData] diff --git a/homeassistant/components/shelly/event.py b/homeassistant/components/shelly/event.py index bfd705f447a..ec5810581b1 100644 --- a/homeassistant/components/shelly/event.py +++ b/homeassistant/components/shelly/event.py @@ -34,7 +34,6 @@ from .utils import ( get_device_entry_gen, get_rpc_entity_name, get_rpc_key_instances, - get_rpc_script_event_types, is_block_momentary_input, is_rpc_momentary_input, ) @@ -109,18 +108,15 @@ async def async_setup_entry( script_instances = get_rpc_key_instances( coordinator.device.status, SCRIPT_EVENT.key ) + script_events = config_entry.runtime_data.rpc_script_events for script in script_instances: script_name = get_rpc_entity_name(coordinator.device, script) if script_name == BLE_SCRIPT_NAME: continue - event_types = await get_rpc_script_event_types( - coordinator.device, int(script.split(":")[-1]) - ) - if not event_types: - continue - - entities.append(ShellyRpcScriptEvent(coordinator, script, event_types)) + script_id = int(script.split(":")[-1]) + if script_events and (event_types := script_events[script_id]): + entities.append(ShellyRpcScriptEvent(coordinator, script, event_types)) # If a script is removed, from the device configuration, we need to remove orphaned entities async_remove_orphaned_entities( diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index 19897dbb185..474e2bb9410 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -664,3 +664,20 @@ def get_shelly_air_lamp_life(lamp_seconds: int) -> float: if lamp_hours >= SHAIR_MAX_WORK_HOURS: return 0.0 return 100 * (1 - lamp_hours / SHAIR_MAX_WORK_HOURS) + + +async def get_rpc_scripts_event_types( + device: RpcDevice, ignore_scripts: list[str] +) -> dict[int, list[str]]: + """Return a dict of all scripts and their event types.""" + script_instances = get_rpc_key_instances(device.status, "script") + script_events = {} + for script in script_instances: + script_name = get_rpc_entity_name(device, script) + if script_name in ignore_scripts: + continue + + script_id = int(script.split(":")[-1]) + script_events[script_id] = await get_rpc_script_event_types(device, script_id) + + return script_events diff --git a/tests/components/shelly/conftest.py b/tests/components/shelly/conftest.py index 8030df6e473..8f8255235be 100644 --- a/tests/components/shelly/conftest.py +++ b/tests/components/shelly/conftest.py @@ -513,6 +513,9 @@ def _mock_blu_rtv_device(version: str | None = None): firmware_version="some fw string", initialized=True, connected=True, + script_getcode=AsyncMock( + side_effect=lambda script_id: {"data": MOCK_SCRIPTS[script_id - 1]} + ), xmod_info={}, ) type(device).name = PropertyMock(return_value="Test name") diff --git a/tests/components/shelly/test_init.py b/tests/components/shelly/test_init.py index ef9b8f72616..0cec6383461 100644 --- a/tests/components/shelly/test_init.py +++ b/tests/components/shelly/test_init.py @@ -10,6 +10,7 @@ from aioshelly.exceptions import ( DeviceConnectionError, InvalidAuthError, MacAddressMismatchError, + RpcCallError, ) from aioshelly.rpc_device.utils import bluetooth_mac_from_primary_mac import pytest @@ -555,3 +556,17 @@ async def test_bluetooth_cleanup_on_remove_entry( remove_mock.assert_called_once_with( hass, format_mac(bluetooth_mac_from_primary_mac(entry.unique_id)).upper() ) + + +async def test_device_script_getcode_error( + hass: HomeAssistant, + mock_rpc_device: Mock, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Test device script get code error.""" + monkeypatch.setattr( + mock_rpc_device, "script_getcode", AsyncMock(side_effect=RpcCallError(0)) + ) + + entry = await init_integration(hass, 2) + assert entry.state is ConfigEntryState.SETUP_RETRY