tesla-fleet-api 1.0.1__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/__init__.py +5 -7
- tesla_fleet_api/const.py +15 -1
- tesla_fleet_api/exceptions.py +39 -5
- tesla_fleet_api/tesla/__init__.py +3 -3
- tesla_fleet_api/tesla/bluetooth.py +11 -4
- tesla_fleet_api/tesla/charging.py +1 -1
- tesla_fleet_api/tesla/energysite.py +2 -2
- tesla_fleet_api/tesla/fleet.py +8 -8
- tesla_fleet_api/tesla/oauth.py +2 -2
- tesla_fleet_api/tesla/partner.py +1 -1
- tesla_fleet_api/tesla/user.py +1 -1
- tesla_fleet_api/tesla/vehicle/__init__.py +4 -4
- tesla_fleet_api/tesla/vehicle/bluetooth.py +245 -63
- tesla_fleet_api/tesla/vehicle/commands.py +140 -62
- tesla_fleet_api/tesla/vehicle/fleet.py +3 -3
- tesla_fleet_api/tesla/vehicle/signed.py +14 -21
- tesla_fleet_api/tesla/vehicle/vehicle.py +1 -1
- tesla_fleet_api/tesla/vehicle/vehicles.py +6 -6
- tesla_fleet_api/teslemetry/__init__.py +1 -1
- tesla_fleet_api/teslemetry/teslemetry.py +6 -6
- tesla_fleet_api/teslemetry/vehicle.py +5 -7
- tesla_fleet_api/tessie/__init__.py +1 -1
- tesla_fleet_api/tessie/tessie.py +6 -6
- tesla_fleet_api/tessie/vehicle.py +3 -9
- {tesla_fleet_api-1.0.1.dist-info → tesla_fleet_api-1.0.3.dist-info}/METADATA +1 -1
- tesla_fleet_api-1.0.3.dist-info/RECORD +51 -0
- tesla_fleet_api-1.0.1.dist-info/RECORD +0 -51
- {tesla_fleet_api-1.0.1.dist-info → tesla_fleet_api-1.0.3.dist-info}/LICENSE +0 -0
- {tesla_fleet_api-1.0.1.dist-info → tesla_fleet_api-1.0.3.dist-info}/WHEEL +0 -0
- {tesla_fleet_api-1.0.1.dist-info → tesla_fleet_api-1.0.3.dist-info}/top_level.txt +0 -0
@@ -13,16 +13,19 @@ from cryptography.hazmat.primitives.hashes import Hash, SHA256
|
|
13
13
|
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
14
14
|
from asyncio import Lock, sleep
|
15
15
|
|
16
|
-
from
|
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
|
)
|
21
24
|
|
22
|
-
from .vehicle import Vehicle
|
25
|
+
from tesla_fleet_api.tesla.vehicle.vehicle import Vehicle
|
23
26
|
|
24
27
|
|
25
|
-
from
|
28
|
+
from tesla_fleet_api.const import (
|
26
29
|
LOGGER,
|
27
30
|
Trunk,
|
28
31
|
ClimateKeeperMode,
|
@@ -32,40 +35,32 @@ from ...const import (
|
|
32
35
|
)
|
33
36
|
|
34
37
|
# Protocol
|
35
|
-
from .proto.errors_pb2 import GenericError_E
|
36
|
-
from .proto.car_server_pb2 import (
|
38
|
+
from tesla_fleet_api.tesla.vehicle.proto.errors_pb2 import GenericError_E
|
39
|
+
from tesla_fleet_api.tesla.vehicle.proto.car_server_pb2 import (
|
37
40
|
Response,
|
38
41
|
)
|
39
|
-
from .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,
|
42
|
+
from tesla_fleet_api.tesla.vehicle.proto.signatures_pb2 import (
|
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
|
-
from .proto.universal_message_pb2 import (
|
55
|
-
|
56
|
-
DOMAIN_VEHICLE_SECURITY,
|
57
|
-
OPERATIONSTATUS_ERROR,
|
58
|
-
OPERATIONSTATUS_WAIT,
|
51
|
+
from tesla_fleet_api.tesla.vehicle.proto.universal_message_pb2 import (
|
52
|
+
OperationStatus_E,
|
59
53
|
Destination,
|
60
54
|
Domain,
|
61
55
|
RoutableMessage,
|
62
56
|
SessionInfoRequest,
|
57
|
+
Flags,
|
63
58
|
)
|
64
|
-
from .proto.vcsec_pb2 import (
|
65
|
-
OPERATIONSTATUS_OK,
|
59
|
+
from tesla_fleet_api.tesla.vehicle.proto.vcsec_pb2 import (
|
66
60
|
FromVCSECMessage,
|
61
|
+
VehicleStatus,
|
67
62
|
)
|
68
|
-
from .proto.car_server_pb2 import (
|
63
|
+
from tesla_fleet_api.tesla.vehicle.proto.car_server_pb2 import (
|
69
64
|
Action,
|
70
65
|
MediaPlayAction,
|
71
66
|
VehicleAction,
|
@@ -83,8 +78,6 @@ from .proto.car_server_pb2 import (
|
|
83
78
|
HvacSteeringWheelHeaterAction,
|
84
79
|
HvacTemperatureAdjustmentAction,
|
85
80
|
GetNearbyChargingSites,
|
86
|
-
# NearbyChargingSites,
|
87
|
-
# Superchargers,
|
88
81
|
VehicleControlCancelSoftwareUpdateAction,
|
89
82
|
VehicleControlHonkHornAction,
|
90
83
|
VehicleControlResetValetPinAction,
|
@@ -112,24 +105,24 @@ from .proto.car_server_pb2 import (
|
|
112
105
|
MediaPreviousTrack,
|
113
106
|
MediaPreviousFavorite,
|
114
107
|
)
|
115
|
-
from .proto.vehicle_pb2 import VehicleState, ClimateState
|
116
|
-
from .proto.vcsec_pb2 import (
|
108
|
+
from tesla_fleet_api.tesla.vehicle.proto.vehicle_pb2 import VehicleData, VehicleState, ClimateState
|
109
|
+
from tesla_fleet_api.tesla.vehicle.proto.vcsec_pb2 import (
|
117
110
|
UnsignedMessage,
|
118
111
|
RKEAction_E,
|
119
112
|
ClosureMoveRequest,
|
120
113
|
ClosureMoveType_E,
|
121
114
|
)
|
122
|
-
from .proto.signatures_pb2 import (
|
115
|
+
from tesla_fleet_api.tesla.vehicle.proto.signatures_pb2 import (
|
123
116
|
HMAC_Personalized_Signature_Data,
|
124
117
|
)
|
125
|
-
from .proto.common_pb2 import (
|
118
|
+
from tesla_fleet_api.tesla.vehicle.proto.common_pb2 import (
|
126
119
|
Void,
|
127
120
|
PreconditioningTimes,
|
128
121
|
OffPeakChargingTimes,
|
129
122
|
)
|
130
123
|
|
131
124
|
if TYPE_CHECKING:
|
132
|
-
from
|
125
|
+
from tesla_fleet_api.tesla.tesla import Tesla
|
133
126
|
|
134
127
|
# ENUMs to convert ints to proto typed ints
|
135
128
|
AutoSeatClimatePositions = (
|
@@ -175,6 +168,7 @@ class Session:
|
|
175
168
|
hmac: bytes
|
176
169
|
publicKey: bytes
|
177
170
|
lock: Lock
|
171
|
+
ready: bool
|
178
172
|
|
179
173
|
def __init__(self, parent: Commands, domain: Domain):
|
180
174
|
self.parent = parent
|
@@ -260,10 +254,21 @@ class Commands(Vehicle):
|
|
260
254
|
|
261
255
|
|
262
256
|
@abstractmethod
|
263
|
-
async def _send(self, msg: RoutableMessage) -> RoutableMessage:
|
257
|
+
async def _send(self, msg: RoutableMessage, requires: str) -> RoutableMessage:
|
264
258
|
"""Transmit the message to the vehicle."""
|
265
259
|
raise NotImplementedError
|
266
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
|
+
|
267
272
|
@abstractmethod
|
268
273
|
async def _command(self, domain: Domain, command: bytes, attempt: int = 0) -> dict[str, Any]:
|
269
274
|
"""Serialize a message and send to the signed command endpoint."""
|
@@ -279,8 +284,9 @@ class Commands(Vehicle):
|
|
279
284
|
raise ValueError(f"Unknown auth method: {self._auth_method}")
|
280
285
|
|
281
286
|
try:
|
282
|
-
resp = await self._send(msg)
|
287
|
+
resp = await self._send(msg, "protobuf_message_as_bytes")
|
283
288
|
except (
|
289
|
+
#TeslaFleetMessageFaultInvalidSignature,
|
284
290
|
TeslaFleetMessageFaultIncorrectEpoch,
|
285
291
|
TeslaFleetMessageFaultInvalidTokenOrCounter,
|
286
292
|
) as e:
|
@@ -290,7 +296,7 @@ class Commands(Vehicle):
|
|
290
296
|
raise e
|
291
297
|
return await self._command(domain, command, attempt)
|
292
298
|
|
293
|
-
if resp.signedMessageStatus.operation_status == OPERATIONSTATUS_WAIT:
|
299
|
+
if resp.signedMessageStatus.operation_status == OperationStatus_E.OPERATIONSTATUS_WAIT:
|
294
300
|
attempt += 1
|
295
301
|
if attempt > 3:
|
296
302
|
# We tried 3 times, give up, raise the error
|
@@ -300,8 +306,53 @@ class Commands(Vehicle):
|
|
300
306
|
return await self._command(domain, command, attempt)
|
301
307
|
|
302
308
|
if resp.HasField("protobuf_message_as_bytes"):
|
303
|
-
|
304
|
-
|
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
|
305
356
|
LOGGER.debug("VCSEC Response: %s", vcsec)
|
306
357
|
if vcsec.HasField("nominalError"):
|
307
358
|
LOGGER.error("Command failed with reason: %s", vcsec.nominalError.genericError)
|
@@ -311,9 +362,13 @@ class Commands(Vehicle):
|
|
311
362
|
"reason": GenericError_E.Name(vcsec.nominalError.genericError)
|
312
363
|
}
|
313
364
|
}
|
314
|
-
elif vcsec.
|
365
|
+
elif vcsec.HasField("vehicleStatus"):
|
366
|
+
return {
|
367
|
+
"response": vcsec.vehicleStatus
|
368
|
+
}
|
369
|
+
elif vcsec.commandStatus.operationStatus == OperationStatus_E.OPERATIONSTATUS_OK:
|
315
370
|
return {"response": {"result": True, "reason": ""}}
|
316
|
-
elif vcsec.commandStatus.operationStatus == OPERATIONSTATUS_WAIT:
|
371
|
+
elif vcsec.commandStatus.operationStatus == OperationStatus_E.OPERATIONSTATUS_WAIT:
|
317
372
|
attempt += 1
|
318
373
|
if attempt > 3:
|
319
374
|
# We tried 3 times, give up, raise the error
|
@@ -321,29 +376,37 @@ class Commands(Vehicle):
|
|
321
376
|
async with session.lock:
|
322
377
|
await sleep(2)
|
323
378
|
return await self._command(domain, command, attempt)
|
324
|
-
elif vcsec.commandStatus.operationStatus == OPERATIONSTATUS_ERROR:
|
379
|
+
elif vcsec.commandStatus.operationStatus == OperationStatus_E.OPERATIONSTATUS_ERROR:
|
325
380
|
if(resp.HasField("signedMessageStatus")):
|
326
381
|
raise SIGNED_MESSAGE_INFORMATION_FAULTS[vcsec.commandStatus.signedMessageStatus.signedMessageInformation]
|
327
382
|
|
328
|
-
elif(resp.from_destination.domain == DOMAIN_INFOTAINMENT):
|
329
|
-
|
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
|
330
389
|
LOGGER.debug("Infotainment Response: %s", response)
|
331
390
|
if (response.HasField("ping")):
|
332
|
-
print(response.ping)
|
333
391
|
return {
|
334
392
|
"response": {
|
335
393
|
"result": True,
|
336
394
|
"reason": response.ping.local_timestamp
|
337
395
|
}
|
338
396
|
}
|
397
|
+
if response.HasField("vehicleData"):
|
398
|
+
return {
|
399
|
+
"response": response.vehicleData
|
400
|
+
}
|
339
401
|
if response.HasField("actionStatus"):
|
340
402
|
return {
|
341
403
|
"response": {
|
342
|
-
"result": response.actionStatus.result == OPERATIONSTATUS_OK,
|
404
|
+
"result": response.actionStatus.result == OperationStatus_E.OPERATIONSTATUS_OK,
|
343
405
|
"reason": response.actionStatus.result_reason.plain_text or ""
|
344
406
|
}
|
345
407
|
}
|
346
408
|
|
409
|
+
|
347
410
|
return {"response": {"result": True, "reason": ""}}
|
348
411
|
|
349
412
|
async def _commandHmac(self, session: Session, command: bytes, attempt: int = 1) -> RoutableMessage:
|
@@ -353,25 +416,25 @@ class Commands(Vehicle):
|
|
353
416
|
hmac_personalized = session.hmac_personalized()
|
354
417
|
|
355
418
|
metadata = bytes([
|
356
|
-
TAG_SIGNATURE_TYPE,
|
419
|
+
Tag.TAG_SIGNATURE_TYPE,
|
357
420
|
1,
|
358
|
-
SIGNATURE_TYPE_HMAC_PERSONALIZED,
|
359
|
-
TAG_DOMAIN,
|
421
|
+
SignatureType.SIGNATURE_TYPE_HMAC_PERSONALIZED,
|
422
|
+
Tag.TAG_DOMAIN,
|
360
423
|
1,
|
361
424
|
session.domain,
|
362
|
-
TAG_PERSONALIZATION,
|
425
|
+
Tag.TAG_PERSONALIZATION,
|
363
426
|
17,
|
364
427
|
*self.vin.encode(),
|
365
|
-
TAG_EPOCH,
|
428
|
+
Tag.TAG_EPOCH,
|
366
429
|
len(hmac_personalized.epoch),
|
367
430
|
*hmac_personalized.epoch,
|
368
|
-
TAG_EXPIRES_AT,
|
431
|
+
Tag.TAG_EXPIRES_AT,
|
369
432
|
4,
|
370
433
|
*struct.pack(">I", hmac_personalized.expires_at),
|
371
|
-
TAG_COUNTER,
|
434
|
+
Tag.TAG_COUNTER,
|
372
435
|
4,
|
373
436
|
*struct.pack(">I", hmac_personalized.counter),
|
374
|
-
TAG_END,
|
437
|
+
Tag.TAG_END,
|
375
438
|
])
|
376
439
|
|
377
440
|
hmac_personalized.tag = hmac.new(
|
@@ -400,27 +463,31 @@ class Commands(Vehicle):
|
|
400
463
|
LOGGER.debug(f"Sending AES to domain {Domain.Name(session.domain)}")
|
401
464
|
|
402
465
|
aes_personalized = session.aes_gcm_personalized()
|
466
|
+
flags = 1 << Flags.FLAG_ENCRYPT_RESPONSE
|
403
467
|
|
404
468
|
metadata = bytes([
|
405
|
-
TAG_SIGNATURE_TYPE,
|
469
|
+
Tag.TAG_SIGNATURE_TYPE,
|
406
470
|
1,
|
407
|
-
SIGNATURE_TYPE_AES_GCM_PERSONALIZED,
|
408
|
-
TAG_DOMAIN,
|
471
|
+
SignatureType.SIGNATURE_TYPE_AES_GCM_PERSONALIZED,
|
472
|
+
Tag.TAG_DOMAIN,
|
409
473
|
1,
|
410
474
|
session.domain,
|
411
|
-
TAG_PERSONALIZATION,
|
475
|
+
Tag.TAG_PERSONALIZATION,
|
412
476
|
17,
|
413
477
|
*self.vin.encode(),
|
414
|
-
TAG_EPOCH,
|
478
|
+
Tag.TAG_EPOCH,
|
415
479
|
len(aes_personalized.epoch),
|
416
480
|
*aes_personalized.epoch,
|
417
|
-
TAG_EXPIRES_AT,
|
481
|
+
Tag.TAG_EXPIRES_AT,
|
418
482
|
4,
|
419
483
|
*struct.pack(">I", aes_personalized.expires_at),
|
420
|
-
TAG_COUNTER,
|
484
|
+
Tag.TAG_COUNTER,
|
421
485
|
4,
|
422
486
|
*struct.pack(">I", aes_personalized.counter),
|
423
|
-
|
487
|
+
Tag.TAG_FLAGS,
|
488
|
+
4,
|
489
|
+
*struct.pack(">I", flags),
|
490
|
+
Tag.TAG_END,
|
424
491
|
])
|
425
492
|
|
426
493
|
aad = Hash(SHA256())
|
@@ -431,7 +498,6 @@ class Commands(Vehicle):
|
|
431
498
|
|
432
499
|
aes_personalized.tag = ct[-16:]
|
433
500
|
|
434
|
-
# I think this whole section could be improved
|
435
501
|
return RoutableMessage(
|
436
502
|
to_destination=Destination(
|
437
503
|
domain=session.domain,
|
@@ -446,7 +512,8 @@ class Commands(Vehicle):
|
|
446
512
|
public_key=self._public_key
|
447
513
|
),
|
448
514
|
AES_GCM_Personalized_data=aes_personalized,
|
449
|
-
)
|
515
|
+
),
|
516
|
+
flags=flags,
|
450
517
|
)
|
451
518
|
|
452
519
|
|
@@ -454,10 +521,20 @@ class Commands(Vehicle):
|
|
454
521
|
"""Sign and send a message to Infotainment computer."""
|
455
522
|
return await self._command(Domain.DOMAIN_VEHICLE_SECURITY, command.SerializeToString())
|
456
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
|
+
|
457
529
|
async def _sendInfotainment(self, command: Action) -> dict[str, Any]:
|
458
530
|
"""Sign and send a message to Infotainment computer."""
|
459
531
|
return await self._command(Domain.DOMAIN_INFOTAINMENT, command.SerializeToString())
|
460
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
|
+
|
461
538
|
async def handshakeVehicleSecurity(self) -> None:
|
462
539
|
"""Perform a handshake with the vehicle security domain."""
|
463
540
|
await self._handshake(Domain.DOMAIN_VEHICLE_SECURITY)
|
@@ -466,7 +543,7 @@ class Commands(Vehicle):
|
|
466
543
|
"""Perform a handshake with the infotainment domain."""
|
467
544
|
await self._handshake(Domain.DOMAIN_INFOTAINMENT)
|
468
545
|
|
469
|
-
async def _handshake(self, domain: Domain) ->
|
546
|
+
async def _handshake(self, domain: Domain) -> bool:
|
470
547
|
"""Perform a handshake with the vehicle."""
|
471
548
|
|
472
549
|
LOGGER.debug(f"Handshake with domain {Domain.Name(domain)}")
|
@@ -483,7 +560,8 @@ class Commands(Vehicle):
|
|
483
560
|
uuid=randbytes(16)
|
484
561
|
)
|
485
562
|
|
486
|
-
await self._send(msg)
|
563
|
+
await self._send(msg, "session_info")
|
564
|
+
return self._sessions[domain].ready
|
487
565
|
|
488
566
|
async def ping(self) -> dict[str, Any]:
|
489
567
|
"""Ping the vehicle."""
|
@@ -4,7 +4,7 @@ from locale import getlocale
|
|
4
4
|
from time import time
|
5
5
|
from typing import TYPE_CHECKING, Any, List
|
6
6
|
|
7
|
-
from
|
7
|
+
from tesla_fleet_api.const import (
|
8
8
|
CabinOverheatProtectionTemp,
|
9
9
|
ClimateKeeperMode,
|
10
10
|
Level,
|
@@ -15,12 +15,12 @@ from ...const import (
|
|
15
15
|
VehicleDataEndpoint,
|
16
16
|
WindowCommand,
|
17
17
|
)
|
18
|
-
from .vehicle import Vehicle
|
18
|
+
from tesla_fleet_api.tesla.vehicle.vehicle import Vehicle
|
19
19
|
|
20
20
|
DEFAULT_LOCALE = (getlocale()[0] or "en-US").replace("_","-")
|
21
21
|
|
22
22
|
if TYPE_CHECKING:
|
23
|
-
from
|
23
|
+
from tesla_fleet_api.tesla.fleet import TeslaFleetApi
|
24
24
|
|
25
25
|
class VehicleFleet(Vehicle):
|
26
26
|
"""Class describing the Tesla Fleet API vehicle endpoints and commands."""
|
@@ -3,21 +3,22 @@ from __future__ import annotations
|
|
3
3
|
import base64
|
4
4
|
from typing import TYPE_CHECKING
|
5
5
|
|
6
|
-
from .fleet import VehicleFleet
|
7
|
-
from .commands import Commands
|
8
|
-
|
9
|
-
from ...exceptions import (
|
6
|
+
from tesla_fleet_api.tesla.vehicle.fleet import VehicleFleet
|
7
|
+
from tesla_fleet_api.tesla.vehicle.commands import Commands
|
8
|
+
from tesla_fleet_api.exceptions import (
|
10
9
|
MESSAGE_FAULTS,
|
10
|
+
NotOnWhitelistFault,
|
11
11
|
)
|
12
|
-
from .proto.signatures_pb2 import (
|
12
|
+
from tesla_fleet_api.tesla.vehicle.proto.signatures_pb2 import (
|
13
|
+
Session_Info_Status,
|
13
14
|
SessionInfo,
|
14
15
|
)
|
15
|
-
from .proto.universal_message_pb2 import (
|
16
|
+
from tesla_fleet_api.tesla.vehicle.proto.universal_message_pb2 import (
|
16
17
|
RoutableMessage,
|
17
18
|
)
|
18
19
|
|
19
20
|
if TYPE_CHECKING:
|
20
|
-
from
|
21
|
+
from tesla_fleet_api.tesla.fleet import TeslaFleetApi
|
21
22
|
|
22
23
|
|
23
24
|
class VehicleSigned(VehicleFleet, Commands):
|
@@ -32,25 +33,17 @@ class VehicleSigned(VehicleFleet, Commands):
|
|
32
33
|
super(Commands, self).__init__(parent, vin)
|
33
34
|
|
34
35
|
|
35
|
-
async def _send(self, msg: RoutableMessage) -> RoutableMessage:
|
36
|
+
async def _send(self, msg: RoutableMessage, requires: str) -> RoutableMessage:
|
36
37
|
"""Serialize a message and send to the signed command endpoint."""
|
38
|
+
# requires isnt used because Fleet API messages are singular
|
37
39
|
|
38
40
|
async with self._sessions[msg.to_destination.domain].lock:
|
39
|
-
|
41
|
+
json = await self.signed_command(
|
40
42
|
base64.b64encode(msg.SerializeToString()).decode()
|
41
43
|
)
|
42
44
|
|
43
|
-
|
44
|
-
|
45
|
-
# Check UUID?
|
46
|
-
# Check RoutingAdress?
|
47
|
-
|
48
|
-
if resp_msg.session_info:
|
49
|
-
self._sessions[resp_msg.from_destination.domain].update(
|
50
|
-
SessionInfo.FromString(resp_msg.session_info), self.private_key
|
51
|
-
)
|
45
|
+
resp = RoutableMessage.FromString(base64.b64decode(json["response"]))
|
52
46
|
|
53
|
-
|
54
|
-
raise MESSAGE_FAULTS[resp_msg.signedMessageStatus.signed_message_fault]
|
47
|
+
self.validate_msg(resp)
|
55
48
|
|
56
|
-
return
|
49
|
+
return resp
|
@@ -1,13 +1,13 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
from typing import TYPE_CHECKING
|
3
3
|
|
4
|
-
from .signed import VehicleSigned
|
5
|
-
from .bluetooth import VehicleBluetooth
|
6
|
-
from .fleet import VehicleFleet
|
7
|
-
from .vehicle import Vehicle
|
4
|
+
from tesla_fleet_api.tesla.vehicle.signed import VehicleSigned
|
5
|
+
from tesla_fleet_api.tesla.vehicle.bluetooth import VehicleBluetooth
|
6
|
+
from tesla_fleet_api.tesla.vehicle.fleet import VehicleFleet
|
7
|
+
from tesla_fleet_api.tesla.vehicle.vehicle import Vehicle
|
8
8
|
|
9
9
|
if TYPE_CHECKING:
|
10
|
-
from
|
10
|
+
from tesla_fleet_api.tesla.fleet import TeslaFleetApi
|
11
11
|
|
12
12
|
|
13
13
|
|
@@ -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
|
@@ -2,12 +2,12 @@ from typing import Any
|
|
2
2
|
|
3
3
|
import aiohttp
|
4
4
|
|
5
|
-
from
|
6
|
-
from
|
7
|
-
from
|
8
|
-
from .vehicle import TeslemetryVehicles
|
9
|
-
from
|
10
|
-
from
|
5
|
+
from tesla_fleet_api.tesla.charging import Charging
|
6
|
+
from tesla_fleet_api.tesla.energysite import EnergySites
|
7
|
+
from tesla_fleet_api.tesla.user import User
|
8
|
+
from tesla_fleet_api.teslemetry.vehicle import TeslemetryVehicles
|
9
|
+
from tesla_fleet_api.const import LOGGER, Method
|
10
|
+
from tesla_fleet_api.tesla import TeslaFleetApi
|
11
11
|
|
12
12
|
class Teslemetry(TeslaFleetApi):
|
13
13
|
|
@@ -1,15 +1,13 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
from typing import TYPE_CHECKING, Any
|
3
3
|
|
4
|
-
from
|
5
|
-
from
|
6
|
-
from
|
7
|
-
from
|
8
|
-
from ..tesla.vehicle.bluetooth import VehicleBluetooth
|
9
|
-
from ..tesla.vehicle.fleet import VehicleFleet
|
4
|
+
from tesla_fleet_api.const import Method
|
5
|
+
from tesla_fleet_api.tesla.vehicle.vehicle import Vehicle
|
6
|
+
from tesla_fleet_api.tesla.vehicle.vehicles import Vehicles
|
7
|
+
from tesla_fleet_api.tesla.vehicle.fleet import VehicleFleet
|
10
8
|
|
11
9
|
if TYPE_CHECKING:
|
12
|
-
|
10
|
+
pass
|
13
11
|
|
14
12
|
class TeslemetryVehicle(Vehicle):
|
15
13
|
"""Teslemetry specific base vehicle."""
|
tesla_fleet_api/tessie/tessie.py
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
import aiohttp
|
2
2
|
from typing import Any
|
3
3
|
|
4
|
-
from
|
5
|
-
from
|
6
|
-
from
|
7
|
-
from
|
8
|
-
from
|
9
|
-
from .vehicle import TessieVehicles
|
4
|
+
from tesla_fleet_api.tesla.charging import Charging
|
5
|
+
from tesla_fleet_api.tesla.energysite import EnergySites
|
6
|
+
from tesla_fleet_api.tesla.user import User
|
7
|
+
from tesla_fleet_api.tesla import TeslaFleetApi
|
8
|
+
from tesla_fleet_api.const import Method
|
9
|
+
from tesla_fleet_api.tessie.vehicle import TessieVehicles
|
10
10
|
|
11
11
|
class Tessie(TeslaFleetApi):
|
12
12
|
|
@@ -1,15 +1,9 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
from typing import TYPE_CHECKING, Any
|
3
2
|
|
4
|
-
from
|
5
|
-
from
|
6
|
-
from
|
7
|
-
from ..tesla.vehicle.vehicles import Vehicles
|
8
|
-
from ..tesla.vehicle.bluetooth import VehicleBluetooth
|
9
|
-
from ..tesla.vehicle.fleet import VehicleFleet
|
3
|
+
from tesla_fleet_api.tesla.vehicle.vehicle import Vehicle
|
4
|
+
from tesla_fleet_api.tesla.vehicle.vehicles import Vehicles
|
5
|
+
from tesla_fleet_api.tesla.vehicle.fleet import VehicleFleet
|
10
6
|
|
11
|
-
if TYPE_CHECKING:
|
12
|
-
from .tessie import Tessie
|
13
7
|
|
14
8
|
class TessieVehicle(Vehicle):
|
15
9
|
"""Tessie specific base vehicle."""
|