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
@@ -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
|
|
@@ -12,38 +12,61 @@ from cryptography.hazmat.primitives.asymmetric import ec
|
|
12
12
|
|
13
13
|
from tesla_fleet_api.tesla.vehicle.proto.keys_pb2 import Role
|
14
14
|
|
15
|
-
from .commands import Commands
|
15
|
+
from tesla_fleet_api.tesla.vehicle.commands import Commands
|
16
16
|
|
17
|
-
from
|
17
|
+
from tesla_fleet_api.const import (
|
18
18
|
LOGGER,
|
19
|
+
BluetoothVehicleData
|
19
20
|
)
|
20
|
-
from
|
21
|
+
from tesla_fleet_api.exceptions import (
|
21
22
|
MESSAGE_FAULTS,
|
22
23
|
WHITELIST_OPERATION_STATUS,
|
24
|
+
WhitelistOperationStatus,
|
25
|
+
NotOnWhitelistFault,
|
23
26
|
)
|
24
27
|
|
25
28
|
# Protocol
|
26
|
-
from .proto.car_server_pb2 import (
|
29
|
+
from tesla_fleet_api.tesla.vehicle.proto.car_server_pb2 import (
|
30
|
+
Action,
|
31
|
+
VehicleAction,
|
27
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,
|
28
46
|
)
|
29
|
-
from .proto.signatures_pb2 import (
|
47
|
+
from tesla_fleet_api.tesla.vehicle.proto.signatures_pb2 import (
|
30
48
|
SessionInfo,
|
49
|
+
Session_Info_Status
|
31
50
|
)
|
32
|
-
from .proto.universal_message_pb2 import (
|
51
|
+
from tesla_fleet_api.tesla.vehicle.proto.universal_message_pb2 import (
|
33
52
|
Destination,
|
34
53
|
Domain,
|
35
54
|
RoutableMessage,
|
36
55
|
)
|
37
|
-
from .proto.vcsec_pb2 import (
|
56
|
+
from tesla_fleet_api.tesla.vehicle.proto.vcsec_pb2 import (
|
38
57
|
FromVCSECMessage,
|
58
|
+
InformationRequest,
|
59
|
+
InformationRequestType,
|
39
60
|
KeyFormFactor,
|
40
61
|
KeyMetadata,
|
41
62
|
PermissionChange,
|
42
63
|
PublicKey,
|
64
|
+
RKEAction_E,
|
43
65
|
UnsignedMessage,
|
66
|
+
VehicleStatus,
|
44
67
|
WhitelistOperation,
|
45
|
-
|
46
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
|
47
70
|
|
48
71
|
SERVICE_UUID = "00000211-b2d1-43f0-9b88-960cebf8b91e"
|
49
72
|
WRITE_UUID = "00000212-b2d1-43f0-9b88-960cebf8b91e"
|
@@ -51,7 +74,7 @@ READ_UUID = "00000213-b2d1-43f0-9b88-960cebf8b91e"
|
|
51
74
|
VERSION_UUID = "00000214-b2d1-43f0-9b88-960cebf8b91e"
|
52
75
|
|
53
76
|
if TYPE_CHECKING:
|
54
|
-
from
|
77
|
+
from tesla_fleet_api.tesla.tesla import Tesla
|
55
78
|
|
56
79
|
def prependLength(message: bytes) -> bytearray:
|
57
80
|
"""Prepend a 2-byte length to the payload."""
|
@@ -62,40 +85,43 @@ class VehicleBluetooth(Commands):
|
|
62
85
|
|
63
86
|
ble_name: str
|
64
87
|
client: BleakClient
|
65
|
-
|
66
|
-
_futures: dict[Domain, Future]
|
88
|
+
_queues: dict[Domain, asyncio.Queue]
|
67
89
|
_ekey: ec.EllipticCurvePublicKey
|
68
90
|
_recv: bytearray = bytearray()
|
69
91
|
_recv_len: int = 0
|
70
92
|
_auth_method = "aes"
|
71
93
|
|
72
94
|
def __init__(
|
73
|
-
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
|
74
96
|
):
|
75
97
|
super().__init__(parent, vin, key)
|
76
98
|
self.ble_name = "S" + hashlib.sha1(vin.encode('utf-8')).hexdigest()[:16] + "C"
|
77
|
-
self.
|
78
|
-
|
79
|
-
|
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])
|
105
|
+
|
106
|
+
async def find_client(self, scanner: BleakScanner = BleakScanner()) -> BleakClient:
|
80
107
|
"""Find the Tesla BLE device."""
|
81
108
|
|
82
109
|
device = await scanner.find_device_by_name(self.ble_name)
|
83
110
|
if not device:
|
84
111
|
raise ValueError(f"Device {self.ble_name} not found")
|
85
|
-
self.
|
86
|
-
|
87
|
-
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}")
|
88
114
|
return self.client
|
89
115
|
|
90
|
-
def create_client(self,
|
91
|
-
"""Create a client
|
92
|
-
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])
|
93
119
|
return self.client
|
94
120
|
|
95
|
-
async def connect(self,
|
121
|
+
async def connect(self, device: str|BLEDevice | None = None) -> None:
|
96
122
|
"""Connect to the Tesla BLE device."""
|
97
|
-
if
|
98
|
-
self.create_client(
|
123
|
+
if device is not None:
|
124
|
+
self.create_client(device)
|
99
125
|
await self.client.connect()
|
100
126
|
await self.client.start_notify(READ_UUID, self._on_notify)
|
101
127
|
|
@@ -108,11 +134,11 @@ class VehicleBluetooth(Commands):
|
|
108
134
|
await self.connect()
|
109
135
|
return self
|
110
136
|
|
111
|
-
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
137
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
112
138
|
"""Exit the async context."""
|
113
139
|
await self.disconnect()
|
114
140
|
|
115
|
-
def _on_notify(self,sender: BleakGATTCharacteristic,data : bytearray):
|
141
|
+
async def _on_notify(self,sender: BleakGATTCharacteristic,data : bytearray) -> None:
|
116
142
|
"""Receive data from the Tesla BLE device."""
|
117
143
|
if self._recv_len:
|
118
144
|
self._recv += data
|
@@ -122,68 +148,55 @@ class VehicleBluetooth(Commands):
|
|
122
148
|
LOGGER.debug(f"Received {len(self._recv)} of {self._recv_len} bytes")
|
123
149
|
while len(self._recv) > self._recv_len:
|
124
150
|
LOGGER.warn(f"Received more data than expected: {len(self._recv)} > {self._recv_len}")
|
125
|
-
self._on_message(bytes(self._recv[:self._recv_len]))
|
151
|
+
await self._on_message(bytes(self._recv[:self._recv_len]))
|
126
152
|
self._recv_len = int.from_bytes(self._recv[self._recv_len:self._recv_len+2], 'big')
|
127
153
|
self._recv = self._recv[self._recv_len+2:]
|
128
154
|
continue
|
129
155
|
if len(self._recv) == self._recv_len:
|
130
|
-
self._on_message(bytes(self._recv))
|
156
|
+
await self._on_message(bytes(self._recv))
|
131
157
|
self._recv = bytearray()
|
132
158
|
self._recv_len = 0
|
133
159
|
|
134
|
-
def _on_message(self, data:bytes):
|
160
|
+
async def _on_message(self, data:bytes) -> None:
|
135
161
|
"""Receive messages from the Tesla BLE data."""
|
136
162
|
try:
|
137
163
|
msg = RoutableMessage.FromString(data)
|
138
164
|
except DecodeError as e:
|
139
165
|
LOGGER.error(f"Error parsing message: {e}")
|
166
|
+
self._recv = bytearray()
|
167
|
+
self._recv_len = 0
|
140
168
|
return
|
141
169
|
|
142
|
-
# Update Session
|
143
|
-
if(msg.session_info):
|
144
|
-
info = SessionInfo.FromString(msg.session_info)
|
145
|
-
LOGGER.debug(f"Received session info: {info}")
|
146
|
-
self._sessions[msg.from_destination.domain].update(info)
|
147
|
-
|
148
170
|
if(msg.to_destination.routing_address != self._from_destination):
|
149
|
-
#
|
171
|
+
# Ignore ephemeral key broadcasts
|
150
172
|
return
|
151
173
|
|
152
|
-
|
153
|
-
|
154
|
-
print(submsg)
|
155
|
-
elif msg.from_destination.domain == Domain.DOMAIN_INFOTAINMENT:
|
156
|
-
submsg = Response.FromString(msg.protobuf_message_as_bytes)
|
157
|
-
print(submsg)
|
174
|
+
LOGGER.info(f"Received response: {msg}")
|
175
|
+
await self._queues[msg.from_destination.domain].put(msg)
|
158
176
|
|
159
|
-
|
160
|
-
LOGGER.debug(f"Received response for request {msg.request_uuid}")
|
161
|
-
self._futures[msg.from_destination.domain].set_result(msg)
|
162
|
-
return
|
163
|
-
|
164
|
-
async def _create_future(self, domain: Domain) -> Future:
|
165
|
-
if(not self._sessions[domain].lock.locked):
|
166
|
-
raise ValueError("Session is not locked")
|
167
|
-
self._futures[domain] = get_running_loop().create_future()
|
168
|
-
return self._futures[domain]
|
169
|
-
|
170
|
-
async def _send(self, msg: RoutableMessage) -> RoutableMessage:
|
177
|
+
async def _send(self, msg: RoutableMessage, requires: str) -> RoutableMessage:
|
171
178
|
"""Serialize a message and send to the vehicle and wait for a response."""
|
172
179
|
domain = msg.to_destination.domain
|
173
180
|
async with self._sessions[domain].lock:
|
174
181
|
LOGGER.debug(f"Sending message {msg}")
|
175
|
-
|
182
|
+
|
176
183
|
payload = prependLength(msg.SerializeToString())
|
177
184
|
|
185
|
+
# Empty the queue before sending the message
|
186
|
+
while not self._queues[domain].empty():
|
187
|
+
await self._queues[domain].get()
|
178
188
|
await self.client.write_gatt_char(WRITE_UUID, payload, True)
|
179
189
|
|
180
|
-
|
181
|
-
|
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}")
|
182
195
|
|
183
|
-
|
184
|
-
raise MESSAGE_FAULTS[resp.signedMessageStatus.signed_message_fault]
|
196
|
+
self.validate_msg(resp)
|
185
197
|
|
186
|
-
|
198
|
+
if resp.HasField(requires):
|
199
|
+
return resp
|
187
200
|
|
188
201
|
async def pair(self, role: Role = Role.ROLE_OWNER, form: KeyFormFactor = KeyFormFactor.KEY_FORM_FACTOR_CLOUD_KEY):
|
189
202
|
"""Pair the key."""
|
@@ -210,10 +223,179 @@ class VehicleBluetooth(Commands):
|
|
210
223
|
)
|
211
224
|
resp = await self._send(msg)
|
212
225
|
respMsg = FromVCSECMessage.FromString(resp.protobuf_message_as_bytes)
|
213
|
-
print(respMsg)
|
214
226
|
if(respMsg.commandStatus.whitelistOperationStatus.whitelistOperationInformation):
|
215
227
|
if(respMsg.commandStatus.whitelistOperationStatus.whitelistOperationInformation < len(WHITELIST_OPERATION_STATUS)):
|
216
228
|
raise WHITELIST_OPERATION_STATUS[respMsg.commandStatus.whitelistOperationStatus.whitelistOperationInformation]
|
217
229
|
else:
|
218
|
-
raise
|
230
|
+
raise WhitelistOperationStatus(f"Unknown whitelist operation failure: {respMsg.commandStatus.whitelistOperationStatus.whitelistOperationInformation}")
|
219
231
|
return
|
232
|
+
|
233
|
+
async def wake_up(self):
|
234
|
+
"""Wake up the vehicle."""
|
235
|
+
return await self._sendVehicleSecurity(
|
236
|
+
UnsignedMessage(RKEAction=RKEAction_E.RKE_ACTION_WAKE_VEHICLE)
|
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
|
+
)
|