diff --git a/homeassistant/components/bluetooth/__init__.py b/homeassistant/components/bluetooth/__init__.py index 8d3bc0ae5e2..bf4dbf81f01 100644 --- a/homeassistant/components/bluetooth/__init__.py +++ b/homeassistant/components/bluetooth/__init__.py @@ -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__) diff --git a/homeassistant/components/bluetooth/base_scanner.py b/homeassistant/components/bluetooth/base_scanner.py index 8f7750fe322..e8de285138e 100644 --- a/homeassistant/components/bluetooth/base_scanner.py +++ b/homeassistant/components/bluetooth/base_scanner.py @@ -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, ) ) diff --git a/homeassistant/components/esphome/bluetooth/scanner.py b/homeassistant/components/esphome/bluetooth/scanner.py index 85ab991df4e..5013a288dcf 100644 --- a/homeassistant/components/esphome/bluetooth/scanner.py +++ b/homeassistant/components/esphome/bluetooth/scanner.py @@ -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, ) diff --git a/homeassistant/components/ruuvi_gateway/bluetooth.py b/homeassistant/components/ruuvi_gateway/bluetooth.py index 4dd973155a9..47a9bbfdde0 100644 --- a/homeassistant/components/ruuvi_gateway/bluetooth.py +++ b/homeassistant/components/ruuvi_gateway/bluetooth.py @@ -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 diff --git a/homeassistant/components/shelly/bluetooth/scanner.py b/homeassistant/components/shelly/bluetooth/scanner.py index 5b302e0da62..7c0dc3c792a 100644 --- a/homeassistant/components/shelly/bluetooth/scanner.py +++ b/homeassistant/components/shelly/bluetooth/scanner.py @@ -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(), ) diff --git a/tests/components/bluetooth/test_api.py b/tests/components/bluetooth/test_api.py index 77d802264e1..63b60c8f487 100644 --- a/tests/components/bluetooth/test_api.py +++ b/tests/components/bluetooth/test_api.py @@ -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 diff --git a/tests/components/bluetooth/test_base_scanner.py b/tests/components/bluetooth/test_base_scanner.py index 8817acad468..5662bc6324b 100644 --- a/tests/components/bluetooth/test_base_scanner.py +++ b/tests/components/bluetooth/test_base_scanner.py @@ -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 diff --git a/tests/components/bluetooth/test_diagnostics.py b/tests/components/bluetooth/test_diagnostics.py index 7ffd3f00131..765e2a9a612 100644 --- a/tests/components/bluetooth/test_diagnostics.py +++ b/tests/components/bluetooth/test_diagnostics.py @@ -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( diff --git a/tests/components/bluetooth/test_manager.py b/tests/components/bluetooth/test_manager.py index 85da27b027e..67b0b594249 100644 --- a/tests/components/bluetooth/test_manager.py +++ b/tests/components/bluetooth/test_manager.py @@ -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: diff --git a/tests/components/bluetooth/test_wrappers.py b/tests/components/bluetooth/test_wrappers.py index e1656b39c18..de646f8ef9c 100644 --- a/tests/components/bluetooth/test_wrappers.py +++ b/tests/components/bluetooth/test_wrappers.py @@ -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(), )