tesla-fleet-api 1.0.2__py3-none-any.whl → 1.0.3__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 +15 -1
- tesla_fleet_api/exceptions.py +23 -3
- tesla_fleet_api/tesla/bluetooth.py +4 -2
- tesla_fleet_api/tesla/partner.py +1 -1
- tesla_fleet_api/tesla/vehicle/bluetooth.py +225 -50
- tesla_fleet_api/tesla/vehicle/commands.py +127 -47
- tesla_fleet_api/tesla/vehicle/signed.py +8 -14
- tesla_fleet_api/tesla/vehicle/vehicles.py +1 -1
- {tesla_fleet_api-1.0.2.dist-info → tesla_fleet_api-1.0.3.dist-info}/METADATA +1 -1
- {tesla_fleet_api-1.0.2.dist-info → tesla_fleet_api-1.0.3.dist-info}/RECORD +13 -13
- {tesla_fleet_api-1.0.2.dist-info → tesla_fleet_api-1.0.3.dist-info}/LICENSE +0 -0
- {tesla_fleet_api-1.0.2.dist-info → tesla_fleet_api-1.0.3.dist-info}/WHEEL +0 -0
- {tesla_fleet_api-1.0.2.dist-info → tesla_fleet_api-1.0.3.dist-info}/top_level.txt +0 -0
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.
|
6
|
+
VERSION = "1.0.3"
|
7
7
|
LOGGER = logging.getLogger(__package__)
|
8
8
|
SERVERS = {
|
9
9
|
"na": "https://fleet-api.prd.na.vn.cloud.tesla.com",
|
@@ -159,3 +159,17 @@ class TeslaEnergyPeriod(StrEnum):
|
|
159
159
|
MONTH = "month"
|
160
160
|
YEAR = "year"
|
161
161
|
LIFETIME = "lifetime"
|
162
|
+
|
163
|
+
class BluetoothVehicleData(StrEnum):
|
164
|
+
CHARGE_STATE = "GetChargeState"
|
165
|
+
CLIMATE_STATE = "GetClimateState"
|
166
|
+
DRIVE_STATE = "GetDriveState"
|
167
|
+
LOCATION_STATE = "GetLocationState"
|
168
|
+
CLOSURES_STATE = "GetClosuresState"
|
169
|
+
CHARGE_SCHEDULE_STATE = "GetChargeScheduleState"
|
170
|
+
PRECONDITIONING_SCHEDULE_STATE = "GetPreconditioningScheduleState"
|
171
|
+
TIRE_PRESSURE_STATE = "GetTirePressureState"
|
172
|
+
MEDIA_STATE = "GetMediaState"
|
173
|
+
MEDIA_DETAIL_STATE = "GetMediaDetailState"
|
174
|
+
SOFTWARE_UPDATE_STATE = "GetSoftwareUpdateState"
|
175
|
+
PARENTAL_CONTROLS_STATE = "GetParentalControlsState"
|
tesla_fleet_api/exceptions.py
CHANGED
@@ -656,6 +656,26 @@ class TeslaFleetMessageFaultResponseSizeExceedsMTU(TeslaFleetMessageFault):
|
|
656
656
|
message = "Client's request was received, but response size exceeded MTU"
|
657
657
|
code = 25
|
658
658
|
|
659
|
+
class TeslaFleetMessageFaultRepeatedCounter(TeslaFleetMessageFault):
|
660
|
+
"""The vehicle has seen this counter value before. Reset the counter and try again"""
|
661
|
+
|
662
|
+
message = "The vehicle has seen this counter value before. Reset the counter and try again"
|
663
|
+
code = 26
|
664
|
+
|
665
|
+
|
666
|
+
class TeslaFleetMessageFaultInvalidKeyHandle(TeslaFleetMessageFault):
|
667
|
+
"""The key handle is not valid. The key may have been revoked or expired"""
|
668
|
+
|
669
|
+
message = "The key handle is not valid. The key may have been revoked or expired"
|
670
|
+
code = 27
|
671
|
+
|
672
|
+
|
673
|
+
class TeslaFleetMessageFaultRequiresResponseEncryption(TeslaFleetMessageFault):
|
674
|
+
"""The response requires encryption but encryption was not requested"""
|
675
|
+
|
676
|
+
message = "The response requires encryption but encryption was not requested"
|
677
|
+
code = 28
|
678
|
+
|
659
679
|
|
660
680
|
MESSAGE_FAULTS = [
|
661
681
|
None,
|
@@ -684,9 +704,9 @@ MESSAGE_FAULTS = [
|
|
684
704
|
TeslaFleetMessageFaultCommandRequiresAccountCredentials,
|
685
705
|
TeslaFleetMessageFaultFieldExceedsMTU,
|
686
706
|
TeslaFleetMessageFaultResponseSizeExceedsMTU,
|
687
|
-
|
688
|
-
|
689
|
-
|
707
|
+
TeslaFleetMessageFaultRepeatedCounter,
|
708
|
+
TeslaFleetMessageFaultInvalidKeyHandle,
|
709
|
+
TeslaFleetMessageFaultRequiresResponseEncryption,
|
690
710
|
]
|
691
711
|
|
692
712
|
class SignedMessageInformationFault(TeslaFleetError):
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
import hashlib
|
4
4
|
import re
|
5
|
+
from bleak.backends.device import BLEDevice
|
6
|
+
from cryptography.hazmat.primitives.asymmetric import ec
|
5
7
|
|
6
8
|
from tesla_fleet_api.tesla.tesla import Tesla
|
7
9
|
from tesla_fleet_api.tesla.vehicle.bluetooth import VehicleBluetooth
|
@@ -36,8 +38,8 @@ class Vehicles(dict[str, VehicleBluetooth]):
|
|
36
38
|
"""Creates a specific vehicle."""
|
37
39
|
return self.createBluetooth(vin)
|
38
40
|
|
39
|
-
def createBluetooth(self, vin: str) -> VehicleBluetooth:
|
41
|
+
def createBluetooth(self, vin: str, key: ec.EllipticCurvePrivateKey | None = None, device: None | str | BLEDevice = None) -> VehicleBluetooth:
|
40
42
|
"""Creates a specific vehicle."""
|
41
|
-
vehicle = VehicleBluetooth(self._parent, vin)
|
43
|
+
vehicle = VehicleBluetooth(self._parent, vin, key, device)
|
42
44
|
self[vin] = vehicle
|
43
45
|
return vehicle
|
tesla_fleet_api/tesla/partner.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import hashlib
|
4
|
-
|
4
|
+
import asyncio
|
5
5
|
from typing import TYPE_CHECKING
|
6
6
|
from google.protobuf.message import DecodeError
|
7
7
|
|
@@ -16,19 +16,37 @@ from tesla_fleet_api.tesla.vehicle.commands import Commands
|
|
16
16
|
|
17
17
|
from tesla_fleet_api.const import (
|
18
18
|
LOGGER,
|
19
|
+
BluetoothVehicleData
|
19
20
|
)
|
20
21
|
from tesla_fleet_api.exceptions import (
|
21
22
|
MESSAGE_FAULTS,
|
22
23
|
WHITELIST_OPERATION_STATUS,
|
23
24
|
WhitelistOperationStatus,
|
25
|
+
NotOnWhitelistFault,
|
24
26
|
)
|
25
27
|
|
26
28
|
# Protocol
|
27
29
|
from tesla_fleet_api.tesla.vehicle.proto.car_server_pb2 import (
|
30
|
+
Action,
|
31
|
+
VehicleAction,
|
28
32
|
Response,
|
33
|
+
GetVehicleData,
|
34
|
+
GetChargeState,
|
35
|
+
GetClimateState,
|
36
|
+
GetDriveState,
|
37
|
+
GetLocationState,
|
38
|
+
GetClosuresState,
|
39
|
+
GetChargeScheduleState,
|
40
|
+
GetPreconditioningScheduleState,
|
41
|
+
GetTirePressureState,
|
42
|
+
GetMediaState,
|
43
|
+
GetMediaDetailState,
|
44
|
+
GetSoftwareUpdateState,
|
45
|
+
GetParentalControlsState,
|
29
46
|
)
|
30
47
|
from tesla_fleet_api.tesla.vehicle.proto.signatures_pb2 import (
|
31
48
|
SessionInfo,
|
49
|
+
Session_Info_Status
|
32
50
|
)
|
33
51
|
from tesla_fleet_api.tesla.vehicle.proto.universal_message_pb2 import (
|
34
52
|
Destination,
|
@@ -37,15 +55,18 @@ from tesla_fleet_api.tesla.vehicle.proto.universal_message_pb2 import (
|
|
37
55
|
)
|
38
56
|
from tesla_fleet_api.tesla.vehicle.proto.vcsec_pb2 import (
|
39
57
|
FromVCSECMessage,
|
58
|
+
InformationRequest,
|
59
|
+
InformationRequestType,
|
40
60
|
KeyFormFactor,
|
41
61
|
KeyMetadata,
|
42
62
|
PermissionChange,
|
43
63
|
PublicKey,
|
44
64
|
RKEAction_E,
|
45
65
|
UnsignedMessage,
|
66
|
+
VehicleStatus,
|
46
67
|
WhitelistOperation,
|
47
|
-
|
48
68
|
)
|
69
|
+
from tesla_fleet_api.tesla.vehicle.proto.vehicle_pb2 import ChargeScheduleState, ChargeState, ClimateState, ClosuresState, DriveState, LocationState, MediaDetailState, MediaState, ParentalControlsState, PreconditioningScheduleState, SoftwareUpdateState, TirePressureState, VehicleData
|
49
70
|
|
50
71
|
SERVICE_UUID = "00000211-b2d1-43f0-9b88-960cebf8b91e"
|
51
72
|
WRITE_UUID = "00000212-b2d1-43f0-9b88-960cebf8b91e"
|
@@ -64,19 +85,23 @@ class VehicleBluetooth(Commands):
|
|
64
85
|
|
65
86
|
ble_name: str
|
66
87
|
client: BleakClient
|
67
|
-
|
68
|
-
_futures: dict[Domain, Future]
|
88
|
+
_queues: dict[Domain, asyncio.Queue]
|
69
89
|
_ekey: ec.EllipticCurvePublicKey
|
70
90
|
_recv: bytearray = bytearray()
|
71
91
|
_recv_len: int = 0
|
72
92
|
_auth_method = "aes"
|
73
93
|
|
74
94
|
def __init__(
|
75
|
-
self, parent: Tesla, vin: str, key: ec.EllipticCurvePrivateKey | None = None
|
95
|
+
self, parent: Tesla, vin: str, key: ec.EllipticCurvePrivateKey | None = None, device: None | str | BLEDevice = None
|
76
96
|
):
|
77
97
|
super().__init__(parent, vin, key)
|
78
98
|
self.ble_name = "S" + hashlib.sha1(vin.encode('utf-8')).hexdigest()[:16] + "C"
|
79
|
-
self.
|
99
|
+
self._queues = {
|
100
|
+
Domain.DOMAIN_VEHICLE_SECURITY: asyncio.Queue(),
|
101
|
+
Domain.DOMAIN_INFOTAINMENT: asyncio.Queue(),
|
102
|
+
}
|
103
|
+
if device is not None:
|
104
|
+
self.client = BleakClient(device, services=[SERVICE_UUID])
|
80
105
|
|
81
106
|
async def find_client(self, scanner: BleakScanner = BleakScanner()) -> BleakClient:
|
82
107
|
"""Find the Tesla BLE device."""
|
@@ -84,20 +109,19 @@ class VehicleBluetooth(Commands):
|
|
84
109
|
device = await scanner.find_device_by_name(self.ble_name)
|
85
110
|
if not device:
|
86
111
|
raise ValueError(f"Device {self.ble_name} not found")
|
87
|
-
self.
|
88
|
-
|
89
|
-
LOGGER.debug(f"Discovered device {self._device.name} {self._device.address}")
|
112
|
+
self.client = BleakClient(device, services=[SERVICE_UUID])
|
113
|
+
LOGGER.debug(f"Discovered device {device.name} {device.address}")
|
90
114
|
return self.client
|
91
115
|
|
92
|
-
def create_client(self,
|
93
|
-
"""Create a client using a MAC address."""
|
94
|
-
self.client = BleakClient(
|
116
|
+
def create_client(self, device: str|BLEDevice) -> BleakClient:
|
117
|
+
"""Create a client using a MAC address or Bleak Device."""
|
118
|
+
self.client = BleakClient(device, services=[SERVICE_UUID])
|
95
119
|
return self.client
|
96
120
|
|
97
|
-
async def connect(self,
|
121
|
+
async def connect(self, device: str|BLEDevice | None = None) -> None:
|
98
122
|
"""Connect to the Tesla BLE device."""
|
99
|
-
if
|
100
|
-
self.create_client(
|
123
|
+
if device is not None:
|
124
|
+
self.create_client(device)
|
101
125
|
await self.client.connect()
|
102
126
|
await self.client.start_notify(READ_UUID, self._on_notify)
|
103
127
|
|
@@ -114,7 +138,7 @@ class VehicleBluetooth(Commands):
|
|
114
138
|
"""Exit the async context."""
|
115
139
|
await self.disconnect()
|
116
140
|
|
117
|
-
def _on_notify(self,sender: BleakGATTCharacteristic,data : bytearray) -> None:
|
141
|
+
async def _on_notify(self,sender: BleakGATTCharacteristic,data : bytearray) -> None:
|
118
142
|
"""Receive data from the Tesla BLE device."""
|
119
143
|
if self._recv_len:
|
120
144
|
self._recv += data
|
@@ -124,68 +148,55 @@ class VehicleBluetooth(Commands):
|
|
124
148
|
LOGGER.debug(f"Received {len(self._recv)} of {self._recv_len} bytes")
|
125
149
|
while len(self._recv) > self._recv_len:
|
126
150
|
LOGGER.warn(f"Received more data than expected: {len(self._recv)} > {self._recv_len}")
|
127
|
-
self._on_message(bytes(self._recv[:self._recv_len]))
|
151
|
+
await self._on_message(bytes(self._recv[:self._recv_len]))
|
128
152
|
self._recv_len = int.from_bytes(self._recv[self._recv_len:self._recv_len+2], 'big')
|
129
153
|
self._recv = self._recv[self._recv_len+2:]
|
130
154
|
continue
|
131
155
|
if len(self._recv) == self._recv_len:
|
132
|
-
self._on_message(bytes(self._recv))
|
156
|
+
await self._on_message(bytes(self._recv))
|
133
157
|
self._recv = bytearray()
|
134
158
|
self._recv_len = 0
|
135
159
|
|
136
|
-
def _on_message(self, data:bytes) -> None:
|
160
|
+
async def _on_message(self, data:bytes) -> None:
|
137
161
|
"""Receive messages from the Tesla BLE data."""
|
138
162
|
try:
|
139
163
|
msg = RoutableMessage.FromString(data)
|
140
164
|
except DecodeError as e:
|
141
165
|
LOGGER.error(f"Error parsing message: {e}")
|
166
|
+
self._recv = bytearray()
|
167
|
+
self._recv_len = 0
|
142
168
|
return
|
143
169
|
|
144
|
-
# Update Session
|
145
|
-
if(msg.session_info):
|
146
|
-
info = SessionInfo.FromString(msg.session_info)
|
147
|
-
LOGGER.debug(f"Received session info: {info}")
|
148
|
-
self._sessions[msg.from_destination.domain].update(info)
|
149
|
-
|
150
170
|
if(msg.to_destination.routing_address != self._from_destination):
|
151
|
-
#
|
152
|
-
return
|
153
|
-
|
154
|
-
if(self._futures[msg.from_destination.domain]):
|
155
|
-
LOGGER.debug(f"Received response for request {msg.request_uuid}")
|
156
|
-
self._futures[msg.from_destination.domain].set_result(msg)
|
171
|
+
# Ignore ephemeral key broadcasts
|
157
172
|
return
|
158
173
|
|
159
|
-
|
160
|
-
|
161
|
-
LOGGER.warning(f"Received orphaned VCSEC response: {submsg}")
|
162
|
-
elif msg.from_destination.domain == Domain.DOMAIN_INFOTAINMENT:
|
163
|
-
submsg = Response.FromString(msg.protobuf_message_as_bytes)
|
164
|
-
LOGGER.warning(f"Received orphaned INFOTAINMENT response: {submsg}")
|
174
|
+
LOGGER.info(f"Received response: {msg}")
|
175
|
+
await self._queues[msg.from_destination.domain].put(msg)
|
165
176
|
|
166
|
-
async def
|
167
|
-
if(not self._sessions[domain].lock.locked):
|
168
|
-
raise ValueError("Session is not locked")
|
169
|
-
self._futures[domain] = get_running_loop().create_future()
|
170
|
-
return self._futures[domain]
|
171
|
-
|
172
|
-
async def _send(self, msg: RoutableMessage) -> RoutableMessage:
|
177
|
+
async def _send(self, msg: RoutableMessage, requires: str) -> RoutableMessage:
|
173
178
|
"""Serialize a message and send to the vehicle and wait for a response."""
|
174
179
|
domain = msg.to_destination.domain
|
175
180
|
async with self._sessions[domain].lock:
|
176
181
|
LOGGER.debug(f"Sending message {msg}")
|
177
|
-
|
182
|
+
|
178
183
|
payload = prependLength(msg.SerializeToString())
|
179
184
|
|
185
|
+
# Empty the queue before sending the message
|
186
|
+
while not self._queues[domain].empty():
|
187
|
+
await self._queues[domain].get()
|
180
188
|
await self.client.write_gatt_char(WRITE_UUID, payload, True)
|
181
189
|
|
182
|
-
|
183
|
-
|
190
|
+
# Process the response
|
191
|
+
async with asyncio.timeout(10):
|
192
|
+
while True:
|
193
|
+
resp = await self._queues[domain].get()
|
194
|
+
LOGGER.debug(f"Received message {resp}")
|
184
195
|
|
185
|
-
|
186
|
-
raise MESSAGE_FAULTS[resp.signedMessageStatus.signed_message_fault]
|
196
|
+
self.validate_msg(resp)
|
187
197
|
|
188
|
-
|
198
|
+
if resp.HasField(requires):
|
199
|
+
return resp
|
189
200
|
|
190
201
|
async def pair(self, role: Role = Role.ROLE_OWNER, form: KeyFormFactor = KeyFormFactor.KEY_FORM_FACTOR_CLOUD_KEY):
|
191
202
|
"""Pair the key."""
|
@@ -224,3 +235,167 @@ class VehicleBluetooth(Commands):
|
|
224
235
|
return await self._sendVehicleSecurity(
|
225
236
|
UnsignedMessage(RKEAction=RKEAction_E.RKE_ACTION_WAKE_VEHICLE)
|
226
237
|
)
|
238
|
+
|
239
|
+
async def vehicle_data(self, endpoints: list[BluetoothVehicleData]) -> VehicleData:
|
240
|
+
"""Get vehicle data."""
|
241
|
+
return await self._getInfotainment(
|
242
|
+
Action(
|
243
|
+
vehicleAction=VehicleAction(
|
244
|
+
getVehicleData=GetVehicleData(
|
245
|
+
getChargeState = GetChargeState() if BluetoothVehicleData.CHARGE_STATE in endpoints else None,
|
246
|
+
getClimateState = GetClimateState() if BluetoothVehicleData.CLIMATE_STATE in endpoints else None,
|
247
|
+
getDriveState = GetDriveState() if BluetoothVehicleData.DRIVE_STATE in endpoints else None,
|
248
|
+
getLocationState = GetLocationState() if BluetoothVehicleData.LOCATION_STATE in endpoints else None,
|
249
|
+
getClosuresState = GetClosuresState() if BluetoothVehicleData.CLOSURES_STATE in endpoints else None,
|
250
|
+
getChargeScheduleState = GetChargeScheduleState() if BluetoothVehicleData.CHARGE_SCHEDULE_STATE in endpoints else None,
|
251
|
+
getPreconditioningScheduleState = GetPreconditioningScheduleState() if BluetoothVehicleData.PRECONDITIONING_SCHEDULE_STATE in endpoints else None,
|
252
|
+
getTirePressureState = GetTirePressureState() if BluetoothVehicleData.TIRE_PRESSURE_STATE in endpoints else None,
|
253
|
+
getMediaState = GetMediaState() if BluetoothVehicleData.MEDIA_STATE in endpoints else None,
|
254
|
+
getMediaDetailState = GetMediaDetailState() if BluetoothVehicleData.MEDIA_DETAIL_STATE in endpoints else None,
|
255
|
+
getSoftwareUpdateState = GetSoftwareUpdateState() if BluetoothVehicleData.SOFTWARE_UPDATE_STATE in endpoints else None,
|
256
|
+
getParentalControlsState = GetParentalControlsState() if BluetoothVehicleData.PARENTAL_CONTROLS_STATE in endpoints else None,
|
257
|
+
)
|
258
|
+
)
|
259
|
+
)
|
260
|
+
)
|
261
|
+
|
262
|
+
async def charge_state(self) -> ChargeState:
|
263
|
+
return (await self._getInfotainment(
|
264
|
+
Action(
|
265
|
+
vehicleAction=VehicleAction(
|
266
|
+
getVehicleData=GetVehicleData(
|
267
|
+
getChargeState=GetChargeState()
|
268
|
+
)
|
269
|
+
)
|
270
|
+
)
|
271
|
+
)).charge_state
|
272
|
+
|
273
|
+
async def climate_state(self) -> ClimateState:
|
274
|
+
return (await self._getInfotainment(
|
275
|
+
Action(
|
276
|
+
vehicleAction=VehicleAction(
|
277
|
+
getVehicleData=GetVehicleData(
|
278
|
+
getClimateState=GetClimateState()
|
279
|
+
)
|
280
|
+
)
|
281
|
+
)
|
282
|
+
)).climate_state
|
283
|
+
|
284
|
+
async def drive_state(self) -> DriveState:
|
285
|
+
return (await self._getInfotainment(
|
286
|
+
Action(
|
287
|
+
vehicleAction=VehicleAction(
|
288
|
+
getVehicleData=GetVehicleData(
|
289
|
+
getDriveState=GetDriveState()
|
290
|
+
)
|
291
|
+
)
|
292
|
+
)
|
293
|
+
)).drive_state
|
294
|
+
|
295
|
+
async def location_state(self) -> LocationState:
|
296
|
+
return (await self._getInfotainment(
|
297
|
+
Action(
|
298
|
+
vehicleAction=VehicleAction(
|
299
|
+
getVehicleData=GetVehicleData(
|
300
|
+
getLocationState=GetLocationState()
|
301
|
+
)
|
302
|
+
)
|
303
|
+
)
|
304
|
+
)).location_state
|
305
|
+
|
306
|
+
async def closures_state(self) -> ClosuresState:
|
307
|
+
return (await self._getInfotainment(
|
308
|
+
Action(
|
309
|
+
vehicleAction=VehicleAction(
|
310
|
+
getVehicleData=GetVehicleData(
|
311
|
+
getClosuresState=GetClosuresState()
|
312
|
+
)
|
313
|
+
)
|
314
|
+
)
|
315
|
+
)).closures_state
|
316
|
+
|
317
|
+
async def charge_schedule_state(self) -> ChargeScheduleState:
|
318
|
+
return (await self._getInfotainment(
|
319
|
+
Action(
|
320
|
+
vehicleAction=VehicleAction(
|
321
|
+
getVehicleData=GetVehicleData(
|
322
|
+
getChargeScheduleState=GetChargeScheduleState()
|
323
|
+
)
|
324
|
+
)
|
325
|
+
)
|
326
|
+
)).charge_schedule_state
|
327
|
+
|
328
|
+
async def preconditioning_schedule_state(self) -> PreconditioningScheduleState:
|
329
|
+
return (await self._getInfotainment(
|
330
|
+
Action(
|
331
|
+
vehicleAction=VehicleAction(
|
332
|
+
getVehicleData=GetVehicleData(
|
333
|
+
getPreconditioningScheduleState=GetPreconditioningScheduleState()
|
334
|
+
)
|
335
|
+
)
|
336
|
+
)
|
337
|
+
)).preconditioning_schedule_state
|
338
|
+
|
339
|
+
async def tire_pressure_state(self) -> TirePressureState:
|
340
|
+
return (await self._getInfotainment(
|
341
|
+
Action(
|
342
|
+
vehicleAction=VehicleAction(
|
343
|
+
getVehicleData=GetVehicleData(
|
344
|
+
getTirePressureState=GetTirePressureState()
|
345
|
+
)
|
346
|
+
)
|
347
|
+
)
|
348
|
+
)).tire_pressure_state
|
349
|
+
|
350
|
+
async def media_state(self) -> MediaState:
|
351
|
+
return (await self._getInfotainment(
|
352
|
+
Action(
|
353
|
+
vehicleAction=VehicleAction(
|
354
|
+
getVehicleData=GetVehicleData(
|
355
|
+
getMediaState=GetMediaState()
|
356
|
+
)
|
357
|
+
)
|
358
|
+
)
|
359
|
+
)).media_state
|
360
|
+
|
361
|
+
async def media_detail_state(self) -> MediaDetailState:
|
362
|
+
return (await self._getInfotainment(
|
363
|
+
Action(
|
364
|
+
vehicleAction=VehicleAction(
|
365
|
+
getVehicleData=GetVehicleData(
|
366
|
+
getMediaDetailState=GetMediaDetailState()
|
367
|
+
)
|
368
|
+
)
|
369
|
+
)
|
370
|
+
)).media_detail_state
|
371
|
+
|
372
|
+
async def software_update_state(self) -> SoftwareUpdateState:
|
373
|
+
return (await self._getInfotainment(
|
374
|
+
Action(
|
375
|
+
vehicleAction=VehicleAction(
|
376
|
+
getVehicleData=GetVehicleData(
|
377
|
+
getSoftwareUpdateState=GetSoftwareUpdateState()
|
378
|
+
)
|
379
|
+
)
|
380
|
+
)
|
381
|
+
)).software_update_state
|
382
|
+
|
383
|
+
async def parental_controls_state(self) -> ParentalControlsState:
|
384
|
+
return (await self._getInfotainment(
|
385
|
+
Action(
|
386
|
+
vehicleAction=VehicleAction(
|
387
|
+
getVehicleData=GetVehicleData(
|
388
|
+
getParentalControlsState=GetParentalControlsState()
|
389
|
+
)
|
390
|
+
)
|
391
|
+
)
|
392
|
+
)).parental_controls_state
|
393
|
+
|
394
|
+
async def vehicle_state(self) -> VehicleStatus:
|
395
|
+
return await self._getVehicleSecurity(
|
396
|
+
UnsignedMessage(
|
397
|
+
InformationRequest=InformationRequest(
|
398
|
+
informationRequestType=InformationRequestType.INFORMATION_REQUEST_TYPE_GET_STATUS
|
399
|
+
)
|
400
|
+
)
|
401
|
+
)
|
@@ -14,7 +14,10 @@ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
14
14
|
from asyncio import Lock, sleep
|
15
15
|
|
16
16
|
from tesla_fleet_api.exceptions import (
|
17
|
+
MESSAGE_FAULTS,
|
17
18
|
SIGNED_MESSAGE_INFORMATION_FAULTS,
|
19
|
+
NotOnWhitelistFault,
|
20
|
+
#TeslaFleetMessageFaultInvalidSignature,
|
18
21
|
TeslaFleetMessageFaultIncorrectEpoch,
|
19
22
|
TeslaFleetMessageFaultInvalidTokenOrCounter,
|
20
23
|
)
|
@@ -37,33 +40,25 @@ from tesla_fleet_api.tesla.vehicle.proto.car_server_pb2 import (
|
|
37
40
|
Response,
|
38
41
|
)
|
39
42
|
from tesla_fleet_api.tesla.vehicle.proto.signatures_pb2 import (
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
TAG_DOMAIN,
|
44
|
-
TAG_END,
|
45
|
-
TAG_EPOCH,
|
46
|
-
TAG_EXPIRES_AT,
|
47
|
-
TAG_PERSONALIZATION,
|
48
|
-
TAG_SIGNATURE_TYPE,
|
43
|
+
Session_Info_Status,
|
44
|
+
SignatureType,
|
45
|
+
Tag,
|
49
46
|
AES_GCM_Personalized_Signature_Data,
|
50
47
|
KeyIdentity,
|
51
48
|
SessionInfo,
|
52
49
|
SignatureData,
|
53
50
|
)
|
54
51
|
from tesla_fleet_api.tesla.vehicle.proto.universal_message_pb2 import (
|
55
|
-
|
56
|
-
DOMAIN_VEHICLE_SECURITY,
|
57
|
-
OPERATIONSTATUS_ERROR,
|
58
|
-
OPERATIONSTATUS_WAIT,
|
52
|
+
OperationStatus_E,
|
59
53
|
Destination,
|
60
54
|
Domain,
|
61
55
|
RoutableMessage,
|
62
56
|
SessionInfoRequest,
|
57
|
+
Flags,
|
63
58
|
)
|
64
59
|
from tesla_fleet_api.tesla.vehicle.proto.vcsec_pb2 import (
|
65
|
-
OPERATIONSTATUS_OK,
|
66
60
|
FromVCSECMessage,
|
61
|
+
VehicleStatus,
|
67
62
|
)
|
68
63
|
from tesla_fleet_api.tesla.vehicle.proto.car_server_pb2 import (
|
69
64
|
Action,
|
@@ -110,7 +105,7 @@ from tesla_fleet_api.tesla.vehicle.proto.car_server_pb2 import (
|
|
110
105
|
MediaPreviousTrack,
|
111
106
|
MediaPreviousFavorite,
|
112
107
|
)
|
113
|
-
from tesla_fleet_api.tesla.vehicle.proto.vehicle_pb2 import VehicleState, ClimateState
|
108
|
+
from tesla_fleet_api.tesla.vehicle.proto.vehicle_pb2 import VehicleData, VehicleState, ClimateState
|
114
109
|
from tesla_fleet_api.tesla.vehicle.proto.vcsec_pb2 import (
|
115
110
|
UnsignedMessage,
|
116
111
|
RKEAction_E,
|
@@ -173,6 +168,7 @@ class Session:
|
|
173
168
|
hmac: bytes
|
174
169
|
publicKey: bytes
|
175
170
|
lock: Lock
|
171
|
+
ready: bool
|
176
172
|
|
177
173
|
def __init__(self, parent: Commands, domain: Domain):
|
178
174
|
self.parent = parent
|
@@ -258,10 +254,21 @@ class Commands(Vehicle):
|
|
258
254
|
|
259
255
|
|
260
256
|
@abstractmethod
|
261
|
-
async def _send(self, msg: RoutableMessage) -> RoutableMessage:
|
257
|
+
async def _send(self, msg: RoutableMessage, requires: str) -> RoutableMessage:
|
262
258
|
"""Transmit the message to the vehicle."""
|
263
259
|
raise NotImplementedError
|
264
260
|
|
261
|
+
def validate_msg(self, msg: RoutableMessage) -> None:
|
262
|
+
"""Validate the message."""
|
263
|
+
if(msg.session_info):
|
264
|
+
info = SessionInfo.FromString(msg.session_info)
|
265
|
+
if(info.status == Session_Info_Status.SESSION_INFO_STATUS_KEY_NOT_ON_WHITELIST):
|
266
|
+
raise NotOnWhitelistFault
|
267
|
+
self._sessions[msg.from_destination.domain].update(info)
|
268
|
+
|
269
|
+
if msg.signedMessageStatus.signed_message_fault > 0:
|
270
|
+
raise MESSAGE_FAULTS[msg.signedMessageStatus.signed_message_fault]
|
271
|
+
|
265
272
|
@abstractmethod
|
266
273
|
async def _command(self, domain: Domain, command: bytes, attempt: int = 0) -> dict[str, Any]:
|
267
274
|
"""Serialize a message and send to the signed command endpoint."""
|
@@ -277,8 +284,9 @@ class Commands(Vehicle):
|
|
277
284
|
raise ValueError(f"Unknown auth method: {self._auth_method}")
|
278
285
|
|
279
286
|
try:
|
280
|
-
resp = await self._send(msg)
|
287
|
+
resp = await self._send(msg, "protobuf_message_as_bytes")
|
281
288
|
except (
|
289
|
+
#TeslaFleetMessageFaultInvalidSignature,
|
282
290
|
TeslaFleetMessageFaultIncorrectEpoch,
|
283
291
|
TeslaFleetMessageFaultInvalidTokenOrCounter,
|
284
292
|
) as e:
|
@@ -288,7 +296,7 @@ class Commands(Vehicle):
|
|
288
296
|
raise e
|
289
297
|
return await self._command(domain, command, attempt)
|
290
298
|
|
291
|
-
if resp.signedMessageStatus.operation_status == OPERATIONSTATUS_WAIT:
|
299
|
+
if resp.signedMessageStatus.operation_status == OperationStatus_E.OPERATIONSTATUS_WAIT:
|
292
300
|
attempt += 1
|
293
301
|
if attempt > 3:
|
294
302
|
# We tried 3 times, give up, raise the error
|
@@ -298,8 +306,53 @@ class Commands(Vehicle):
|
|
298
306
|
return await self._command(domain, command, attempt)
|
299
307
|
|
300
308
|
if resp.HasField("protobuf_message_as_bytes"):
|
301
|
-
|
302
|
-
|
309
|
+
#decrypt
|
310
|
+
if(resp.signature_data.HasField("AES_GCM_Response_data")):
|
311
|
+
if(msg.signature_data.HasField("AES_GCM_Personalized_data")):
|
312
|
+
request_hash = bytes([SignatureType.SIGNATURE_TYPE_AES_GCM_PERSONALIZED]) + msg.signature_data.AES_GCM_Personalized_data.tag
|
313
|
+
elif(msg.signature_data.HasField("HMAC_Personalized_data")):
|
314
|
+
request_hash = bytes([SignatureType.SIGNATURE_TYPE_HMAC_PERSONALIZED]) + msg.signature_data.HMAC_Personalized_data.tag
|
315
|
+
if(session.domain == Domain.DOMAIN_VEHICLE_SECURITY):
|
316
|
+
request_hash = request_hash[:17]
|
317
|
+
else:
|
318
|
+
raise ValueError("Invalid request signature data")
|
319
|
+
|
320
|
+
metadata = bytes([
|
321
|
+
Tag.TAG_SIGNATURE_TYPE,
|
322
|
+
1,
|
323
|
+
SignatureType.SIGNATURE_TYPE_AES_GCM_RESPONSE,
|
324
|
+
Tag.TAG_DOMAIN,
|
325
|
+
1,
|
326
|
+
resp.from_destination.domain,
|
327
|
+
Tag.TAG_PERSONALIZATION,
|
328
|
+
17,
|
329
|
+
*self.vin.encode(),
|
330
|
+
Tag.TAG_COUNTER,
|
331
|
+
4,
|
332
|
+
*struct.pack(">I", resp.signature_data.AES_GCM_Response_data.counter),
|
333
|
+
Tag.TAG_FLAGS,
|
334
|
+
4,
|
335
|
+
*struct.pack(">I", resp.flags),
|
336
|
+
Tag.TAG_REQUEST_HASH,
|
337
|
+
17,
|
338
|
+
*request_hash,
|
339
|
+
Tag.TAG_FAULT,
|
340
|
+
4,
|
341
|
+
*struct.pack(">I", resp.signedMessageStatus.signed_message_fault),
|
342
|
+
Tag.TAG_END,
|
343
|
+
])
|
344
|
+
|
345
|
+
aad = Hash(SHA256())
|
346
|
+
aad.update(metadata)
|
347
|
+
aesgcm = AESGCM(session.sharedKey)
|
348
|
+
resp.protobuf_message_as_bytes = aesgcm.decrypt(resp.signature_data.AES_GCM_Response_data.nonce, resp.protobuf_message_as_bytes + resp.signature_data.AES_GCM_Response_data.tag, aad.finalize())
|
349
|
+
|
350
|
+
if(resp.from_destination.domain == Domain.DOMAIN_VEHICLE_SECURITY):
|
351
|
+
try:
|
352
|
+
vcsec = FromVCSECMessage.FromString(resp.protobuf_message_as_bytes)
|
353
|
+
except Exception as e:
|
354
|
+
LOGGER.error("Failed to parse VCSEC message: %s %s", e, resp)
|
355
|
+
raise e
|
303
356
|
LOGGER.debug("VCSEC Response: %s", vcsec)
|
304
357
|
if vcsec.HasField("nominalError"):
|
305
358
|
LOGGER.error("Command failed with reason: %s", vcsec.nominalError.genericError)
|
@@ -309,9 +362,13 @@ class Commands(Vehicle):
|
|
309
362
|
"reason": GenericError_E.Name(vcsec.nominalError.genericError)
|
310
363
|
}
|
311
364
|
}
|
312
|
-
elif vcsec.
|
365
|
+
elif vcsec.HasField("vehicleStatus"):
|
366
|
+
return {
|
367
|
+
"response": vcsec.vehicleStatus
|
368
|
+
}
|
369
|
+
elif vcsec.commandStatus.operationStatus == OperationStatus_E.OPERATIONSTATUS_OK:
|
313
370
|
return {"response": {"result": True, "reason": ""}}
|
314
|
-
elif vcsec.commandStatus.operationStatus == OPERATIONSTATUS_WAIT:
|
371
|
+
elif vcsec.commandStatus.operationStatus == OperationStatus_E.OPERATIONSTATUS_WAIT:
|
315
372
|
attempt += 1
|
316
373
|
if attempt > 3:
|
317
374
|
# We tried 3 times, give up, raise the error
|
@@ -319,29 +376,37 @@ class Commands(Vehicle):
|
|
319
376
|
async with session.lock:
|
320
377
|
await sleep(2)
|
321
378
|
return await self._command(domain, command, attempt)
|
322
|
-
elif vcsec.commandStatus.operationStatus == OPERATIONSTATUS_ERROR:
|
379
|
+
elif vcsec.commandStatus.operationStatus == OperationStatus_E.OPERATIONSTATUS_ERROR:
|
323
380
|
if(resp.HasField("signedMessageStatus")):
|
324
381
|
raise SIGNED_MESSAGE_INFORMATION_FAULTS[vcsec.commandStatus.signedMessageStatus.signedMessageInformation]
|
325
382
|
|
326
|
-
elif(resp.from_destination.domain == DOMAIN_INFOTAINMENT):
|
327
|
-
|
383
|
+
elif(resp.from_destination.domain == Domain.DOMAIN_INFOTAINMENT):
|
384
|
+
try:
|
385
|
+
response = Response.FromString(resp.protobuf_message_as_bytes)
|
386
|
+
except Exception as e:
|
387
|
+
LOGGER.error("Failed to parse Infotainment Response: %s %s", e, resp)
|
388
|
+
raise e
|
328
389
|
LOGGER.debug("Infotainment Response: %s", response)
|
329
390
|
if (response.HasField("ping")):
|
330
|
-
print(response.ping)
|
331
391
|
return {
|
332
392
|
"response": {
|
333
393
|
"result": True,
|
334
394
|
"reason": response.ping.local_timestamp
|
335
395
|
}
|
336
396
|
}
|
397
|
+
if response.HasField("vehicleData"):
|
398
|
+
return {
|
399
|
+
"response": response.vehicleData
|
400
|
+
}
|
337
401
|
if response.HasField("actionStatus"):
|
338
402
|
return {
|
339
403
|
"response": {
|
340
|
-
"result": response.actionStatus.result == OPERATIONSTATUS_OK,
|
404
|
+
"result": response.actionStatus.result == OperationStatus_E.OPERATIONSTATUS_OK,
|
341
405
|
"reason": response.actionStatus.result_reason.plain_text or ""
|
342
406
|
}
|
343
407
|
}
|
344
408
|
|
409
|
+
|
345
410
|
return {"response": {"result": True, "reason": ""}}
|
346
411
|
|
347
412
|
async def _commandHmac(self, session: Session, command: bytes, attempt: int = 1) -> RoutableMessage:
|
@@ -351,25 +416,25 @@ class Commands(Vehicle):
|
|
351
416
|
hmac_personalized = session.hmac_personalized()
|
352
417
|
|
353
418
|
metadata = bytes([
|
354
|
-
TAG_SIGNATURE_TYPE,
|
419
|
+
Tag.TAG_SIGNATURE_TYPE,
|
355
420
|
1,
|
356
|
-
SIGNATURE_TYPE_HMAC_PERSONALIZED,
|
357
|
-
TAG_DOMAIN,
|
421
|
+
SignatureType.SIGNATURE_TYPE_HMAC_PERSONALIZED,
|
422
|
+
Tag.TAG_DOMAIN,
|
358
423
|
1,
|
359
424
|
session.domain,
|
360
|
-
TAG_PERSONALIZATION,
|
425
|
+
Tag.TAG_PERSONALIZATION,
|
361
426
|
17,
|
362
427
|
*self.vin.encode(),
|
363
|
-
TAG_EPOCH,
|
428
|
+
Tag.TAG_EPOCH,
|
364
429
|
len(hmac_personalized.epoch),
|
365
430
|
*hmac_personalized.epoch,
|
366
|
-
TAG_EXPIRES_AT,
|
431
|
+
Tag.TAG_EXPIRES_AT,
|
367
432
|
4,
|
368
433
|
*struct.pack(">I", hmac_personalized.expires_at),
|
369
|
-
TAG_COUNTER,
|
434
|
+
Tag.TAG_COUNTER,
|
370
435
|
4,
|
371
436
|
*struct.pack(">I", hmac_personalized.counter),
|
372
|
-
TAG_END,
|
437
|
+
Tag.TAG_END,
|
373
438
|
])
|
374
439
|
|
375
440
|
hmac_personalized.tag = hmac.new(
|
@@ -398,27 +463,31 @@ class Commands(Vehicle):
|
|
398
463
|
LOGGER.debug(f"Sending AES to domain {Domain.Name(session.domain)}")
|
399
464
|
|
400
465
|
aes_personalized = session.aes_gcm_personalized()
|
466
|
+
flags = 1 << Flags.FLAG_ENCRYPT_RESPONSE
|
401
467
|
|
402
468
|
metadata = bytes([
|
403
|
-
TAG_SIGNATURE_TYPE,
|
469
|
+
Tag.TAG_SIGNATURE_TYPE,
|
404
470
|
1,
|
405
|
-
SIGNATURE_TYPE_AES_GCM_PERSONALIZED,
|
406
|
-
TAG_DOMAIN,
|
471
|
+
SignatureType.SIGNATURE_TYPE_AES_GCM_PERSONALIZED,
|
472
|
+
Tag.TAG_DOMAIN,
|
407
473
|
1,
|
408
474
|
session.domain,
|
409
|
-
TAG_PERSONALIZATION,
|
475
|
+
Tag.TAG_PERSONALIZATION,
|
410
476
|
17,
|
411
477
|
*self.vin.encode(),
|
412
|
-
TAG_EPOCH,
|
478
|
+
Tag.TAG_EPOCH,
|
413
479
|
len(aes_personalized.epoch),
|
414
480
|
*aes_personalized.epoch,
|
415
|
-
TAG_EXPIRES_AT,
|
481
|
+
Tag.TAG_EXPIRES_AT,
|
416
482
|
4,
|
417
483
|
*struct.pack(">I", aes_personalized.expires_at),
|
418
|
-
TAG_COUNTER,
|
484
|
+
Tag.TAG_COUNTER,
|
419
485
|
4,
|
420
486
|
*struct.pack(">I", aes_personalized.counter),
|
421
|
-
|
487
|
+
Tag.TAG_FLAGS,
|
488
|
+
4,
|
489
|
+
*struct.pack(">I", flags),
|
490
|
+
Tag.TAG_END,
|
422
491
|
])
|
423
492
|
|
424
493
|
aad = Hash(SHA256())
|
@@ -429,7 +498,6 @@ class Commands(Vehicle):
|
|
429
498
|
|
430
499
|
aes_personalized.tag = ct[-16:]
|
431
500
|
|
432
|
-
# I think this whole section could be improved
|
433
501
|
return RoutableMessage(
|
434
502
|
to_destination=Destination(
|
435
503
|
domain=session.domain,
|
@@ -444,7 +512,8 @@ class Commands(Vehicle):
|
|
444
512
|
public_key=self._public_key
|
445
513
|
),
|
446
514
|
AES_GCM_Personalized_data=aes_personalized,
|
447
|
-
)
|
515
|
+
),
|
516
|
+
flags=flags,
|
448
517
|
)
|
449
518
|
|
450
519
|
|
@@ -452,10 +521,20 @@ class Commands(Vehicle):
|
|
452
521
|
"""Sign and send a message to Infotainment computer."""
|
453
522
|
return await self._command(Domain.DOMAIN_VEHICLE_SECURITY, command.SerializeToString())
|
454
523
|
|
524
|
+
async def _getVehicleSecurity(self, command: UnsignedMessage) -> VehicleStatus:
|
525
|
+
"""Sign and send a message to Infotainment computer."""
|
526
|
+
reply = await self._command(Domain.DOMAIN_VEHICLE_SECURITY, command.SerializeToString())
|
527
|
+
return reply["response"]
|
528
|
+
|
455
529
|
async def _sendInfotainment(self, command: Action) -> dict[str, Any]:
|
456
530
|
"""Sign and send a message to Infotainment computer."""
|
457
531
|
return await self._command(Domain.DOMAIN_INFOTAINMENT, command.SerializeToString())
|
458
532
|
|
533
|
+
async def _getInfotainment(self, command: Action) -> VehicleData:
|
534
|
+
"""Sign and send a message to Infotainment computer."""
|
535
|
+
reply = await self._command(Domain.DOMAIN_INFOTAINMENT, command.SerializeToString())
|
536
|
+
return reply["response"]
|
537
|
+
|
459
538
|
async def handshakeVehicleSecurity(self) -> None:
|
460
539
|
"""Perform a handshake with the vehicle security domain."""
|
461
540
|
await self._handshake(Domain.DOMAIN_VEHICLE_SECURITY)
|
@@ -464,7 +543,7 @@ class Commands(Vehicle):
|
|
464
543
|
"""Perform a handshake with the infotainment domain."""
|
465
544
|
await self._handshake(Domain.DOMAIN_INFOTAINMENT)
|
466
545
|
|
467
|
-
async def _handshake(self, domain: Domain) ->
|
546
|
+
async def _handshake(self, domain: Domain) -> bool:
|
468
547
|
"""Perform a handshake with the vehicle."""
|
469
548
|
|
470
549
|
LOGGER.debug(f"Handshake with domain {Domain.Name(domain)}")
|
@@ -481,7 +560,8 @@ class Commands(Vehicle):
|
|
481
560
|
uuid=randbytes(16)
|
482
561
|
)
|
483
562
|
|
484
|
-
await self._send(msg)
|
563
|
+
await self._send(msg, "session_info")
|
564
|
+
return self._sessions[domain].ready
|
485
565
|
|
486
566
|
async def ping(self) -> dict[str, Any]:
|
487
567
|
"""Ping the vehicle."""
|
@@ -7,8 +7,10 @@ from tesla_fleet_api.tesla.vehicle.fleet import VehicleFleet
|
|
7
7
|
from tesla_fleet_api.tesla.vehicle.commands import Commands
|
8
8
|
from tesla_fleet_api.exceptions import (
|
9
9
|
MESSAGE_FAULTS,
|
10
|
+
NotOnWhitelistFault,
|
10
11
|
)
|
11
12
|
from tesla_fleet_api.tesla.vehicle.proto.signatures_pb2 import (
|
13
|
+
Session_Info_Status,
|
12
14
|
SessionInfo,
|
13
15
|
)
|
14
16
|
from tesla_fleet_api.tesla.vehicle.proto.universal_message_pb2 import (
|
@@ -31,25 +33,17 @@ class VehicleSigned(VehicleFleet, Commands):
|
|
31
33
|
super(Commands, self).__init__(parent, vin)
|
32
34
|
|
33
35
|
|
34
|
-
async def _send(self, msg: RoutableMessage) -> RoutableMessage:
|
36
|
+
async def _send(self, msg: RoutableMessage, requires: str) -> RoutableMessage:
|
35
37
|
"""Serialize a message and send to the signed command endpoint."""
|
38
|
+
# requires isnt used because Fleet API messages are singular
|
36
39
|
|
37
40
|
async with self._sessions[msg.to_destination.domain].lock:
|
38
|
-
|
41
|
+
json = await self.signed_command(
|
39
42
|
base64.b64encode(msg.SerializeToString()).decode()
|
40
43
|
)
|
41
44
|
|
42
|
-
|
45
|
+
resp = RoutableMessage.FromString(base64.b64decode(json["response"]))
|
43
46
|
|
44
|
-
|
45
|
-
# Check RoutingAdress?
|
47
|
+
self.validate_msg(resp)
|
46
48
|
|
47
|
-
|
48
|
-
self._sessions[resp_msg.from_destination.domain].update(
|
49
|
-
SessionInfo.FromString(resp_msg.session_info), self.private_key
|
50
|
-
)
|
51
|
-
|
52
|
-
if resp_msg.signedMessageStatus.signed_message_fault:
|
53
|
-
raise MESSAGE_FAULTS[resp_msg.signedMessageStatus.signed_message_fault]
|
54
|
-
|
55
|
-
return resp_msg
|
49
|
+
return resp
|
@@ -31,7 +31,7 @@ class Vehicles(dict[str, Vehicle]):
|
|
31
31
|
self[vin] = vehicle
|
32
32
|
return vehicle
|
33
33
|
|
34
|
-
def createBluetooth(self, vin: str)
|
34
|
+
def createBluetooth(self, vin: str):
|
35
35
|
"""Creates a bluetooth vehicle that uses command protocol."""
|
36
36
|
vehicle = VehicleBluetooth(self._parent, vin)
|
37
37
|
self[vin] = vehicle
|
@@ -1,23 +1,23 @@
|
|
1
1
|
tesla_fleet_api/__init__.py,sha256=3DZMoZ-5srW-7SooAjqcRubQDuZPY8rMKH7eqIp4qtg,392
|
2
|
-
tesla_fleet_api/const.py,sha256=
|
3
|
-
tesla_fleet_api/exceptions.py,sha256=
|
2
|
+
tesla_fleet_api/const.py,sha256=RB2ZgI0_y0DkLUJtMSpklCRH9aNlrninO4zot5cA-H0,3748
|
3
|
+
tesla_fleet_api/exceptions.py,sha256=1EkMnDxQYxZBn4ApjGodIBZyNPu9oa_Pgpdl6lQQ5gk,35523
|
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=UCKXF3u_TfuGlJ-LYEIxgmbbzlElZfuJOInsWJ_ltr0,1471
|
7
7
|
tesla_fleet_api/tesla/charging.py,sha256=D7I7cAf-3-95sIjyP6wpVqCq9Cppj6U-VPFQGpQQ8bs,1704
|
8
8
|
tesla_fleet_api/tesla/energysite.py,sha256=96Q5npsJ2YIa257o_NL5_3gJNUS-ioAL7sTeQeGPgAM,6110
|
9
9
|
tesla_fleet_api/tesla/fleet.py,sha256=X74tzwGO9w65j9YUGuW04CwG7Bx6biEHwxIjWGCzB4c,5670
|
10
10
|
tesla_fleet_api/tesla/oauth.py,sha256=aWBsWmnM-QxzaU8W9TXVNxGsYn_LraXnpexwdE8wOqo,4104
|
11
|
-
tesla_fleet_api/tesla/partner.py,sha256=
|
11
|
+
tesla_fleet_api/tesla/partner.py,sha256=e-l6sEP6-IupjFEQieSUjhhvRXF3aL4ebPNahcGFRCE,1238
|
12
12
|
tesla_fleet_api/tesla/tesla.py,sha256=Jlz90-fM0nJbhnQN0k3ukNv59-9KqZZbyQ91IiLIbfo,2010
|
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=fhV24gni_tkhRJmK5I-xUX0dqxEw_lumRViEwmkXDSw,15665
|
16
|
+
tesla_fleet_api/tesla/vehicle/commands.py,sha256=zgfc2yo1UxEh8ePqSb-4h0UTK0RmpCG_9LZX44CES2c,51268
|
17
17
|
tesla_fleet_api/tesla/vehicle/fleet.py,sha256=K9BVZj6CChJSDSMFroa7Cz0KrsYWj32ILtQumarkLaU,32080
|
18
|
-
tesla_fleet_api/tesla/vehicle/signed.py,sha256=
|
18
|
+
tesla_fleet_api/tesla/vehicle/signed.py,sha256=ggdtq8PydKoCk044L5b2262nfNzZqPBnsJ4SonTFbb4,1539
|
19
19
|
tesla_fleet_api/tesla/vehicle/vehicle.py,sha256=TyW5-LRlgRulWsm2indE3utSTdrJJRfG7H45Cc-ZASk,505
|
20
|
-
tesla_fleet_api/tesla/vehicle/vehicles.py,sha256=
|
20
|
+
tesla_fleet_api/tesla/vehicle/vehicles.py,sha256=wyMLfHNK_QaNTDtU9mkGRl2fB3Gb6lvmSfcgXzKu7WY,1565
|
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/vehicle.py,sha256=9_2N1iNNDouqfb6YBBWAFjnlzVRTf5frhXi
|
|
44
44
|
tesla_fleet_api/tessie/__init__.py,sha256=9lhQJaB6X4PObUL9QdaaZYqs2BxiTidu3zmHcBESLVw,78
|
45
45
|
tesla_fleet_api/tessie/tessie.py,sha256=qdMZ61TcQi5JRuv2qaxuLHtOuy8WZJ1WNqWg5WDAwwU,2615
|
46
46
|
tesla_fleet_api/tessie/vehicle.py,sha256=9khv4oCkGGLxHzQ2FYhDPH7wczxEDiUppsDXalawarE,1125
|
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.3.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
48
|
+
tesla_fleet_api-1.0.3.dist-info/METADATA,sha256=ARBm4GR744Ru7F2JumVFrw4UgeSxnYeV1k25KlzgqhY,4056
|
49
|
+
tesla_fleet_api-1.0.3.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
50
|
+
tesla_fleet_api-1.0.3.dist-info/top_level.txt,sha256=jeNbog_1saXBFrGpom9WyPWmilxsyP3szL_G7JLWQfM,16
|
51
|
+
tesla_fleet_api-1.0.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|