Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """The BTHome Bluetooth integration."""
2 
3 from __future__ import annotations
4 
5 from functools import partial
6 import logging
7 
8 from bthome_ble import BTHomeBluetoothDeviceData, SensorUpdate
9 from bthome_ble.parser import EncryptionScheme
10 
12  DOMAIN as BLUETOOTH_DOMAIN,
13  BluetoothScanningMode,
14  BluetoothServiceInfoBleak,
15 )
16 from homeassistant.const import Platform
17 from homeassistant.core import HomeAssistant
18 from homeassistant.helpers import device_registry as dr
19 from homeassistant.helpers.device_registry import CONNECTION_BLUETOOTH, DeviceRegistry
20 from homeassistant.helpers.dispatcher import async_dispatcher_send
21 from homeassistant.util.signal_type import SignalType
22 
23 from .const import (
24  BTHOME_BLE_EVENT,
25  CONF_BINDKEY,
26  CONF_DISCOVERED_EVENT_CLASSES,
27  CONF_SLEEPY_DEVICE,
28  DOMAIN,
29  BTHomeBleEvent,
30 )
31 from .coordinator import BTHomePassiveBluetoothProcessorCoordinator
32 from .types import BTHomeConfigEntry
33 
34 PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.EVENT, Platform.SENSOR]
35 
36 _LOGGER = logging.getLogger(__name__)
37 
38 
40  hass: HomeAssistant,
41  entry: BTHomeConfigEntry,
42  device_registry: DeviceRegistry,
43  service_info: BluetoothServiceInfoBleak,
44 ) -> SensorUpdate:
45  """Process a BluetoothServiceInfoBleak, running side effects and returning sensor data."""
46  coordinator = entry.runtime_data
47  data = coordinator.device_data
48  update = data.update(service_info)
49  discovered_event_classes = coordinator.discovered_event_classes
50  if entry.data.get(CONF_SLEEPY_DEVICE, False) != data.sleepy_device:
51  hass.config_entries.async_update_entry(
52  entry,
53  data=entry.data | {CONF_SLEEPY_DEVICE: data.sleepy_device},
54  )
55  if update.events:
56  address = service_info.device.address
57  for device_key, event in update.events.items():
58  sensor_device_info = update.devices[device_key.device_id]
59  device = device_registry.async_get_or_create(
60  config_entry_id=entry.entry_id,
61  connections={(CONNECTION_BLUETOOTH, address)},
62  identifiers={(BLUETOOTH_DOMAIN, address)},
63  manufacturer=sensor_device_info.manufacturer,
64  model=sensor_device_info.model,
65  name=sensor_device_info.name,
66  sw_version=sensor_device_info.sw_version,
67  hw_version=sensor_device_info.hw_version,
68  )
69  # event_class may be postfixed with a number, ie 'button_2'
70  # but if there is only one button then it will be 'button'
71  event_class = event.device_key.key
72  event_type = event.event_type
73 
74  ble_event = BTHomeBleEvent(
75  device_id=device.id,
76  address=address,
77  event_class=event_class, # ie 'button'
78  event_type=event_type, # ie 'press'
79  event_properties=event.event_properties,
80  )
81 
82  if event_class not in discovered_event_classes:
83  discovered_event_classes.add(event_class)
84  hass.config_entries.async_update_entry(
85  entry,
86  data=entry.data
87  | {CONF_DISCOVERED_EVENT_CLASSES: list(discovered_event_classes)},
88  )
90  hass, format_discovered_event_class(address), event_class, ble_event
91  )
92 
93  hass.bus.async_fire(BTHOME_BLE_EVENT, ble_event)
95  hass,
96  format_event_dispatcher_name(address, event_class),
97  ble_event,
98  )
99 
100  # If payload is encrypted and the bindkey is not verified then we need to reauth
101  if data.encryption_scheme != EncryptionScheme.NONE and not data.bindkey_verified:
102  entry.async_start_reauth(hass, data={"device": data})
103 
104  return update
105 
106 
108  address: str, event_class: str
109 ) -> SignalType[BTHomeBleEvent]:
110  """Format an event dispatcher name."""
111  return SignalType(f"{DOMAIN}_event_{address}_{event_class}")
112 
113 
114 def format_discovered_event_class(address: str) -> SignalType[str, BTHomeBleEvent]:
115  """Format a discovered event class."""
116  return SignalType(f"{DOMAIN}_discovered_event_class_{address}")
117 
118 
119 async def async_setup_entry(hass: HomeAssistant, entry: BTHomeConfigEntry) -> bool:
120  """Set up BTHome Bluetooth from a config entry."""
121  address = entry.unique_id
122  assert address is not None
123 
124  kwargs = {}
125  if bindkey := entry.data.get(CONF_BINDKEY):
126  kwargs[CONF_BINDKEY] = bytes.fromhex(bindkey)
127  data = BTHomeBluetoothDeviceData(**kwargs)
128 
129  device_registry = dr.async_get(hass)
130  event_classes = set(entry.data.get(CONF_DISCOVERED_EVENT_CLASSES, ()))
132  hass,
133  _LOGGER,
134  address=address,
135  mode=BluetoothScanningMode.PASSIVE,
136  update_method=partial(process_service_info, hass, entry, device_registry),
137  device_data=data,
138  discovered_event_classes=event_classes,
139  connectable=False,
140  entry=entry,
141  )
142  entry.runtime_data = coordinator
143  await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
144 
145  # only start after all platforms have had a chance to subscribe
146  entry.async_on_unload(coordinator.async_start())
147  return True
148 
149 
150 async def async_unload_entry(hass: HomeAssistant, entry: BTHomeConfigEntry) -> bool:
151  """Unload a config entry."""
152  return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
SensorUpdate process_service_info(HomeAssistant hass, BTHomeConfigEntry entry, DeviceRegistry device_registry, BluetoothServiceInfoBleak service_info)
Definition: __init__.py:44
SignalType[BTHomeBleEvent] format_event_dispatcher_name(str address, str event_class)
Definition: __init__.py:109
SignalType[str, BTHomeBleEvent] format_discovered_event_class(str address)
Definition: __init__.py:114
bool async_unload_entry(HomeAssistant hass, BTHomeConfigEntry entry)
Definition: __init__.py:150
bool async_setup_entry(HomeAssistant hass, BTHomeConfigEntry entry)
Definition: __init__.py:119
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:193