1 """The bluetooth integration."""
3 from __future__
import annotations
5 from collections.abc
import Callable, Iterable
6 from functools
import partial
10 from bleak_retry_connector
import BleakSlotManager
11 from bluetooth_adapters
import BluetoothAdapters
12 from habluetooth
import BaseHaRemoteScanner, BaseHaScanner, BluetoothManager
14 from homeassistant
import config_entries
20 callback
as hass_callback,
25 from .const
import DOMAIN
30 BluetoothCallbackMatcher,
31 BluetoothCallbackMatcherIndex,
32 BluetoothCallbackMatcherWithCallback,
36 from .models
import BluetoothCallback, BluetoothChange, BluetoothServiceInfoBleak
37 from .storage
import BluetoothStorage
38 from .util
import async_load_history_from_system
40 _LOGGER = logging.getLogger(__name__)
44 """Manage Bluetooth for Home Assistant."""
49 "_integration_matcher",
51 "_cancel_logging_listener",
57 integration_matcher: IntegrationMatcher,
58 bluetooth_adapters: BluetoothAdapters,
59 storage: BluetoothStorage,
60 slot_manager: BleakSlotManager,
62 """Init bluetooth manager."""
68 super().
__init__(bluetooth_adapters, slot_manager)
73 """Handle logging change."""
74 self.
_debug_debug = _LOGGER.isEnabledFor(logging.DEBUG)
77 self, service_info: BluetoothServiceInfoBleak
79 """Trigger discovery for matching domains."""
80 discovery_key = discovery_flow.DiscoveryKey(
82 key=service_info.address,
86 discovery_flow.async_create_flow(
89 {
"source": config_entries.SOURCE_BLUETOOTH},
91 discovery_key=discovery_key,
96 """Trigger discovery of devices which have already been seen."""
101 if service_info := self._all_history.
get(address):
109 self._async_describe_source(service_info),
114 for match
in self.
_callback_index_callback_index.match_callbacks(service_info):
115 callback = match[CALLBACK]
117 callback(service_info, BluetoothChange.ADVERTISEMENT)
119 _LOGGER.exception(
"Error in bluetooth callback")
121 if not matched_domains:
124 discovery_key = discovery_flow.DiscoveryKey(
126 key=service_info.address,
129 for domain
in matched_domains:
130 discovery_flow.async_create_flow(
133 {
"source": config_entries.SOURCE_BLUETOOTH},
135 discovery_key=discovery_key,
139 """Dismiss all discoveries for the given address."""
141 for flow
in self.
hasshass.config_entries.flow.async_progress_by_init_data_type(
142 BluetoothServiceInfoBleak,
143 lambda service_info: bool(service_info.address == address),
145 self.
hasshass.config_entries.flow.async_abort(flow[
"flow_id"])
148 """Set up the bluetooth manager."""
151 self._bluetooth_adapters, self.
storagestorage
156 self.
hasshass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.
async_stopasync_stop)
157 seen: set[str] = set()
158 for address, service_info
in itertools.chain(
167 config_entries.signal_discovered_config_entry_removed(DOMAIN),
173 callback: BluetoothCallback,
174 matcher: BluetoothCallbackMatcher |
None,
175 ) -> Callable[[],
None]:
176 """Register a callback."""
179 callback_matcher[CONNECTABLE] =
True
183 callback_matcher.update(matcher)
184 callback_matcher[CONNECTABLE] = matcher.get(CONNECTABLE,
True)
186 connectable = callback_matcher[CONNECTABLE]
187 self.
_callback_index_callback_index.add_callback_matcher(callback_matcher)
189 def _async_remove_callback() -> None:
190 self.
_callback_index_callback_index.remove_callback_matcher(callback_matcher)
196 service_infos: Iterable[BluetoothServiceInfoBleak] = []
197 if address := callback_matcher.get(ADDRESS):
198 if service_info := history.get(address):
199 service_infos = [service_info]
201 service_infos = history.values()
203 for service_info
in service_infos:
206 callback(service_info, BluetoothChange.ADVERTISEMENT)
208 _LOGGER.exception(
"Error in bluetooth callback")
210 return _async_remove_callback
214 """Stop the Bluetooth integration at shutdown."""
215 _LOGGER.debug(
"Stopping bluetooth manager")
223 """Save the scanner histories."""
224 for scanner
in itertools.chain(
225 self._connectable_scanners, self._non_connectable_scanners
230 """Save the scanner history."""
231 if isinstance(scanner, BaseHaRemoteScanner):
232 self.
storagestorage.async_set_advertisement_history(
233 scanner.source, scanner.serialize_discovered_devices()
237 self, scanner: BaseHaScanner, unregister: CALLBACK_TYPE
239 """Unregister a scanner."""
245 scanner: BaseHaScanner,
246 connection_slots: int |
None =
None,
248 """Register a scanner."""
249 if isinstance(scanner, BaseHaRemoteScanner):
250 if history := self.
storagestorage.async_get_advertisement_history(scanner.source):
251 scanner.restore_discovered_devices(history)
259 entry: config_entries.ConfigEntry,
261 """Handle config entry changes."""
262 for discovery_key
in entry.discovery_keys[DOMAIN]:
263 if discovery_key.version != 1
or not isinstance(discovery_key.key, str):
265 address = discovery_key.key
266 _LOGGER.debug(
"Rediscover address %s", address)
Callable[[], None] async_register_callback(self, BluetoothCallback callback, BluetoothCallbackMatcher|None matcher)
None _async_unregister_scanner(self, BaseHaScanner scanner, CALLBACK_TYPE unregister)
None async_rediscover_address(self, str address)
None _handle_config_entry_removed(self, config_entries.ConfigEntry entry)
None __init__(self, HomeAssistant hass, IntegrationMatcher integration_matcher, BluetoothAdapters bluetooth_adapters, BluetoothStorage storage, BleakSlotManager slot_manager)
None _async_trigger_matching_discovery(self, BluetoothServiceInfoBleak service_info)
CALLBACK_TYPE async_register_scanner(self, BaseHaScanner scanner, int|None connection_slots=None)
None _async_logging_changed(self, Event|None event=None)
None _address_disappeared(self, str address)
None _discover_service_info(self, BluetoothServiceInfoBleak service_info)
None _async_save_scanner_history(self, BaseHaScanner scanner)
None _async_save_scanner_histories(self)
None async_stop(self, Event|None event=None)
bool ble_device_matches(BluetoothMatcherOptional matcher, BluetoothServiceInfoBleak service_info)
tuple[dict[str, BluetoothServiceInfoBleak], dict[str, BluetoothServiceInfoBleak]] async_load_history_from_system(BluetoothAdapters adapters, BluetoothStorage storage)
web.Response get(self, web.Request request, str config_key)
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)