88from threading import Thread
99from typing import Optional
1010
11- from bleak import BleakClient , BleakScanner , BLEDevice
1211import print_color
12+ from bleak import BleakClient , BleakScanner , BLEDevice
13+ from bleak .exc import BleakDBusError , BleakError
1314
1415from meshtastic .mesh_interface import MeshInterface
1516
@@ -62,14 +63,14 @@ def __init__(
6263 if not self .noProto :
6364 self ._waitConnected (timeout = 60.0 )
6465 self .waitForConfig ()
65- logging .debug ("Mesh init finished" )
6666
6767 logging .debug ("Register FROMNUM notify callback" )
6868 self .client .start_notify (FROMNUM_UUID , self .from_num_handler )
6969
7070 # We MUST run atexit (if we can) because otherwise (at least on linux) the BLE device is not disconnected
7171 # and future connection attempts will fail. (BlueZ kinda sucks)
72- self ._exit_handler = atexit .register (self .close )
72+ # Note: the on disconnected callback will call our self.close which will make us nicely wait for threads to exit
73+ self ._exit_handler = atexit .register (self .client .disconnect )
7374
7475 def from_num_handler (self , _ , b ): # pylint: disable=C0116
7576 """Handle callbacks for fromnum notify.
@@ -143,7 +144,7 @@ def connect(self, address: Optional[str] = None):
143144
144145 # Bleak docs recommend always doing a scan before connecting (even if we know addr)
145146 device = self .find_device (address )
146- client = BLEClient (device .address )
147+ client = BLEClient (device .address , disconnected_callback = lambda _ : self . close )
147148 client .connect ()
148149 client .discover ()
149150 return client
@@ -153,11 +154,20 @@ def _receiveFromRadioImpl(self):
153154 if self .should_read :
154155 self .should_read = False
155156 retries = 0
156- while True :
157+ while self . _want_receive :
157158 try :
158159 b = bytes (self .client .read_gatt_char (FROMRADIO_UUID ))
159- except Exception as e :
160- raise BLEInterface .BLEError ("Error reading BLE" ) from e
160+ except BleakDBusError as e :
161+ # Device disconnected probably, so end our read loop immediately
162+ logging .debug (f"Device disconnected, shutting down { e } " )
163+ self ._want_receive = False
164+ except BleakError as e :
165+ # We were definitely disconnected
166+ if "Not connected" in str (e ):
167+ logging .debug (f"Device disconnected, shutting down { e } " )
168+ self ._want_receive = False
169+ else :
170+ raise BLEInterface .BLEError ("Error reading BLE" ) from e
161171 if not b :
162172 if retries < 5 :
163173 time .sleep (0.1 )
@@ -188,7 +198,10 @@ def _sendToRadioImpl(self, toRadio):
188198
189199 def close (self ):
190200 atexit .unregister (self ._exit_handler )
191- MeshInterface .close (self )
201+ try :
202+ MeshInterface .close (self )
203+ except Exception as e :
204+ logging .error (f"Error closing mesh interface: { e } " )
192205
193206 if self ._want_receive :
194207 self .want_receive = False # Tell the thread we want it to stop
0 commit comments