Skip to content

Commit a991dcb

Browse files
authored
Add Bluetooth API to clear address from match history (home-assistant#154355)
1 parent 6f79a65 commit a991dcb

File tree

4 files changed

+85
-0
lines changed

4 files changed

+85
-0
lines changed

homeassistant/components/bluetooth/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
_get_manager,
5858
async_address_present,
5959
async_ble_device_from_address,
60+
async_clear_address_from_match_history,
6061
async_current_scanners,
6162
async_discovered_service_info,
6263
async_get_advertisement_callback,
@@ -115,6 +116,7 @@
115116
"HomeAssistantRemoteScanner",
116117
"async_address_present",
117118
"async_ble_device_from_address",
119+
"async_clear_address_from_match_history",
118120
"async_current_scanners",
119121
"async_discovered_service_info",
120122
"async_get_advertisement_callback",

homeassistant/components/bluetooth/api.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,20 @@ def async_rediscover_address(hass: HomeAssistant, address: str) -> None:
193193
_get_manager(hass).async_rediscover_address(address)
194194

195195

196+
@hass_callback
197+
def async_clear_address_from_match_history(hass: HomeAssistant, address: str) -> None:
198+
"""Clear an address from the integration matcher history.
199+
200+
This allows future advertisements from this address to trigger discovery
201+
even if the advertisement content has changed but the service data UUIDs
202+
remain the same.
203+
204+
Unlike async_rediscover_address, this does not immediately re-trigger
205+
discovery with the current advertisement in history.
206+
"""
207+
_get_manager(hass).async_clear_address_from_match_history(address)
208+
209+
196210
@hass_callback
197211
def async_register_scanner(
198212
hass: HomeAssistant,

homeassistant/components/bluetooth/manager.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,19 @@ def async_rediscover_address(self, address: str) -> None:
120120
if service_info := self._all_history.get(address):
121121
self._async_trigger_matching_discovery(service_info)
122122

123+
@hass_callback
124+
def async_clear_address_from_match_history(self, address: str) -> None:
125+
"""Clear an address from the integration matcher history.
126+
127+
This allows future advertisements from this address to trigger discovery
128+
even if the advertisement content has changed but the service data UUIDs
129+
remain the same.
130+
131+
Unlike async_rediscover_address, this does not immediately re-trigger
132+
discovery with the current advertisement in history.
133+
"""
134+
self._integration_matcher.async_clear_address(address)
135+
123136
def _discover_service_info(self, service_info: BluetoothServiceInfoBleak) -> None:
124137
matched_domains = self._integration_matcher.match_domains(service_info)
125138
if self._debug:

tests/components/bluetooth/test_init.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
BluetoothScanningMode,
2020
BluetoothServiceInfo,
2121
HaBluetoothConnector,
22+
async_clear_address_from_match_history,
2223
async_process_advertisements,
2324
async_rediscover_address,
2425
async_track_unavailable,
@@ -1175,6 +1176,61 @@ async def test_rediscovery(
11751176
assert mock_config_flow.mock_calls[1][1][0] == "switchbot"
11761177

11771178

1179+
@pytest.mark.usefixtures("enable_bluetooth")
1180+
async def test_clear_address_from_match_history(
1181+
hass: HomeAssistant, mock_bleak_scanner_start: MagicMock
1182+
) -> None:
1183+
"""Test clearing match history without re-triggering discovery."""
1184+
mock_bt = [
1185+
{"domain": "switchbot", "service_uuid": "cba20d00-224d-11e6-9fb8-0002a5d5c51b"}
1186+
]
1187+
with (
1188+
patch(
1189+
"homeassistant.components.bluetooth.async_get_bluetooth",
1190+
return_value=mock_bt,
1191+
),
1192+
patch.object(hass.config_entries.flow, "async_init") as mock_config_flow,
1193+
):
1194+
await async_setup_with_default_adapter(hass)
1195+
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
1196+
await hass.async_block_till_done()
1197+
1198+
assert len(mock_bleak_scanner_start.mock_calls) == 1
1199+
1200+
switchbot_device = generate_ble_device("44:44:33:11:23:45", "wohand")
1201+
switchbot_adv = generate_advertisement_data(
1202+
local_name="wohand", service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"]
1203+
)
1204+
switchbot_adv_2 = generate_advertisement_data(
1205+
local_name="wohand",
1206+
service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"],
1207+
manufacturer_data={1: b"\x01"},
1208+
)
1209+
inject_advertisement(hass, switchbot_device, switchbot_adv)
1210+
await hass.async_block_till_done()
1211+
1212+
inject_advertisement(hass, switchbot_device, switchbot_adv)
1213+
await hass.async_block_till_done()
1214+
1215+
assert len(mock_config_flow.mock_calls) == 1
1216+
assert mock_config_flow.mock_calls[0][1][0] == "switchbot"
1217+
1218+
# Clear match history - should NOT trigger immediate rediscovery
1219+
async_clear_address_from_match_history(hass, "44:44:33:11:23:45")
1220+
await hass.async_block_till_done()
1221+
1222+
# No new discovery should have been triggered
1223+
assert len(mock_config_flow.mock_calls) == 1
1224+
1225+
# But when we inject new advertisement with different data, it should be discovered
1226+
inject_advertisement(hass, switchbot_device, switchbot_adv_2)
1227+
await hass.async_block_till_done()
1228+
1229+
# Now discovery should happen because history was cleared and data changed
1230+
assert len(mock_config_flow.mock_calls) == 2
1231+
assert mock_config_flow.mock_calls[1][1][0] == "switchbot"
1232+
1233+
11781234
@pytest.mark.usefixtures("macos_adapter")
11791235
async def test_async_discovered_device_api(
11801236
hass: HomeAssistant, mock_bleak_scanner_start: MagicMock

0 commit comments

Comments
 (0)