Filter devices with active discovery flows from Shelly user step (#157201)

This commit is contained in:
J. Nick Koston
2025-11-24 14:52:05 -06:00
committed by GitHub
parent ca088d81c3
commit 16669e39bd
2 changed files with 82 additions and 15 deletions

View File

@@ -42,7 +42,13 @@ from homeassistant.components.bluetooth import (
async_clear_address_from_match_history,
async_discovered_service_info,
)
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow
from homeassistant.config_entries import (
SOURCE_BLUETOOTH,
SOURCE_ZEROCONF,
ConfigFlow,
ConfigFlowResult,
OptionsFlow,
)
from homeassistant.const import (
CONF_DEVICE,
CONF_HOST,
@@ -109,6 +115,7 @@ BLE_SCANNER_OPTIONS = [
INTERNAL_WIFI_AP_IP = "192.168.33.1"
MANUAL_ENTRY_STRING = "manual"
DISCOVERY_SOURCES = {SOURCE_BLUETOOTH, SOURCE_ZEROCONF}
async def async_get_ip_from_ble(ble_device: BLEDevice) -> str | None:
@@ -445,11 +452,13 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN):
discovered_devices.update(await self._async_discover_zeroconf_devices())
# Filter out already-configured devices (excluding ignored)
# and devices with active discovery flows (already being offered to user)
current_ids = self._async_current_ids(include_ignore=False)
in_progress_macs = self._async_get_in_progress_discovery_macs()
discovered_devices = {
mac: device
for mac, device in discovered_devices.items()
if mac not in current_ids
if mac not in current_ids and mac not in in_progress_macs
}
# Store discovered devices for use in selection
@@ -575,6 +584,22 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN):
step_id="credentials", data_schema=vol.Schema(schema), errors=errors
)
@callback
def _async_get_in_progress_discovery_macs(self) -> set[str]:
"""Get MAC addresses of devices with active discovery flows.
Returns MAC addresses from bluetooth and zeroconf discovery flows
that are already in progress, so they can be filtered from the
user step device list (since they're already being offered).
"""
return {
mac
for flow in self._async_in_progress(include_uninitialized=True)
if flow["flow_id"] != self.flow_id
and flow["context"].get("source") in DISCOVERY_SOURCES
and (mac := flow["context"].get("unique_id"))
}
def _abort_idle_ble_flows(self, mac: str) -> None:
"""Abort idle BLE provisioning flows for this device.

View File

@@ -1805,20 +1805,12 @@ async def test_user_flow_select_ble_device(
# Mock empty zeroconf discovery
mock_discovery.return_value = []
# Inject BLE device with RPC-over-BLE enabled
# Inject BLE device with RPC-over-BLE enabled (no discovery flow created)
inject_bluetooth_service_info_bleak(hass, BLE_DISCOVERY_INFO_GEN3)
# Wait for bluetooth discovery to process
await hass.async_block_till_done()
# Start a bluetooth discovery flow manually to simulate auto-discovery
ble_result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_BLUETOOTH},
data=BLE_DISCOVERY_INFO_GEN3,
)
ble_flow_id = ble_result["flow_id"]
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
@@ -1826,7 +1818,7 @@ async def test_user_flow_select_ble_device(
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
# Select the BLE device - should take over from the discovery flow
# Select the BLE device
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_DEVICE: "CCBA97C2D670"}, # MAC from manufacturer data
@@ -1891,9 +1883,59 @@ async def test_user_flow_select_ble_device(
assert result["result"].unique_id == "CCBA97C2D670"
assert result["title"] == "Test BLE Device"
# Verify the original bluetooth discovery flow no longer exists
flows = hass.config_entries.flow.async_progress_by_handler(DOMAIN)
assert not any(f["flow_id"] == ble_flow_id for f in flows)
async def test_user_flow_filters_devices_with_active_discovery_flows(
hass: HomeAssistant,
mock_discovery: AsyncMock,
mock_rpc_device: Mock,
) -> None:
"""Test user flow filters out devices that already have discovery flows."""
# Mock empty zeroconf discovery
mock_discovery.return_value = []
# Inject BLE device with RPC-over-BLE enabled
inject_bluetooth_service_info_bleak(hass, BLE_DISCOVERY_INFO_GEN3)
# Wait for bluetooth discovery to process
await hass.async_block_till_done()
# Start a bluetooth discovery flow to simulate auto-discovery
await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_BLUETOOTH},
data=BLE_DISCOVERY_INFO_GEN3,
)
# Start a user flow - should go to manual entry since the only
# discovered device already has an active discovery flow
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
# Should go directly to manual entry since the BLE device is filtered
# out (it already has an active discovery flow being offered to the user)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user_manual"
# Complete the manual entry flow to reach terminal state
with patch(
"homeassistant.components.shelly.config_flow.get_info",
return_value={"mac": "aabbccddeeff", "model": MODEL_PLUS_2PM, "gen": 2},
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_HOST: "10.10.10.10"},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "Test name"
assert result["data"] == {
CONF_HOST: "10.10.10.10",
CONF_PORT: DEFAULT_HTTP_PORT,
CONF_SLEEP_PERIOD: 0,
CONF_MODEL: MODEL_PLUS_2PM,
CONF_GEN: 2,
}
@pytest.mark.parametrize(