@@ -79,7 +79,8 @@ async def _scan(self,
79
79
device_descriptions :Dict [Optional [str ],Optional [Set [str ]]],
80
80
blacklist :Set [str ],
81
81
case_sensitive :bool ,
82
- scan_timeout :float ) -> 'Tuple[Optional[BLEDevice], Optional[str]]' :
82
+ scan_timeout :float ,
83
+ address :Optional [str ]) -> 'Tuple[Optional[BLEDevice], Optional[str]]' :
83
84
try :
84
85
async with asyncio .timeout (scan_timeout ): # type:ignore[attr-defined]
85
86
async with BleakScanner () as scanner :
@@ -88,7 +89,7 @@ async def _scan(self,
88
89
if self ._terminate_scan_event .is_set ():
89
90
return None , None
90
91
# _log.debug("device %s, (%s): %s", bd.name, ad.local_name, ad.service_uuids)
91
- if bd .address not in blacklist :
92
+ if bd .address not in blacklist and ( address is None or bd . address == address ) :
92
93
res :bool
93
94
res_service_uuid :Optional [str ]
94
95
res , res_service_uuid = self .description_match (bd ,ad ,device_descriptions ,case_sensitive )
@@ -106,14 +107,16 @@ async def _scan_and_connect(self,
106
107
blacklist :Set [str ], # client addresses to ignore
107
108
case_sensitive :bool ,
108
109
disconnected_callback :Optional [Callable [[BleakClient ], None ]],
109
- scan_timeout :float , connect_timeout :float ) -> Tuple [Optional [BleakClient ], Optional [str ]]:
110
+ scan_timeout :float , connect_timeout :float ,
111
+ address :Optional [str ] = None # if given, connect only to the device with this ble address
112
+ ) -> Tuple [Optional [BleakClient ], Optional [str ]]:
110
113
async with self ._scan_and_connect_lock :
111
114
# the lock ensures that only one scan/connect operation is running at any time
112
115
# as trying to establish a connection to two devices at the same time
113
116
# can cause errors
114
117
discovered_bd :Optional [BLEDevice ] = None
115
118
service_uuid :Optional [str ] = None
116
- discovered_bd , service_uuid = await self ._scan (device_descriptions , blacklist , case_sensitive , scan_timeout )
119
+ discovered_bd , service_uuid = await self ._scan (device_descriptions , blacklist , case_sensitive , scan_timeout , address )
117
120
if discovered_bd is None :
118
121
return None , None
119
122
client = BleakClient (
@@ -159,7 +162,9 @@ def scan_and_connect(self,
159
162
case_sensitive :bool = True ,
160
163
disconnected_callback :Optional [Callable [[BleakClient ], None ]] = None ,
161
164
scan_timeout :float = 10 ,
162
- connect_timeout :float = 3 ) -> Tuple [Optional [BleakClient ], Optional [str ]]:
165
+ connect_timeout :float = 3 ,
166
+ address :Optional [str ] = None # if given, connect only to the device with this ble address
167
+ ) -> Tuple [Optional [BleakClient ], Optional [str ]]:
163
168
if hasattr (self , '_asyncLoopThread' ) and self ._asyncLoopThread is None :
164
169
self ._asyncLoopThread = AsyncLoopThread ()
165
170
assert self ._asyncLoopThread is not None
@@ -170,7 +175,8 @@ def scan_and_connect(self,
170
175
case_sensitive ,
171
176
disconnected_callback ,
172
177
scan_timeout ,
173
- connect_timeout ),
178
+ connect_timeout ,
179
+ address ),
174
180
self ._asyncLoopThread .loop )
175
181
try :
176
182
return fut .result ()
@@ -250,8 +256,9 @@ def start_notifications(self) -> None:
250
256
try :
251
257
ble .start_notify (self ._ble_client , notify_uuid , callback )
252
258
self ._active_notification_uuids .add (notify_uuid )
259
+ _log .debug ('notification on characteristic %s started' , notify_uuid )
253
260
except BleakCharacteristicNotFoundError :
254
- _log .debug ('start_notifications: characteristic {notify_uuid} not found' )
261
+ _log .debug ('start_notifications: characteristic %s not found' , notify_uuid )
255
262
256
263
# Notifications are stopped automatically on disconnect, so this method does not need to be called
257
264
# unless notifications need to be stopped before the device disconnects
@@ -262,7 +269,7 @@ def stop_notifications(self) -> None:
262
269
ble .stop_notify (self ._ble_client , notify_uuid )
263
270
_log .debug ('notifications on %s stopped' , notify_uuid )
264
271
except BleakCharacteristicNotFoundError :
265
- _log .debug ('start_notifications: characteristic {notify_uuid} not found' )
272
+ _log .debug ('start_notifications: characteristic %s not found' , notify_uuid )
266
273
self ._active_notification_uuids = set ()
267
274
268
275
def _disconnect (self ) -> None :
@@ -277,7 +284,7 @@ def connected(self) -> Optional[str]:
277
284
278
285
279
286
# connect and re-connect while self._running to BLE
280
- async def _connect (self , case_sensitive :bool = True , scan_timeout :float = 10 , connect_timeout :float = 4 ) -> None :
287
+ async def _connect (self , case_sensitive :bool = True , scan_timeout :float = 10 , connect_timeout :float = 4 , address : Optional [ str ] = None ) -> None :
281
288
blacklist :Set [str ] = set ()
282
289
while self ._running :
283
290
# scan and connect
@@ -290,7 +297,8 @@ async def _connect(self, case_sensitive:bool=True, scan_timeout:float=10, connec
290
297
case_sensitive ,
291
298
self .disconnected_callback ,
292
299
scan_timeout ,
293
- connect_timeout )
300
+ connect_timeout ,
301
+ address )
294
302
if service_uuid is not None and self ._ble_client is not None and self ._ble_client .is_connected :
295
303
# validate correct service
296
304
try :
@@ -380,13 +388,13 @@ async def _keep_alive(self) -> None:
380
388
await asyncio .sleep (self ._heartbeat_frequency )
381
389
self .heartbeat ()
382
390
383
- async def _connect_and_keep_alive (self ,case_sensitive :bool ,scan_timeout :float , connect_timeout :float ) -> None :
391
+ async def _connect_and_keep_alive (self ,case_sensitive :bool ,scan_timeout :float , connect_timeout :float , address : Optional [ str ] = None ) -> None :
384
392
await asyncio .gather (
385
- self ._connect (case_sensitive ,scan_timeout ,connect_timeout ),
393
+ self ._connect (case_sensitive ,scan_timeout ,connect_timeout , address ),
386
394
self ._keep_alive ())
387
395
388
396
389
- def start (self , case_sensitive :bool = True , scan_timeout :float = 10 , connect_timeout :float = 4 ) -> None :
397
+ def start (self , case_sensitive :bool = True , scan_timeout :float = 10 , connect_timeout :float = 4 , address : Optional [ str ] = None ) -> None :
390
398
_log .debug ('start' )
391
399
if self ._running :
392
400
_log .error ('BLE client already running' )
@@ -397,7 +405,7 @@ def start(self, case_sensitive:bool=True, scan_timeout:float=10, connect_timeout
397
405
self ._async_loop_thread = AsyncLoopThread ()
398
406
# run _connect in async loop
399
407
asyncio .run_coroutine_threadsafe (
400
- self ._connect_and_keep_alive (case_sensitive , scan_timeout , connect_timeout ),
408
+ self ._connect_and_keep_alive (case_sensitive , scan_timeout , connect_timeout , address ),
401
409
self ._async_loop_thread .loop )
402
410
_log .debug ('BLE client started' )
403
411
self .on_start ()
@@ -480,3 +488,19 @@ def on_stop(self) -> None: # pylint: disable=no-self-use
480
488
...
481
489
def heartbeat (self ) -> None : # pylint: disable=no-self-use
482
490
...
491
+
492
+
493
+ ##
494
+
495
+ # scans for named BLE devices providing any of the provided servie_uuids
496
+ # returns a list of triples (name, address, Optional[BLEDevice])
497
+ def scan_ble (timeout : float = 3.0 ) -> 'List[Tuple[BLEDevice, AdvertisementData]]' :
498
+ coro = BleakScanner .discover (
499
+ timeout = timeout ,
500
+ return_adv = True )
501
+ try :
502
+ loop = asyncio .get_running_loop ()
503
+ res = asyncio .run_coroutine_threadsafe (coro , loop ).result ()
504
+ except RuntimeError :
505
+ res = asyncio .run (coro )
506
+ return list (res .values ())
0 commit comments