mirror of
https://github.com/Electric-Special/ha-core.git
synced 2026-03-21 06:05:26 +01:00
Fix Bluetooth discovery for devices with alternating advertisement names (#154347)
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user