tesla-fleet-api 1.0.10__py3-none-any.whl → 1.0.12__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 +114 -49
- tesla_fleet_api/tesla/vehicle/commands.py +0 -1
- tesla_fleet_api/tesla/vehicle/vehicles.py +0 -24
- {tesla_fleet_api-1.0.10.dist-info → tesla_fleet_api-1.0.12.dist-info}/METADATA +1 -1
- {tesla_fleet_api-1.0.10.dist-info → tesla_fleet_api-1.0.12.dist-info}/RECORD +10 -10
- {tesla_fleet_api-1.0.10.dist-info → tesla_fleet_api-1.0.12.dist-info}/LICENSE +0 -0
- {tesla_fleet_api-1.0.10.dist-info → tesla_fleet_api-1.0.12.dist-info}/WHEEL +0 -0
- {tesla_fleet_api-1.0.10.dist-info → tesla_fleet_api-1.0.12.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
|
|
@@ -2,7 +2,8 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import hashlib
|
4
4
|
import asyncio
|
5
|
-
|
5
|
+
import struct
|
6
|
+
from typing import TYPE_CHECKING, Callable, Any
|
6
7
|
from google.protobuf.message import DecodeError
|
7
8
|
from bleak_retry_connector import establish_connection, MAX_CONNECT_ATTEMPTS
|
8
9
|
from bleak import BleakClient, BleakScanner
|
@@ -75,16 +76,79 @@ def prependLength(message: bytes) -> bytearray:
|
|
75
76
|
"""Prepend a 2-byte length to the payload."""
|
76
77
|
return bytearray([len(message) >> 8, len(message) & 0xFF]) + message
|
77
78
|
|
79
|
+
class ReassemblingBuffer:
|
80
|
+
"""
|
81
|
+
Reassembles bytearray streams where the first two bytes indicate the length of the message.
|
82
|
+
Handles potential packet corruption by discarding *entire* packets and retrying.
|
83
|
+
Uses a callback to process parsed messages.
|
84
|
+
"""
|
85
|
+
|
86
|
+
def __init__(self, callback: Callable[[RoutableMessage], None]):
|
87
|
+
"""
|
88
|
+
Initializes the buffer.
|
89
|
+
|
90
|
+
Args:
|
91
|
+
message_type: The protobuf message type (e.g., RoutableMessage) to parse the assembled data.
|
92
|
+
callback: A function that will be called with each parsed message.
|
93
|
+
"""
|
94
|
+
self.buffer = bytearray()
|
95
|
+
self.expected_length = None
|
96
|
+
self.packet_starts = []
|
97
|
+
self.callback = callback
|
98
|
+
|
99
|
+
def receive_data(self, data: bytearray):
|
100
|
+
"""
|
101
|
+
Receives a chunk of bytearray data and attempts to assemble a complete message.
|
102
|
+
|
103
|
+
Args:
|
104
|
+
data: The received bytearray data.
|
105
|
+
"""
|
106
|
+
self.packet_starts.append(len(self.buffer))
|
107
|
+
self.buffer.extend(data)
|
108
|
+
|
109
|
+
while True:
|
110
|
+
if self.expected_length is None and len(self.buffer) >= 2:
|
111
|
+
self.expected_length = struct.unpack(">H", self.buffer[:2])[0] + 2
|
112
|
+
|
113
|
+
LOGGER.info(f"Buffer length: {len(self.buffer)}, Packet starts: {self.packet_starts}, Expected length: {self.expected_length}")
|
114
|
+
|
115
|
+
if self.expected_length is not None and self.expected_length > 1024:
|
116
|
+
LOGGER.warning(f"Expected length too large: {self.expected_length}")
|
117
|
+
self.discard_packet()
|
118
|
+
|
119
|
+
elif self.expected_length is not None and len(self.buffer) >= self.expected_length:
|
120
|
+
try:
|
121
|
+
message = RoutableMessage()
|
122
|
+
message.ParseFromString(bytes(self.buffer[2:self.expected_length]))
|
123
|
+
self.buffer = self.buffer[self.expected_length:]
|
124
|
+
self.packet_starts = [x - self.expected_length for x in self.packet_starts if x >= self.expected_length]
|
125
|
+
self.expected_length = None
|
126
|
+
self.callback(message) # Call the callback with the parsed message
|
127
|
+
|
128
|
+
except DecodeError:
|
129
|
+
self.discard_packet()
|
130
|
+
else:
|
131
|
+
return
|
132
|
+
|
133
|
+
def discard_packet(self):
|
134
|
+
self.packet_starts.pop(0)
|
135
|
+
if len(self.packet_starts) > 0:
|
136
|
+
self.buffer = self.buffer[self.packet_starts[0]:]
|
137
|
+
self.packet_starts = [x - self.packet_starts[0] for x in self.packet_starts]
|
138
|
+
else:
|
139
|
+
self.buffer = bytearray()
|
140
|
+
self.packet_starts = []
|
141
|
+
self.expected_length = None
|
142
|
+
|
78
143
|
class VehicleBluetooth(Commands):
|
79
144
|
"""Class describing the Tesla Fleet API vehicle endpoints and commands for a specific vehicle with command signing."""
|
80
145
|
|
81
146
|
ble_name: str
|
82
|
-
device: BLEDevice
|
83
|
-
client: BleakClient
|
147
|
+
device: BLEDevice | None = None
|
148
|
+
client: BleakClient | None = None
|
84
149
|
_queues: dict[Domain, asyncio.Queue]
|
85
150
|
_ekey: ec.EllipticCurvePublicKey
|
86
|
-
|
87
|
-
_recv_len: int = 0
|
151
|
+
_buffer: ReassemblingBuffer
|
88
152
|
_auth_method = "aes"
|
89
153
|
|
90
154
|
def __init__(
|
@@ -96,8 +160,9 @@ class VehicleBluetooth(Commands):
|
|
96
160
|
Domain.DOMAIN_VEHICLE_SECURITY: asyncio.Queue(),
|
97
161
|
Domain.DOMAIN_INFOTAINMENT: asyncio.Queue(),
|
98
162
|
}
|
99
|
-
|
100
|
-
|
163
|
+
self.device = device
|
164
|
+
self._connect_lock = asyncio.Lock()
|
165
|
+
self._buffer = ReassemblingBuffer(self._on_message)
|
101
166
|
|
102
167
|
async def find_vehicle(self, name: str | None = None, address: str | None = None, scanner: BleakScanner | None = None) -> BLEDevice:
|
103
168
|
"""Find the Tesla BLE device."""
|
@@ -119,12 +184,12 @@ class VehicleBluetooth(Commands):
|
|
119
184
|
def set_device(self, device: BLEDevice) -> None:
|
120
185
|
self.device = device
|
121
186
|
|
122
|
-
def get_device(self) -> BLEDevice:
|
187
|
+
def get_device(self) -> BLEDevice | None:
|
123
188
|
return self.device
|
124
189
|
|
125
190
|
async def connect(self, max_attempts: int = MAX_CONNECT_ATTEMPTS) -> None:
|
126
191
|
"""Connect to the Tesla BLE device."""
|
127
|
-
if not
|
192
|
+
if not self.device:
|
128
193
|
raise ValueError(f"BLEDevice {self.ble_name} has not been found or set")
|
129
194
|
self.client = await establish_connection(
|
130
195
|
BleakClient,
|
@@ -138,8 +203,17 @@ class VehicleBluetooth(Commands):
|
|
138
203
|
|
139
204
|
async def disconnect(self) -> bool:
|
140
205
|
"""Disconnect from the Tesla BLE device."""
|
206
|
+
if not self.client:
|
207
|
+
return False
|
141
208
|
return await self.client.disconnect()
|
142
209
|
|
210
|
+
async def connect_if_needed(self, max_attempts: int = MAX_CONNECT_ATTEMPTS) -> None:
|
211
|
+
"""Connect to the Tesla BLE device if not already connected."""
|
212
|
+
async with self._connect_lock:
|
213
|
+
if not self.client or not self.client.is_connected:
|
214
|
+
LOGGER.info(f"Reconnecting to {self.ble_name}")
|
215
|
+
await self.connect(max_attempts=max_attempts)
|
216
|
+
|
143
217
|
async def __aenter__(self) -> VehicleBluetooth:
|
144
218
|
"""Enter the async context."""
|
145
219
|
await self.connect()
|
@@ -149,66 +223,44 @@ class VehicleBluetooth(Commands):
|
|
149
223
|
"""Exit the async context."""
|
150
224
|
await self.disconnect()
|
151
225
|
|
152
|
-
|
226
|
+
def _on_notify(self,sender: BleakGATTCharacteristic, data: bytearray) -> None:
|
153
227
|
"""Receive data from the Tesla BLE device."""
|
154
|
-
if
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
self._recv = bytearray()
|
161
|
-
self._recv_len = 0
|
162
|
-
return
|
163
|
-
self._recv = data[2:]
|
164
|
-
#while len(self._recv) > self._recv_len:
|
165
|
-
#
|
166
|
-
# # Maybe this needs to trigger a reset
|
167
|
-
# await self._on_message(bytes(self._recv[:self._recv_len]))
|
168
|
-
# self._recv_len = int.from_bytes(self._recv[self._recv_len:self._recv_len+2], 'big')
|
169
|
-
# self._recv = self._recv[self._recv_len+2:]
|
170
|
-
# continue
|
171
|
-
if len(self._recv) >= self._recv_len:
|
172
|
-
if len(self._recv) > self._recv_len:
|
173
|
-
LOGGER.debug(f"Received more data than expected: {len(self._recv)} > {self._recv_len}")
|
174
|
-
try:
|
175
|
-
msg = RoutableMessage.FromString(bytes(self._recv[:self._recv_len]))
|
176
|
-
await self._on_message(msg)
|
177
|
-
self._recv = bytearray()
|
178
|
-
self._recv_len = 0
|
179
|
-
except DecodeError:
|
180
|
-
# Attempt parsing the whole payload
|
181
|
-
msg = RoutableMessage.FromString(bytes(self._recv))
|
182
|
-
LOGGER.warn(f"Parsed more data than length: {len(self._recv)} > {self._recv_len}")
|
183
|
-
await self._on_message(msg)
|
184
|
-
self._recv = bytearray()
|
185
|
-
self._recv_len = 0
|
186
|
-
|
187
|
-
async def _on_message(self, msg: RoutableMessage) -> None:
|
228
|
+
if sender.uuid != READ_UUID:
|
229
|
+
LOGGER.error(f"Unexpected sender: {sender}")
|
230
|
+
return
|
231
|
+
self._buffer.receive_data(data)
|
232
|
+
|
233
|
+
def _on_message(self, msg: RoutableMessage) -> None:
|
188
234
|
"""Receive messages from the Tesla BLE data."""
|
189
235
|
|
190
236
|
if(msg.to_destination.routing_address != self._from_destination):
|
191
237
|
# Ignore ephemeral key broadcasts
|
192
238
|
return
|
193
239
|
|
194
|
-
LOGGER.
|
195
|
-
|
240
|
+
LOGGER.info(f"Received response: {msg}")
|
241
|
+
self._queues[msg.from_destination.domain].put_nowait(msg)
|
196
242
|
|
197
|
-
async def _send(self, msg: RoutableMessage, requires: str, timeout: int =
|
243
|
+
async def _send(self, msg: RoutableMessage, requires: str, timeout: int = 5) -> RoutableMessage:
|
198
244
|
"""Serialize a message and send to the vehicle and wait for a response."""
|
245
|
+
|
199
246
|
domain = msg.to_destination.domain
|
200
247
|
async with self._sessions[domain].lock:
|
201
|
-
LOGGER.
|
248
|
+
LOGGER.info(f"Sending message {msg}")
|
202
249
|
|
203
250
|
payload = prependLength(msg.SerializeToString())
|
204
251
|
|
205
252
|
# Empty the queue before sending the message
|
206
253
|
while not self._queues[domain].empty():
|
207
|
-
await self._queues[domain].get()
|
254
|
+
msg = await self._queues[domain].get()
|
255
|
+
LOGGER.warning(f"Discarded message {msg}")
|
256
|
+
|
257
|
+
await self.connect_if_needed()
|
258
|
+
assert self.client is not None
|
208
259
|
await self.client.write_gatt_char(WRITE_UUID, payload, True)
|
209
260
|
|
210
261
|
# Process the response
|
211
262
|
async with asyncio.timeout(timeout):
|
263
|
+
LOGGER.info(f"Waiting for response with {requires}")
|
212
264
|
while True:
|
213
265
|
resp = await self._queues[domain].get()
|
214
266
|
LOGGER.debug(f"Received message {resp}")
|
@@ -217,12 +269,16 @@ class VehicleBluetooth(Commands):
|
|
217
269
|
|
218
270
|
if resp.HasField(requires):
|
219
271
|
return resp
|
272
|
+
else:
|
273
|
+
LOGGER.warning(f"Ignoring message since it does not contain the required field {requires}, {resp.HasField(requires)}")
|
220
274
|
|
221
275
|
async def query_display_name(self, max_attempts=5) -> str | None:
|
222
276
|
"""Read the device name via GATT characteristic if available"""
|
223
277
|
for i in range(max_attempts):
|
224
278
|
try:
|
225
279
|
# Standard GATT Device Name characteristic (0x2A00)
|
280
|
+
await self.connect_if_needed()
|
281
|
+
assert self.client
|
226
282
|
device_name = (await self.client.read_gatt_char(NAME_UUID)).decode('utf-8')
|
227
283
|
if device_name.startswith("🔑 "):
|
228
284
|
return device_name.replace("🔑 ","")
|
@@ -235,6 +291,8 @@ class VehicleBluetooth(Commands):
|
|
235
291
|
"""Read the device appearance via GATT characteristic if available"""
|
236
292
|
try:
|
237
293
|
# Standard GATT Appearance characteristic (0x2A01)
|
294
|
+
await self.connect_if_needed()
|
295
|
+
assert self.client
|
238
296
|
return await self.client.read_gatt_char(APPEARANCE_UUID)
|
239
297
|
except Exception as e:
|
240
298
|
LOGGER.error(f"Failed to read device appearance: {e}")
|
@@ -244,6 +302,8 @@ class VehicleBluetooth(Commands):
|
|
244
302
|
"""Read the device version via GATT characteristic if available"""
|
245
303
|
try:
|
246
304
|
# Custom GATT Version characteristic (0x2A02)
|
305
|
+
await self.connect_if_needed()
|
306
|
+
assert self.client
|
247
307
|
device_version = await self.client.read_gatt_char(VERSION_UUID)
|
248
308
|
# Convert the bytes to an integer
|
249
309
|
if device_version and len(device_version) > 0:
|
@@ -252,6 +312,11 @@ class VehicleBluetooth(Commands):
|
|
252
312
|
LOGGER.error(f"Failed to read device version: {e}")
|
253
313
|
return None
|
254
314
|
|
315
|
+
async def _command(self, domain: Domain, command: bytes, attempt: int = 0) -> dict[str, Any]:
|
316
|
+
"""Serialize a message and send to the signed command endpoint."""
|
317
|
+
await self.connect_if_needed()
|
318
|
+
return await super()._command(domain, command, attempt)
|
319
|
+
|
255
320
|
async def pair(self, role: Role = Role.ROLE_OWNER, form: KeyFormFactor = KeyFormFactor.KEY_FORM_FACTOR_CLOUD_KEY, timeout: int = 60):
|
256
321
|
"""Pair the key."""
|
257
322
|
|
@@ -269,7 +269,6 @@ class Commands(Vehicle):
|
|
269
269
|
if msg.signedMessageStatus.signed_message_fault > 0:
|
270
270
|
raise MESSAGE_FAULTS[msg.signedMessageStatus.signed_message_fault]
|
271
271
|
|
272
|
-
@abstractmethod
|
273
272
|
async def _command(self, domain: Domain, command: bytes, attempt: int = 0) -> dict[str, Any]:
|
274
273
|
"""Serialize a message and send to the signed command endpoint."""
|
275
274
|
session = self._sessions[domain]
|
@@ -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=GyNepuHCbWnq7w_1TgJT5PjBpTNQhoCZdWCwIPKz4a8,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=
|
16
|
-
tesla_fleet_api/tesla/vehicle/commands.py,sha256=
|
15
|
+
tesla_fleet_api/tesla/vehicle/bluetooth.py,sha256=rMxKOrc8T3Wz_sbuGYFot4ffCbOVP2jFSCd5tVuSWv0,21035
|
16
|
+
tesla_fleet_api/tesla/vehicle/commands.py,sha256=L3apfGwry-IBuSSLbuoPPjW_q4new2xTr6Iy_kUa2pA,51268
|
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.12.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
48
|
+
tesla_fleet_api-1.0.12.dist-info/METADATA,sha256=Z3qVhTn4BJs_P54gNAn4W02VUgvFhXkRh7yTveJvPn4,4383
|
49
|
+
tesla_fleet_api-1.0.12.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
|
50
|
+
tesla_fleet_api-1.0.12.dist-info/top_level.txt,sha256=jeNbog_1saXBFrGpom9WyPWmilxsyP3szL_G7JLWQfM,16
|
51
|
+
tesla_fleet_api-1.0.12.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|