From ef3acfda245dbd013082678d72905eb826f035de Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 14 Sep 2026 07:56:38 +0200 Subject: [PATCH] Match the known address first in Yale Access Bluetooth discovery (#182018) --- homeassistant/components/yalexs_ble/util.py | 19 +++++++--- tests/components/yalexs_ble/__init__.py | 17 +++++++++ .../components/yalexs_ble/test_config_flow.py | 36 +++++++++++++++++++ 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/yalexs_ble/util.py b/homeassistant/components/yalexs_ble/util.py index 15ff53dd1d46..0d5540d20de2 100644 --- a/homeassistant/components/yalexs_ble/util.py +++ b/homeassistant/components/yalexs_ble/util.py @@ -37,13 +37,24 @@ def async_find_existing_service_info( ) -> BluetoothServiceInfoBleak | None: """Return the service info for the given local_name and address.""" has_unique_local_name = local_name_is_unique(local_name) + by_local_name: BluetoothServiceInfoBleak | None = None + for service_info in async_discovered_service_info(hass): device = service_info.device - if ( - has_unique_local_name and device.name == local_name - ) or device.address == address: + if device.address == address: return service_info - return None + + # The local name is all we have to go on when the address is hidden + # behind a system UUID, but anything can advertise that name, so it + # only counts when nothing answers to the address we were given. + if ( + by_local_name is None + and has_unique_local_name + and device.name == local_name + ): + by_local_name = service_info + + return by_local_name def short_address(address: str) -> str: diff --git a/tests/components/yalexs_ble/__init__.py b/tests/components/yalexs_ble/__init__.py index d6ce326cbe2e..8baf337a681f 100644 --- a/tests/components/yalexs_ble/__init__.py +++ b/tests/components/yalexs_ble/__init__.py @@ -41,6 +41,23 @@ LOCK_DISCOVERY_INFO_UUID_ADDRESS = BluetoothServiceInfoBleak( tx_power=-127, ) +SAME_LOCAL_NAME_DISCOVERY_INFO = BluetoothServiceInfoBleak( + name="M1012LU", + address="A8:51:AB:91:1C:FA", + rssi=-40, + manufacturer_data={ + 76: b"\x061\x00Z\x8f\x93\xb2\xec\x85\x06\x00i\x00\x02\x02Q\xed\x1d\xf0" + }, + service_uuids=[], + service_data={}, + source="local", + device=generate_ble_device(address="A8:51:AB:91:1C:FA", name="M1012LU"), + advertisement=generate_advertisement_data(), + time=0, + connectable=True, + tx_power=-127, +) + OLD_FIRMWARE_LOCK_DISCOVERY_INFO = BluetoothServiceInfoBleak( name="Aug", address="AA:BB:CC:DD:EE:FF", diff --git a/tests/components/yalexs_ble/test_config_flow.py b/tests/components/yalexs_ble/test_config_flow.py index ad716261a39a..725451d91975 100644 --- a/tests/components/yalexs_ble/test_config_flow.py +++ b/tests/components/yalexs_ble/test_config_flow.py @@ -23,6 +23,7 @@ from . import ( LOCK_DISCOVERY_INFO_UUID_ADDRESS, NOT_YALE_DISCOVERY_INFO, OLD_FIRMWARE_LOCK_DISCOVERY_INFO, + SAME_LOCAL_NAME_DISCOVERY_INFO, YALE_ACCESS_LOCK_DISCOVERY_INFO, ) @@ -579,6 +580,41 @@ async def test_integration_discovery_success(hass: HomeAssistant) -> None: assert len(mock_setup_entry.mock_calls) == 1 +async def test_integration_discovery_prefers_address(hass: HomeAssistant) -> None: + """Test integration discovery binds to the address it was handed.""" + with patch( + "homeassistant.components.yalexs_ble.util.async_discovered_service_info", + # Another device is advertising the same cached local name, and it is + # seen before the lock itself + return_value=[ + SAME_LOCAL_NAME_DISCOVERY_INFO, + YALE_ACCESS_LOCK_DISCOVERY_INFO, + ], + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, + data={ + "name": "Front Door", + "address": YALE_ACCESS_LOCK_DISCOVERY_INFO.address, + "key": "2fd51b8621c6a139eaffbedcb846b60f", + "slot": 66, + "serial": "M1XXX012LU", + }, + ) + assert result["type"] is FlowResultType.FORM + + with patch( + "homeassistant.components.yalexs_ble.async_setup_entry", + return_value=True, + ): + result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {}) + await hass.async_block_till_done() + + assert result2["type"] is FlowResultType.CREATE_ENTRY + assert result2["data"][CONF_ADDRESS] == YALE_ACCESS_LOCK_DISCOVERY_INFO.address + + async def test_integration_discovery_device_not_found(hass: HomeAssistant) -> None: """Test integration discovery when the device is not found.""" with patch(