@@ -79,8 +79,8 @@ class VehicleBluetooth(Commands):
7979 """Class describing the Tesla Fleet API vehicle endpoints and commands for a specific vehicle with command signing."""
8080
8181 ble_name : str
82- device : BLEDevice
83- client : BleakClient
82+ device : BLEDevice | None = None
83+ client : BleakClient | None = None
8484 _queues : dict [Domain , asyncio .Queue ]
8585 _ekey : ec .EllipticCurvePublicKey
8686 _recv : bytearray = bytearray ()
@@ -96,8 +96,8 @@ def __init__(
9696 Domain .DOMAIN_VEHICLE_SECURITY : asyncio .Queue (),
9797 Domain .DOMAIN_INFOTAINMENT : asyncio .Queue (),
9898 }
99- if device is not None :
100- self .device = device
99+ self . device = device
100+ self ._connect_lock = asyncio . Lock ()
101101
102102 async def find_vehicle (self , name : str | None = None , address : str | None = None , scanner : BleakScanner | None = None ) -> BLEDevice :
103103 """Find the Tesla BLE device."""
@@ -119,12 +119,12 @@ async def find_vehicle(self, name: str | None = None, address: str | None = None
119119 def set_device (self , device : BLEDevice ) -> None :
120120 self .device = device
121121
122- def get_device (self ) -> BLEDevice :
122+ def get_device (self ) -> BLEDevice | None :
123123 return self .device
124124
125125 async def connect (self , max_attempts : int = MAX_CONNECT_ATTEMPTS ) -> None :
126126 """Connect to the Tesla BLE device."""
127- if not hasattr ( self , ' device' ) :
127+ if not self . device :
128128 raise ValueError (f"BLEDevice { self .ble_name } has not been found or set" )
129129 self .client = await establish_connection (
130130 BleakClient ,
@@ -138,8 +138,16 @@ async def connect(self, max_attempts: int = MAX_CONNECT_ATTEMPTS) -> None:
138138
139139 async def disconnect (self ) -> bool :
140140 """Disconnect from the Tesla BLE device."""
141+ if not self .client :
142+ return False
141143 return await self .client .disconnect ()
142144
145+ async def connect_if_needed (self , max_attempts : int = MAX_CONNECT_ATTEMPTS ) -> None :
146+ """Connect to the Tesla BLE device if not already connected."""
147+ async with self ._connect_lock :
148+ if not self .client or not self .client .is_connected :
149+ await self .connect (max_attempts = max_attempts )
150+
143151 async def __aenter__ (self ) -> VehicleBluetooth :
144152 """Enter the async context."""
145153 await self .connect ()
@@ -196,6 +204,7 @@ async def _on_message(self, msg: RoutableMessage) -> None:
196204
197205 async def _send (self , msg : RoutableMessage , requires : str , timeout : int = 2 ) -> RoutableMessage :
198206 """Serialize a message and send to the vehicle and wait for a response."""
207+
199208 domain = msg .to_destination .domain
200209 async with self ._sessions [domain ].lock :
201210 LOGGER .debug (f"Sending message { msg } " )
@@ -205,6 +214,9 @@ async def _send(self, msg: RoutableMessage, requires: str, timeout: int = 2) ->
205214 # Empty the queue before sending the message
206215 while not self ._queues [domain ].empty ():
207216 await self ._queues [domain ].get ()
217+
218+ await self .connect_if_needed ()
219+ assert self .client is not None
208220 await self .client .write_gatt_char (WRITE_UUID , payload , True )
209221
210222 # Process the response
@@ -223,6 +235,8 @@ async def query_display_name(self, max_attempts=5) -> str | None:
223235 for i in range (max_attempts ):
224236 try :
225237 # Standard GATT Device Name characteristic (0x2A00)
238+ await self .connect_if_needed ()
239+ assert self .client
226240 device_name = (await self .client .read_gatt_char (NAME_UUID )).decode ('utf-8' )
227241 if device_name .startswith ("🔑 " ):
228242 return device_name .replace ("🔑 " ,"" )
@@ -235,6 +249,8 @@ async def query_appearance(self) -> bytearray | None:
235249 """Read the device appearance via GATT characteristic if available"""
236250 try :
237251 # Standard GATT Appearance characteristic (0x2A01)
252+ await self .connect_if_needed ()
253+ assert self .client
238254 return await self .client .read_gatt_char (APPEARANCE_UUID )
239255 except Exception as e :
240256 LOGGER .error (f"Failed to read device appearance: { e } " )
@@ -244,6 +260,8 @@ async def query_version(self) -> int | None:
244260 """Read the device version via GATT characteristic if available"""
245261 try :
246262 # Custom GATT Version characteristic (0x2A02)
263+ await self .connect_if_needed ()
264+ assert self .client
247265 device_version = await self .client .read_gatt_char (VERSION_UUID )
248266 # Convert the bytes to an integer
249267 if device_version and len (device_version ) > 0 :
0 commit comments