remotivelabs-cli 0.5.0a1__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.
- remotivelabs/cli/__init__.py +0 -0
- remotivelabs/cli/api/cloud/tokens.py +62 -0
- remotivelabs/cli/broker/__init__.py +33 -0
- remotivelabs/cli/broker/defaults.py +1 -0
- remotivelabs/cli/broker/discovery.py +43 -0
- remotivelabs/cli/broker/export.py +92 -0
- remotivelabs/cli/broker/files.py +119 -0
- remotivelabs/cli/broker/lib/__about__.py +4 -0
- remotivelabs/cli/broker/lib/broker.py +625 -0
- remotivelabs/cli/broker/lib/client.py +224 -0
- remotivelabs/cli/broker/lib/helper.py +277 -0
- remotivelabs/cli/broker/lib/signalcreator.py +196 -0
- remotivelabs/cli/broker/license_flows.py +167 -0
- remotivelabs/cli/broker/licenses.py +98 -0
- remotivelabs/cli/broker/playback.py +117 -0
- remotivelabs/cli/broker/record.py +41 -0
- remotivelabs/cli/broker/recording_session/__init__.py +3 -0
- remotivelabs/cli/broker/recording_session/client.py +67 -0
- remotivelabs/cli/broker/recording_session/cmd.py +254 -0
- remotivelabs/cli/broker/recording_session/time.py +49 -0
- remotivelabs/cli/broker/scripting.py +129 -0
- remotivelabs/cli/broker/signals.py +220 -0
- remotivelabs/cli/broker/version.py +31 -0
- remotivelabs/cli/cloud/__init__.py +17 -0
- remotivelabs/cli/cloud/auth/__init__.py +3 -0
- remotivelabs/cli/cloud/auth/cmd.py +128 -0
- remotivelabs/cli/cloud/auth/login.py +283 -0
- remotivelabs/cli/cloud/auth_tokens.py +149 -0
- remotivelabs/cli/cloud/brokers.py +109 -0
- remotivelabs/cli/cloud/configs.py +109 -0
- remotivelabs/cli/cloud/licenses/__init__.py +0 -0
- remotivelabs/cli/cloud/licenses/cmd.py +14 -0
- remotivelabs/cli/cloud/organisations.py +112 -0
- remotivelabs/cli/cloud/projects.py +44 -0
- remotivelabs/cli/cloud/recordings.py +580 -0
- remotivelabs/cli/cloud/recordings_playback.py +274 -0
- remotivelabs/cli/cloud/resumable_upload.py +87 -0
- remotivelabs/cli/cloud/sample_recordings.py +25 -0
- remotivelabs/cli/cloud/service_account_tokens.py +62 -0
- remotivelabs/cli/cloud/service_accounts.py +72 -0
- remotivelabs/cli/cloud/storage/__init__.py +5 -0
- remotivelabs/cli/cloud/storage/cmd.py +76 -0
- remotivelabs/cli/cloud/storage/copy.py +86 -0
- remotivelabs/cli/cloud/storage/uri_or_path.py +45 -0
- remotivelabs/cli/cloud/uri.py +113 -0
- remotivelabs/cli/connect/__init__.py +0 -0
- remotivelabs/cli/connect/connect.py +118 -0
- remotivelabs/cli/connect/protopie/protopie.py +185 -0
- remotivelabs/cli/py.typed +0 -0
- remotivelabs/cli/remotive.py +123 -0
- remotivelabs/cli/settings/__init__.py +20 -0
- remotivelabs/cli/settings/config_file.py +113 -0
- remotivelabs/cli/settings/core.py +333 -0
- remotivelabs/cli/settings/migration/__init__.py +0 -0
- remotivelabs/cli/settings/migration/migrate_all_token_files.py +80 -0
- remotivelabs/cli/settings/migration/migrate_config_file.py +64 -0
- remotivelabs/cli/settings/migration/migrate_legacy_dirs.py +50 -0
- remotivelabs/cli/settings/migration/migrate_token_file.py +52 -0
- remotivelabs/cli/settings/migration/migration_tools.py +38 -0
- remotivelabs/cli/settings/state_file.py +67 -0
- remotivelabs/cli/settings/token_file.py +128 -0
- remotivelabs/cli/tools/__init__.py +0 -0
- remotivelabs/cli/tools/can/__init__.py +0 -0
- remotivelabs/cli/tools/can/can.py +78 -0
- remotivelabs/cli/tools/tools.py +9 -0
- remotivelabs/cli/topology/__init__.py +28 -0
- remotivelabs/cli/topology/all.py +322 -0
- remotivelabs/cli/topology/cli/__init__.py +3 -0
- remotivelabs/cli/topology/cli/run_in_docker.py +58 -0
- remotivelabs/cli/topology/cli/topology_cli.py +16 -0
- remotivelabs/cli/topology/cmd.py +130 -0
- remotivelabs/cli/topology/start_trial.py +134 -0
- remotivelabs/cli/typer/__init__.py +0 -0
- remotivelabs/cli/typer/typer_utils.py +27 -0
- remotivelabs/cli/utils/__init__.py +0 -0
- remotivelabs/cli/utils/console.py +99 -0
- remotivelabs/cli/utils/rest_helper.py +369 -0
- remotivelabs/cli/utils/time.py +11 -0
- remotivelabs/cli/utils/versions.py +120 -0
- remotivelabs_cli-0.5.0a1.dist-info/METADATA +51 -0
- remotivelabs_cli-0.5.0a1.dist-info/RECORD +84 -0
- remotivelabs_cli-0.5.0a1.dist-info/WHEEL +4 -0
- remotivelabs_cli-0.5.0a1.dist-info/entry_points.txt +3 -0
- remotivelabs_cli-0.5.0a1.dist-info/licenses/LICENSE +17 -0
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import binascii
|
|
4
|
+
import hashlib
|
|
5
|
+
import itertools
|
|
6
|
+
import ntpath
|
|
7
|
+
import os
|
|
8
|
+
import posixpath
|
|
9
|
+
import queue
|
|
10
|
+
import signal as os_signal
|
|
11
|
+
import sys
|
|
12
|
+
import tempfile
|
|
13
|
+
import time
|
|
14
|
+
import zipfile
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from glob import glob
|
|
17
|
+
from threading import Thread
|
|
18
|
+
from typing import Any, BinaryIO, Callable, Dict, Generator, Iterable, List, Sequence, Union
|
|
19
|
+
|
|
20
|
+
import grpc
|
|
21
|
+
import typer
|
|
22
|
+
from google.protobuf.json_format import MessageToDict
|
|
23
|
+
|
|
24
|
+
import remotivelabs.broker._generated.common_pb2 as common
|
|
25
|
+
import remotivelabs.broker._generated.network_api_pb2 as network_api
|
|
26
|
+
import remotivelabs.broker._generated.network_api_pb2_grpc as network_api_grpc
|
|
27
|
+
import remotivelabs.broker._generated.system_api_pb2 as system_api
|
|
28
|
+
import remotivelabs.broker._generated.system_api_pb2_grpc as system_api_grpc
|
|
29
|
+
import remotivelabs.broker._generated.traffic_api_pb2 as traffic_api
|
|
30
|
+
import remotivelabs.broker._generated.traffic_api_pb2_grpc as traffic_api_grpc
|
|
31
|
+
from remotivelabs.cli.broker.lib.helper import act_on_scripted_signal, act_on_signal, create_channel
|
|
32
|
+
from remotivelabs.cli.broker.lib.signalcreator import SignalCreator
|
|
33
|
+
from remotivelabs.cli.settings import settings
|
|
34
|
+
from remotivelabs.cli.utils.console import print_generic_error, print_hint, print_success
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class SubscribableSignal:
|
|
39
|
+
name: str
|
|
40
|
+
namespace: str
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class LicenseInfo:
|
|
45
|
+
valid: bool
|
|
46
|
+
expires: str
|
|
47
|
+
email: str
|
|
48
|
+
machine_id: str
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_sha256(path: str) -> str:
|
|
52
|
+
"""
|
|
53
|
+
Calculate SHA256 for a file.
|
|
54
|
+
"""
|
|
55
|
+
with open(path, "rb") as f:
|
|
56
|
+
b = f.read() # read entire file as bytes
|
|
57
|
+
return hashlib.sha256(b).hexdigest()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def generate_data(file: BinaryIO, dest_path: str, chunk_size: int, sha256: str) -> Generator[system_api.FileUploadRequest, None, None]:
|
|
61
|
+
for x in itertools.count(start=0):
|
|
62
|
+
if x == 0:
|
|
63
|
+
file_description = system_api.FileDescription(sha256=sha256, path=dest_path)
|
|
64
|
+
yield system_api.FileUploadRequest(fileDescription=file_description)
|
|
65
|
+
else:
|
|
66
|
+
buf = file.read(chunk_size)
|
|
67
|
+
if not buf:
|
|
68
|
+
break
|
|
69
|
+
yield system_api.FileUploadRequest(chunk=buf)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class Broker:
|
|
73
|
+
def __init__(self, url: str, api_key: Union[str, None] = None) -> None:
|
|
74
|
+
self.url = url
|
|
75
|
+
self.api_key = api_key
|
|
76
|
+
self.q: queue.Queue[Any] = queue.Queue()
|
|
77
|
+
"""Main function, checking arguments passed to script, setting up stubs, configuration and starting Threads."""
|
|
78
|
+
# Setting up stubs and configuration
|
|
79
|
+
|
|
80
|
+
if api_key is None or api_key == "":
|
|
81
|
+
if url.startswith("https"):
|
|
82
|
+
self.intercept_channel = create_channel(url, None, settings.get_active_token())
|
|
83
|
+
# TODO - Temporary solution to print proper error message, remove ENV once api-key is gone
|
|
84
|
+
os.environ["ACCESS_TOKEN"] = "true"
|
|
85
|
+
else:
|
|
86
|
+
os.environ["ACCESS_TOKEN"] = "false"
|
|
87
|
+
self.intercept_channel = create_channel(url, None, None)
|
|
88
|
+
else:
|
|
89
|
+
print_hint("Option --api-key will is deprecated and will be removed. Use access access tokens by logging in with cli.")
|
|
90
|
+
os.environ["ACCESS_TOKEN"] = "false"
|
|
91
|
+
self.intercept_channel = create_channel(url, api_key, None)
|
|
92
|
+
|
|
93
|
+
self.network_stub = network_api_grpc.NetworkServiceStub(self.intercept_channel)
|
|
94
|
+
self.system_stub = system_api_grpc.SystemServiceStub(self.intercept_channel)
|
|
95
|
+
self.traffic_stub = traffic_api_grpc.TrafficServiceStub(self.intercept_channel)
|
|
96
|
+
self.signal_creator = SignalCreator(self.system_stub)
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def __check_playbackmode_result(status: traffic_api.PlaybackInfos) -> None:
|
|
100
|
+
err_cnt = 0
|
|
101
|
+
for mode in status.playbackInfo:
|
|
102
|
+
if mode.playbackMode.errorMessage:
|
|
103
|
+
print_generic_error(mode.playbackMode.errorMessage)
|
|
104
|
+
err_cnt = err_cnt + 1
|
|
105
|
+
if err_cnt > 0:
|
|
106
|
+
raise typer.Exit(1)
|
|
107
|
+
|
|
108
|
+
def seek(self, recording_and_namespace: List[Any], offset: int, silent: bool = True) -> traffic_api.PlaybackInfos:
|
|
109
|
+
def to_playback(rec: Any) -> Dict[str, Any]:
|
|
110
|
+
return {"namespace": rec["namespace"], "path": rec["recording"], "mode": traffic_api.Mode.SEEK, "offsettime": offset}
|
|
111
|
+
|
|
112
|
+
playback_list = map(to_playback, recording_and_namespace)
|
|
113
|
+
|
|
114
|
+
infos = traffic_api.PlaybackInfos(playbackInfo=list(map(self.__create_playback_config, playback_list)))
|
|
115
|
+
status: traffic_api.PlaybackInfos = self.traffic_stub.PlayTraffic(infos)
|
|
116
|
+
if not silent:
|
|
117
|
+
self.__check_playbackmode_result(status)
|
|
118
|
+
return status
|
|
119
|
+
|
|
120
|
+
def play(self, recording_and_namespace: List[Any], silent: bool = False) -> traffic_api.PlaybackInfos:
|
|
121
|
+
def to_playback(rec: Any) -> Dict[str, Any]:
|
|
122
|
+
return {
|
|
123
|
+
"namespace": rec["namespace"],
|
|
124
|
+
"path": rec["recording"],
|
|
125
|
+
"mode": traffic_api.Mode.PLAY,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
playback_list = map(to_playback, recording_and_namespace)
|
|
129
|
+
|
|
130
|
+
status: traffic_api.PlaybackInfos = self.traffic_stub.PlayTraffic(
|
|
131
|
+
traffic_api.PlaybackInfos(playbackInfo=list(map(self.__create_playback_config, playback_list)))
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
if not silent:
|
|
135
|
+
self.__check_playbackmode_result(status)
|
|
136
|
+
return status
|
|
137
|
+
|
|
138
|
+
def stop_play(self, recording_and_namespace: List[Any], silent: bool = False) -> traffic_api.PlaybackInfos:
|
|
139
|
+
def to_playback(rec: Any) -> Dict[str, Any]:
|
|
140
|
+
return {
|
|
141
|
+
"namespace": rec["namespace"],
|
|
142
|
+
"path": rec["recording"],
|
|
143
|
+
"mode": traffic_api.Mode.STOP,
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
playback_list = map(to_playback, recording_and_namespace)
|
|
147
|
+
|
|
148
|
+
status: traffic_api.PlaybackInfos = self.traffic_stub.PlayTraffic(
|
|
149
|
+
traffic_api.PlaybackInfos(playbackInfo=list(map(self.__create_playback_config, playback_list)))
|
|
150
|
+
)
|
|
151
|
+
if not silent:
|
|
152
|
+
self.__check_playbackmode_result(status)
|
|
153
|
+
return status
|
|
154
|
+
|
|
155
|
+
def pause_play(self, recording_and_namespace: List[Any], silent: bool = False) -> traffic_api.PlaybackInfos:
|
|
156
|
+
def to_playback(rec: Any) -> Dict[str, Any]:
|
|
157
|
+
return {
|
|
158
|
+
"namespace": rec["namespace"],
|
|
159
|
+
"path": rec["recording"],
|
|
160
|
+
"mode": traffic_api.Mode.PAUSE,
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
playback_list = map(to_playback, recording_and_namespace)
|
|
164
|
+
|
|
165
|
+
status: traffic_api.PlaybackInfos = self.traffic_stub.PlayTraffic(
|
|
166
|
+
traffic_api.PlaybackInfos(playbackInfo=list(map(self.__create_playback_config, playback_list)))
|
|
167
|
+
)
|
|
168
|
+
if not silent:
|
|
169
|
+
self.__check_playbackmode_result(status)
|
|
170
|
+
return status
|
|
171
|
+
|
|
172
|
+
def record_multiple(self, namespaces: List[str], path: str) -> traffic_api.PlaybackInfos:
|
|
173
|
+
def to_playback(namespace: str) -> Dict[str, Any]:
|
|
174
|
+
return {
|
|
175
|
+
"namespace": namespace,
|
|
176
|
+
"path": path + "_" + namespace,
|
|
177
|
+
"mode": traffic_api.Mode.RECORD,
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
playback_list = list(map(to_playback, namespaces))
|
|
181
|
+
|
|
182
|
+
status: traffic_api.PlaybackInfos = self.traffic_stub.PlayTraffic(
|
|
183
|
+
traffic_api.PlaybackInfos(playbackInfo=list(map(self.__create_playback_config, playback_list)))
|
|
184
|
+
)
|
|
185
|
+
self.__check_playbackmode_result(status)
|
|
186
|
+
return status
|
|
187
|
+
|
|
188
|
+
def record(self, namespace: str, path: str) -> traffic_api.PlaybackInfos:
|
|
189
|
+
playback_list = [
|
|
190
|
+
{
|
|
191
|
+
"namespace": namespace,
|
|
192
|
+
"path": path,
|
|
193
|
+
"mode": traffic_api.Mode.RECORD,
|
|
194
|
+
}
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
status: traffic_api.PlaybackInfos = self.traffic_stub.PlayTraffic(
|
|
198
|
+
traffic_api.PlaybackInfos(playbackInfo=list(map(self.__create_playback_config, playback_list)))
|
|
199
|
+
)
|
|
200
|
+
self.__check_playbackmode_result(status)
|
|
201
|
+
return status
|
|
202
|
+
|
|
203
|
+
def stop(self, namespace: str, path: str, silent: bool = False) -> traffic_api.PlaybackInfos:
|
|
204
|
+
playback_list = [
|
|
205
|
+
{
|
|
206
|
+
"namespace": namespace,
|
|
207
|
+
"path": path,
|
|
208
|
+
"mode": traffic_api.Mode.STOP,
|
|
209
|
+
}
|
|
210
|
+
]
|
|
211
|
+
|
|
212
|
+
status: traffic_api.PlaybackInfos = self.traffic_stub.PlayTraffic(
|
|
213
|
+
traffic_api.PlaybackInfos(playbackInfo=list(map(self.__create_playback_config, playback_list)))
|
|
214
|
+
)
|
|
215
|
+
if not silent:
|
|
216
|
+
self.__check_playbackmode_result(status)
|
|
217
|
+
return status
|
|
218
|
+
|
|
219
|
+
def listen_on_playback(self, repeat: bool, recording_and_namespace: List[Any], callback: Callable[[int, int, str], None]) -> None:
|
|
220
|
+
# include recording_and_namespace if we want to loop the recording
|
|
221
|
+
# This can probably be improved
|
|
222
|
+
def get_mode(mode: int) -> str:
|
|
223
|
+
if mode == 0:
|
|
224
|
+
return "playing"
|
|
225
|
+
if mode == 1:
|
|
226
|
+
return "paused"
|
|
227
|
+
if mode == 2:
|
|
228
|
+
return "stopped"
|
|
229
|
+
raise ValueError("Unknown Mode")
|
|
230
|
+
|
|
231
|
+
sub = self.traffic_stub.PlayTrafficStatus(common.Empty())
|
|
232
|
+
for playback_state in sub:
|
|
233
|
+
# p = typing.cast(br.traffic_api_pb2.PlaybackInfos, playback_state) # REDUNDANT CAST
|
|
234
|
+
p = playback_state
|
|
235
|
+
offset_length = int(p.playbackInfo[0].playbackMode.offsetTime / 1000000)
|
|
236
|
+
start_time = p.playbackInfo[0].playbackMode.startTime
|
|
237
|
+
end_time = p.playbackInfo[0].playbackMode.endTime
|
|
238
|
+
mode = p.playbackInfo[0].playbackMode.mode
|
|
239
|
+
|
|
240
|
+
total_length = int((end_time - start_time) / 1000000)
|
|
241
|
+
|
|
242
|
+
if mode == 2 and repeat:
|
|
243
|
+
# If we get a stop and is fairly (this is mostly not 100%) close to the end
|
|
244
|
+
# we repeat the recording when files are included
|
|
245
|
+
if abs(total_length - offset_length) < 5:
|
|
246
|
+
self.play(recording_and_namespace)
|
|
247
|
+
callback(offset_length, total_length, get_mode(mode))
|
|
248
|
+
|
|
249
|
+
def listen_on_frame_distribution(self, namespace: str, callback: Callable[[Dict[str, Any]], None]) -> None:
|
|
250
|
+
config = network_api.FramesDistributionConfig(namespace=common.NameSpace(name=namespace))
|
|
251
|
+
frame_distribution_stream = self.network_stub.SubscribeToFramesDistribution(config)
|
|
252
|
+
for frame in frame_distribution_stream:
|
|
253
|
+
f = MessageToDict(frame, preserving_proto_field_name=True)
|
|
254
|
+
callback(f)
|
|
255
|
+
|
|
256
|
+
def pause(self, namespace: str, path: str, silent: bool = False) -> traffic_api.PlaybackInfos:
|
|
257
|
+
playback_list = [
|
|
258
|
+
{
|
|
259
|
+
"namespace": namespace,
|
|
260
|
+
"path": path,
|
|
261
|
+
"mode": traffic_api.Mode.PAUSE,
|
|
262
|
+
}
|
|
263
|
+
]
|
|
264
|
+
|
|
265
|
+
status: traffic_api.PlaybackInfos = self.traffic_stub.PlayTraffic(
|
|
266
|
+
traffic_api.PlaybackInfos(playbackInfo=list(map(self.__create_playback_config, playback_list)))
|
|
267
|
+
)
|
|
268
|
+
if not silent:
|
|
269
|
+
self.__check_playbackmode_result(status)
|
|
270
|
+
return status
|
|
271
|
+
|
|
272
|
+
def stop_multiple(self, namespaces: List[str], path: str) -> traffic_api.PlaybackInfos:
|
|
273
|
+
def to_playback(namespace: str) -> Dict[str, Any]:
|
|
274
|
+
return {
|
|
275
|
+
"namespace": namespace,
|
|
276
|
+
"path": path + "_" + namespace,
|
|
277
|
+
"mode": traffic_api.Mode.STOP,
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
playback_list = list(map(to_playback, namespaces))
|
|
281
|
+
|
|
282
|
+
status: traffic_api.PlaybackInfos = self.traffic_stub.PlayTraffic(
|
|
283
|
+
traffic_api.PlaybackInfos(playbackInfo=list(map(self.__create_playback_config, playback_list)))
|
|
284
|
+
)
|
|
285
|
+
self.__check_playbackmode_result(status)
|
|
286
|
+
return status
|
|
287
|
+
|
|
288
|
+
def diagnose_stop(self, namespace: List[str]) -> None:
|
|
289
|
+
recording_name = "diagnose__"
|
|
290
|
+
self.stop_multiple(namespace, recording_name)
|
|
291
|
+
|
|
292
|
+
def diagnose(self, namespace: List[str], wait_for_traffic: bool = False) -> None:
|
|
293
|
+
recording_name = "diagnose__"
|
|
294
|
+
|
|
295
|
+
keep_running = True
|
|
296
|
+
keep_running_during_recording = True
|
|
297
|
+
|
|
298
|
+
def exit_on_ctrlc(_sig: Any, _frame: Any) -> None:
|
|
299
|
+
nonlocal keep_running
|
|
300
|
+
keep_running = False
|
|
301
|
+
nonlocal keep_running_during_recording
|
|
302
|
+
keep_running_during_recording = False
|
|
303
|
+
# progress.add_task(description=f"Cleaning up, please wait...", total=None)
|
|
304
|
+
|
|
305
|
+
os_signal.signal(os_signal.SIGINT, exit_on_ctrlc)
|
|
306
|
+
|
|
307
|
+
while keep_running:
|
|
308
|
+
keep_running = wait_for_traffic
|
|
309
|
+
self.record_multiple(namespace, recording_name)
|
|
310
|
+
for i in range(5):
|
|
311
|
+
if keep_running_during_recording:
|
|
312
|
+
time.sleep(1)
|
|
313
|
+
|
|
314
|
+
self.stop_multiple(namespace, recording_name)
|
|
315
|
+
|
|
316
|
+
response = []
|
|
317
|
+
with tempfile.TemporaryDirectory() as tmpdirname:
|
|
318
|
+
for ns in namespace:
|
|
319
|
+
path = recording_name + "_" + ns
|
|
320
|
+
tmp_file = os.path.join(tmpdirname, path)
|
|
321
|
+
self.download(path, tmp_file)
|
|
322
|
+
self.delete_files([path], False)
|
|
323
|
+
with zipfile.ZipFile(tmp_file, "r") as zip_ref:
|
|
324
|
+
zip_ref.extractall(tmpdirname)
|
|
325
|
+
|
|
326
|
+
file_stat = os.stat(os.path.join(tmpdirname, path + ".raw"))
|
|
327
|
+
response.append({"namespace": ns, "data": file_stat.st_size > 0})
|
|
328
|
+
|
|
329
|
+
for r in response:
|
|
330
|
+
if r["data"]:
|
|
331
|
+
print_success(f"Received traffic on {r['namespace']}")
|
|
332
|
+
keep_running = False
|
|
333
|
+
elif not wait_for_traffic or (not keep_running and not keep_running_during_recording):
|
|
334
|
+
print_generic_error(f"Namespace {r['namespace']} did not receive any traffic")
|
|
335
|
+
|
|
336
|
+
def upload(self, file: str, dest: str) -> None:
|
|
337
|
+
sha256 = get_sha256(file)
|
|
338
|
+
with open(file, "rb") as f:
|
|
339
|
+
upload_iterator = generate_data(f, dest.replace(ntpath.sep, posixpath.sep), 1000000, sha256)
|
|
340
|
+
try:
|
|
341
|
+
self.system_stub.UploadFile(upload_iterator, compression=grpc.Compression.Gzip)
|
|
342
|
+
except grpc.RpcError as rpc_error:
|
|
343
|
+
print_generic_error(f"Failed to upload file - {rpc_error.details()} ({rpc_error.code()})")
|
|
344
|
+
raise typer.Exit(1)
|
|
345
|
+
|
|
346
|
+
def delete_files(self, path: List[str], exit_on_faliure: bool) -> None:
|
|
347
|
+
for file in path:
|
|
348
|
+
try:
|
|
349
|
+
self.system_stub.BatchDeleteFiles(
|
|
350
|
+
system_api.FileDescriptions(fileDescriptions=[system_api.FileDescription(path=file.replace(ntpath.sep, posixpath.sep))])
|
|
351
|
+
)
|
|
352
|
+
except grpc.RpcError as rpc_error:
|
|
353
|
+
print_generic_error(f"Failed to delete file - {rpc_error.details()} ({rpc_error.code()})")
|
|
354
|
+
if exit_on_faliure:
|
|
355
|
+
raise typer.Exit(1)
|
|
356
|
+
|
|
357
|
+
def upload_folder(self, folder: str) -> None:
|
|
358
|
+
files = [y for x in os.walk(folder) for y in glob(os.path.join(x[0], "*")) if not os.path.isdir(y)]
|
|
359
|
+
assert len(files) != 0, "Specified upload folder is empty or does not exist"
|
|
360
|
+
for file in files:
|
|
361
|
+
self.upload(file, file.replace(folder, ""))
|
|
362
|
+
|
|
363
|
+
def download(self, file: str, dest: str, delegate_err: bool = False) -> None:
|
|
364
|
+
try:
|
|
365
|
+
with open(dest, "wb") as f:
|
|
366
|
+
for response in self.system_stub.BatchDownloadFiles(
|
|
367
|
+
system_api.FileDescriptions(fileDescriptions=[system_api.FileDescription(path=file.replace(ntpath.sep, posixpath.sep))])
|
|
368
|
+
):
|
|
369
|
+
assert not response.HasField("errorMessage"), f"Error uploading file, message is: {response.errorMessage}"
|
|
370
|
+
f.write(response.chunk)
|
|
371
|
+
except grpc.RpcError as rpc_error:
|
|
372
|
+
if delegate_err:
|
|
373
|
+
raise rpc_error
|
|
374
|
+
print_generic_error(f"Failed to download file - {rpc_error.details()} ({rpc_error.code()})")
|
|
375
|
+
# There will be an empty file if the download fails so remove that one here
|
|
376
|
+
os.remove(dest)
|
|
377
|
+
raise typer.Exit(1)
|
|
378
|
+
|
|
379
|
+
def reload_config(self) -> None:
|
|
380
|
+
try:
|
|
381
|
+
request = common.Empty()
|
|
382
|
+
response = self.system_stub.ReloadConfiguration(request, timeout=60000)
|
|
383
|
+
if response.errorMessage:
|
|
384
|
+
print_generic_error(f"Failed to reload config: {response.errorMessage}")
|
|
385
|
+
raise typer.Exit(1)
|
|
386
|
+
# br.helper.reload_configuration(system_stub=self.system_stub)
|
|
387
|
+
except grpc.RpcError as rpc_error:
|
|
388
|
+
print_generic_error(f"Failed to reload configuration - {rpc_error.details()} ({rpc_error.code()})")
|
|
389
|
+
raise typer.Exit(1)
|
|
390
|
+
|
|
391
|
+
def list_namespaces(self) -> List[str]:
|
|
392
|
+
# Lists available signals
|
|
393
|
+
configuration = self.system_stub.GetConfiguration(common.Empty())
|
|
394
|
+
namespaces = []
|
|
395
|
+
for network_info in configuration.networkInfo:
|
|
396
|
+
namespaces.append(network_info.namespace.name)
|
|
397
|
+
return namespaces
|
|
398
|
+
|
|
399
|
+
def list_signal_names(self, prefix: Union[str, None], suffix: Union[str, None]) -> List[Dict[str, Any]]:
|
|
400
|
+
# Lists available signals
|
|
401
|
+
configuration = self.system_stub.GetConfiguration(common.Empty())
|
|
402
|
+
|
|
403
|
+
signal_names = []
|
|
404
|
+
for network_info in configuration.networkInfo:
|
|
405
|
+
res = self.system_stub.ListSignals(network_info.namespace)
|
|
406
|
+
for finfo in res.frame:
|
|
407
|
+
if (prefix is None or finfo.signalInfo.id.name.startswith(prefix)) and (
|
|
408
|
+
suffix is None or finfo.signalInfo.id.name.endswith(suffix)
|
|
409
|
+
):
|
|
410
|
+
metadata_dict = MessageToDict(
|
|
411
|
+
finfo.signalInfo.metaData,
|
|
412
|
+
preserving_proto_field_name=True,
|
|
413
|
+
)
|
|
414
|
+
sig_dict = {
|
|
415
|
+
"signal": finfo.signalInfo.id.name,
|
|
416
|
+
"namespace": network_info.namespace.name,
|
|
417
|
+
}
|
|
418
|
+
signal_names.append({**sig_dict, **metadata_dict})
|
|
419
|
+
|
|
420
|
+
for sinfo in finfo.childInfo:
|
|
421
|
+
# For signals we can simply skip if prefix and suffix exists does not match
|
|
422
|
+
if (prefix is not None and not sinfo.id.name.startswith(prefix)) or (
|
|
423
|
+
suffix is not None and not sinfo.id.name.endswith(suffix)
|
|
424
|
+
):
|
|
425
|
+
continue
|
|
426
|
+
|
|
427
|
+
metadata_dict = MessageToDict(
|
|
428
|
+
sinfo.metaData,
|
|
429
|
+
preserving_proto_field_name=True,
|
|
430
|
+
)
|
|
431
|
+
sig_dict = {
|
|
432
|
+
"signal": sinfo.id.name,
|
|
433
|
+
"namespace": network_info.namespace.name,
|
|
434
|
+
}
|
|
435
|
+
signal_names.append({**sig_dict, **metadata_dict})
|
|
436
|
+
|
|
437
|
+
return signal_names
|
|
438
|
+
|
|
439
|
+
def subscribe_on_script(
|
|
440
|
+
self,
|
|
441
|
+
script: bytes,
|
|
442
|
+
on_frame: Callable[[Sequence[network_api.Signal]], None],
|
|
443
|
+
changed_values_only: bool = False,
|
|
444
|
+
) -> Any:
|
|
445
|
+
client_id = common.ClientId(id="cli")
|
|
446
|
+
thread = Thread(
|
|
447
|
+
target=act_on_scripted_signal,
|
|
448
|
+
args=(
|
|
449
|
+
client_id,
|
|
450
|
+
self.network_stub,
|
|
451
|
+
script,
|
|
452
|
+
changed_values_only, # True: only report when signal changes
|
|
453
|
+
lambda frame: self.__each_signal(frame, on_frame),
|
|
454
|
+
lambda sub: (self.q.put(("cli", sub))),
|
|
455
|
+
),
|
|
456
|
+
)
|
|
457
|
+
thread.start()
|
|
458
|
+
# wait for subscription to settle
|
|
459
|
+
return self.q.get()
|
|
460
|
+
|
|
461
|
+
def validate_and_get_subscribed_signals(
|
|
462
|
+
self, subscribed_namespaces: List[str], subscribed_signals: List[str]
|
|
463
|
+
) -> List[SubscribableSignal]:
|
|
464
|
+
# Since we cannot know which list[signals] belongs to which namespace we need to fetch
|
|
465
|
+
# all signals from the broker and find the proper signal with namespace. Finally we
|
|
466
|
+
# also filter out namespaces that we do not need since we might have duplicated signal names
|
|
467
|
+
# over namespaces
|
|
468
|
+
# Begin
|
|
469
|
+
|
|
470
|
+
def verify_namespace(available_signal: List[Dict[str, str]]) -> List[str]:
|
|
471
|
+
return list(filter(lambda namespace: available_signal["namespace"] == namespace, subscribed_namespaces)) # type: ignore
|
|
472
|
+
|
|
473
|
+
def find_subscribed_signal(available_signal: List[Dict[str, str]]) -> List[str]:
|
|
474
|
+
return list(filter(lambda s: available_signal["signal"] == s, subscribed_signals)) # type: ignore
|
|
475
|
+
|
|
476
|
+
existing_signals = self.list_signal_names(prefix=None, suffix=None)
|
|
477
|
+
existing_ns = set(map(lambda s: s["namespace"], existing_signals))
|
|
478
|
+
ns_not_matching = []
|
|
479
|
+
for ns in subscribed_namespaces:
|
|
480
|
+
if ns not in existing_ns:
|
|
481
|
+
ns_not_matching.append(ns)
|
|
482
|
+
if len(ns_not_matching) > 0:
|
|
483
|
+
print_hint(f"Namespace(s) {ns_not_matching} does not exist on broker. Namespaces found on broker: {existing_ns}")
|
|
484
|
+
sys.exit(1)
|
|
485
|
+
|
|
486
|
+
available_signals = list(filter(verify_namespace, existing_signals)) # type: ignore
|
|
487
|
+
signals_to_subscribe_to = list(filter(find_subscribed_signal, available_signals)) # type: ignore
|
|
488
|
+
|
|
489
|
+
# Check if subscription is done on signal that is not in any of these namespaces
|
|
490
|
+
signals_subscribed_to_but_does_not_exist = set(subscribed_signals) - set(map(lambda s: s["signal"], signals_to_subscribe_to))
|
|
491
|
+
|
|
492
|
+
if len(signals_subscribed_to_but_does_not_exist) > 0:
|
|
493
|
+
print_hint(f"One or more signals you subscribed to does not exist {signals_subscribed_to_but_does_not_exist}")
|
|
494
|
+
sys.exit(1)
|
|
495
|
+
|
|
496
|
+
return list(map(lambda s: SubscribableSignal(s["signal"], s["namespace"]), signals_to_subscribe_to))
|
|
497
|
+
|
|
498
|
+
def long_name_subscribe(
|
|
499
|
+
self, signals_to_subscribe_to: List[SubscribableSignal], on_frame: Callable[..., Any], changed_values_only: bool = True
|
|
500
|
+
) -> Any:
|
|
501
|
+
client_id = common.ClientId(id="cli")
|
|
502
|
+
|
|
503
|
+
# TODO - This can be improved moving forward and we also need to move the validation into api
|
|
504
|
+
self.validate_and_get_subscribed_signals(
|
|
505
|
+
list(map(lambda s: s.namespace, signals_to_subscribe_to)), (list(map(lambda s: s.name, signals_to_subscribe_to)))
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
def to_protobuf_signal(s: SubscribableSignal) -> common.SignalId:
|
|
509
|
+
return self.signal_creator.signal(s.name, s.namespace)
|
|
510
|
+
|
|
511
|
+
signals_to_subscribe_on = list(map(to_protobuf_signal, signals_to_subscribe_to))
|
|
512
|
+
|
|
513
|
+
Thread(
|
|
514
|
+
target=act_on_signal,
|
|
515
|
+
args=(
|
|
516
|
+
client_id,
|
|
517
|
+
self.network_stub,
|
|
518
|
+
signals_to_subscribe_on,
|
|
519
|
+
changed_values_only, # True: only report when signal changes
|
|
520
|
+
lambda frame: self.__each_signal(frame, on_frame),
|
|
521
|
+
lambda sub: (self.q.put(("cloud_demo", sub))),
|
|
522
|
+
),
|
|
523
|
+
).start()
|
|
524
|
+
# Wait for subscription
|
|
525
|
+
ecu, subscription = self.q.get()
|
|
526
|
+
return subscription
|
|
527
|
+
|
|
528
|
+
def subscribe(
|
|
529
|
+
self,
|
|
530
|
+
subscribed_signals: list[str],
|
|
531
|
+
subscribed_namespaces: list[str],
|
|
532
|
+
on_frame: Callable[..., Any],
|
|
533
|
+
changed_values_only: bool = True,
|
|
534
|
+
) -> Any:
|
|
535
|
+
client_id = common.ClientId(id="cli")
|
|
536
|
+
|
|
537
|
+
signals_to_subscribe_to: List[SubscribableSignal] = self.validate_and_get_subscribed_signals(
|
|
538
|
+
subscribed_namespaces, subscribed_signals
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
def to_protobuf_signal(s: SubscribableSignal) -> common.SignalId:
|
|
542
|
+
return self.signal_creator.signal(s.name, s.namespace)
|
|
543
|
+
|
|
544
|
+
signals_to_subscribe_on = list(map(to_protobuf_signal, signals_to_subscribe_to))
|
|
545
|
+
|
|
546
|
+
Thread(
|
|
547
|
+
target=act_on_signal,
|
|
548
|
+
args=(
|
|
549
|
+
client_id,
|
|
550
|
+
self.network_stub,
|
|
551
|
+
signals_to_subscribe_on,
|
|
552
|
+
changed_values_only, # True: only report when signal changes
|
|
553
|
+
lambda frame: self.__each_signal(frame, on_frame),
|
|
554
|
+
lambda sub: (self.q.put(("cloud_demo", sub))),
|
|
555
|
+
),
|
|
556
|
+
).start()
|
|
557
|
+
# Wait for subscription
|
|
558
|
+
ecu, subscription = self.q.get()
|
|
559
|
+
return subscription
|
|
560
|
+
|
|
561
|
+
def __each_signal(self, signals: Iterable[network_api.Signal], callback: Callable[..., Any]) -> None:
|
|
562
|
+
callback(
|
|
563
|
+
map(
|
|
564
|
+
lambda s: {"timestamp_us": s.timestamp, "namespace": s.id.namespace.name, "name": s.id.name, "value": self.__get_value(s)},
|
|
565
|
+
signals,
|
|
566
|
+
)
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
@staticmethod
|
|
570
|
+
def __get_value(signal: network_api.Signal) -> Any:
|
|
571
|
+
if signal.raw != b"":
|
|
572
|
+
return "0x" + binascii.hexlify(signal.raw).decode("ascii")
|
|
573
|
+
if signal.HasField("integer"):
|
|
574
|
+
return signal.integer
|
|
575
|
+
if signal.HasField("double"):
|
|
576
|
+
return signal.double
|
|
577
|
+
if signal.HasField("arbitration"):
|
|
578
|
+
return signal.arbitration
|
|
579
|
+
return "empty"
|
|
580
|
+
|
|
581
|
+
@staticmethod
|
|
582
|
+
def __create_playback_config(item: Dict[str, Any]) -> traffic_api.PlaybackInfo:
|
|
583
|
+
"""Creating configuration for playback
|
|
584
|
+
|
|
585
|
+
Parameters
|
|
586
|
+
----------
|
|
587
|
+
item : dict
|
|
588
|
+
Dictionary containing 'path', 'namespace' and 'mode'
|
|
589
|
+
|
|
590
|
+
Returns
|
|
591
|
+
-------
|
|
592
|
+
PlaybackInfo
|
|
593
|
+
Object instance of class
|
|
594
|
+
|
|
595
|
+
"""
|
|
596
|
+
|
|
597
|
+
def get_offset_time() -> int:
|
|
598
|
+
if "offsettime" in item:
|
|
599
|
+
return int(item["offsettime"])
|
|
600
|
+
return 0
|
|
601
|
+
|
|
602
|
+
playback_config = traffic_api.PlaybackConfig(
|
|
603
|
+
fileDescription=system_api.FileDescription(path=item["path"]),
|
|
604
|
+
namespace=common.NameSpace(name=item["namespace"]),
|
|
605
|
+
)
|
|
606
|
+
return traffic_api.PlaybackInfo(
|
|
607
|
+
playbackConfig=playback_config,
|
|
608
|
+
playbackMode=traffic_api.PlaybackMode(mode=item["mode"], offsetTime=get_offset_time()),
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
def get_license(self) -> LicenseInfo:
|
|
612
|
+
license_info = self.system_stub.GetLicenseInfo(common.Empty())
|
|
613
|
+
return LicenseInfo(
|
|
614
|
+
valid=license_info.status == system_api.LicenseStatus.VALID,
|
|
615
|
+
expires=license_info.expires,
|
|
616
|
+
email=license_info.requestId,
|
|
617
|
+
machine_id=license_info.requestMachineId.decode("utf-8"),
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
def apply_license(self, license_data_b64: bytes) -> LicenseInfo:
|
|
621
|
+
license = system_api.License()
|
|
622
|
+
license.data = license_data_b64
|
|
623
|
+
license.termsAgreement = True
|
|
624
|
+
self.system_stub.SetLicense(license)
|
|
625
|
+
return self.get_license()
|