Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions homeassistant/components/bluetooth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
async_address_present,
async_ble_device_from_address,
async_clear_address_from_match_history,
async_clear_advertisement_history,
async_current_scanners,
async_discovered_service_info,
async_get_advertisement_callback,
Expand Down Expand Up @@ -116,6 +117,7 @@
"async_address_present",
"async_ble_device_from_address",
"async_clear_address_from_match_history",
"async_clear_advertisement_history",
"async_current_scanners",
"async_discovered_service_info",
"async_get_advertisement_callback",
Expand Down
13 changes: 13 additions & 0 deletions homeassistant/components/bluetooth/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,19 @@ def async_clear_address_from_match_history(hass: HomeAssistant, address: str) ->
_get_manager(hass).async_clear_address_from_match_history(address)


@hass_callback
def async_clear_advertisement_history(hass: HomeAssistant, address: str) -> None:
"""Clear cached advertisement history for a device.

Causes the next advertisement from this address to be treated as new
data, bypassing the change-detection guard in the Bluetooth manager.
Intended for devices that emit static advertisements as a wake-up
signal, for example, devices that require an active GATT connection
to read sensor data and whose advertisement payload never changes.
"""
_get_manager(hass).async_clear_advertisement_history(address)


@hass_callback
def async_register_scanner(
hass: HomeAssistant,
Expand Down
52 changes: 51 additions & 1 deletion tests/components/bluetooth/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@
from homeassistant.components.bluetooth import (
MONOTONIC_TIME,
BaseHaRemoteScanner,
BluetoothChange,
BluetoothScanningMode,
BluetoothServiceInfo,
HaBluetoothConnector,
async_clear_advertisement_history,
async_scanner_by_source,
async_scanner_devices_by_address,
)
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback

from . import (
FakeRemoteScanner,
Expand All @@ -23,6 +26,7 @@
_get_manager,
generate_advertisement_data,
generate_ble_device,
inject_advertisement,
)


Expand Down Expand Up @@ -228,3 +232,49 @@ async def test_async_current_scanners(hass: HomeAssistant) -> None:
# Verify we're back to the initial scanner
final_scanners = bluetooth.async_current_scanners(hass)
assert len(final_scanners) == initial_scanner_count


@pytest.mark.usefixtures("enable_bluetooth")
async def test_clear_advertisement_history(hass: HomeAssistant) -> None:
"""Test clearing advertisement history bypasses the dedup guard."""
callbacks: list[tuple[BluetoothServiceInfo, BluetoothChange]] = []

@callback
def _fake_subscriber(
service_info: BluetoothServiceInfo, change: BluetoothChange
) -> None:
callbacks.append((service_info, change))

cancel = bluetooth.async_register_callback(
hass,
_fake_subscriber,
{"address": "44:44:33:11:23:45"},
BluetoothScanningMode.ACTIVE,
)

switchbot_device = generate_ble_device("44:44:33:11:23:45", "wohand")
switchbot_adv = generate_advertisement_data(
local_name="wohand",
service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"],
manufacturer_data={89: b"\xd8.\xad\xcd\r\x85"},
)

inject_advertisement(hass, switchbot_device, switchbot_adv)
await hass.async_block_till_done()

# Identical advertisement is deduplicated by the manager
inject_advertisement(hass, switchbot_device, switchbot_adv)
await hass.async_block_till_done()

assert len(callbacks) == 1

# Clearing the advertisement history makes the next identical
# advertisement be treated as new data
async_clear_advertisement_history(hass, "44:44:33:11:23:45")

inject_advertisement(hass, switchbot_device, switchbot_adv)
await hass.async_block_till_done()

assert len(callbacks) == 2

cancel()