Fix iBeacons with infrequent random mac address changes unexpectedly going unavailable (#82668)
fixes https://github.com/home-assistant/core/issues/79781pull/82680/head
parent
47cec8da8e
commit
09c3df7eb2
|
@ -354,7 +354,25 @@ class IBeaconCoordinator:
|
|||
for group_id in self._group_ids_random_macs
|
||||
if group_id not in self._unavailable_group_ids
|
||||
and (service_info := self._last_seen_by_group_id.get(group_id))
|
||||
and now - service_info.time > UNAVAILABLE_TIMEOUT
|
||||
and (
|
||||
# We will not be callbacks for iBeacons with random macs
|
||||
# that rotate infrequently since their advertisement data is
|
||||
# does not change as the bluetooth.async_register_callback API
|
||||
# suppresses callbacks for duplicate advertisements to avoid
|
||||
# exposing integrations to the firehose of bluetooth advertisements.
|
||||
#
|
||||
# To solve this we need to ask for the latest service info for
|
||||
# the address we last saw to get the latest timestamp.
|
||||
#
|
||||
# If there is no last service info for the address we know that
|
||||
# the device is no longer advertising.
|
||||
not (
|
||||
latest_service_info := bluetooth.async_last_service_info(
|
||||
self.hass, service_info.address, connectable=False
|
||||
)
|
||||
)
|
||||
or now - latest_service_info.time > UNAVAILABLE_TIMEOUT
|
||||
)
|
||||
]
|
||||
for group_id in gone_unavailable:
|
||||
self._unavailable_group_ids.add(group_id)
|
||||
|
|
|
@ -7,8 +7,17 @@ from unittest.mock import patch
|
|||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.bluetooth import (
|
||||
BluetoothServiceInfoBleak,
|
||||
async_ble_device_from_address,
|
||||
async_last_service_info,
|
||||
)
|
||||
from homeassistant.components.bluetooth.const import UNAVAILABLE_TRACK_SECONDS
|
||||
from homeassistant.components.ibeacon.const import DOMAIN, UNAVAILABLE_TIMEOUT
|
||||
from homeassistant.components.ibeacon.const import (
|
||||
DOMAIN,
|
||||
UNAVAILABLE_TIMEOUT,
|
||||
UPDATE_INTERVAL,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_FRIENDLY_NAME,
|
||||
STATE_HOME,
|
||||
|
@ -27,6 +36,7 @@ from . import (
|
|||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
from tests.components.bluetooth import (
|
||||
inject_bluetooth_service_info,
|
||||
inject_bluetooth_service_info_bleak,
|
||||
patch_all_discovered_devices,
|
||||
)
|
||||
|
||||
|
@ -130,3 +140,108 @@ async def test_device_tracker_random_address(hass):
|
|||
tracker_attributes = tracker.attributes
|
||||
assert tracker.state == STATE_HOME
|
||||
assert tracker_attributes[ATTR_FRIENDLY_NAME] == "RandomAddress_1234"
|
||||
|
||||
|
||||
async def test_device_tracker_random_address_infrequent_changes(hass):
|
||||
"""Test creating and updating device_tracker with a random mac that only changes once per day."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
start_time = time.monotonic()
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
for i in range(20):
|
||||
inject_bluetooth_service_info(
|
||||
hass,
|
||||
replace(
|
||||
BEACON_RANDOM_ADDRESS_SERVICE_INFO, address=f"AA:BB:CC:DD:EE:{i:02X}"
|
||||
),
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
tracker = hass.states.get("device_tracker.randomaddress_1234")
|
||||
tracker_attributes = tracker.attributes
|
||||
assert tracker.state == STATE_HOME
|
||||
assert tracker_attributes[ATTR_FRIENDLY_NAME] == "RandomAddress_1234"
|
||||
|
||||
await hass.async_block_till_done()
|
||||
with patch_all_discovered_devices([]), patch(
|
||||
"homeassistant.components.ibeacon.coordinator.MONOTONIC_TIME",
|
||||
return_value=start_time + UNAVAILABLE_TIMEOUT + 1,
|
||||
):
|
||||
async_fire_time_changed(
|
||||
hass, dt_util.utcnow() + timedelta(seconds=UNAVAILABLE_TIMEOUT)
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
tracker = hass.states.get("device_tracker.randomaddress_1234")
|
||||
assert tracker.state == STATE_NOT_HOME
|
||||
|
||||
inject_bluetooth_service_info(
|
||||
hass, replace(BEACON_RANDOM_ADDRESS_SERVICE_INFO, address="AA:BB:CC:DD:EE:14")
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
tracker = hass.states.get("device_tracker.randomaddress_1234")
|
||||
tracker_attributes = tracker.attributes
|
||||
assert tracker.state == STATE_HOME
|
||||
assert tracker_attributes[ATTR_FRIENDLY_NAME] == "RandomAddress_1234"
|
||||
|
||||
inject_bluetooth_service_info(
|
||||
hass, replace(BEACON_RANDOM_ADDRESS_SERVICE_INFO, address="AA:BB:CC:DD:EE:14")
|
||||
)
|
||||
device = async_ble_device_from_address(hass, "AA:BB:CC:DD:EE:14", False)
|
||||
|
||||
with patch_all_discovered_devices([device]), patch(
|
||||
"homeassistant.components.ibeacon.coordinator.MONOTONIC_TIME",
|
||||
return_value=start_time + UPDATE_INTERVAL.total_seconds() + 1,
|
||||
):
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + UPDATE_INTERVAL)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
tracker = hass.states.get("device_tracker.randomaddress_1234")
|
||||
tracker_attributes = tracker.attributes
|
||||
assert tracker.state == STATE_HOME
|
||||
assert tracker_attributes[ATTR_FRIENDLY_NAME] == "RandomAddress_1234"
|
||||
|
||||
one_day_future = start_time + 86400
|
||||
previous_service_info = async_last_service_info(
|
||||
hass, "AA:BB:CC:DD:EE:14", connectable=False
|
||||
)
|
||||
inject_bluetooth_service_info_bleak(
|
||||
hass,
|
||||
BluetoothServiceInfoBleak(
|
||||
name="RandomAddress_1234",
|
||||
address="AA:BB:CC:DD:EE:14",
|
||||
rssi=-63,
|
||||
service_data={},
|
||||
manufacturer_data={76: b"\x02\x15RandCharmBeacons\x0e\xfe\x13U\xc5"},
|
||||
service_uuids=[],
|
||||
source="local",
|
||||
time=one_day_future,
|
||||
connectable=False,
|
||||
device=device,
|
||||
advertisement=previous_service_info.advertisement,
|
||||
),
|
||||
)
|
||||
device = async_ble_device_from_address(hass, "AA:BB:CC:DD:EE:14", False)
|
||||
assert (
|
||||
async_last_service_info(hass, "AA:BB:CC:DD:EE:14", connectable=False).time
|
||||
== one_day_future
|
||||
)
|
||||
|
||||
with patch_all_discovered_devices([device]), patch(
|
||||
"homeassistant.components.ibeacon.coordinator.MONOTONIC_TIME",
|
||||
return_value=start_time + UNAVAILABLE_TIMEOUT + 1,
|
||||
):
|
||||
async_fire_time_changed(
|
||||
hass, dt_util.utcnow() + timedelta(seconds=UNAVAILABLE_TIMEOUT + 1)
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
tracker = hass.states.get("device_tracker.randomaddress_1234")
|
||||
tracker_attributes = tracker.attributes
|
||||
assert tracker.state == STATE_HOME
|
||||
assert tracker_attributes[ATTR_FRIENDLY_NAME] == "RandomAddress_1234"
|
||||
|
|
Loading…
Reference in New Issue