1
1
"""Passive BLE monitor integration."""
2
2
import asyncio
3
- import copy
4
- from Cryptodome .Cipher import AES
5
- import json
6
3
import logging
7
4
import queue
8
5
import struct
9
6
from threading import Thread
10
7
import voluptuous as vol
11
-
12
- from homeassistant .config_entries import SOURCE_IMPORT , ConfigEntry
8
+ from homeassistant . helpers import config_validation as cv
9
+ from homeassistant .helpers import discovery
13
10
from homeassistant .const import (
14
11
CONF_DEVICES ,
15
12
CONF_DISCOVERY ,
16
13
CONF_MAC ,
17
14
CONF_NAME ,
18
15
CONF_TEMPERATURE_UNIT ,
19
16
EVENT_HOMEASSISTANT_STOP ,
20
- TEMP_CELSIUS ,
21
- TEMP_FAHRENHEIT ,
22
- )
23
- from homeassistant .core import HomeAssistant
24
- from homeassistant .helpers import config_validation as cv
25
- from homeassistant .helpers .entity_registry import (
26
- async_entries_for_device ,
27
17
)
28
18
19
+ from Cryptodome .Cipher import AES
20
+
29
21
# It was decided to temporarily include this file in the integration bundle
30
22
# until the issue with checking the adapter's capabilities is resolved in the official aioblescan repo
31
23
# see https://github.com/frawau/aioblescan/pull/30, thanks to @vicamo
38
30
DEFAULT_LOG_SPIKES ,
39
31
DEFAULT_USE_MEDIAN ,
40
32
DEFAULT_ACTIVE_SCAN ,
33
+ DEFAULT_HCI_INTERFACE ,
41
34
DEFAULT_BATT_ENTITIES ,
42
35
DEFAULT_REPORT_UNKNOWN ,
43
36
DEFAULT_DISCOVERY ,
44
37
DEFAULT_RESTORE_STATE ,
45
- DEFAULT_HCI_INTERFACE ,
46
38
CONF_ROUNDING ,
47
39
CONF_DECIMALS ,
48
40
CONF_PERIOD ,
54
46
CONF_REPORT_UNKNOWN ,
55
47
CONF_RESTORE_STATE ,
56
48
CONF_ENCRYPTION_KEY ,
57
- CONFIG_IS_FLOW ,
58
49
DOMAIN ,
59
- MAC_REGEX ,
60
- AES128KEY_REGEX ,
61
50
XIAOMI_TYPE_DICT ,
62
- SERVICE_CLEANUP_ENTRIES ,
63
51
)
64
52
65
53
_LOGGER = logging .getLogger (__name__ )
72
60
ILL_STRUCT = struct .Struct ("<I" )
73
61
FMDH_STRUCT = struct .Struct ("<H" )
74
62
75
- PLATFORMS = ["binary_sensor" , "sensor" ]
76
-
77
- CONFIG_YAML = {}
78
- UPDATE_UNLISTENER = None
63
+ # regex constants for configuration schema
64
+ MAC_REGEX = "(?i)^(?:[0-9A-F]{2}[:]){5}(?:[0-9A-F]{2})$"
65
+ AES128KEY_REGEX = "(?i)^[A-F0-9]{32}$"
79
66
80
67
DEVICE_SCHEMA = vol .Schema (
81
68
{
116
103
extra = vol .ALLOW_EXTRA ,
117
104
)
118
105
119
- SERVICE_CLEANUP_ENTRIES_SCHEMA = vol .Schema ({})
120
106
121
-
122
- async def async_setup (hass : HomeAssistant , config ):
107
+ def setup (hass , config ):
123
108
"""Set up integration."""
124
-
125
- async def service_cleanup_entries (service_call ):
126
- # service = service_call.service
127
- service_data = service_call .data
128
-
129
- await async_cleanup_entries_service (hass , service_data )
130
-
131
- hass .services .async_register (
132
- DOMAIN ,
133
- SERVICE_CLEANUP_ENTRIES ,
134
- service_cleanup_entries ,
135
- schema = SERVICE_CLEANUP_ENTRIES_SCHEMA ,
136
- )
137
-
138
- if DOMAIN not in config :
139
- return True
140
-
141
- if DOMAIN in hass .data :
142
- # One instance only
143
- return False
144
-
145
- # Save and set default for the YAML config
146
- global CONFIG_YAML
147
- CONFIG_YAML = json .loads (json .dumps (config [DOMAIN ]))
148
- CONFIG_YAML [CONFIG_IS_FLOW ] = False
149
-
150
- _LOGGER .debug ("Initializing BLE Monitor integration (YAML): %s" , CONFIG_YAML )
151
-
152
- hass .async_add_job (
153
- hass .config_entries .flow .async_init (
154
- DOMAIN , context = {"source" : SOURCE_IMPORT }, data = copy .deepcopy (CONFIG_YAML )
155
- )
156
- )
157
-
158
- return True
159
-
160
-
161
- async def async_setup_entry (hass : HomeAssistant , config_entry : ConfigEntry ):
162
- """Set up BLE Monitor from a config entry."""
163
- _LOGGER .debug ("Initializing BLE Monitor entry (config entry): %s" , config_entry )
164
-
165
- # Prevent unload to be triggered each time we update the config entry
166
- global UPDATE_UNLISTENER
167
- if UPDATE_UNLISTENER :
168
- UPDATE_UNLISTENER ()
169
-
170
- if not config_entry .unique_id :
171
- hass .config_entries .async_update_entry (config_entry , unique_id = config_entry .title )
172
-
173
- _LOGGER .debug ("async_setup_entry: domain %s" , CONFIG_YAML )
174
-
175
- config = {}
176
-
177
- if not CONFIG_YAML :
178
- for key , value in config_entry .data .items ():
179
- config [key ] = value
180
-
181
- for key , value in config_entry .options .items ():
182
- config [key ] = value
183
-
184
- config [CONFIG_IS_FLOW ] = True
185
- if CONF_DEVICES not in config :
186
- config [CONF_DEVICES ] = []
187
- else :
188
- for key , value in CONFIG_YAML .items ():
189
- config [key ] = value
190
- if CONF_HCI_INTERFACE in CONFIG_YAML :
191
- hci_list = []
192
- if isinstance (CONFIG_YAML [CONF_HCI_INTERFACE ], list ):
193
- for hci in CONFIG_YAML [CONF_HCI_INTERFACE ]:
194
- hci_list .append (str (hci ))
195
- else :
196
- hci_list .append (str (CONFIG_YAML [CONF_HCI_INTERFACE ]))
197
- config [CONF_HCI_INTERFACE ] = hci_list
198
-
199
- hass .config_entries .async_update_entry (config_entry , data = {}, options = config )
200
-
201
- _LOGGER .debug ("async_setup_entry: %s" , config )
202
-
203
- UPDATE_UNLISTENER = config_entry .add_update_listener (_async_update_listener )
204
-
205
- if CONF_HCI_INTERFACE not in config :
206
- config [CONF_HCI_INTERFACE ] = [DEFAULT_HCI_INTERFACE ]
207
- else :
208
- hci_list = config_entry .options .get (CONF_HCI_INTERFACE )
209
- for i , hci in enumerate (hci_list ):
210
- hci_list [i ] = int (hci )
211
- config [CONF_HCI_INTERFACE ] = hci_list
212
- _LOGGER .debug ("HCI interface is %s" , config [CONF_HCI_INTERFACE ])
213
-
214
- blemonitor = BLEmonitor (config )
215
- hass .bus .async_listen (EVENT_HOMEASSISTANT_STOP , blemonitor .shutdown_handler )
109
+ blemonitor = BLEmonitor (config [DOMAIN ])
110
+ hass .bus .listen (EVENT_HOMEASSISTANT_STOP , blemonitor .shutdown_handler )
216
111
blemonitor .start ()
217
-
218
- hass .data [DOMAIN ] = {}
219
- hass .data [DOMAIN ]["blemonitor" ] = blemonitor
220
- hass .data [DOMAIN ]["config_entry_id" ] = config_entry .entry_id
221
-
222
- for component in PLATFORMS :
223
- hass .async_create_task (
224
- hass .config_entries .async_forward_entry_setup (config_entry , component )
225
- )
226
-
112
+ hass .data [DOMAIN ] = blemonitor
113
+ discovery .load_platform (hass , "sensor" , DOMAIN , {}, config )
114
+ discovery .load_platform (hass , "binary_sensor" , DOMAIN , {}, config )
227
115
return True
228
116
229
117
230
- async def async_unload_entry (hass : HomeAssistant , entry : ConfigEntry ):
231
- """Unload a config entry."""
232
- _LOGGER .debug ("async_unload_entry: %s" , entry )
233
-
234
- unload_ok = all (
235
- await asyncio .gather (
236
- * [
237
- hass .config_entries .async_forward_entry_unload (entry , component )
238
- for component in PLATFORMS
239
- ]
240
- )
241
- )
242
- blemonitor : BLEmonitor = hass .data [DOMAIN ]["blemonitor" ]
243
- if blemonitor :
244
- blemonitor .stop ()
245
-
246
- return unload_ok
247
-
248
-
249
- async def _async_update_listener (hass : HomeAssistant , entry : ConfigEntry ) -> None :
250
- """Handle options update."""
251
- await hass .config_entries .async_reload (entry .entry_id )
252
-
253
-
254
- async def async_cleanup_entries_service (hass : HomeAssistant , data ):
255
- """Remove orphaned entries from device and entity registries."""
256
- _LOGGER .debug ("async_cleanup_entries_service" )
257
-
258
- entity_registry = await hass .helpers .entity_registry .async_get_registry ()
259
- device_registry = await hass .helpers .device_registry .async_get_registry ()
260
- config_entry_id = hass .data [DOMAIN ]["config_entry_id" ]
261
-
262
- # entity_entries = async_entries_for_config_entry(
263
- # entity_registry, config_entry_id
264
- # )
265
-
266
- # entities_to_be_removed = []
267
- devices_to_be_removed = [
268
- entry .id
269
- for entry in device_registry .devices .values ()
270
- if config_entry_id in entry .config_entries
271
- ]
272
-
273
- # for entry in entity_entries:
274
-
275
- # # Don't remove available entities
276
- # if entry.unique_id in gateway.entities[entry.domain]:
277
-
278
- # # Don't remove devices with available entities
279
- # if entry.device_id in devices_to_be_removed:
280
- # devices_to_be_removed.remove(entry.device_id)
281
- # continue
282
- # # Remove entities that are not available
283
- # entities_to_be_removed.append(entry.entity_id)
284
-
285
- # # Remove unavailable entities
286
- # for entity_id in entities_to_be_removed:
287
- # entity_registry.async_remove(entity_id)
288
-
289
- # Remove devices that don't belong to any entity
290
- for device_id in devices_to_be_removed :
291
- if len (async_entries_for_device (entity_registry , device_id )) == 0 :
292
- device_registry .async_remove_device (device_id )
293
- _LOGGER .debug ("device %s will be deleted" , device_id )
294
-
295
-
296
118
class BLEmonitor :
297
119
"""BLE scanner."""
298
120
@@ -303,8 +125,6 @@ def __init__(self, config):
303
125
"measuring" : queue .SimpleQueue (),
304
126
}
305
127
self .config = config
306
- if config [CONF_REPORT_UNKNOWN ] is True :
307
- _LOGGER .info ("Attention! Option report_unknown is enabled, be ready for a huge output..." )
308
128
self .dumpthread = None
309
129
310
130
def shutdown_handler (self , event ):
@@ -421,17 +241,17 @@ def reverse_mac(rmac):
421
241
self .report_unknown = False
422
242
if self .config [CONF_REPORT_UNKNOWN ]:
423
243
self .report_unknown = True
424
- _LOGGER .debug (
244
+ _LOGGER .info (
425
245
"Attention! Option report_unknown is enabled, be ready for a huge output..."
426
246
)
427
247
# prepare device:key lists to speedup parser
428
248
if self .config [CONF_DEVICES ]:
429
249
for device in self .config [CONF_DEVICES ]:
430
- if CONF_ENCRYPTION_KEY in device and device [ CONF_ENCRYPTION_KEY ] :
250
+ if "encryption_key" in device :
431
251
p_mac = bytes .fromhex (
432
252
reverse_mac (device ["mac" ].replace (":" , "" )).lower ()
433
253
)
434
- p_key = bytes .fromhex (device [CONF_ENCRYPTION_KEY ].lower ())
254
+ p_key = bytes .fromhex (device ["encryption_key" ].lower ())
435
255
self .aeskeys [p_mac ] = p_key
436
256
else :
437
257
continue
0 commit comments