Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore remote discovered devices between remote scanner restarts #83699

Merged
merged 42 commits into from
Dec 11, 2022
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
bc7fa2a
Restore discovered devices between remote scanner restarts
bdraco Dec 10, 2022
cb754c5
Restore discovered devices between remote scanner restarts
bdraco Dec 10, 2022
dd347a9
make it serializable
bdraco Dec 10, 2022
8674b17
restore
bdraco Dec 10, 2022
9ecb975
wip
bdraco Dec 10, 2022
4d8fcc4
fix
bdraco Dec 10, 2022
1269ad0
fix
bdraco Dec 10, 2022
78bec89
fix
bdraco Dec 10, 2022
fec2c84
cleanup
bdraco Dec 10, 2022
6fcaf26
fixes
bdraco Dec 10, 2022
19d0fc6
fix
bdraco Dec 10, 2022
a6e5e8b
restore int
bdraco Dec 10, 2022
0691e80
typing
bdraco Dec 10, 2022
a3213a0
typing
bdraco Dec 10, 2022
557baff
typing
bdraco Dec 10, 2022
689ae2b
typing
bdraco Dec 10, 2022
ca21293
typing
bdraco Dec 10, 2022
2c8d7c6
typing
bdraco Dec 10, 2022
0de55ae
Update homeassistant/components/bluetooth/base_scanner.py
bdraco Dec 10, 2022
85bc7b8
offload
bdraco Dec 10, 2022
cb42b1b
Merge remote-tracking branch 'origin/esphome_restore_addresses' into …
bdraco Dec 10, 2022
4a2cd07
offload more
bdraco Dec 10, 2022
060781f
bump home-assistant-bluetooth==1.9.0
bdraco Dec 10, 2022
7a12387
cleanups
bdraco Dec 10, 2022
f63633b
bump again
bdraco Dec 10, 2022
77ee89c
Merge branch 'dev' into esphome_restore_addresses
bdraco Dec 10, 2022
a79ffc3
fix mocking
bdraco Dec 10, 2022
56e1bd9
cover
bdraco Dec 10, 2022
6bfe9b2
reduce delay
bdraco Dec 10, 2022
3f1b196
preen
bdraco Dec 10, 2022
b04aeff
restore
bdraco Dec 11, 2022
fbc6aed
tweak
bdraco Dec 11, 2022
52b0349
fixtures
bdraco Dec 11, 2022
4166942
Merge branch 'dev' into esphome_restore_addresses
bdraco Dec 11, 2022
3cbbcd9
empty
bdraco Dec 11, 2022
56e1cef
empty
bdraco Dec 11, 2022
fe21e75
Merge branch 'dev' into esphome_restore_addresses
bdraco Dec 11, 2022
58e0ae4
Merge branch 'dev' into esphome_restore_addresses
bdraco Dec 11, 2022
bcc5d43
fix missing mocking
bdraco Dec 11, 2022
87d0469
Merge branch 'dev' into esphome_restore_addresses
bdraco Dec 11, 2022
a325be7
mock
bdraco Dec 11, 2022
7c74e1f
Merge remote-tracking branch 'origin/esphome_restore_addresses' into …
bdraco Dec 11, 2022
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
15 changes: 12 additions & 3 deletions homeassistant/components/bluetooth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,14 @@
)
from .manager import BluetoothManager
from .match import BluetoothCallbackMatcher, IntegrationMatcher
from .models import BluetoothCallback, BluetoothChange, BluetoothScanningMode
from .models import (
BluetoothCallback,
BluetoothChange,
BluetoothScanningMode,
HaBluetoothConnector,
)
from .scanner import HaScanner, ScannerStartError
from .wrappers import HaBluetoothConnector
from .storage import BluetoothStorage

if TYPE_CHECKING:
from homeassistant.helpers.typing import ConfigType
Expand Down Expand Up @@ -158,7 +163,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
integration_matcher = IntegrationMatcher(await async_get_bluetooth(hass))
integration_matcher.async_setup()
bluetooth_adapters = get_adapters()
manager = BluetoothManager(hass, integration_matcher, bluetooth_adapters)
bluetooth_storage = BluetoothStorage(hass)
await bluetooth_storage.async_setup()
manager = BluetoothManager(
hass, integration_matcher, bluetooth_adapters, bluetooth_storage
)
await manager.async_setup()
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, manager.async_stop)
hass.data[DATA_MANAGER] = models.MANAGER = manager
Expand Down
66 changes: 57 additions & 9 deletions homeassistant/components/bluetooth/base_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,47 @@
from bluetooth_adapters import adapter_human_name
from home_assistant_bluetooth import BluetoothServiceInfoBleak

from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback as hass_callback
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import (
CALLBACK_TYPE,
Event,
HomeAssistant,
callback as hass_callback,
)
from homeassistant.helpers.event import async_track_time_interval
import homeassistant.util.dt as dt_util
from homeassistant.util.dt import monotonic_time_coarse

from . import models
from .const import (
CONNECTABLE_FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS,
FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS,
)
from .models import HaBluetoothConnector

REMOTE_SCANNER_STORAGE_VERSION = 1
bdraco marked this conversation as resolved.
Show resolved Hide resolved
MONOTONIC_TIME: Final = monotonic_time_coarse


class BaseHaScanner(ABC):
"""Base class for Ha Scanners."""

__slots__ = ("hass", "source", "_connecting", "name", "scanning")
__slots__ = (
"hass",
"connectable",
"source",
"connector",
"_connecting",
"name",
"scanning",
)

def __init__(self, hass: HomeAssistant, source: str, adapter: str) -> None:
"""Initialize the scanner."""
self.hass = hass
self.connectable = False
self.source = source
self.connector: HaBluetoothConnector | None = None
self._connecting = 0
self.name = adapter_human_name(adapter, source) if adapter != source else source
self.scanning = True
Expand Down Expand Up @@ -87,10 +106,9 @@ class BaseHaRemoteScanner(BaseHaScanner):
"_new_info_callback",
"_discovered_device_advertisement_datas",
"_discovered_device_timestamps",
"_connector",
"_connectable",
"_details",
"_expire_seconds",
"_storage",
)

def __init__(
Expand All @@ -109,22 +127,52 @@ def __init__(
str, tuple[BLEDevice, AdvertisementData]
] = {}
self._discovered_device_timestamps: dict[str, float] = {}
self._connector = connector
self._connectable = connectable
self.connectable = connectable
self.connector = connector
self._details: dict[str, str | HaBluetoothConnector] = {"source": scanner_id}
self._expire_seconds = FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS
assert models.MANAGER is not None
self._storage = models.MANAGER.storage
if connectable:
self._details["connector"] = connector
self._expire_seconds = (
CONNECTABLE_FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS
)

@hass_callback
def async_setup(self) -> CALLBACK_TYPE:
"""Set up the scanner."""
return async_track_time_interval(
if history := self._storage.async_get_advertisement_history(self.source):
self._discovered_device_advertisement_datas = (
history.discovered_device_advertisement_datas
)
self._discovered_device_timestamps = history.discovered_device_timestamps
# Expire anything that is too old
self._async_expire_devices(dt_util.utcnow())

cancel_track = async_track_time_interval(
self.hass, self._async_expire_devices, timedelta(seconds=30)
)
cancel_stop = self.hass.bus.async_listen(
EVENT_HOMEASSISTANT_STOP, self._save_history
)

@hass_callback
def _cancel() -> None:
self._save_history()
cancel_track()
cancel_stop()

return _cancel

def _save_history(self, event: Event | None = None) -> None:
"""Save the history."""
self._storage.async_set_advertisement_history(
self.source,
self.connectable,
self._expire_seconds,
self._discovered_device_advertisement_datas,
self._discovered_device_timestamps,
)

def _async_expire_devices(self, _datetime: datetime.datetime) -> None:
"""Expire old devices."""
Expand Down Expand Up @@ -222,7 +270,7 @@ def _async_on_advertisement(
source=self.source,
device=device,
advertisement=advertisement_data,
connectable=self._connectable,
connectable=self.connectable,
time=now,
)
)
Expand Down
12 changes: 6 additions & 6 deletions homeassistant/components/bluetooth/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
ble_device_matches,
)
from .models import BluetoothCallback, BluetoothChange, BluetoothServiceInfoBleak
from .storage import BluetoothStorage
from .usage import install_multiple_bleak_catcher, uninstall_multiple_bleak_catcher
from .util import async_load_history_from_system

Expand Down Expand Up @@ -102,6 +103,7 @@ def __init__(
hass: HomeAssistant,
integration_matcher: IntegrationMatcher,
bluetooth_adapters: BluetoothAdapters,
storage: BluetoothStorage,
) -> None:
"""Init bluetooth manager."""
self.hass = hass
Expand All @@ -128,6 +130,7 @@ def __init__(
self._adapters: dict[str, AdapterDetails] = {}
self._sources: dict[str, BaseHaScanner] = {}
self._bluetooth_adapters = bluetooth_adapters
self.storage = storage

@property
def supports_passive_scan(self) -> bool:
Expand Down Expand Up @@ -196,12 +199,9 @@ async def async_setup(self) -> None:
"""Set up the bluetooth manager."""
await self._bluetooth_adapters.refresh()
install_multiple_bleak_catcher()
history = async_load_history_from_system(self._bluetooth_adapters)
# Everything is connectable so it fall into both
# buckets since the host system can only provide
# connectable devices
self._all_history = history.copy()
self._connectable_history = history.copy()
self._all_history, self._connectable_history = async_load_history_from_system(
self._bluetooth_adapters, self.storage
)
self.async_setup_unavailable_tracking()

@hass_callback
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/bluetooth/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ def __init__(
"""Init bluetooth discovery."""
source = address if address != DEFAULT_ADDRESS else adapter or SOURCE_LOCAL
super().__init__(hass, source, adapter)
self.connectable = True
self.mode = mode
self.adapter = adapter
self._start_stop_lock = asyncio.Lock()
Expand Down
Loading