tesla-fleet-api 0.9.10__py3-none-any.whl → 1.0.1__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 +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
|