Fix timestamps for bluetooth scanners that bundle advertisements (#94511)

#94138 added support for raw/bundled advertisements. We should use the
same monotonic time for all advertisements in the bundle if not time
is passed, or calculate the timestamp and pass it if its known
This commit is contained in:
J. Nick Koston 2023-06-14 15:47:00 -10:00 committed by GitHub
parent 22dfa8797f
commit 2a5ffa9a5b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 42 additions and 8 deletions

View file

@ -76,7 +76,7 @@ from .models import (
BluetoothScanningMode,
HaBluetoothConnector,
)
from .scanner import HaScanner, ScannerStartError
from .scanner import MONOTONIC_TIME, HaScanner, ScannerStartError
from .storage import BluetoothStorage
if TYPE_CHECKING:
@ -108,6 +108,7 @@ __all__ = [
"HaBluetoothConnector",
"SOURCE_LOCAL",
"FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS",
"MONOTONIC_TIME",
]
_LOGGER = logging.getLogger(__name__)

View file

@ -299,10 +299,10 @@ class BaseHaRemoteScanner(BaseHaScanner):
manufacturer_data: dict[int, bytes],
tx_power: int | None,
details: dict[Any, Any],
advertisement_monotonic_time: float,
) -> None:
"""Call the registered callback."""
now = MONOTONIC_TIME()
self._last_detection = now
self._last_detection = advertisement_monotonic_time
if prev_discovery := self._discovered_device_advertisement_datas.get(address):
# Merge the new data with the old data
# to function the same as BlueZ which
@ -365,7 +365,7 @@ class BaseHaRemoteScanner(BaseHaScanner):
device,
advertisement_data,
)
self._discovered_device_timestamps[address] = now
self._discovered_device_timestamps[address] = advertisement_monotonic_time
self._new_info_callback(
BluetoothServiceInfoBleak(
name=local_name or address,
@ -378,7 +378,7 @@ class BaseHaRemoteScanner(BaseHaScanner):
device=device,
advertisement=advertisement_data,
connectable=self.connectable,
time=now,
time=advertisement_monotonic_time,
)
)

View file

@ -4,7 +4,7 @@ from __future__ import annotations
from aioesphomeapi import BluetoothLEAdvertisement, BluetoothLERawAdvertisement
from bluetooth_data_tools import int_to_bluetooth_address, parse_advertisement_data
from homeassistant.components.bluetooth import BaseHaRemoteScanner
from homeassistant.components.bluetooth import MONOTONIC_TIME, BaseHaRemoteScanner
from homeassistant.core import callback
@ -24,6 +24,7 @@ class ESPHomeScanner(BaseHaRemoteScanner):
adv.manufacturer_data,
None,
{"address_type": adv.address_type},
MONOTONIC_TIME(),
)
@callback
@ -31,6 +32,7 @@ class ESPHomeScanner(BaseHaRemoteScanner):
self, advertisements: list[BluetoothLERawAdvertisement]
) -> None:
"""Call the registered callback."""
now = MONOTONIC_TIME()
for adv in advertisements:
parsed = parse_advertisement_data((adv.data,))
self._async_on_advertisement(
@ -42,4 +44,5 @@ class ESPHomeScanner(BaseHaRemoteScanner):
parsed.manufacturer_data,
None,
{"address_type": adv.address_type},
now,
)

View file

@ -9,6 +9,7 @@ from home_assistant_bluetooth import BluetoothServiceInfoBleak
from homeassistant.components.bluetooth import (
FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS,
MONOTONIC_TIME,
BaseHaRemoteScanner,
async_get_advertisement_callback,
async_register_scanner,
@ -47,6 +48,7 @@ class RuuviGatewayScanner(BaseHaRemoteScanner):
@callback
def _async_handle_new_data(self) -> None:
now = time.time()
monotonic_now = MONOTONIC_TIME()
for tag_data in self.coordinator.data:
data_age_seconds = now - tag_data.timestamp # Both are Unix time
if data_age_seconds > FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS:
@ -62,6 +64,7 @@ class RuuviGatewayScanner(BaseHaRemoteScanner):
manufacturer_data=anno.manufacturer_data,
tx_power=anno.tx_power,
details={},
advertisement_monotonic_time=monotonic_now - data_age_seconds,
)
@callback

View file

@ -6,7 +6,7 @@ from typing import Any
from aioshelly.ble import parse_ble_scan_result_event
from aioshelly.ble.const import BLE_SCAN_RESULT_EVENT, BLE_SCAN_RESULT_VERSION
from homeassistant.components.bluetooth import BaseHaRemoteScanner
from homeassistant.components.bluetooth import MONOTONIC_TIME, BaseHaRemoteScanner
from homeassistant.core import callback
from ..const import LOGGER
@ -44,4 +44,5 @@ class ShellyBLEScanner(BaseHaRemoteScanner):
parsed.manufacturer_data,
parsed.tx_power,
{},
MONOTONIC_TIME(),
)

View file

@ -1,8 +1,12 @@
"""Tests for the Bluetooth integration API."""
import time
from bleak.backends.scanner import AdvertisementData, BLEDevice
import pytest
from homeassistant.components import bluetooth
from homeassistant.components.bluetooth import (
MONOTONIC_TIME,
BaseHaRemoteScanner,
BaseHaScanner,
HaBluetoothConnector,
@ -31,6 +35,11 @@ async def test_scanner_by_source(hass: HomeAssistant, enable_bluetooth: None) ->
assert async_scanner_by_source(hass, "hci2") is None
async def test_monotonic_time() -> None:
"""Test monotonic time."""
assert MONOTONIC_TIME() == pytest.approx(time.monotonic(), abs=0.1)
async def test_async_scanner_devices_by_address_connectable(
hass: HomeAssistant, enable_bluetooth: None
) -> None:
@ -51,6 +60,7 @@ async def test_async_scanner_devices_by_address_connectable(
advertisement_data.manufacturer_data,
advertisement_data.tx_power,
{"scanner_specific_data": "test"},
MONOTONIC_TIME(),
)
new_info_callback = manager.scanner_adv_received

View file

@ -12,6 +12,7 @@ import pytest
from homeassistant.components import bluetooth
from homeassistant.components.bluetooth import (
MONOTONIC_TIME,
BaseHaRemoteScanner,
HaBluetoothConnector,
storage,
@ -84,6 +85,7 @@ async def test_remote_scanner(hass: HomeAssistant, enable_bluetooth: None) -> No
advertisement_data.manufacturer_data,
advertisement_data.tx_power,
{"scanner_specific_data": "test"},
MONOTONIC_TIME(),
)
new_info_callback = manager.scanner_adv_received
@ -158,6 +160,7 @@ async def test_remote_scanner_expires_connectable(
advertisement_data.manufacturer_data,
advertisement_data.tx_power,
{"scanner_specific_data": "test"},
MONOTONIC_TIME(),
)
new_info_callback = manager.scanner_adv_received
@ -232,6 +235,7 @@ async def test_remote_scanner_expires_non_connectable(
advertisement_data.manufacturer_data,
advertisement_data.tx_power,
{"scanner_specific_data": "test"},
MONOTONIC_TIME(),
)
new_info_callback = manager.scanner_adv_received
@ -329,6 +333,7 @@ async def test_base_scanner_connecting_behavior(
advertisement_data.manufacturer_data,
advertisement_data.tx_power,
{"scanner_specific_data": "test"},
MONOTONIC_TIME(),
)
new_info_callback = manager.scanner_adv_received
@ -452,6 +457,7 @@ async def test_device_with_ten_minute_advertising_interval(
advertisement_data.manufacturer_data,
advertisement_data.tx_power,
{"scanner_specific_data": "test"},
MONOTONIC_TIME(),
)
new_info_callback = manager.scanner_adv_received

View file

@ -5,7 +5,11 @@ from bleak.backends.scanner import AdvertisementData, BLEDevice
from bluetooth_adapters import DEFAULT_ADDRESS
from homeassistant.components import bluetooth
from homeassistant.components.bluetooth import BaseHaRemoteScanner, HaBluetoothConnector
from homeassistant.components.bluetooth import (
MONOTONIC_TIME,
BaseHaRemoteScanner,
HaBluetoothConnector,
)
from homeassistant.core import HomeAssistant
from . import (
@ -450,6 +454,7 @@ async def test_diagnostics_remote_adapter(
advertisement_data.manufacturer_data,
advertisement_data.tx_power,
{"scanner_specific_data": "test"},
MONOTONIC_TIME(),
)
with patch(

View file

@ -11,6 +11,7 @@ import pytest
from homeassistant.components import bluetooth
from homeassistant.components.bluetooth import (
MONOTONIC_TIME,
BaseHaRemoteScanner,
BluetoothChange,
BluetoothScanningMode,
@ -711,6 +712,7 @@ async def test_goes_unavailable_connectable_only_and_recovers(
advertisement_data.manufacturer_data,
advertisement_data.tx_power,
{"scanner_specific_data": "test"},
MONOTONIC_TIME(),
)
new_info_callback = async_get_advertisement_callback(hass)
@ -883,6 +885,7 @@ async def test_goes_unavailable_dismisses_discovery_and_makes_discoverable(
advertisement_data.manufacturer_data,
advertisement_data.tx_power,
{"scanner_specific_data": "test"},
MONOTONIC_TIME(),
)
def clear_all_devices(self) -> None:

View file

@ -10,6 +10,7 @@ from bleak.backends.scanner import AdvertisementData
import pytest
from homeassistant.components.bluetooth import (
MONOTONIC_TIME,
BaseHaRemoteScanner,
BluetoothServiceInfoBleak,
HaBluetoothConnector,
@ -59,6 +60,7 @@ class FakeScanner(BaseHaRemoteScanner):
advertisement_data.manufacturer_data,
advertisement_data.tx_power,
device.details | {"scanner_specific_data": "test"},
MONOTONIC_TIME(),
)