1 """The bluetooth integration."""
3 from __future__
import annotations
8 from typing
import TYPE_CHECKING
10 from bleak_retry_connector
import BleakSlotManager
11 from bluetooth_adapters
import (
13 ADAPTER_CONNECTION_SLOTS,
18 DEFAULT_CONNECTION_SLOTS,
26 from bluetooth_data_tools
import monotonic_time_coarse
as MONOTONIC_TIME
27 from habluetooth
import (
30 BluetoothScannerDevice,
31 BluetoothScanningMode,
37 from home_assistant_bluetooth
import BluetoothServiceInfo, BluetoothServiceInfoBleak
42 from homeassistant.core import Event, HassJob, HomeAssistant, callback
as hass_callback
45 config_validation
as cv,
46 device_registry
as dr,
54 from .
import passive_update_processor
57 async_address_present,
58 async_ble_device_from_address,
59 async_discovered_service_info,
60 async_get_advertisement_callback,
61 async_get_fallback_availability_interval,
62 async_get_learned_advertising_interval,
64 async_last_service_info,
65 async_process_advertisements,
66 async_rediscover_address,
67 async_register_callback,
68 async_register_scanner,
69 async_scanner_by_source,
71 async_scanner_devices_by_address,
72 async_set_fallback_availability_interval,
73 async_track_unavailable,
76 BLUETOOTH_DISCOVERY_COOLDOWN_SECONDS,
81 FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS,
82 LINUX_FIRMWARE_LOAD_FALLBACK_SECONDS,
85 from .manager
import HomeAssistantBluetoothManager
86 from .match
import BluetoothCallbackMatcher, IntegrationMatcher
87 from .models
import BluetoothCallback, BluetoothChange
88 from .storage
import BluetoothStorage
89 from .util
import adapter_title
95 "async_address_present",
96 "async_ble_device_from_address",
97 "async_discovered_service_info",
98 "async_get_fallback_availability_interval",
99 "async_get_learned_advertising_interval",
101 "async_last_service_info",
102 "async_process_advertisements",
103 "async_rediscover_address",
104 "async_register_callback",
105 "async_register_scanner",
106 "async_set_fallback_availability_interval",
107 "async_track_unavailable",
108 "async_scanner_by_source",
109 "async_scanner_count",
110 "async_scanner_devices_by_address",
111 "async_get_advertisement_callback",
113 "HomeAssistantRemoteScanner",
114 "BluetoothCallbackMatcher",
116 "BluetoothServiceInfo",
117 "BluetoothServiceInfoBleak",
118 "BluetoothScanningMode",
120 "BluetoothScannerDevice",
121 "HaBluetoothConnector",
122 "BaseHaRemoteScanner",
124 "FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS",
128 _LOGGER = logging.getLogger(__name__)
130 CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
135 manager: HomeAssistantBluetoothManager,
136 bluetooth_adapters: BluetoothAdapters,
138 """Start adapter discovery."""
139 adapters = await manager.async_get_bluetooth_adapters()
143 async
def _async_rediscover_adapters() -> None:
144 """Rediscover adapters when a new one may be available."""
145 discovered_adapters = await manager.async_get_bluetooth_adapters(cached=
False)
146 _LOGGER.debug(
"Rediscovered adapters: %s", discovered_adapters)
152 cooldown=BLUETOOTH_DISCOVERY_COOLDOWN_SECONDS,
154 function=_async_rediscover_adapters,
159 def _async_shutdown_debouncer(_: Event) ->
None:
160 """Shutdown debouncer."""
161 discovery_debouncer.async_shutdown()
163 hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_shutdown_debouncer)
165 async
def _async_call_debouncer(now: datetime.datetime) ->
None:
166 """Call the debouncer at a later time."""
167 await discovery_debouncer.async_call()
169 call_debouncer_job =
HassJob(_async_call_debouncer, cancel_on_shutdown=
True)
171 def _async_trigger_discovery() -> None:
178 _LOGGER.debug(
"Triggering bluetooth usb discovery")
179 hass.async_create_task(discovery_debouncer.async_call())
188 BLUETOOTH_DISCOVERY_COOLDOWN_SECONDS + LINUX_FIRMWARE_LOAD_FALLBACK_SECONDS,
192 cancel = usb.async_register_scan_request_callback(hass, _async_trigger_discovery)
193 hass.bus.async_listen_once(
194 EVENT_HOMEASSISTANT_STOP,
195 hass_callback(
lambda event: cancel()),
199 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
200 """Set up the bluetooth integration."""
201 if platform.system() ==
"Linux":
208 for entry
in list(hass.config_entries.async_entries(DOMAIN)):
209 if entry.unique_id == DEFAULT_ADDRESS:
210 await hass.config_entries.async_remove(entry.entry_id)
212 bluetooth_adapters = get_adapters()
214 slot_manager = BleakSlotManager()
217 slot_manager_setup_task = hass.async_create_task(
218 slot_manager.async_setup(),
"slot_manager setup", eager_start=
True
220 processor_setup_task = hass.async_create_task(
221 passive_update_processor.async_setup(hass),
222 "passive_update_processor setup",
225 storage_setup_task = hass.async_create_task(
226 bluetooth_storage.async_setup(),
"bluetooth storage setup", eager_start=
True
228 integration_matcher.async_setup()
230 hass, integration_matcher, bluetooth_adapters, bluetooth_storage, slot_manager
233 await storage_setup_task
234 await manager.async_setup()
236 hass.async_create_background_task(
238 "start_adapter_discovery",
240 await slot_manager_setup_task
242 await processor_setup_task
248 hass: HomeAssistant, adapters: dict[str, AdapterDetails], default_adapter: str
250 """Migrate config entries to support multiple."""
251 current_entries = hass.config_entries.async_entries(DOMAIN)
253 for entry
in current_entries:
257 address = DEFAULT_ADDRESS
258 adapter = entry.options.get(CONF_ADAPTER, default_adapter)
259 if adapter
in adapters:
260 address = adapters[adapter][ADAPTER_ADDRESS]
261 hass.config_entries.async_update_entry(
262 entry, title=adapter_unique_name(adapter, address), unique_id=address
268 adapters: dict[str, AdapterDetails],
270 """Discover adapters and start flows."""
271 system = platform.system()
272 if system ==
"Windows":
278 for adapter, details
in adapters.items():
279 if system ==
"Linux" and details[ADAPTER_ADDRESS] == DEFAULT_ADDRESS:
284 discovery_flow.async_create_flow(
287 context={
"source": SOURCE_INTEGRATION_DISCOVERY},
288 data={CONF_ADAPTER: adapter, CONF_DETAILS: details},
293 hass: HomeAssistant, entry: ConfigEntry, adapter: str, details: AdapterDetails
295 """Update device registry entry.
297 The physical adapter can change from hci0/hci1 on reboot
298 or if the user moves around the usb sticks so we need to
299 update the device with the new location so they can
300 figure out where the adapter is.
302 dr.async_get(hass).async_get_or_create(
303 config_entry_id=entry.entry_id,
304 name=adapter_human_name(adapter, details[ADAPTER_ADDRESS]),
305 connections={(dr.CONNECTION_BLUETOOTH, details[ADAPTER_ADDRESS])},
306 manufacturer=details[ADAPTER_MANUFACTURER],
307 model=adapter_model(details),
308 sw_version=details.get(ADAPTER_SW_VERSION),
309 hw_version=details.get(ADAPTER_HW_VERSION),
314 """Set up a config entry for a bluetooth scanner."""
316 address = entry.unique_id
317 assert address
is not None
318 adapter = await manager.async_get_adapter_from_address_or_recover(address)
321 f
"Bluetooth adapter {adapter} with address {address} not found"
323 passive = entry.options.get(CONF_PASSIVE)
324 mode = BluetoothScanningMode.PASSIVE
if passive
else BluetoothScanningMode.ACTIVE
325 scanner = HaScanner(mode, adapter, address)
326 scanner.async_setup()
328 await scanner.async_start()
329 except (RuntimeError, ScannerStartError)
as err:
331 f
"{adapter_human_name(adapter, address)}: {err}"
333 adapters = await manager.async_get_bluetooth_adapters()
334 details = adapters[adapter]
335 if entry.title == address:
336 hass.config_entries.async_update_entry(
339 slots: int = details.get(ADAPTER_CONNECTION_SLOTS)
or DEFAULT_CONNECTION_SLOTS
342 entry.async_on_unload(entry.add_update_listener(async_update_listener))
343 entry.async_on_unload(scanner.async_stop)
348 """Handle options update."""
349 await hass.config_entries.async_reload(entry.entry_id)
353 """Unload a config entry."""
CALLBACK_TYPE async_register_scanner(HomeAssistant hass, BaseHaScanner scanner, int|None connection_slots=None)
HomeAssistantBluetoothManager _get_manager(HomeAssistant hass)
str adapter_title(str adapter, AdapterDetails details)
bool async_setup(HomeAssistant hass, ConfigType config)
None async_update_device(HomeAssistant hass, ConfigEntry entry, str adapter, AdapterDetails details)
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
None async_discover_adapters(HomeAssistant hass, dict[str, AdapterDetails] adapters)
None async_update_listener(HomeAssistant hass, ConfigEntry entry)
None _async_start_adapter_discovery(HomeAssistant hass, HomeAssistantBluetoothManager manager, BluetoothAdapters bluetooth_adapters)
None async_migrate_entries(HomeAssistant hass, dict[str, AdapterDetails] adapters, str default_adapter)
None async_delete_issue(HomeAssistant hass, str entry_id)
CALLBACK_TYPE async_call_later(HomeAssistant hass, float|timedelta delay, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action)
list[BluetoothMatcher] async_get_bluetooth(HomeAssistant hass)