Fix Bluetooth discovery for devices with alternating advertisement names (#154347)

This commit is contained in:
J. Nick Koston
2025-10-13 05:44:16 -10:00
committed by GitHub
parent f185ffddf1
commit 94d015e00a
2 changed files with 71 additions and 2 deletions

View File

@@ -68,12 +68,17 @@ class IntegrationMatchHistory:
manufacturer_data: bool
service_data: set[str]
service_uuids: set[str]
name: str
def seen_all_fields(
previous_match: IntegrationMatchHistory, advertisement_data: AdvertisementData
previous_match: IntegrationMatchHistory,
advertisement_data: AdvertisementData,
name: str,
) -> bool:
"""Return if we have seen all fields."""
if previous_match.name != name:
return False
if not previous_match.manufacturer_data and advertisement_data.manufacturer_data:
return False
if advertisement_data.service_data and (
@@ -122,10 +127,11 @@ class IntegrationMatcher:
device = service_info.device
advertisement_data = service_info.advertisement
connectable = service_info.connectable
name = service_info.name
matched = self._matched_connectable if connectable else self._matched
matched_domains: set[str] = set()
if (previous_match := matched.get(device.address)) and seen_all_fields(
previous_match, advertisement_data
previous_match, advertisement_data, name
):
# We have seen all fields so we can skip the rest of the matchers
return matched_domains
@@ -140,11 +146,13 @@ class IntegrationMatcher:
)
previous_match.service_data |= set(advertisement_data.service_data)
previous_match.service_uuids |= set(advertisement_data.service_uuids)
previous_match.name = name
else:
matched[device.address] = IntegrationMatchHistory(
manufacturer_data=bool(advertisement_data.manufacturer_data),
service_data=set(advertisement_data.service_data),
service_uuids=set(advertisement_data.service_uuids),
name=name,
)
return matched_domains

View File

@@ -704,6 +704,67 @@ async def test_discovery_match_by_local_name(
assert mock_config_flow.mock_calls[0][1][0] == "switchbot"
@pytest.mark.usefixtures("enable_bluetooth")
async def test_discovery_match_by_service_uuid_when_name_changes_from_mac(
hass: HomeAssistant, mock_bleak_scanner_start: MagicMock
) -> None:
"""Test bluetooth discovery still matches when name changes from MAC address to real name."""
mock_bt = [
{
"domain": "improv_ble",
"service_uuid": "00467768-6228-2272-4663-277478268000",
}
]
with (
patch(
"homeassistant.components.bluetooth.async_get_bluetooth",
return_value=mock_bt,
),
patch.object(hass.config_entries.flow, "async_init") as mock_config_flow,
):
await async_setup_with_default_adapter(hass)
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
assert len(mock_bleak_scanner_start.mock_calls) == 1
# First advertisement: name is MAC address, with service UUID
# This should trigger discovery
device_mac_name = generate_ble_device("64:E8:33:7E:0D:9E", "64:E8:33:7E:0D:9E")
adv_mac_name = generate_advertisement_data(
local_name="64:E8:33:7E:0D:9E",
service_uuids=["00467768-6228-2272-4663-277478268000"],
service_data={
"00004677-0000-1000-8000-00805f9b34fb": b"\x02\x00\x00\x00\x00\x00"
},
)
inject_advertisement(hass, device_mac_name, adv_mac_name)
await hass.async_block_till_done()
assert len(mock_config_flow.mock_calls) == 1
assert mock_config_flow.mock_calls[0][1][0] == "improv_ble"
mock_config_flow.reset_mock()
# Second advertisement: name changes to real name, same service UUID
# This should trigger discovery again because the name changed
device_real_name = generate_ble_device("64:E8:33:7E:0D:9E", "improvtest")
adv_real_name = generate_advertisement_data(
local_name="improvtest",
service_uuids=["00467768-6228-2272-4663-277478268000"],
service_data={
"00004677-0000-1000-8000-00805f9b34fb": b"\x02\x00\x00\x00\x00\x00"
},
)
inject_advertisement(hass, device_real_name, adv_real_name)
await hass.async_block_till_done()
# Should still match improv_ble even though name changed
assert len(mock_config_flow.mock_calls) == 1
assert mock_config_flow.mock_calls[0][1][0] == "improv_ble"
@pytest.mark.usefixtures("macos_adapter")
async def test_discovery_match_by_manufacturer_id_and_manufacturer_data_start(
hass: HomeAssistant, mock_bleak_scanner_start: MagicMock