tesla-fleet-api 0.9.10__py3-none-any.whl → 1.0.1__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- tesla_fleet_api/__init__.py +7 -22
- tesla_fleet_api/const.py +1 -225
- tesla_fleet_api/exceptions.py +117 -0
- tesla_fleet_api/tesla/__init__.py +11 -0
- tesla_fleet_api/tesla/bluetooth.py +38 -0
- tesla_fleet_api/{charging.py → tesla/charging.py} +1 -1
- tesla_fleet_api/{energy.py → tesla/energysite.py} +41 -33
- tesla_fleet_api/{teslafleetapi.py → tesla/fleet.py} +8 -53
- tesla_fleet_api/{teslafleetoauth.py → tesla/oauth.py} +3 -4
- tesla_fleet_api/{partner.py → tesla/partner.py} +1 -1
- tesla_fleet_api/tesla/tesla.py +52 -0
- tesla_fleet_api/{user.py → tesla/user.py} +1 -1
- tesla_fleet_api/tesla/vehicle/__init__.py +13 -0
- tesla_fleet_api/tesla/vehicle/bluetooth.py +219 -0
- tesla_fleet_api/{vehiclesigned.py → tesla/vehicle/commands.py} +321 -164
- tesla_fleet_api/{vehicle.py → tesla/vehicle/fleet.py} +173 -206
- tesla_fleet_api/tesla/vehicle/signed.py +56 -0
- tesla_fleet_api/tesla/vehicle/vehicle.py +19 -0
- tesla_fleet_api/tesla/vehicle/vehicles.py +46 -0
- tesla_fleet_api/teslemetry/__init__.py +5 -0
- tesla_fleet_api/{teslemetry.py → teslemetry/teslemetry.py} +16 -25
- tesla_fleet_api/teslemetry/vehicle.py +73 -0
- tesla_fleet_api/tessie/__init__.py +5 -0
- tesla_fleet_api/{tessie.py → tessie/tessie.py} +17 -9
- tesla_fleet_api/tessie/vehicle.py +41 -0
- {tesla_fleet_api-0.9.10.dist-info → tesla_fleet_api-1.0.1.dist-info}/METADATA +3 -2
- tesla_fleet_api-1.0.1.dist-info/RECORD +51 -0
- tesla_fleet_api/energyspecific.py +0 -125
- tesla_fleet_api/teslafleetopensource.py +0 -61
- tesla_fleet_api/vehiclespecific.py +0 -509
- tesla_fleet_api-0.9.10.dist-info/RECORD +0 -42
- /tesla_fleet_api/{pb2 → tesla/vehicle/proto}/__init__.py +0 -0
- /tesla_fleet_api/{pb2 → tesla/vehicle/proto}/__init__.pyi +0 -0
- /tesla_fleet_api/{pb2 → tesla/vehicle/proto}/car_server_pb2.py +0 -0
- /tesla_fleet_api/{pb2 → tesla/vehicle/proto}/car_server_pb2.pyi +0 -0
- /tesla_fleet_api/{pb2 → tesla/vehicle/proto}/common_pb2.py +0 -0
- /tesla_fleet_api/{pb2 → tesla/vehicle/proto}/common_pb2.pyi +0 -0
- /tesla_fleet_api/{pb2 → tesla/vehicle/proto}/errors_pb2.py +0 -0
- /tesla_fleet_api/{pb2 → tesla/vehicle/proto}/errors_pb2.pyi +0 -0
- /tesla_fleet_api/{pb2 → tesla/vehicle/proto}/keys_pb2.py +0 -0
- /tesla_fleet_api/{pb2 → tesla/vehicle/proto}/keys_pb2.pyi +0 -0
- /tesla_fleet_api/{pb2 → tesla/vehicle/proto}/managed_charging_pb2.py +0 -0
- /tesla_fleet_api/{pb2 → tesla/vehicle/proto}/managed_charging_pb2.pyi +0 -0
- /tesla_fleet_api/{pb2 → tesla/vehicle/proto}/signatures_pb2.py +0 -0
- /tesla_fleet_api/{pb2 → tesla/vehicle/proto}/signatures_pb2.pyi +0 -0
- /tesla_fleet_api/{pb2 → tesla/vehicle/proto}/universal_message_pb2.py +0 -0
- /tesla_fleet_api/{pb2 → tesla/vehicle/proto}/universal_message_pb2.pyi +0 -0
- /tesla_fleet_api/{pb2 → tesla/vehicle/proto}/vcsec_pb2.py +0 -0
- /tesla_fleet_api/{pb2 → tesla/vehicle/proto}/vcsec_pb2.pyi +0 -0
- /tesla_fleet_api/{pb2 → tesla/vehicle/proto}/vehicle_pb2.py +0 -0
- /tesla_fleet_api/{pb2 → tesla/vehicle/proto}/vehicle_pb2.pyi +0 -0
- {tesla_fleet_api-0.9.10.dist-info → tesla_fleet_api-1.0.1.dist-info}/LICENSE +0 -0
- {tesla_fleet_api-0.9.10.dist-info → tesla_fleet_api-1.0.1.dist-info}/WHEEL +0 -0
- {tesla_fleet_api-0.9.10.dist-info → tesla_fleet_api-1.0.1.dist-info}/top_level.txt +0 -0
@@ -2,26 +2,20 @@
|
|
2
2
|
|
3
3
|
from json import dumps
|
4
4
|
from typing import Any, Awaitable
|
5
|
-
from os.path import exists
|
6
5
|
import aiohttp
|
7
|
-
import aiofiles
|
8
6
|
|
9
|
-
|
10
|
-
from
|
11
|
-
from
|
12
|
-
from cryptography.hazmat.backends import default_backend
|
13
|
-
|
14
|
-
from .exceptions import raise_for_status, InvalidRegion, LibraryError, ResponseError
|
15
|
-
from .const import SERVERS, Method, LOGGER, VERSION
|
7
|
+
from .tesla import Tesla
|
8
|
+
from ..exceptions import raise_for_status, InvalidRegion, LibraryError, ResponseError
|
9
|
+
from ..const import SERVERS, Method, LOGGER, VERSION
|
16
10
|
from .charging import Charging
|
17
|
-
from .
|
11
|
+
from .energysite import EnergySites
|
18
12
|
from .partner import Partner
|
19
13
|
from .user import User
|
20
|
-
from .vehicle import
|
14
|
+
from .vehicle.vehicles import Vehicles
|
21
15
|
|
22
16
|
|
23
17
|
# Based on https://developer.tesla.com/docs/fleet-api
|
24
|
-
class TeslaFleetApi:
|
18
|
+
class TeslaFleetApi(Tesla):
|
25
19
|
"""Class describing the Tesla Fleet API."""
|
26
20
|
|
27
21
|
access_token: str | None = None
|
@@ -30,7 +24,6 @@ class TeslaFleetApi:
|
|
30
24
|
session: aiohttp.ClientSession
|
31
25
|
headers: dict[str, str]
|
32
26
|
refresh_hook: Awaitable | None = None
|
33
|
-
private_key: ec.EllipticCurvePrivateKey | None = None
|
34
27
|
|
35
28
|
def __init__(
|
36
29
|
self,
|
@@ -63,13 +56,13 @@ class TeslaFleetApi:
|
|
63
56
|
if charging_scope:
|
64
57
|
self.charging = Charging(self)
|
65
58
|
if energy_scope:
|
66
|
-
self.
|
59
|
+
self.energySites = EnergySites(self)
|
67
60
|
if user_scope:
|
68
61
|
self.user = User(self)
|
69
62
|
if partner_scope:
|
70
63
|
self.partner = Partner(self)
|
71
64
|
if vehicle_scope:
|
72
|
-
self.
|
65
|
+
self.vehicles = Vehicles(self)
|
73
66
|
|
74
67
|
async def find_server(self) -> str:
|
75
68
|
"""Find the server URL for the Tesla Fleet API."""
|
@@ -162,41 +155,3 @@ class TeslaFleetApi:
|
|
162
155
|
Method.GET,
|
163
156
|
"api/1/products",
|
164
157
|
)
|
165
|
-
|
166
|
-
async def get_private_key(
|
167
|
-
self, path: str = "private_key.pem"
|
168
|
-
) -> ec.EllipticCurvePrivateKey:
|
169
|
-
"""Get or create the private key."""
|
170
|
-
if not exists(path):
|
171
|
-
self.private_key = ec.generate_private_key(
|
172
|
-
ec.SECP256R1(), default_backend()
|
173
|
-
)
|
174
|
-
# save the key
|
175
|
-
pem = self.private_key.private_bytes(
|
176
|
-
encoding=serialization.Encoding.PEM,
|
177
|
-
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
178
|
-
encryption_algorithm=serialization.NoEncryption(),
|
179
|
-
)
|
180
|
-
async with aiofiles.open(path, "wb") as key_file:
|
181
|
-
await key_file.write(pem)
|
182
|
-
else:
|
183
|
-
try:
|
184
|
-
async with aiofiles.open(path, "rb") as key_file:
|
185
|
-
key_data = await key_file.read()
|
186
|
-
value = serialization.load_pem_private_key(
|
187
|
-
key_data, password=None, backend=default_backend()
|
188
|
-
)
|
189
|
-
except FileNotFoundError:
|
190
|
-
raise FileNotFoundError(f"Private key file not found at {path}")
|
191
|
-
except PermissionError:
|
192
|
-
raise PermissionError(f"Permission denied when trying to read {path}")
|
193
|
-
|
194
|
-
if not isinstance(value, ec.EllipticCurvePrivateKey):
|
195
|
-
raise AssertionError("Loaded key is not an EllipticCurvePrivateKey")
|
196
|
-
self.private_key = value
|
197
|
-
return self.private_key
|
198
|
-
|
199
|
-
@property
|
200
|
-
def has_private_key(self) -> bool:
|
201
|
-
"""Check if the private key has been set."""
|
202
|
-
return self.private_key is not None
|
@@ -2,16 +2,15 @@ from typing import Any
|
|
2
2
|
import aiohttp
|
3
3
|
import time
|
4
4
|
|
5
|
-
from
|
6
|
-
from
|
7
|
-
from .const import Scope, SERVERS
|
5
|
+
from . import TeslaFleetApi
|
6
|
+
from ..const import Scope, SERVERS, Method
|
8
7
|
|
9
8
|
|
10
9
|
class TeslaFleetOAuth(TeslaFleetApi):
|
11
10
|
"""Tesla Fleet OAuth API."""
|
12
11
|
|
13
12
|
expires: int
|
14
|
-
refresh_token: str
|
13
|
+
refresh_token: str | None
|
15
14
|
redirect_uri: str | None
|
16
15
|
_client_secret: str | None
|
17
16
|
|
@@ -0,0 +1,52 @@
|
|
1
|
+
"""Tesla Fleet API for Python."""
|
2
|
+
|
3
|
+
from os.path import exists
|
4
|
+
import aiofiles
|
5
|
+
|
6
|
+
# cryptography
|
7
|
+
from cryptography.hazmat.primitives.asymmetric import ec
|
8
|
+
from cryptography.hazmat.primitives import serialization
|
9
|
+
from cryptography.hazmat.backends import default_backend
|
10
|
+
|
11
|
+
class Tesla:
|
12
|
+
"""Base class describing interactions with Tesla products."""
|
13
|
+
|
14
|
+
private_key: ec.EllipticCurvePrivateKey | None = None
|
15
|
+
|
16
|
+
async def get_private_key(
|
17
|
+
self, path: str = "private_key.pem"
|
18
|
+
) -> ec.EllipticCurvePrivateKey:
|
19
|
+
"""Get or create the private key."""
|
20
|
+
if not exists(path):
|
21
|
+
self.private_key = ec.generate_private_key(
|
22
|
+
ec.SECP256R1(), default_backend()
|
23
|
+
)
|
24
|
+
# save the key
|
25
|
+
pem = self.private_key.private_bytes(
|
26
|
+
encoding=serialization.Encoding.PEM,
|
27
|
+
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
28
|
+
encryption_algorithm=serialization.NoEncryption(),
|
29
|
+
)
|
30
|
+
async with aiofiles.open(path, "wb") as key_file:
|
31
|
+
await key_file.write(pem)
|
32
|
+
else:
|
33
|
+
try:
|
34
|
+
async with aiofiles.open(path, "rb") as key_file:
|
35
|
+
key_data = await key_file.read()
|
36
|
+
value = serialization.load_pem_private_key(
|
37
|
+
key_data, password=None, backend=default_backend()
|
38
|
+
)
|
39
|
+
except FileNotFoundError:
|
40
|
+
raise FileNotFoundError(f"Private key file not found at {path}")
|
41
|
+
except PermissionError:
|
42
|
+
raise PermissionError(f"Permission denied when trying to read {path}")
|
43
|
+
|
44
|
+
if not isinstance(value, ec.EllipticCurvePrivateKey):
|
45
|
+
raise AssertionError("Loaded key is not an EllipticCurvePrivateKey")
|
46
|
+
self.private_key = value
|
47
|
+
return self.private_key
|
48
|
+
|
49
|
+
@property
|
50
|
+
def has_private_key(self) -> bool:
|
51
|
+
"""Check if the private key has been set."""
|
52
|
+
return self.private_key is not None
|
@@ -0,0 +1,13 @@
|
|
1
|
+
"""Tesla Fleet API classes."""
|
2
|
+
|
3
|
+
from .vehicles import Vehicles
|
4
|
+
from .fleet import VehicleFleet
|
5
|
+
from .bluetooth import VehicleBluetooth
|
6
|
+
from .signed import VehicleSigned
|
7
|
+
|
8
|
+
__all__ = [
|
9
|
+
"Vehicles",
|
10
|
+
"VehicleFleet",
|
11
|
+
"VehicleBluetooth",
|
12
|
+
"VehicleSigned",
|
13
|
+
]
|
@@ -0,0 +1,219 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import hashlib
|
4
|
+
from asyncio import Future, get_running_loop
|
5
|
+
from typing import TYPE_CHECKING
|
6
|
+
from google.protobuf.message import DecodeError
|
7
|
+
|
8
|
+
from bleak import BleakClient, BleakScanner
|
9
|
+
from bleak.backends.characteristic import BleakGATTCharacteristic
|
10
|
+
from bleak.backends.device import BLEDevice
|
11
|
+
from cryptography.hazmat.primitives.asymmetric import ec
|
12
|
+
|
13
|
+
from tesla_fleet_api.tesla.vehicle.proto.keys_pb2 import Role
|
14
|
+
|
15
|
+
from .commands import Commands
|
16
|
+
|
17
|
+
from ...const import (
|
18
|
+
LOGGER,
|
19
|
+
)
|
20
|
+
from ...exceptions import (
|
21
|
+
MESSAGE_FAULTS,
|
22
|
+
WHITELIST_OPERATION_STATUS,
|
23
|
+
)
|
24
|
+
|
25
|
+
# Protocol
|
26
|
+
from .proto.car_server_pb2 import (
|
27
|
+
Response,
|
28
|
+
)
|
29
|
+
from .proto.signatures_pb2 import (
|
30
|
+
SessionInfo,
|
31
|
+
)
|
32
|
+
from .proto.universal_message_pb2 import (
|
33
|
+
Destination,
|
34
|
+
Domain,
|
35
|
+
RoutableMessage,
|
36
|
+
)
|
37
|
+
from .proto.vcsec_pb2 import (
|
38
|
+
FromVCSECMessage,
|
39
|
+
KeyFormFactor,
|
40
|
+
KeyMetadata,
|
41
|
+
PermissionChange,
|
42
|
+
PublicKey,
|
43
|
+
UnsignedMessage,
|
44
|
+
WhitelistOperation,
|
45
|
+
|
46
|
+
)
|
47
|
+
|
48
|
+
SERVICE_UUID = "00000211-b2d1-43f0-9b88-960cebf8b91e"
|
49
|
+
WRITE_UUID = "00000212-b2d1-43f0-9b88-960cebf8b91e"
|
50
|
+
READ_UUID = "00000213-b2d1-43f0-9b88-960cebf8b91e"
|
51
|
+
VERSION_UUID = "00000214-b2d1-43f0-9b88-960cebf8b91e"
|
52
|
+
|
53
|
+
if TYPE_CHECKING:
|
54
|
+
from ..tesla import Tesla
|
55
|
+
|
56
|
+
def prependLength(message: bytes) -> bytearray:
|
57
|
+
"""Prepend a 2-byte length to the payload."""
|
58
|
+
return bytearray([len(message) >> 8, len(message) & 0xFF]) + message
|
59
|
+
|
60
|
+
class VehicleBluetooth(Commands):
|
61
|
+
"""Class describing the Tesla Fleet API vehicle endpoints and commands for a specific vehicle with command signing."""
|
62
|
+
|
63
|
+
ble_name: str
|
64
|
+
client: BleakClient
|
65
|
+
_device: BLEDevice
|
66
|
+
_futures: dict[Domain, Future]
|
67
|
+
_ekey: ec.EllipticCurvePublicKey
|
68
|
+
_recv: bytearray = bytearray()
|
69
|
+
_recv_len: int = 0
|
70
|
+
_auth_method = "aes"
|
71
|
+
|
72
|
+
def __init__(
|
73
|
+
self, parent: Tesla, vin: str, key: ec.EllipticCurvePrivateKey | None = None
|
74
|
+
):
|
75
|
+
super().__init__(parent, vin, key)
|
76
|
+
self.ble_name = "S" + hashlib.sha1(vin.encode('utf-8')).hexdigest()[:16] + "C"
|
77
|
+
self._futures = {}
|
78
|
+
|
79
|
+
async def discover(self, scanner: BleakScanner = BleakScanner()) -> BleakClient:
|
80
|
+
"""Find the Tesla BLE device."""
|
81
|
+
|
82
|
+
device = await scanner.find_device_by_name(self.ble_name)
|
83
|
+
if not device:
|
84
|
+
raise ValueError(f"Device {self.ble_name} not found")
|
85
|
+
self._device = device
|
86
|
+
self.client = BleakClient(self._device, services=[SERVICE_UUID])
|
87
|
+
LOGGER.debug(f"Discovered device {self._device.name} {self._device.address}")
|
88
|
+
return self.client
|
89
|
+
|
90
|
+
def create_client(self, mac:str):
|
91
|
+
"""Create a client with a MAC."""
|
92
|
+
self.client = BleakClient(mac, services=[SERVICE_UUID])
|
93
|
+
return self.client
|
94
|
+
|
95
|
+
async def connect(self, mac:str | None = None) -> None:
|
96
|
+
"""Connect to the Tesla BLE device."""
|
97
|
+
if mac is not None:
|
98
|
+
self.create_client(mac)
|
99
|
+
await self.client.connect()
|
100
|
+
await self.client.start_notify(READ_UUID, self._on_notify)
|
101
|
+
|
102
|
+
async def disconnect(self) -> bool:
|
103
|
+
"""Disconnect from the Tesla BLE device."""
|
104
|
+
return await self.client.disconnect()
|
105
|
+
|
106
|
+
async def __aenter__(self) -> VehicleBluetooth:
|
107
|
+
"""Enter the async context."""
|
108
|
+
await self.connect()
|
109
|
+
return self
|
110
|
+
|
111
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
112
|
+
"""Exit the async context."""
|
113
|
+
await self.disconnect()
|
114
|
+
|
115
|
+
def _on_notify(self,sender: BleakGATTCharacteristic,data : bytearray):
|
116
|
+
"""Receive data from the Tesla BLE device."""
|
117
|
+
if self._recv_len:
|
118
|
+
self._recv += data
|
119
|
+
else:
|
120
|
+
self._recv_len = int.from_bytes(data[:2], 'big')
|
121
|
+
self._recv = data[2:]
|
122
|
+
LOGGER.debug(f"Received {len(self._recv)} of {self._recv_len} bytes")
|
123
|
+
while len(self._recv) > self._recv_len:
|
124
|
+
LOGGER.warn(f"Received more data than expected: {len(self._recv)} > {self._recv_len}")
|
125
|
+
self._on_message(bytes(self._recv[:self._recv_len]))
|
126
|
+
self._recv_len = int.from_bytes(self._recv[self._recv_len:self._recv_len+2], 'big')
|
127
|
+
self._recv = self._recv[self._recv_len+2:]
|
128
|
+
continue
|
129
|
+
if len(self._recv) == self._recv_len:
|
130
|
+
self._on_message(bytes(self._recv))
|
131
|
+
self._recv = bytearray()
|
132
|
+
self._recv_len = 0
|
133
|
+
|
134
|
+
def _on_message(self, data:bytes):
|
135
|
+
"""Receive messages from the Tesla BLE data."""
|
136
|
+
try:
|
137
|
+
msg = RoutableMessage.FromString(data)
|
138
|
+
except DecodeError as e:
|
139
|
+
LOGGER.error(f"Error parsing message: {e}")
|
140
|
+
return
|
141
|
+
|
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
|
+
if(msg.to_destination.routing_address != self._from_destination):
|
149
|
+
# Get the ephemeral key here and save to self._ekey
|
150
|
+
return
|
151
|
+
|
152
|
+
if msg.from_destination.domain == Domain.DOMAIN_VEHICLE_SECURITY:
|
153
|
+
submsg = FromVCSECMessage.FromString(msg.protobuf_message_as_bytes)
|
154
|
+
print(submsg)
|
155
|
+
elif msg.from_destination.domain == Domain.DOMAIN_INFOTAINMENT:
|
156
|
+
submsg = Response.FromString(msg.protobuf_message_as_bytes)
|
157
|
+
print(submsg)
|
158
|
+
|
159
|
+
if(self._futures[msg.from_destination.domain]):
|
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:
|
171
|
+
"""Serialize a message and send to the vehicle and wait for a response."""
|
172
|
+
domain = msg.to_destination.domain
|
173
|
+
async with self._sessions[domain].lock:
|
174
|
+
LOGGER.debug(f"Sending message {msg}")
|
175
|
+
future = await self._create_future(domain)
|
176
|
+
payload = prependLength(msg.SerializeToString())
|
177
|
+
|
178
|
+
await self.client.write_gatt_char(WRITE_UUID, payload, True)
|
179
|
+
|
180
|
+
resp = await future
|
181
|
+
LOGGER.debug(f"Received message {resp}")
|
182
|
+
|
183
|
+
if resp.signedMessageStatus.signed_message_fault:
|
184
|
+
raise MESSAGE_FAULTS[resp.signedMessageStatus.signed_message_fault]
|
185
|
+
|
186
|
+
return resp
|
187
|
+
|
188
|
+
async def pair(self, role: Role = Role.ROLE_OWNER, form: KeyFormFactor = KeyFormFactor.KEY_FORM_FACTOR_CLOUD_KEY):
|
189
|
+
"""Pair the key."""
|
190
|
+
|
191
|
+
request = UnsignedMessage(
|
192
|
+
WhitelistOperation=WhitelistOperation(
|
193
|
+
addKeyToWhitelistAndAddPermissions=PermissionChange(
|
194
|
+
key=PublicKey(PublicKeyRaw=self._public_key),
|
195
|
+
keyRole=role
|
196
|
+
),
|
197
|
+
metadataForKey=KeyMetadata(
|
198
|
+
keyFormFactor=form
|
199
|
+
)
|
200
|
+
)
|
201
|
+
)
|
202
|
+
msg = RoutableMessage(
|
203
|
+
to_destination=Destination(
|
204
|
+
domain=Domain.DOMAIN_VEHICLE_SECURITY
|
205
|
+
),
|
206
|
+
from_destination=Destination(
|
207
|
+
routing_address=self._from_destination
|
208
|
+
),
|
209
|
+
protobuf_message_as_bytes=request.SerializeToString(),
|
210
|
+
)
|
211
|
+
resp = await self._send(msg)
|
212
|
+
respMsg = FromVCSECMessage.FromString(resp.protobuf_message_as_bytes)
|
213
|
+
print(respMsg)
|
214
|
+
if(respMsg.commandStatus.whitelistOperationStatus.whitelistOperationInformation):
|
215
|
+
if(respMsg.commandStatus.whitelistOperationStatus.whitelistOperationInformation < len(WHITELIST_OPERATION_STATUS)):
|
216
|
+
raise WHITELIST_OPERATION_STATUS[respMsg.commandStatus.whitelistOperationStatus.whitelistOperationInformation]
|
217
|
+
else:
|
218
|
+
raise ValueError(f"Unknown whitelist operation status: {respMsg.commandStatus.whitelistOperationStatus.whitelistOperationInformation}")
|
219
|
+
return
|