From ae3925118c80afccb021ffb5a807e20b80b4d209 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Tue, 29 Apr 2025 10:12:34 +0200 Subject: [PATCH] Do not allow to enable BT scanner for Shelly Gen4 device with Zigbee enabled (#143824) * Bluetooth is not supported when Zigbee is enabled * Update tests * Format --- homeassistant/components/shelly/__init__.py | 1 + homeassistant/components/shelly/config_flow.py | 2 ++ homeassistant/components/shelly/coordinator.py | 6 +++++- homeassistant/components/shelly/strings.json | 3 ++- tests/components/shelly/conftest.py | 1 + tests/components/shelly/test_config_flow.py | 13 +++++++++++++ tests/components/shelly/test_coordinator.py | 16 ++++++++++++++-- 7 files changed, 38 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index ee28c41f18b..b6464bd07ba 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -293,6 +293,7 @@ async def _async_setup_rpc_entry(hass: HomeAssistant, entry: ShellyConfigEntry) translation_key="firmware_unsupported", translation_placeholders={"device": entry.title}, ) + runtime_data.rpc_zigbee_enabled = device.zigbee_enabled runtime_data.rpc_supports_scripts = await device.supports_scripts() if runtime_data.rpc_supports_scripts: runtime_data.rpc_script_events = await get_rpc_scripts_event_types( diff --git a/homeassistant/components/shelly/config_flow.py b/homeassistant/components/shelly/config_flow.py index f0985171752..bde57f6f9bc 100644 --- a/homeassistant/components/shelly/config_flow.py +++ b/homeassistant/components/shelly/config_flow.py @@ -475,6 +475,8 @@ class OptionsFlowHandler(OptionsFlow): return self.async_abort(reason="cannot_connect") if not supports_scripts: return self.async_abort(reason="no_scripts_support") + if self.config_entry.runtime_data.rpc_zigbee_enabled: + return self.async_abort(reason="zigbee_enabled") if user_input is not None: return self.async_create_entry(title="", data=user_input) diff --git a/homeassistant/components/shelly/coordinator.py b/homeassistant/components/shelly/coordinator.py index 4a1ea72f38a..f980ba8f914 100644 --- a/homeassistant/components/shelly/coordinator.py +++ b/homeassistant/components/shelly/coordinator.py @@ -90,6 +90,7 @@ class ShellyEntryData: rpc_poll: ShellyRpcPollingCoordinator | None = None rpc_script_events: dict[int, list[str]] | None = None rpc_supports_scripts: bool | None = None + rpc_zigbee_enabled: bool | None = None type ShellyConfigEntry = ConfigEntry[ShellyEntryData] @@ -717,7 +718,10 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]): is updated. """ if not self.sleep_period: - if self.config_entry.runtime_data.rpc_supports_scripts: + if ( + self.config_entry.runtime_data.rpc_supports_scripts + and not self.config_entry.runtime_data.rpc_zigbee_enabled + ): await self._async_connect_ble_scanner() else: await self._async_setup_outbound_websocket() diff --git a/homeassistant/components/shelly/strings.json b/homeassistant/components/shelly/strings.json index fe7ab9271cf..b8263e6c292 100644 --- a/homeassistant/components/shelly/strings.json +++ b/homeassistant/components/shelly/strings.json @@ -104,7 +104,8 @@ }, "abort": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "no_scripts_support": "Device does not support scripts and cannot be used as a Bluetooth scanner." + "no_scripts_support": "Device does not support scripts and cannot be used as a Bluetooth scanner.", + "zigbee_enabled": "Device with Zigbee enabled cannot be used as a Bluetooth scanner. Please disable it to use the device as a Bluetooth scanner." } }, "selector": { diff --git a/tests/components/shelly/conftest.py b/tests/components/shelly/conftest.py index be5e5749731..a2624f4c070 100644 --- a/tests/components/shelly/conftest.py +++ b/tests/components/shelly/conftest.py @@ -498,6 +498,7 @@ def _mock_rpc_device(version: str | None = None): } ), xmod_info={}, + zigbee_enabled=False, ) type(device).name = PropertyMock(return_value="Test name") return device diff --git a/tests/components/shelly/test_config_flow.py b/tests/components/shelly/test_config_flow.py index e093dcf11d2..93893035a3e 100644 --- a/tests/components/shelly/test_config_flow.py +++ b/tests/components/shelly/test_config_flow.py @@ -870,6 +870,19 @@ async def test_options_flow_abort_no_scripts_support( assert result["reason"] == "no_scripts_support" +async def test_options_flow_abort_zigbee_enabled( + hass: HomeAssistant, mock_rpc_device: Mock, monkeypatch: pytest.MonkeyPatch +) -> None: + """Test ble options abort if Zigbee is enabled for the device.""" + monkeypatch.setattr(mock_rpc_device, "zigbee_enabled", True) + entry = await init_integration(hass, 4) + + result = await hass.config_entries.options.async_init(entry.entry_id) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "zigbee_enabled" + + async def test_zeroconf_already_configured(hass: HomeAssistant) -> None: """Test we get the form.""" diff --git a/tests/components/shelly/test_coordinator.py b/tests/components/shelly/test_coordinator.py index f89bec8853a..cf7f82014a0 100644 --- a/tests/components/shelly/test_coordinator.py +++ b/tests/components/shelly/test_coordinator.py @@ -853,17 +853,28 @@ async def test_rpc_update_entry_fw_ver( assert device.sw_version == "99.0.0" -@pytest.mark.parametrize(("supports_scripts"), [True, False]) +@pytest.mark.parametrize( + ("supports_scripts", "zigbee_enabled", "result"), + [ + (True, False, True), + (True, True, False), + (False, True, False), + (False, False, False), + ], +) async def test_rpc_runs_connected_events_when_initialized( hass: HomeAssistant, mock_rpc_device: Mock, monkeypatch: pytest.MonkeyPatch, supports_scripts: bool, + zigbee_enabled: bool, + result: bool, ) -> None: """Test RPC runs connected events when initialized.""" monkeypatch.setattr( mock_rpc_device, "supports_scripts", AsyncMock(return_value=supports_scripts) ) + monkeypatch.setattr(mock_rpc_device, "zigbee_enabled", zigbee_enabled) monkeypatch.setattr(mock_rpc_device, "initialized", False) await init_integration(hass, 2) @@ -876,7 +887,8 @@ async def test_rpc_runs_connected_events_when_initialized( assert call.supports_scripts() in mock_rpc_device.mock_calls # BLE script list is called during connected events if device supports scripts - assert bool(call.script_list() in mock_rpc_device.mock_calls) == supports_scripts + # and Zigbee is disabled + assert bool(call.script_list() in mock_rpc_device.mock_calls) == result async def test_rpc_sleeping_device_unload_ignore_ble_scanner(