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 +1 -1
- tesla_fleet_api/tesla/bluetooth.py +30 -0
- tesla_fleet_api/tesla/vehicle/bluetooth.py +24 -6
- tesla_fleet_api/tesla/vehicle/vehicles.py +0 -24
- {tesla_fleet_api-1.0.10.dist-info → tesla_fleet_api-1.0.11.dist-info}/METADATA +1 -1
- {tesla_fleet_api-1.0.10.dist-info → tesla_fleet_api-1.0.11.dist-info}/RECORD +9 -9
- {tesla_fleet_api-1.0.10.dist-info → tesla_fleet_api-1.0.11.dist-info}/LICENSE +0 -0
- {tesla_fleet_api-1.0.10.dist-info → tesla_fleet_api-1.0.11.dist-info}/WHEEL +0 -0
- {tesla_fleet_api-1.0.10.dist-info → tesla_fleet_api-1.0.11.dist-info}/top_level.txt +0 -0
tesla_fleet_api/const.py
CHANGED
@@ -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
|
-
|
100
|
-
|
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
|
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,9 +1,9 @@
|
|
1
1
|
tesla_fleet_api/__init__.py,sha256=3DZMoZ-5srW-7SooAjqcRubQDuZPY8rMKH7eqIp4qtg,392
|
2
|
-
tesla_fleet_api/const.py,sha256=
|
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=
|
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=
|
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=
|
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.
|
48
|
-
tesla_fleet_api-1.0.
|
49
|
-
tesla_fleet_api-1.0.
|
50
|
-
tesla_fleet_api-1.0.
|
51
|
-
tesla_fleet_api-1.0.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|