11"""Bluetooth interface
22"""
3+ import asyncio
34import logging
4- import time
55import struct
6- import asyncio
7- from threading import Thread , Event
6+ import time
7+ from threading import Event , Thread
88from typing import Optional
99
10- from bleak import BleakScanner , BleakClient , BLEDevice
10+ from bleak import BleakClient , BleakScanner , BLEDevice
1111
1212from meshtastic .mesh_interface import MeshInterface
1313from meshtastic .util import our_exit
2020
2121class BLEInterface (MeshInterface ):
2222 """MeshInterface using BLE to connect to devices"""
23+
2324 class BLEError (Exception ):
2425 """An exception class for BLE errors"""
26+
2527 def __init__ (self , message ):
2628 self .message = message
2729 super ().__init__ (self .message )
2830
29- class BLEState (): # pylint: disable=C0115
31+ class BLEState : # pylint: disable=C0115
3032 THREADS = False
3133 BLE = False
3234 MESH = False
3335
34-
35- def __init__ (self , address : Optional [str ], noProto : bool = False , debugOut = None , noNodes : bool = False ):
36+ def __init__ (
37+ self ,
38+ address : Optional [str ],
39+ noProto : bool = False ,
40+ debugOut = None ,
41+ noNodes : bool = False ,
42+ ):
3643 self .state = BLEInterface .BLEState ()
3744
3845 self .should_read = False
3946
4047 logging .debug ("Threads starting" )
41- self ._receiveThread = Thread (target = self ._receiveFromRadioImpl )
48+ self ._receiveThread = Thread (target = self ._receiveFromRadioImpl )
4249 self ._receiveThread_started = Event ()
4350 self ._receiveThread_stopped = Event ()
4451 self ._receiveThread .start ()
@@ -56,72 +63,75 @@ def __init__(self, address: Optional[str], noProto: bool = False, debugOut = Non
5663 raise e
5764
5865 logging .debug ("Mesh init starting" )
59- MeshInterface .__init__ (self , debugOut = debugOut , noProto = noProto , noNodes = noNodes )
66+ MeshInterface .__init__ (
67+ self , debugOut = debugOut , noProto = noProto , noNodes = noNodes
68+ )
6069 self ._startConfig ()
6170 if not self .noProto :
62- self ._waitConnected (timeout = 60.0 )
71+ self ._waitConnected (timeout = 60.0 )
6372 self .waitForConfig ()
6473 self .state .MESH = True
6574 logging .debug ("Mesh init finished" )
6675
6776 logging .debug ("Register FROMNUM notify callback" )
6877 self .client .start_notify (FROMNUM_UUID , self .from_num_handler )
6978
70-
71- async def from_num_handler (self , _ , b ): # pylint: disable=C0116
72- from_num = struct .unpack ('<I' , bytes (b ))[0 ]
79+ async def from_num_handler (self , _ , b ): # pylint: disable=C0116
80+ from_num = struct .unpack ("<I" , bytes (b ))[0 ]
7381 logging .debug (f"FROMNUM notify: { from_num } " )
7482 self .should_read = True
7583
76-
77- def scan (self ) -> list [BLEDevice ]:
84+ @ staticmethod
85+ def scan () -> list [BLEDevice ]:
7886 """Scan for available BLE devices."""
7987 with BLEClient () as client :
80- response = client .discover (
81- return_adv = True ,
82- service_uuids = [SERVICE_UUID ]
83- )
88+ response = client .discover (return_adv = True , service_uuids = [SERVICE_UUID ])
8489
8590 devices = response .values ()
8691
8792 # bleak sometimes returns devices we didn't ask for, so filter the response
8893 # to only return true meshtastic devices
8994 # d[0] is the device. d[1] is the advertisement data
90- devices = list (filter (lambda d : SERVICE_UUID in d [1 ].service_uuids , devices ))
95+ devices = list (
96+ filter (lambda d : SERVICE_UUID in d [1 ].service_uuids , devices )
97+ )
9198 return list (map (lambda d : d [0 ], devices ))
9299
93-
94100 def find_device (self , address : Optional [str ]) -> BLEDevice :
95- """Find a device by address"""
96- addressed_devices = self .scan ()
101+ """Find a device by address. """
102+ addressed_devices = BLEInterface .scan ()
97103
98104 if address :
99- addressed_devices = list (filter (lambda x : address == x .name or address == x .address , addressed_devices ))
105+ addressed_devices = list (
106+ filter (
107+ lambda x : address == x .name or address == x .address ,
108+ addressed_devices ,
109+ )
110+ )
100111
101112 if len (addressed_devices ) == 0 :
102- raise BLEInterface .BLEError (f"No Meshtastic BLE peripheral with identifier or address '{ address } ' found. Try --ble-scan to find it." )
113+ raise BLEInterface .BLEError (
114+ f"No Meshtastic BLE peripheral with identifier or address '{ address } ' found. Try --ble-scan to find it."
115+ )
103116 if len (addressed_devices ) > 1 :
104- raise BLEInterface .BLEError (f"More than one Meshtastic BLE peripheral with identifier or address '{ address } ' found." )
117+ raise BLEInterface .BLEError (
118+ f"More than one Meshtastic BLE peripheral with identifier or address '{ address } ' found."
119+ )
105120 return addressed_devices [0 ]
106121
107- def _sanitize_address (address ): # pylint: disable=E0213
108- "Standardize BLE address by removing extraneous characters and lowercasing"
109- return address \
110- .replace ("-" , "" ) \
111- .replace ("_" , "" ) \
112- .replace (":" , "" ) \
113- .lower ()
122+ def _sanitize_address (address ): # pylint: disable=E0213
123+ "Standardize BLE address by removing extraneous characters and lowercasing."
124+ return address .replace ("-" , "" ).replace ("_" , "" ).replace (":" , "" ).lower ()
114125
115126 def connect (self , address : Optional [str ] = None ):
116- "Connect to a device by address"
127+ "Connect to a device by address. "
117128
118129 # Bleak docs recommend always doing a scan before connecting (even if we know addr)
119130 device = self .find_device (address )
120131 client = BLEClient (device .address )
121132 client .connect ()
122133 return client
123134
124-
125135 def _receiveFromRadioImpl (self ):
126136 self ._receiveThread_started .set ()
127137 while self ._receiveThread_started .is_set ():
@@ -146,12 +156,11 @@ def _sendToRadioImpl(self, toRadio):
146156 b = toRadio .SerializeToString ()
147157 if b :
148158 logging .debug (f"TORADIO write: { b .hex ()} " )
149- self .client .write_gatt_char (TORADIO_UUID , b , response = True )
159+ self .client .write_gatt_char (TORADIO_UUID , b , response = True )
150160 # Allow to propagate and then make sure we read
151161 time .sleep (0.1 )
152162 self .should_read = True
153163
154-
155164 def close (self ):
156165 if self .state .MESH :
157166 MeshInterface .close (self )
@@ -165,10 +174,11 @@ def close(self):
165174 self .client .close ()
166175
167176
168- class BLEClient () :
177+ class BLEClient :
169178 """Client for managing connection to a BLE device"""
170- def __init__ (self , address = None , ** kwargs ):
171- self ._eventThread = Thread (target = self ._run_event_loop )
179+
180+ def __init__ (self , address = None , ** kwargs ):
181+ self ._eventThread = Thread (target = self ._run_event_loop )
172182 self ._eventThread_started = Event ()
173183 self ._eventThread_stopped = Event ()
174184 self ._eventThread .start ()
@@ -180,47 +190,46 @@ def __init__(self, address = None, **kwargs):
180190
181191 self .bleak_client = BleakClient (address , ** kwargs )
182192
183-
184- def discover (self , ** kwargs ): # pylint: disable=C0116
193+ def discover (self , ** kwargs ): # pylint: disable=C0116
185194 return self .async_await (BleakScanner .discover (** kwargs ))
186195
187- def pair (self , ** kwargs ): # pylint: disable=C0116
196+ def pair (self , ** kwargs ): # pylint: disable=C0116
188197 return self .async_await (self .bleak_client .pair (** kwargs ))
189198
190- def connect (self , ** kwargs ): # pylint: disable=C0116
199+ def connect (self , ** kwargs ): # pylint: disable=C0116
191200 return self .async_await (self .bleak_client .connect (** kwargs ))
192201
193- def disconnect (self , ** kwargs ): # pylint: disable=C0116
202+ def disconnect (self , ** kwargs ): # pylint: disable=C0116
194203 self .async_await (self .bleak_client .disconnect (** kwargs ))
195204
196- def read_gatt_char (self , * args , ** kwargs ): # pylint: disable=C0116
205+ def read_gatt_char (self , * args , ** kwargs ): # pylint: disable=C0116
197206 return self .async_await (self .bleak_client .read_gatt_char (* args , ** kwargs ))
198207
199- def write_gatt_char (self , * args , ** kwargs ): # pylint: disable=C0116
208+ def write_gatt_char (self , * args , ** kwargs ): # pylint: disable=C0116
200209 self .async_await (self .bleak_client .write_gatt_char (* args , ** kwargs ))
201210
202- def start_notify (self , * args , ** kwargs ): # pylint: disable=C0116
211+ def start_notify (self , * args , ** kwargs ): # pylint: disable=C0116
203212 self .async_await (self .bleak_client .start_notify (* args , ** kwargs ))
204213
205- def close (self ): # pylint: disable=C0116
214+ def close (self ): # pylint: disable=C0116
206215 self .async_run (self ._stop_event_loop ())
207216 self ._eventThread_stopped .wait (5 )
208217
209218 def __enter__ (self ):
210219 return self
211-
220+
212221 def __exit__ (self , _type , _value , _traceback ):
213222 self .close ()
214223
215- def async_await (self , coro , timeout = None ): # pylint: disable=C0116
224+ def async_await (self , coro , timeout = None ): # pylint: disable=C0116
216225 return self .async_run (coro ).result (timeout )
217226
218- def async_run (self , coro ): # pylint: disable=C0116
227+ def async_run (self , coro ): # pylint: disable=C0116
219228 return asyncio .run_coroutine_threadsafe (coro , self ._eventLoop )
220229
221230 def _run_event_loop (self ):
222231 # I don't know if the event loop can be initialized in __init__ so silencing pylint
223- self ._eventLoop = asyncio .new_event_loop () # pylint: disable=W0201
232+ self ._eventLoop = asyncio .new_event_loop () # pylint: disable=W0201
224233 self ._eventThread_started .set ()
225234 try :
226235 self ._eventLoop .run_forever ()
0 commit comments