remotivelabs-cli 0.0.1a26__tar.gz → 0.0.13__tar.gz
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-0.0.13/PKG-INFO +33 -0
- remotivelabs_cli-0.0.13/README.md +7 -0
- {remotivelabs_cli-0.0.1a26 → remotivelabs_cli-0.0.13}/cli/__about__.py +1 -1
- remotivelabs_cli-0.0.13/cli/__init__.py +0 -0
- remotivelabs_cli-0.0.13/cli/broker/brokers.py +91 -0
- remotivelabs_cli-0.0.13/cli/broker/export.py +110 -0
- remotivelabs_cli-0.0.13/cli/broker/files.py +119 -0
- {remotivelabs_cli-0.0.1a26 → remotivelabs_cli-0.0.13}/cli/broker/lib/__about__.py +1 -1
- remotivelabs_cli-0.0.13/cli/broker/lib/broker.py +522 -0
- remotivelabs_cli-0.0.13/cli/broker/playback.py +137 -0
- remotivelabs_cli-0.0.13/cli/broker/record.py +41 -0
- remotivelabs_cli-0.0.13/cli/broker/scripting.py +119 -0
- remotivelabs_cli-0.0.13/cli/broker/signals.py +189 -0
- {remotivelabs_cli-0.0.1a26 → remotivelabs_cli-0.0.13}/cli/cloud/auth.py +24 -34
- {remotivelabs_cli-0.0.1a26 → remotivelabs_cli-0.0.13}/cli/cloud/auth_tokens.py +22 -26
- remotivelabs_cli-0.0.13/cli/cloud/brokers.py +121 -0
- {remotivelabs_cli-0.0.1a26 → remotivelabs_cli-0.0.13}/cli/cloud/cloud_cli.py +7 -7
- {remotivelabs_cli-0.0.1a26 → remotivelabs_cli-0.0.13}/cli/cloud/configs.py +40 -15
- {remotivelabs_cli-0.0.1a26 → remotivelabs_cli-0.0.13}/cli/cloud/projects.py +14 -13
- remotivelabs_cli-0.0.13/cli/cloud/recordings.py +487 -0
- remotivelabs_cli-0.0.13/cli/cloud/rest_helper.py +194 -0
- remotivelabs_cli-0.0.13/cli/cloud/sample_recordings.py +24 -0
- {remotivelabs_cli-0.0.1a26 → remotivelabs_cli-0.0.13}/cli/cloud/service_account_tokens.py +21 -15
- {remotivelabs_cli-0.0.1a26 → remotivelabs_cli-0.0.13}/cli/cloud/service_accounts.py +13 -15
- remotivelabs_cli-0.0.13/cli/connect/__init__.py +1 -0
- remotivelabs_cli-0.0.13/cli/connect/connect.py +106 -0
- remotivelabs_cli-0.0.13/cli/connect/protopie/protopie.py +173 -0
- remotivelabs_cli-0.0.13/cli/errors.py +36 -0
- remotivelabs_cli-0.0.13/cli/remotive.py +64 -0
- remotivelabs_cli-0.0.13/cli/settings.py +25 -0
- remotivelabs_cli-0.0.13/cli/tools/__init__.py +1 -0
- remotivelabs_cli-0.0.13/cli/tools/can/__init__.py +1 -0
- remotivelabs_cli-0.0.13/cli/tools/can/can.py +79 -0
- remotivelabs_cli-0.0.13/cli/tools/tools.py +10 -0
- remotivelabs_cli-0.0.13/pyproject.toml +39 -0
- remotivelabs_cli-0.0.1a26/PKG-INFO +0 -46
- remotivelabs_cli-0.0.1a26/README.md +0 -26
- remotivelabs_cli-0.0.1a26/cli/__init__.py +0 -1
- remotivelabs_cli-0.0.1a26/cli/broker/brokers.py +0 -138
- remotivelabs_cli-0.0.1a26/cli/broker/files.py +0 -110
- remotivelabs_cli-0.0.1a26/cli/broker/lib/broker.py +0 -406
- remotivelabs_cli-0.0.1a26/cli/broker/playback.py +0 -132
- remotivelabs_cli-0.0.1a26/cli/broker/record.py +0 -29
- remotivelabs_cli-0.0.1a26/cli/broker/signals.py +0 -63
- remotivelabs_cli-0.0.1a26/cli/cloud/brokers.py +0 -104
- remotivelabs_cli-0.0.1a26/cli/cloud/recordings.py +0 -331
- remotivelabs_cli-0.0.1a26/cli/cloud/rest_helper.py +0 -118
- remotivelabs_cli-0.0.1a26/cli/remotive.py +0 -41
- remotivelabs_cli-0.0.1a26/cli/test/test_simple.py +0 -5
- remotivelabs_cli-0.0.1a26/pyproject.toml +0 -23
- {remotivelabs_cli-0.0.1a26 → remotivelabs_cli-0.0.13}/LICENSE +0 -0
- {remotivelabs_cli-0.0.1a26 → remotivelabs_cli-0.0.13}/cli/cloud/__init__.py +0 -0
- {remotivelabs_cli-0.0.1a26 → remotivelabs_cli-0.0.13}/cli/requirements.txt +0 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: remotivelabs-cli
|
3
|
+
Version: 0.0.13
|
4
|
+
Summary: CLI for operating RemotiveCloud and RemotiveBroker
|
5
|
+
Author: Johan Rask
|
6
|
+
Author-email: johan.rask@remotivelabs.com
|
7
|
+
Requires-Python: >=3.8,<4.0
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
9
|
+
Classifier: Programming Language :: Python :: 3.8
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
14
|
+
Requires-Dist: plotext (>=5.2,<6.0)
|
15
|
+
Requires-Dist: pyjwt (>=2.6,<3.0)
|
16
|
+
Requires-Dist: python-can (>=4.3.1)
|
17
|
+
Requires-Dist: python-socketio (>=4.6.1)
|
18
|
+
Requires-Dist: remotivelabs-broker (>=0.1.17)
|
19
|
+
Requires-Dist: rich (>=13.7.0,<13.8.0)
|
20
|
+
Requires-Dist: trogon (>=0.5.0)
|
21
|
+
Requires-Dist: typer (>=0.9.0,<0.10.0)
|
22
|
+
Requires-Dist: websocket-client (>=1.6,<2.0)
|
23
|
+
Requires-Dist: zeroconf (>=0.127.0,<0.128.0)
|
24
|
+
Description-Content-Type: text/markdown
|
25
|
+
|
26
|
+
# RemotiveLabs - CLI
|
27
|
+
[](https://pypi.org/project/remotivelabs-cli)
|
28
|
+
|
29
|
+
Use this CLI with our cloud and broker as a compliment to code and web tools.
|
30
|
+
|
31
|
+
Read more at https://docs.remotivelabs.com/docs/remotive-cli
|
32
|
+
|
33
|
+
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# RemotiveLabs - CLI
|
2
|
+
[](https://pypi.org/project/remotivelabs-cli)
|
3
|
+
|
4
|
+
Use this CLI with our cloud and broker as a compliment to code and web tools.
|
5
|
+
|
6
|
+
Read more at https://docs.remotivelabs.com/docs/remotive-cli
|
7
|
+
|
File without changes
|
@@ -0,0 +1,91 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import os
|
4
|
+
from time import sleep
|
5
|
+
|
6
|
+
import typer
|
7
|
+
from zeroconf import (
|
8
|
+
IPVersion,
|
9
|
+
ServiceBrowser,
|
10
|
+
ServiceStateChange,
|
11
|
+
Zeroconf,
|
12
|
+
)
|
13
|
+
|
14
|
+
from . import export, files, playback, record, scripting, signals
|
15
|
+
|
16
|
+
app = typer.Typer(rich_markup_mode="rich")
|
17
|
+
|
18
|
+
|
19
|
+
@app.callback()
|
20
|
+
def main(
|
21
|
+
url: str = typer.Option(None, is_eager=False, help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
|
22
|
+
):
|
23
|
+
# This can be used to override the --url per command, lets see if this is a better approach
|
24
|
+
if url is not None:
|
25
|
+
os.environ["REMOTIVE_BROKER_URL"] = url
|
26
|
+
# Do other global stuff, handle other global options here
|
27
|
+
return
|
28
|
+
|
29
|
+
|
30
|
+
@app.command(help="Discover brokers on this network")
|
31
|
+
def discover():
|
32
|
+
# print("Not implemented")
|
33
|
+
|
34
|
+
zeroconf = Zeroconf(ip_version=IPVersion.V4Only)
|
35
|
+
|
36
|
+
services = ["_remotivebroker._tcp.local."]
|
37
|
+
# services = list(ZeroconfServiceTypes.find(zc=zeroconf))
|
38
|
+
|
39
|
+
print("\nLooking for RemotiveBrokers on your network, press Ctrl-C to exit...\n")
|
40
|
+
ServiceBrowser(zeroconf, services, handlers=[on_service_state_change])
|
41
|
+
|
42
|
+
try:
|
43
|
+
while True:
|
44
|
+
sleep(0.1)
|
45
|
+
except KeyboardInterrupt:
|
46
|
+
pass
|
47
|
+
finally:
|
48
|
+
zeroconf.close()
|
49
|
+
|
50
|
+
|
51
|
+
def on_service_state_change(zeroconf: Zeroconf, service_type: str, name: str, state_change: ServiceStateChange) -> None:
|
52
|
+
# print(f"Service {name} state changed: {state_change}")
|
53
|
+
|
54
|
+
if state_change is ServiceStateChange.Removed:
|
55
|
+
print(f"Service {name} was removed")
|
56
|
+
|
57
|
+
if state_change is ServiceStateChange.Updated:
|
58
|
+
print(f"Service {name} was updated")
|
59
|
+
|
60
|
+
if state_change is ServiceStateChange.Added:
|
61
|
+
print(f"[ {name} ]")
|
62
|
+
info = zeroconf.get_service_info(service_type, name)
|
63
|
+
# print("Info from zeroconf.get_service_info: %r" % (info))
|
64
|
+
|
65
|
+
if info:
|
66
|
+
# addresses = ["%s:%d" % (addr, cast(int, info.port)) for addr in info.parsed_scoped_addresses()]
|
67
|
+
for addr in info.parsed_scoped_addresses():
|
68
|
+
print(f"RemotiveBrokerApp: http://{addr}:8080")
|
69
|
+
print(f"RemotiveBroker http://{addr}:50051")
|
70
|
+
# print(" Weight: %d, priority: %d" % (info.weight, info.priority))
|
71
|
+
# print(f" Server: {info.server}")
|
72
|
+
# if info.properties:
|
73
|
+
# print(" Properties are:")
|
74
|
+
# for key, value in info.properties.items():
|
75
|
+
# print(f" {key}: {value}")
|
76
|
+
# else:
|
77
|
+
# print(" No properties")
|
78
|
+
else:
|
79
|
+
print(" No info")
|
80
|
+
print("\n")
|
81
|
+
|
82
|
+
|
83
|
+
app.add_typer(playback.app, name="playback", help="Manage playing recordings")
|
84
|
+
app.add_typer(record.app, name="record", help="Record data on buses")
|
85
|
+
app.add_typer(files.app, name="files", help="Upload/Download configurations and recordings")
|
86
|
+
app.add_typer(signals.app, name="signals", help="Find and subscribe to signals")
|
87
|
+
app.add_typer(export.app, name="export", help="Export to external formats")
|
88
|
+
app.add_typer(scripting.app, name="scripting")
|
89
|
+
|
90
|
+
if __name__ == "__main__":
|
91
|
+
app()
|
@@ -0,0 +1,110 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import os
|
4
|
+
import signal as os_signal
|
5
|
+
from typing import List
|
6
|
+
|
7
|
+
import grpc
|
8
|
+
import typer
|
9
|
+
|
10
|
+
from cli.errors import ErrorPrinter
|
11
|
+
|
12
|
+
from .lib.broker import Broker
|
13
|
+
|
14
|
+
app = typer.Typer(
|
15
|
+
rich_markup_mode="rich",
|
16
|
+
help="""
|
17
|
+
Export subscribed signals to different formats, currently only InfluxDB line protocol
|
18
|
+
but more formats will come soon
|
19
|
+
""",
|
20
|
+
)
|
21
|
+
|
22
|
+
|
23
|
+
@app.command()
|
24
|
+
def influxdb(
|
25
|
+
url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
|
26
|
+
api_key: str = typer.Option(None, help="Cloud Broker API-KEY", envvar="REMOTIVE_BROKER_API_KEY"),
|
27
|
+
signal: List[str] = typer.Option(..., help="List of signal names to subscribe to"),
|
28
|
+
namespace: str = typer.Option(..., help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
|
29
|
+
on_change_only: bool = typer.Option(default=False, help="Only get signal if value is changed"),
|
30
|
+
output: str = typer.Option(None, help="Write results to file, defaults to stdout"),
|
31
|
+
):
|
32
|
+
"""
|
33
|
+
Exports subscribed signals to InfluxDB line-protocol, really useful to dump some signals into
|
34
|
+
influxdb for offline analysis and insights.
|
35
|
+
|
36
|
+
This is a sample for exporting and importing to InfluxDB using remotive-cli and influx-cli
|
37
|
+
|
38
|
+
Export:
|
39
|
+
remotive broker export influxdb --url [URL] --output signals.influx --namespace VehicleBus \\
|
40
|
+
--signal Control.SteeringWheel_Position --signal Control.Accelerator_PedalPosition \\
|
41
|
+
--signal GpsPosition.GPS_Longitude --signal GpsPosition.GPS_Latitude
|
42
|
+
|
43
|
+
Output:
|
44
|
+
Control, SteeringWheel_Position=1.0,Accelerator_PedalPosition=0,Speed=0 1664787032944374000
|
45
|
+
GpsPosition, GPS_Longitude=12.982076,GPS_Latitude=55.618748 1664787032948256000
|
46
|
+
|
47
|
+
Import:
|
48
|
+
influx write --org myorg -b my-bucket -p ns --format=lp -f signals.influx
|
49
|
+
|
50
|
+
|
51
|
+
"""
|
52
|
+
|
53
|
+
if output is not None:
|
54
|
+
f = open(output, "w")
|
55
|
+
|
56
|
+
def exit_on_ctrlc(sig, frame):
|
57
|
+
if output is not None:
|
58
|
+
f.close()
|
59
|
+
os._exit(0)
|
60
|
+
|
61
|
+
def per_frame_influx_line_protocol(x):
|
62
|
+
signals = list(x)
|
63
|
+
if len(signals) == 0:
|
64
|
+
return
|
65
|
+
|
66
|
+
frame_name = signals[0]["name"].split(".")[0]
|
67
|
+
signals_str = ",".join(list(map(lambda s: f'{s["name"].split(".")[1]}={s["value"]}', signals)))
|
68
|
+
influx_lp = f"{frame_name},namespace={namespace} {signals_str} {round(signals[0]['timestamp_us'] * 1000)}"
|
69
|
+
if output is not None:
|
70
|
+
f.write(f"{influx_lp}\n")
|
71
|
+
else:
|
72
|
+
print(f"{influx_lp}")
|
73
|
+
|
74
|
+
# TODO - support for csv
|
75
|
+
# def csv(x):
|
76
|
+
# list = list(x)
|
77
|
+
# print(x)
|
78
|
+
# l = list(x)
|
79
|
+
# print(l)
|
80
|
+
# ll=list(map(lambda s : s, l))
|
81
|
+
# for s in l:
|
82
|
+
# dt = datetime.fromtimestamp(s["timestamp_nanos"] / 1000000)
|
83
|
+
# t=datetime.isoformat(dt)
|
84
|
+
# t=rfc3339.format_millisecond(dt)
|
85
|
+
# rich_rprint(len(l))
|
86
|
+
# lat = (l[0])
|
87
|
+
# lon = l[1]
|
88
|
+
# dt = datetime.fromtimestamp(lat["timestamp_nanos"] / 1000000)
|
89
|
+
# t=datetime.isoformat(dt)
|
90
|
+
# t = rfc3339.format_millisecond(dt)
|
91
|
+
# name = s["name"]
|
92
|
+
# value = s["value"]
|
93
|
+
# if output is not None:
|
94
|
+
# f.write(f'coord,{lat["value"]},{lon["value"]},{t}\n')
|
95
|
+
# else:
|
96
|
+
# print(f'coord,{lat["value"]},{lon["value"]},{t}')
|
97
|
+
# if output is not None:
|
98
|
+
# f.flush()
|
99
|
+
# print(x["timestamp_nanos"])
|
100
|
+
# rich_rprint(json.dumps(list(x)))
|
101
|
+
|
102
|
+
os_signal.signal(os_signal.SIGINT, exit_on_ctrlc)
|
103
|
+
|
104
|
+
# print(namespace)
|
105
|
+
# signals2 = list(map( lambda s: s['signal'], broker.list_signal_names2(namespace)))
|
106
|
+
try:
|
107
|
+
broker = Broker(url, api_key)
|
108
|
+
broker.subscribe(signal, namespace, per_frame_influx_line_protocol, on_change_only)
|
109
|
+
except grpc.RpcError as rpc_error:
|
110
|
+
ErrorPrinter.print_grpc_error(rpc_error)
|
@@ -0,0 +1,119 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import os
|
4
|
+
from typing import List
|
5
|
+
|
6
|
+
import grpc
|
7
|
+
import typer
|
8
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
9
|
+
|
10
|
+
from cli.errors import ErrorPrinter
|
11
|
+
|
12
|
+
from .lib.broker import Broker
|
13
|
+
|
14
|
+
app = typer.Typer(help=help)
|
15
|
+
|
16
|
+
|
17
|
+
@app.command()
|
18
|
+
def reload_configuration(
|
19
|
+
url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
|
20
|
+
api_key: str = typer.Option("offline", help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
|
21
|
+
):
|
22
|
+
try:
|
23
|
+
broker = Broker(url, api_key)
|
24
|
+
broker.reload_config()
|
25
|
+
print("Configuration successfully reloaded")
|
26
|
+
except grpc.RpcError as err:
|
27
|
+
ErrorPrinter.print_grpc_error(err)
|
28
|
+
|
29
|
+
|
30
|
+
@app.command()
|
31
|
+
def delete(
|
32
|
+
path: List[str] = typer.Argument(..., help="Paths to files on broker to delete"),
|
33
|
+
exit_on_failure: bool = typer.Option(False, help="Exits if there was a problem deleting a file"),
|
34
|
+
url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
|
35
|
+
api_key: str = typer.Option("offline", help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
|
36
|
+
):
|
37
|
+
"""
|
38
|
+
Deletes the specified files from the broker
|
39
|
+
"""
|
40
|
+
try:
|
41
|
+
broker = Broker(url, api_key)
|
42
|
+
|
43
|
+
if len(path) == 0:
|
44
|
+
print("At least one path must be suppled")
|
45
|
+
raise typer.Exit(1)
|
46
|
+
|
47
|
+
broker.delete_files(path, exit_on_failure)
|
48
|
+
except grpc.RpcError as err:
|
49
|
+
ErrorPrinter.print_grpc_error(err)
|
50
|
+
|
51
|
+
|
52
|
+
@app.command()
|
53
|
+
def download(
|
54
|
+
path: str = typer.Argument(..., help="Path to file on broker to download"),
|
55
|
+
output: str = typer.Option("", help="Optional output file name"),
|
56
|
+
url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
|
57
|
+
api_key: str = typer.Option("offline", help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
|
58
|
+
):
|
59
|
+
"""
|
60
|
+
Downloads a file from a broker
|
61
|
+
"""
|
62
|
+
try:
|
63
|
+
with Progress(
|
64
|
+
SpinnerColumn(),
|
65
|
+
TextColumn("[progress.description]{task.description}"),
|
66
|
+
transient=True,
|
67
|
+
) as progress:
|
68
|
+
progress.add_task(description=f"Downloading {path}...", total=None)
|
69
|
+
broker = Broker(url, api_key)
|
70
|
+
output_file = os.path.basename(path)
|
71
|
+
if output != "":
|
72
|
+
output_file = output
|
73
|
+
if os.path.exists(output_file):
|
74
|
+
print(f"File already exist {output_file}, please use another output file name")
|
75
|
+
else:
|
76
|
+
broker.download(path, output_file)
|
77
|
+
print(f"Successfully saved {output_file}")
|
78
|
+
except grpc.RpcError as err:
|
79
|
+
ErrorPrinter.print_grpc_error(err)
|
80
|
+
|
81
|
+
|
82
|
+
@app.command()
|
83
|
+
def upload(
|
84
|
+
path: str = typer.Argument(..., help="Path to local file to upload"),
|
85
|
+
output: str = typer.Option("", help="Optional output path on broker"),
|
86
|
+
url: str = typer.Option(..., help="Broker URL", envvar="REMOTIVE_BROKER_URL"),
|
87
|
+
api_key: str = typer.Option("offline", help="Cloud Broker API-KEY or access token", envvar="REMOTIVE_BROKER_API_KEY"),
|
88
|
+
):
|
89
|
+
"""
|
90
|
+
Uploads a file to a broker - physical or in cloud.
|
91
|
+
"""
|
92
|
+
try:
|
93
|
+
with Progress(
|
94
|
+
SpinnerColumn(),
|
95
|
+
TextColumn("[progress.description]{task.description}"),
|
96
|
+
transient=True,
|
97
|
+
) as progress:
|
98
|
+
progress.add_task(description=f"Uploading {path}...", total=None)
|
99
|
+
|
100
|
+
if path == ".":
|
101
|
+
path = "./" ## Does not work otherwise
|
102
|
+
|
103
|
+
if not os.path.exists(path):
|
104
|
+
print(f"File {path} does not exist")
|
105
|
+
raise typer.Exit(1)
|
106
|
+
|
107
|
+
broker = Broker(url, api_key)
|
108
|
+
|
109
|
+
if os.path.isdir(path):
|
110
|
+
broker.upload_folder(path)
|
111
|
+
print(f"Successfully uploaded {path}")
|
112
|
+
else:
|
113
|
+
output_file = os.path.basename(path)
|
114
|
+
if output != "":
|
115
|
+
output_file = output
|
116
|
+
broker.upload(path, output_file)
|
117
|
+
print(f"Successfully uploaded {path}")
|
118
|
+
except grpc.RpcError as err:
|
119
|
+
ErrorPrinter.print_grpc_error(err)
|