tesla-fleet-api 1.0.10__py3-none-any.whl → 1.0.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
tesla_fleet_api/const.py CHANGED
@@ -3,7 +3,7 @@
3
3
  from enum import Enum
4
4
  import logging
5
5
 
6
- VERSION = "1.0.10"
6
+ VERSION = "1.0.11"
7
7
  LOGGER = logging.getLogger(__package__)
8
8
  SERVERS = {
9
9
  "na": "https://fleet-api.prd.na.vn.cloud.tesla.com",
@@ -1,10 +1,16 @@
1
1
  """Bluetooth only interface."""
2
2
 
3
+ import asyncio
3
4
  import hashlib
4
5
  import re
6
+ from bleak import BleakClient
7
+ from bleak.backends.device import BLEDevice
8
+ from bleak_retry_connector import establish_connection
5
9
  from google.protobuf.json_format import MessageToJson, MessageToDict
6
10
 
11
+ from tesla_fleet_api.const import LOGGER
7
12
  from tesla_fleet_api.tesla.tesla import Tesla
13
+ from tesla_fleet_api.tesla.vehicle.bluetooth import NAME_UUID
8
14
  from tesla_fleet_api.tesla.vehicle.vehicles import VehiclesBluetooth
9
15
 
10
16
  class TeslaBluetooth(Tesla):
@@ -27,6 +33,30 @@ class TeslaBluetooth(Tesla):
27
33
  """Get the name of a vehicle."""
28
34
  return "S" + hashlib.sha1(vin.encode('utf-8')).hexdigest()[:16] + "C"
29
35
 
36
+ async def query_display_name(self, device: BLEDevice, max_attempts=5) -> str | None:
37
+ """Queries the name of a bluetooth vehicle."""
38
+ client = await establish_connection(
39
+ BleakClient,
40
+ device,
41
+ device.name or "Unknown",
42
+ max_attempts=max_attempts
43
+ )
44
+ name: str | None = None
45
+ for i in range(max_attempts):
46
+ try:
47
+ # Standard GATT Device Name characteristic (0x2A00)
48
+ device_name = (await client.read_gatt_char(NAME_UUID)).decode('utf-8')
49
+ if device_name.startswith("🔑 "):
50
+ name = device_name.replace("🔑 ","")
51
+ break
52
+ await asyncio.sleep(1)
53
+ LOGGER.debug(f"Attempt {i+1} to query display name failed, {device_name}")
54
+ except Exception as e:
55
+ LOGGER.error(f"Failed to read device name: {e}")
56
+
57
+ await client.disconnect()
58
+ return name
59
+
30
60
 
31
61
  # Helpers
32
62
 
@@ -79,8 +79,8 @@ class VehicleBluetooth(Commands):
79
79
  """Class describing the Tesla Fleet API vehicle endpoints and commands for a specific vehicle with command signing."""
80
80
 
81
81
  ble_name: str
82
- device: BLEDevice
83
- client: BleakClient
82
+ device: BLEDevice | None = None
83
+ client: BleakClient | None = None
84
84
  _queues: dict[Domain, asyncio.Queue]
85
85
  _ekey: ec.EllipticCurvePublicKey
86
86
  _recv: bytearray = bytearray()
@@ -96,8 +96,8 @@ class VehicleBluetooth(Commands):
96
96
  Domain.DOMAIN_VEHICLE_SECURITY: asyncio.Queue(),
97
97
  Domain.DOMAIN_INFOTAINMENT: asyncio.Queue(),
98
98
  }
99
- if device is not None:
100
- self.device = device
99
+ self.device = device
100
+ self._connect_lock = asyncio.Lock()
101
101
 
102
102
  async def find_vehicle(self, name: str | None = None, address: str | None = None, scanner: BleakScanner | None = None) -> BLEDevice:
103
103
  """Find the Tesla BLE device."""
@@ -119,12 +119,12 @@ class VehicleBluetooth(Commands):
119
119
  def set_device(self, device: BLEDevice) -> None:
120
120
  self.device = device
121
121
 
122
- def get_device(self) -> BLEDevice:
122
+ def get_device(self) -> BLEDevice | None:
123
123
  return self.device
124
124
 
125
125
  async def connect(self, max_attempts: int = MAX_CONNECT_ATTEMPTS) -> None:
126
126
  """Connect to the Tesla BLE device."""
127
- if not hasattr(self, 'device'):
127
+ if not self.device:
128
128
  raise ValueError(f"BLEDevice {self.ble_name} has not been found or set")
129
129
  self.client = await establish_connection(
130
130
  BleakClient,
@@ -138,8 +138,16 @@ class VehicleBluetooth(Commands):
138
138
 
139
139
  async def disconnect(self) -> bool:
140
140
  """Disconnect from the Tesla BLE device."""
141
+ if not self.client:
142
+ return False
141
143
  return await self.client.disconnect()
142
144
 
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
+
143
151
  async def __aenter__(self) -> VehicleBluetooth:
144
152
  """Enter the async context."""
145
153
  await self.connect()
@@ -196,6 +204,7 @@ class VehicleBluetooth(Commands):
196
204
 
197
205
  async def _send(self, msg: RoutableMessage, requires: str, timeout: int = 2) -> RoutableMessage:
198
206
  """Serialize a message and send to the vehicle and wait for a response."""
207
+
199
208
  domain = msg.to_destination.domain
200
209
  async with self._sessions[domain].lock:
201
210
  LOGGER.debug(f"Sending message {msg}")
@@ -205,6 +214,9 @@ class VehicleBluetooth(Commands):
205
214
  # Empty the queue before sending the message
206
215
  while not self._queues[domain].empty():
207
216
  await self._queues[domain].get()
217
+
218
+ await self.connect_if_needed()
219
+ assert self.client is not None
208
220
  await self.client.write_gatt_char(WRITE_UUID, payload, True)
209
221
 
210
222
  # Process the response
@@ -223,6 +235,8 @@ class VehicleBluetooth(Commands):
223
235
  for i in range(max_attempts):
224
236
  try:
225
237
  # Standard GATT Device Name characteristic (0x2A00)
238
+ await self.connect_if_needed()
239
+ assert self.client
226
240
  device_name = (await self.client.read_gatt_char(NAME_UUID)).decode('utf-8')
227
241
  if device_name.startswith("🔑 "):
228
242
  return device_name.replace("🔑 ","")
@@ -235,6 +249,8 @@ class VehicleBluetooth(Commands):
235
249
  """Read the device appearance via GATT characteristic if available"""
236
250
  try:
237
251
  # Standard GATT Appearance characteristic (0x2A01)
252
+ await self.connect_if_needed()
253
+ assert self.client
238
254
  return await self.client.read_gatt_char(APPEARANCE_UUID)
239
255
  except Exception as e:
240
256
  LOGGER.error(f"Failed to read device appearance: {e}")
@@ -244,6 +260,8 @@ class VehicleBluetooth(Commands):
244
260
  """Read the device version via GATT characteristic if available"""
245
261
  try:
246
262
  # Custom GATT Version characteristic (0x2A02)
263
+ await self.connect_if_needed()
264
+ assert self.client
247
265
  device_version = await self.client.read_gatt_char(VERSION_UUID)
248
266
  # Convert the bytes to an integer
249
267
  if device_version and len(device_version) > 0:
@@ -73,27 +73,3 @@ class VehiclesBluetooth(dict[str, Vehicle]):
73
73
  vehicle = self.Bluetooth(self._parent, vin, key, device)
74
74
  self[vin] = vehicle
75
75
  return vehicle
76
-
77
- async def query_display_name(self, device: BLEDevice, max_attempts=5) -> str | None:
78
- """Queries the name of a bluetooth vehicle."""
79
- client = await establish_connection(
80
- BleakClient,
81
- device,
82
- device.name or "Unknown",
83
- max_attempts=max_attempts
84
- )
85
- name: str | None = None
86
- for i in range(max_attempts):
87
- try:
88
- # Standard GATT Device Name characteristic (0x2A00)
89
- device_name = (await client.read_gatt_char(NAME_UUID)).decode('utf-8')
90
- if device_name.startswith("🔑 "):
91
- name = device_name.replace("🔑 ","")
92
- break
93
- await asyncio.sleep(1)
94
- LOGGER.debug(f"Attempt {i+1} to query display name failed, {device_name}")
95
- except Exception as e:
96
- LOGGER.error(f"Failed to read device name: {e}")
97
-
98
- await client.disconnect()
99
- return name
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tesla_fleet_api
3
- Version: 1.0.10
3
+ Version: 1.0.11
4
4
  Summary: Tesla Fleet API library for Python
5
5
  Home-page: https://github.com/Teslemetry/python-tesla-fleet-api
6
6
  Author: Brett Adams
@@ -1,9 +1,9 @@
1
1
  tesla_fleet_api/__init__.py,sha256=3DZMoZ-5srW-7SooAjqcRubQDuZPY8rMKH7eqIp4qtg,392
2
- tesla_fleet_api/const.py,sha256=iX1zonX_WX3NYpR_I9RAoVx3eM2iLHQjSD8C3QcBdTk,3749
2
+ tesla_fleet_api/const.py,sha256=TSs1sofg_Dv2Q-NEcVOB10GNbF0cU25NcO7WooMLoOk,3749
3
3
  tesla_fleet_api/exceptions.py,sha256=GvjJBR77xGt2g3vhiAxcNtysj-e1Me7F-9PwO9dyzOo,35430
4
4
  tesla_fleet_api/ratecalculator.py,sha256=4lz8yruUeouHXh_3ezsXX-CTpIegp1T1J4VuRV_qdHA,1791
5
5
  tesla_fleet_api/tesla/__init__.py,sha256=Cvpqu8OaOFmbuwu9KjgYrje8eVluDp2IU_zwdtXbmO0,282
6
- tesla_fleet_api/tesla/bluetooth.py,sha256=vxDPTM-Uq9IAVJjK54-p_llj26hssEEXAQI5-lVGx8Q,1137
6
+ tesla_fleet_api/tesla/bluetooth.py,sha256=lyPRVf1YdcElrYBsKOMCaLwMPE9rO7Iw1a6nE7VUZ94,2369
7
7
  tesla_fleet_api/tesla/charging.py,sha256=D7I7cAf-3-95sIjyP6wpVqCq9Cppj6U-VPFQGpQQ8bs,1704
8
8
  tesla_fleet_api/tesla/energysite.py,sha256=vStffklBQfQNAO_1wrHLFu7BlBCTVVbLh7_IrAUL3wg,6131
9
9
  tesla_fleet_api/tesla/fleet.py,sha256=zfmagXF4TbbVOPWcngCSKebaGB1daXbw8mTJ9o8einY,5432
@@ -12,12 +12,12 @@ tesla_fleet_api/tesla/partner.py,sha256=e-l6sEP6-IupjFEQieSUjhhvRXF3aL4ebPNahcGF
12
12
  tesla_fleet_api/tesla/tesla.py,sha256=Gs8-L3OsEMhs1N_vdDx-E46bOHKGowXTBxmRiP8NKh4,2391
13
13
  tesla_fleet_api/tesla/user.py,sha256=w8rwiAOIFjuDus8M0RpZ0wucJtw8kYFKtJfYVk7Ekr0,1194
14
14
  tesla_fleet_api/tesla/vehicle/__init__.py,sha256=3A5_wTQHofRShof4pUNOtF78-7lUh62uz2jq2ecnmRY,381
15
- tesla_fleet_api/tesla/vehicle/bluetooth.py,sha256=CJgyE99Q4ezWjDjEaRD5hA6_-bHPpztXXdQcKQh9GZ0,18334
15
+ tesla_fleet_api/tesla/vehicle/bluetooth.py,sha256=8SB3fDdJ1E8jDsaL6cix0Ks0lwlkvOLQ0_3lNZwWs1M,19064
16
16
  tesla_fleet_api/tesla/vehicle/commands.py,sha256=_LXAtuAIkeojJuw8k1QjEabAQQH2MLD5YZyUH58QoSg,51288
17
17
  tesla_fleet_api/tesla/vehicle/fleet.py,sha256=K9BVZj6CChJSDSMFroa7Cz0KrsYWj32ILtQumarkLaU,32080
18
18
  tesla_fleet_api/tesla/vehicle/signed.py,sha256=RUzVnZIfykz3YZW2gaxd1iaN1i8LkLaEoiXrbqZn9kg,1339
19
19
  tesla_fleet_api/tesla/vehicle/vehicle.py,sha256=IBvRO6qGkUf4bi-pFongA9fIe9iScvz_wGXtzEJXilY,870
20
- tesla_fleet_api/tesla/vehicle/vehicles.py,sha256=jLE_T_fWEwFjuYU5_AlJ0KhEvWAPx2wybENESksJ2R8,3777
20
+ tesla_fleet_api/tesla/vehicle/vehicles.py,sha256=wU0wXa57BD7InaUC1xoRhVc7oI5CemsocSryuUItyGI,2792
21
21
  tesla_fleet_api/tesla/vehicle/proto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  tesla_fleet_api/tesla/vehicle/proto/__init__.pyi,sha256=qFXWNIgl71wB260u-XPzaAwWAHL6krw21q-aXnBtop0,252
23
23
  tesla_fleet_api/tesla/vehicle/proto/car_server_pb2.py,sha256=v_eb4NDIkx_ZYPYW29_EFRky5vQ4b2q14gwuQSbouYw,29202
@@ -44,8 +44,8 @@ tesla_fleet_api/teslemetry/vehicles.py,sha256=9nybVg7VHKLa2woMG6fzMmQP6xJIE_jdAd
44
44
  tesla_fleet_api/tessie/__init__.py,sha256=9lhQJaB6X4PObUL9QdaaZYqs2BxiTidu3zmHcBESLVw,78
45
45
  tesla_fleet_api/tessie/tessie.py,sha256=uhg0oOIxpwDvlvdBhKHeF3AGR2PzmdBgzh2-_EkmSq0,2617
46
46
  tesla_fleet_api/tessie/vehicles.py,sha256=gfEatilI_ct-R4CM5xYhrlduqCR9IHlyc56WmJf7v7k,1149
47
- tesla_fleet_api-1.0.10.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
48
- tesla_fleet_api-1.0.10.dist-info/METADATA,sha256=Dr_QRBUiyoZ4PUgBBipQkoTBbqxW7qtE-vJ9GxzY8io,4383
49
- tesla_fleet_api-1.0.10.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
50
- tesla_fleet_api-1.0.10.dist-info/top_level.txt,sha256=jeNbog_1saXBFrGpom9WyPWmilxsyP3szL_G7JLWQfM,16
51
- tesla_fleet_api-1.0.10.dist-info/RECORD,,
47
+ tesla_fleet_api-1.0.11.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
48
+ tesla_fleet_api-1.0.11.dist-info/METADATA,sha256=YYe4EpiOI6j7CcrDsOs0HsjKgYQs3YPwKL9wceXFNcU,4383
49
+ tesla_fleet_api-1.0.11.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
50
+ tesla_fleet_api-1.0.11.dist-info/top_level.txt,sha256=jeNbog_1saXBFrGpom9WyPWmilxsyP3szL_G7JLWQfM,16
51
+ tesla_fleet_api-1.0.11.dist-info/RECORD,,