Skip to content

Commit 16669e3

Browse files
authored
Filter devices with active discovery flows from Shelly user step (home-assistant#157201)
1 parent ca088d8 commit 16669e3

File tree

2 files changed

+82
-15
lines changed

2 files changed

+82
-15
lines changed

homeassistant/components/shelly/config_flow.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,13 @@
4242
async_clear_address_from_match_history,
4343
async_discovered_service_info,
4444
)
45-
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow
45+
from homeassistant.config_entries import (
46+
SOURCE_BLUETOOTH,
47+
SOURCE_ZEROCONF,
48+
ConfigFlow,
49+
ConfigFlowResult,
50+
OptionsFlow,
51+
)
4652
from homeassistant.const import (
4753
CONF_DEVICE,
4854
CONF_HOST,
@@ -109,6 +115,7 @@
109115

110116
INTERNAL_WIFI_AP_IP = "192.168.33.1"
111117
MANUAL_ENTRY_STRING = "manual"
118+
DISCOVERY_SOURCES = {SOURCE_BLUETOOTH, SOURCE_ZEROCONF}
112119

113120

114121
async def async_get_ip_from_ble(ble_device: BLEDevice) -> str | None:
@@ -445,11 +452,13 @@ async def async_step_user(
445452
discovered_devices.update(await self._async_discover_zeroconf_devices())
446453

447454
# Filter out already-configured devices (excluding ignored)
455+
# and devices with active discovery flows (already being offered to user)
448456
current_ids = self._async_current_ids(include_ignore=False)
457+
in_progress_macs = self._async_get_in_progress_discovery_macs()
449458
discovered_devices = {
450459
mac: device
451460
for mac, device in discovered_devices.items()
452-
if mac not in current_ids
461+
if mac not in current_ids and mac not in in_progress_macs
453462
}
454463

455464
# Store discovered devices for use in selection
@@ -575,6 +584,22 @@ async def async_step_credentials(
575584
step_id="credentials", data_schema=vol.Schema(schema), errors=errors
576585
)
577586

587+
@callback
588+
def _async_get_in_progress_discovery_macs(self) -> set[str]:
589+
"""Get MAC addresses of devices with active discovery flows.
590+
591+
Returns MAC addresses from bluetooth and zeroconf discovery flows
592+
that are already in progress, so they can be filtered from the
593+
user step device list (since they're already being offered).
594+
"""
595+
return {
596+
mac
597+
for flow in self._async_in_progress(include_uninitialized=True)
598+
if flow["flow_id"] != self.flow_id
599+
and flow["context"].get("source") in DISCOVERY_SOURCES
600+
and (mac := flow["context"].get("unique_id"))
601+
}
602+
578603
def _abort_idle_ble_flows(self, mac: str) -> None:
579604
"""Abort idle BLE provisioning flows for this device.
580605

tests/components/shelly/test_config_flow.py

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1805,28 +1805,20 @@ async def test_user_flow_select_ble_device(
18051805
# Mock empty zeroconf discovery
18061806
mock_discovery.return_value = []
18071807

1808-
# Inject BLE device with RPC-over-BLE enabled
1808+
# Inject BLE device with RPC-over-BLE enabled (no discovery flow created)
18091809
inject_bluetooth_service_info_bleak(hass, BLE_DISCOVERY_INFO_GEN3)
18101810

18111811
# Wait for bluetooth discovery to process
18121812
await hass.async_block_till_done()
18131813

1814-
# Start a bluetooth discovery flow manually to simulate auto-discovery
1815-
ble_result = await hass.config_entries.flow.async_init(
1816-
DOMAIN,
1817-
context={"source": config_entries.SOURCE_BLUETOOTH},
1818-
data=BLE_DISCOVERY_INFO_GEN3,
1819-
)
1820-
ble_flow_id = ble_result["flow_id"]
1821-
18221814
result = await hass.config_entries.flow.async_init(
18231815
DOMAIN, context={"source": config_entries.SOURCE_USER}
18241816
)
18251817

18261818
assert result["type"] is FlowResultType.FORM
18271819
assert result["step_id"] == "user"
18281820

1829-
# Select the BLE device - should take over from the discovery flow
1821+
# Select the BLE device
18301822
result = await hass.config_entries.flow.async_configure(
18311823
result["flow_id"],
18321824
{CONF_DEVICE: "CCBA97C2D670"}, # MAC from manufacturer data
@@ -1891,9 +1883,59 @@ async def test_user_flow_select_ble_device(
18911883
assert result["result"].unique_id == "CCBA97C2D670"
18921884
assert result["title"] == "Test BLE Device"
18931885

1894-
# Verify the original bluetooth discovery flow no longer exists
1895-
flows = hass.config_entries.flow.async_progress_by_handler(DOMAIN)
1896-
assert not any(f["flow_id"] == ble_flow_id for f in flows)
1886+
1887+
async def test_user_flow_filters_devices_with_active_discovery_flows(
1888+
hass: HomeAssistant,
1889+
mock_discovery: AsyncMock,
1890+
mock_rpc_device: Mock,
1891+
) -> None:
1892+
"""Test user flow filters out devices that already have discovery flows."""
1893+
# Mock empty zeroconf discovery
1894+
mock_discovery.return_value = []
1895+
1896+
# Inject BLE device with RPC-over-BLE enabled
1897+
inject_bluetooth_service_info_bleak(hass, BLE_DISCOVERY_INFO_GEN3)
1898+
1899+
# Wait for bluetooth discovery to process
1900+
await hass.async_block_till_done()
1901+
1902+
# Start a bluetooth discovery flow to simulate auto-discovery
1903+
await hass.config_entries.flow.async_init(
1904+
DOMAIN,
1905+
context={"source": config_entries.SOURCE_BLUETOOTH},
1906+
data=BLE_DISCOVERY_INFO_GEN3,
1907+
)
1908+
1909+
# Start a user flow - should go to manual entry since the only
1910+
# discovered device already has an active discovery flow
1911+
result = await hass.config_entries.flow.async_init(
1912+
DOMAIN, context={"source": config_entries.SOURCE_USER}
1913+
)
1914+
1915+
# Should go directly to manual entry since the BLE device is filtered
1916+
# out (it already has an active discovery flow being offered to the user)
1917+
assert result["type"] is FlowResultType.FORM
1918+
assert result["step_id"] == "user_manual"
1919+
1920+
# Complete the manual entry flow to reach terminal state
1921+
with patch(
1922+
"homeassistant.components.shelly.config_flow.get_info",
1923+
return_value={"mac": "aabbccddeeff", "model": MODEL_PLUS_2PM, "gen": 2},
1924+
):
1925+
result = await hass.config_entries.flow.async_configure(
1926+
result["flow_id"],
1927+
{CONF_HOST: "10.10.10.10"},
1928+
)
1929+
1930+
assert result["type"] is FlowResultType.CREATE_ENTRY
1931+
assert result["title"] == "Test name"
1932+
assert result["data"] == {
1933+
CONF_HOST: "10.10.10.10",
1934+
CONF_PORT: DEFAULT_HTTP_PORT,
1935+
CONF_SLEEP_PERIOD: 0,
1936+
CONF_MODEL: MODEL_PLUS_2PM,
1937+
CONF_GEN: 2,
1938+
}
18971939

18981940

18991941
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)