tesla-fleet-api 1.0.9__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 +59 -6
- tesla_fleet_api/tesla/vehicle/vehicle.py +2 -0
- tesla_fleet_api/tesla/vehicle/vehicles.py +5 -1
- {tesla_fleet_api-1.0.9.dist-info → tesla_fleet_api-1.0.11.dist-info}/METADATA +1 -1
- {tesla_fleet_api-1.0.9.dist-info → tesla_fleet_api-1.0.11.dist-info}/RECORD +10 -10
- {tesla_fleet_api-1.0.9.dist-info → tesla_fleet_api-1.0.11.dist-info}/LICENSE +0 -0
- {tesla_fleet_api-1.0.9.dist-info → tesla_fleet_api-1.0.11.dist-info}/WHEEL +0 -0
- {tesla_fleet_api-1.0.9.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
|
|
@@ -65,6 +65,8 @@ SERVICE_UUID = "00000211-b2d1-43f0-9b88-960cebf8b91e"
|
|
65
65
|
WRITE_UUID = "00000212-b2d1-43f0-9b88-960cebf8b91e"
|
66
66
|
READ_UUID = "00000213-b2d1-43f0-9b88-960cebf8b91e"
|
67
67
|
VERSION_UUID = "00000214-b2d1-43f0-9b88-960cebf8b91e"
|
68
|
+
NAME_UUID = "00002a00-0000-1000-8000-00805f9b34fb"
|
69
|
+
APPEARANCE_UUID = "00002a01-0000-1000-8000-00805f9b34fb"
|
68
70
|
|
69
71
|
if TYPE_CHECKING:
|
70
72
|
from tesla_fleet_api.tesla.tesla import Tesla
|
@@ -77,8 +79,8 @@ class VehicleBluetooth(Commands):
|
|
77
79
|
"""Class describing the Tesla Fleet API vehicle endpoints and commands for a specific vehicle with command signing."""
|
78
80
|
|
79
81
|
ble_name: str
|
80
|
-
device: BLEDevice
|
81
|
-
client: BleakClient
|
82
|
+
device: BLEDevice | None = None
|
83
|
+
client: BleakClient | None = None
|
82
84
|
_queues: dict[Domain, asyncio.Queue]
|
83
85
|
_ekey: ec.EllipticCurvePublicKey
|
84
86
|
_recv: bytearray = bytearray()
|
@@ -94,8 +96,8 @@ class VehicleBluetooth(Commands):
|
|
94
96
|
Domain.DOMAIN_VEHICLE_SECURITY: asyncio.Queue(),
|
95
97
|
Domain.DOMAIN_INFOTAINMENT: asyncio.Queue(),
|
96
98
|
}
|
97
|
-
|
98
|
-
|
99
|
+
self.device = device
|
100
|
+
self._connect_lock = asyncio.Lock()
|
99
101
|
|
100
102
|
async def find_vehicle(self, name: str | None = None, address: str | None = None, scanner: BleakScanner | None = None) -> BLEDevice:
|
101
103
|
"""Find the Tesla BLE device."""
|
@@ -117,12 +119,12 @@ class VehicleBluetooth(Commands):
|
|
117
119
|
def set_device(self, device: BLEDevice) -> None:
|
118
120
|
self.device = device
|
119
121
|
|
120
|
-
def get_device(self) -> BLEDevice:
|
122
|
+
def get_device(self) -> BLEDevice | None:
|
121
123
|
return self.device
|
122
124
|
|
123
125
|
async def connect(self, max_attempts: int = MAX_CONNECT_ATTEMPTS) -> None:
|
124
126
|
"""Connect to the Tesla BLE device."""
|
125
|
-
if not
|
127
|
+
if not self.device:
|
126
128
|
raise ValueError(f"BLEDevice {self.ble_name} has not been found or set")
|
127
129
|
self.client = await establish_connection(
|
128
130
|
BleakClient,
|
@@ -136,8 +138,16 @@ class VehicleBluetooth(Commands):
|
|
136
138
|
|
137
139
|
async def disconnect(self) -> bool:
|
138
140
|
"""Disconnect from the Tesla BLE device."""
|
141
|
+
if not self.client:
|
142
|
+
return False
|
139
143
|
return await self.client.disconnect()
|
140
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
|
+
|
141
151
|
async def __aenter__(self) -> VehicleBluetooth:
|
142
152
|
"""Enter the async context."""
|
143
153
|
await self.connect()
|
@@ -194,6 +204,7 @@ class VehicleBluetooth(Commands):
|
|
194
204
|
|
195
205
|
async def _send(self, msg: RoutableMessage, requires: str, timeout: int = 2) -> RoutableMessage:
|
196
206
|
"""Serialize a message and send to the vehicle and wait for a response."""
|
207
|
+
|
197
208
|
domain = msg.to_destination.domain
|
198
209
|
async with self._sessions[domain].lock:
|
199
210
|
LOGGER.debug(f"Sending message {msg}")
|
@@ -203,6 +214,9 @@ class VehicleBluetooth(Commands):
|
|
203
214
|
# Empty the queue before sending the message
|
204
215
|
while not self._queues[domain].empty():
|
205
216
|
await self._queues[domain].get()
|
217
|
+
|
218
|
+
await self.connect_if_needed()
|
219
|
+
assert self.client is not None
|
206
220
|
await self.client.write_gatt_char(WRITE_UUID, payload, True)
|
207
221
|
|
208
222
|
# Process the response
|
@@ -216,6 +230,45 @@ class VehicleBluetooth(Commands):
|
|
216
230
|
if resp.HasField(requires):
|
217
231
|
return resp
|
218
232
|
|
233
|
+
async def query_display_name(self, max_attempts=5) -> str | None:
|
234
|
+
"""Read the device name via GATT characteristic if available"""
|
235
|
+
for i in range(max_attempts):
|
236
|
+
try:
|
237
|
+
# Standard GATT Device Name characteristic (0x2A00)
|
238
|
+
await self.connect_if_needed()
|
239
|
+
assert self.client
|
240
|
+
device_name = (await self.client.read_gatt_char(NAME_UUID)).decode('utf-8')
|
241
|
+
if device_name.startswith("🔑 "):
|
242
|
+
return device_name.replace("🔑 ","")
|
243
|
+
await asyncio.sleep(1)
|
244
|
+
LOGGER.debug(f"Attempt {i+1} to query display name failed, {device_name}")
|
245
|
+
except Exception as e:
|
246
|
+
LOGGER.error(f"Failed to read device name: {e}")
|
247
|
+
|
248
|
+
async def query_appearance(self) -> bytearray | None:
|
249
|
+
"""Read the device appearance via GATT characteristic if available"""
|
250
|
+
try:
|
251
|
+
# Standard GATT Appearance characteristic (0x2A01)
|
252
|
+
await self.connect_if_needed()
|
253
|
+
assert self.client
|
254
|
+
return await self.client.read_gatt_char(APPEARANCE_UUID)
|
255
|
+
except Exception as e:
|
256
|
+
LOGGER.error(f"Failed to read device appearance: {e}")
|
257
|
+
return None
|
258
|
+
|
259
|
+
async def query_version(self) -> int | None:
|
260
|
+
"""Read the device version via GATT characteristic if available"""
|
261
|
+
try:
|
262
|
+
# Custom GATT Version characteristic (0x2A02)
|
263
|
+
await self.connect_if_needed()
|
264
|
+
assert self.client
|
265
|
+
device_version = await self.client.read_gatt_char(VERSION_UUID)
|
266
|
+
# Convert the bytes to an integer
|
267
|
+
if device_version and len(device_version) > 0:
|
268
|
+
return int.from_bytes(device_version, byteorder='big')
|
269
|
+
except Exception as e:
|
270
|
+
LOGGER.error(f"Failed to read device version: {e}")
|
271
|
+
return None
|
219
272
|
|
220
273
|
async def pair(self, role: Role = Role.ROLE_OWNER, form: KeyFormFactor = KeyFormFactor.KEY_FORM_FACTOR_CLOUD_KEY, timeout: int = 60):
|
221
274
|
"""Pair the key."""
|
@@ -1,12 +1,16 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
+
import asyncio
|
2
3
|
from typing import TYPE_CHECKING
|
4
|
+
from bleak import BleakClient
|
3
5
|
from bleak.backends.device import BLEDevice
|
6
|
+
from bleak_retry_connector import establish_connection
|
4
7
|
from cryptography.hazmat.primitives.asymmetric import ec
|
5
8
|
|
6
9
|
from tesla_fleet_api.tesla.vehicle.signed import VehicleSigned
|
7
|
-
from tesla_fleet_api.tesla.vehicle.bluetooth import VehicleBluetooth
|
10
|
+
from tesla_fleet_api.tesla.vehicle.bluetooth import NAME_UUID, VehicleBluetooth
|
8
11
|
from tesla_fleet_api.tesla.vehicle.fleet import VehicleFleet
|
9
12
|
from tesla_fleet_api.tesla.vehicle.vehicle import Vehicle
|
13
|
+
from tesla_fleet_api.const import LOGGER
|
10
14
|
|
11
15
|
if TYPE_CHECKING:
|
12
16
|
from tesla_fleet_api.tesla.fleet import TeslaFleetApi
|
@@ -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
|
-
tesla_fleet_api/tesla/vehicle/vehicle.py,sha256=
|
20
|
-
tesla_fleet_api/tesla/vehicle/vehicles.py,sha256=
|
19
|
+
tesla_fleet_api/tesla/vehicle/vehicle.py,sha256=IBvRO6qGkUf4bi-pFongA9fIe9iScvz_wGXtzEJXilY,870
|
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
|