RemoteRF 2.0.0__tar.gz → 2.0.2__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.
- {remoterf-2.0.0/src/remoteRF.egg-info → remoterf-2.0.2}/PKG-INFO +1 -1
- {remoterf-2.0.0 → remoterf-2.0.2}/setup.py +1 -1
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF/common/grpc/grpc_pb2.py +7 -7
- remoterf-2.0.2/src/remoteRF/common/utils/process_arg.py +153 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF/core/app.py +7 -2
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF/core/grpc_client.py +33 -5
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF/drivers/dynamic_device.py +55 -2
- {remoterf-2.0.0 → remoterf-2.0.2/src/remoteRF.egg-info}/PKG-INFO +1 -1
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF.egg-info/SOURCES.txt +3 -1
- remoterf-2.0.2/tests/test_dynamic_device_codegen.py +69 -0
- remoterf-2.0.2/tests/test_process_arg.py +44 -0
- remoterf-2.0.0/src/remoteRF/common/utils/process_arg.py +0 -82
- {remoterf-2.0.0 → remoterf-2.0.2}/LICENSE +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/MANIFEST.in +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/README.md +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/setup.cfg +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF/__init__.py +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF/common/__init__.py +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF/common/grpc/__init__.py +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF/common/grpc/grpc_pb2_grpc.py +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF/common/tos_notice.txt +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF/common/utils/__init__.py +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF/common/utils/ansi_codes.py +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF/common/utils/api_token.py +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF/common/utils/list_string.py +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF/config/__init__.py +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF/config/cert_fetcher.py +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF/config/config.py +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF/core/__init__.py +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF/core/acc_login.py +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF/core/banner_note.txt +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF/core/grpc_acc.py +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF/drivers/__init__.py +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF/drivers/adalm_pluto/__init__.py +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF/drivers/adalm_pluto/pluto_remote.py +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF/remoterf_cli.py +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF/version.py +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF.egg-info/dependency_links.txt +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF.egg-info/entry_points.txt +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF.egg-info/requires.txt +0 -0
- {remoterf-2.0.0 → remoterf-2.0.2}/src/remoteRF.egg-info/top_level.txt +0 -0
|
@@ -6,7 +6,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
|
|
|
6
6
|
|
|
7
7
|
setup(
|
|
8
8
|
name="remoterf",
|
|
9
|
-
version="2.0.
|
|
9
|
+
version="2.0.2",
|
|
10
10
|
author="Ethan Ge",
|
|
11
11
|
author_email="ethoGalaxy@gmail.com",
|
|
12
12
|
description="A python API to remotely access signal centric hardware. Client-side only! Courtesy of Wireless Lab @ UCLA & Prof. Ian Roberts.",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
3
|
# NO CHECKED-IN PROTOBUF GENCODE
|
|
4
4
|
# source: grpc.proto
|
|
5
|
-
# Protobuf Python Version: 5.
|
|
5
|
+
# Protobuf Python Version: 5.27.1
|
|
6
6
|
"""Generated protocol buffer code."""
|
|
7
7
|
from google.protobuf import descriptor as _descriptor
|
|
8
8
|
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
@@ -12,8 +12,8 @@ from google.protobuf.internal import builder as _builder
|
|
|
12
12
|
_runtime_version.ValidateProtobufRuntimeVersion(
|
|
13
13
|
_runtime_version.Domain.PUBLIC,
|
|
14
14
|
5,
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
27,
|
|
16
|
+
1,
|
|
17
17
|
'',
|
|
18
18
|
'grpc.proto'
|
|
19
19
|
)
|
|
@@ -24,7 +24,7 @@ _sym_db = _symbol_database.Default()
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\ngrpc.proto\x12\tremote_rf\"\xa2\x01\n\x11GenericRPCRequest\x12\x15\n\rfunction_name\x18\x01 \x01(\t\x12\x34\n\x04\x61rgs\x18\x02 \x03(\x0b\x32&.remote_rf.GenericRPCRequest.ArgsEntry\x1a@\n\tArgsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\"\n\x05value\x18\x02 \x01(\x0b\x32\x13.remote_rf.Argument:\x02\x38\x01\"\x96\x01\n\x12GenericRPCResponse\x12;\n\x07results\x18\x01 \x03(\x0b\x32*.remote_rf.GenericRPCResponse.ResultsEntry\x1a\x43\n\x0cResultsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\"\n\x05value\x18\x02 \x01(\x0b\x32\x13.remote_rf.Argument:\x02\x38\x01\"\x19\n\nArrayShape\x12\x0b\n\x03\x64im\x18\x01 \x03(\x05\"+\n\rComplexNumber\x12\x0c\n\x04real\x18\x01 \x01(\x02\x12\x0c\n\x04imag\x18\x02 \x01(\x02\"a\n\x11\x43omplexNumpyArray\x12$\n\x05shape\x18\x01 \x01(\x0b\x32\x15.remote_rf.ArrayShape\x12&\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32\x18.remote_rf.ComplexNumber\"D\n\x0eRealNumpyArray\x12$\n\x05shape\x18\x01 \x01(\x0b\x32\x15.remote_rf.ArrayShape\x12\x0c\n\x04\x64\x61ta\x18\x02 \x03(\x02\"\
|
|
27
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\ngrpc.proto\x12\tremote_rf\"\xa2\x01\n\x11GenericRPCRequest\x12\x15\n\rfunction_name\x18\x01 \x01(\t\x12\x34\n\x04\x61rgs\x18\x02 \x03(\x0b\x32&.remote_rf.GenericRPCRequest.ArgsEntry\x1a@\n\tArgsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\"\n\x05value\x18\x02 \x01(\x0b\x32\x13.remote_rf.Argument:\x02\x38\x01\"\x96\x01\n\x12GenericRPCResponse\x12;\n\x07results\x18\x01 \x03(\x0b\x32*.remote_rf.GenericRPCResponse.ResultsEntry\x1a\x43\n\x0cResultsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\"\n\x05value\x18\x02 \x01(\x0b\x32\x13.remote_rf.Argument:\x02\x38\x01\"\x19\n\nArrayShape\x12\x0b\n\x03\x64im\x18\x01 \x03(\x05\"+\n\rComplexNumber\x12\x0c\n\x04real\x18\x01 \x01(\x02\x12\x0c\n\x04imag\x18\x02 \x01(\x02\"a\n\x11\x43omplexNumpyArray\x12$\n\x05shape\x18\x01 \x01(\x0b\x32\x15.remote_rf.ArrayShape\x12&\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32\x18.remote_rf.ComplexNumber\"D\n\x0eRealNumpyArray\x12$\n\x05shape\x18\x01 \x01(\x0b\x32\x15.remote_rf.ArrayShape\x12\x0c\n\x04\x64\x61ta\x18\x02 \x03(\x02\"\xed\x01\n\x08\x41rgument\x12\x16\n\x0cstring_value\x18\x01 \x01(\tH\x00\x12\x15\n\x0bint64_value\x18\x02 \x01(\x03H\x00\x12\x15\n\x0b\x66loat_value\x18\x03 \x01(\x02H\x00\x12\x14\n\nbool_value\x18\x04 \x01(\x08H\x00\x12\x35\n\rcomplex_array\x18\x05 \x01(\x0b\x32\x1c.remote_rf.ComplexNumpyArrayH\x00\x12/\n\nreal_array\x18\x06 \x01(\x0b\x32\x19.remote_rf.RealNumpyArrayH\x00\x12\x14\n\njson_value\x18\x07 \x01(\tH\x00\x42\x07\n\x05value2Q\n\nGenericRPC\x12\x43\n\x04\x43\x61ll\x12\x1c.remote_rf.GenericRPCRequest\x1a\x1d.remote_rf.GenericRPCResponseB\x1e\n\x10\x63om.example.demoB\nDemoProtosb\x06proto3')
|
|
28
28
|
|
|
29
29
|
_globals = globals()
|
|
30
30
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
@@ -53,7 +53,7 @@ if not _descriptor._USE_C_DESCRIPTORS:
|
|
|
53
53
|
_globals['_REALNUMPYARRAY']._serialized_start=514
|
|
54
54
|
_globals['_REALNUMPYARRAY']._serialized_end=582
|
|
55
55
|
_globals['_ARGUMENT']._serialized_start=585
|
|
56
|
-
_globals['_ARGUMENT']._serialized_end=
|
|
57
|
-
_globals['_GENERICRPC']._serialized_start=
|
|
58
|
-
_globals['_GENERICRPC']._serialized_end=
|
|
56
|
+
_globals['_ARGUMENT']._serialized_end=822
|
|
57
|
+
_globals['_GENERICRPC']._serialized_start=824
|
|
58
|
+
_globals['_GENERICRPC']._serialized_end=905
|
|
59
59
|
# @@protoc_insertion_point(module_scope)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
from ..grpc import grpc_pb2, grpc_pb2_grpc
|
|
2
|
+
import json
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
_JSON_TYPE_KEY = "__remoterf_json_type__"
|
|
6
|
+
|
|
7
|
+
def _json_safe(value):
|
|
8
|
+
if isinstance(value, np.ndarray):
|
|
9
|
+
if value.dtype == object:
|
|
10
|
+
raise ValueError(f"Cannot JSON-map object-dtype array: {value!r}")
|
|
11
|
+
array = np.asarray(value)
|
|
12
|
+
if np.iscomplexobj(array):
|
|
13
|
+
data = [[float(num.real), float(num.imag)] for num in array.ravel()]
|
|
14
|
+
return {
|
|
15
|
+
_JSON_TYPE_KEY: "ndarray",
|
|
16
|
+
"dtype": str(array.dtype),
|
|
17
|
+
"shape": list(array.shape),
|
|
18
|
+
"complex": True,
|
|
19
|
+
"data": data,
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
_JSON_TYPE_KEY: "ndarray",
|
|
23
|
+
"dtype": str(array.dtype),
|
|
24
|
+
"shape": list(array.shape),
|
|
25
|
+
"complex": False,
|
|
26
|
+
"data": array.tolist(),
|
|
27
|
+
}
|
|
28
|
+
if isinstance(value, (np.bool_,)):
|
|
29
|
+
return bool(value)
|
|
30
|
+
if isinstance(value, (np.integer,)):
|
|
31
|
+
return int(value)
|
|
32
|
+
if isinstance(value, (np.floating,)):
|
|
33
|
+
return float(value)
|
|
34
|
+
if isinstance(value, complex):
|
|
35
|
+
return {_JSON_TYPE_KEY: "complex", "real": float(value.real), "imag": float(value.imag)}
|
|
36
|
+
if isinstance(value, dict):
|
|
37
|
+
return {str(key): _json_safe(item) for key, item in value.items()}
|
|
38
|
+
if isinstance(value, (list, tuple)):
|
|
39
|
+
return [_json_safe(item) for item in value]
|
|
40
|
+
return value
|
|
41
|
+
|
|
42
|
+
def _json_restore(value):
|
|
43
|
+
if isinstance(value, dict):
|
|
44
|
+
kind = value.get(_JSON_TYPE_KEY)
|
|
45
|
+
if kind == "ndarray":
|
|
46
|
+
shape = tuple(value.get("shape", ()))
|
|
47
|
+
dtype = value.get("dtype")
|
|
48
|
+
if value.get("complex"):
|
|
49
|
+
data = [complex(real, imag) for real, imag in value.get("data", [])]
|
|
50
|
+
return np.array(data, dtype=dtype or np.complex64).reshape(shape)
|
|
51
|
+
return np.array(value.get("data", []), dtype=dtype).reshape(shape)
|
|
52
|
+
if kind == "complex":
|
|
53
|
+
return complex(value.get("real", 0.0), value.get("imag", 0.0))
|
|
54
|
+
return {key: _json_restore(item) for key, item in value.items()}
|
|
55
|
+
if isinstance(value, list):
|
|
56
|
+
return [_json_restore(item) for item in value]
|
|
57
|
+
return value
|
|
58
|
+
|
|
59
|
+
def unmap_arg(arg):
|
|
60
|
+
if arg.HasField('int64_value'):
|
|
61
|
+
return arg.int64_value
|
|
62
|
+
elif arg.HasField('float_value'):
|
|
63
|
+
return arg.float_value
|
|
64
|
+
elif arg.HasField('string_value'):
|
|
65
|
+
return arg.string_value
|
|
66
|
+
elif arg.HasField('bool_value'):
|
|
67
|
+
return arg.bool_value
|
|
68
|
+
elif arg.HasField('real_array'):
|
|
69
|
+
shape = tuple(arg.real_array.shape.dim)
|
|
70
|
+
return np.array(arg.real_array.data, dtype=np.float64).reshape(shape)
|
|
71
|
+
elif arg.HasField('complex_array'):
|
|
72
|
+
shape = tuple(arg.complex_array.shape.dim)
|
|
73
|
+
data = [complex(c.real, c.imag) for c in arg.complex_array.data]
|
|
74
|
+
return np.array(data, dtype=np.complex64).reshape(shape)
|
|
75
|
+
elif arg.HasField('json_value'):
|
|
76
|
+
return _json_restore(json.loads(arg.json_value))
|
|
77
|
+
else:
|
|
78
|
+
raise ValueError(f"Unknown argument type during unmapping: {arg}")
|
|
79
|
+
|
|
80
|
+
def map_arg(value):
|
|
81
|
+
arg = grpc_pb2.Argument()
|
|
82
|
+
|
|
83
|
+
if isinstance(value, (bool, np.bool_)):
|
|
84
|
+
arg.bool_value = bool(value)
|
|
85
|
+
elif value is None or isinstance(value, dict):
|
|
86
|
+
arg.json_value = json.dumps(_json_safe(value), separators=(",", ":"))
|
|
87
|
+
elif isinstance(value, (int, np.integer)):
|
|
88
|
+
arg.int64_value = int(value)
|
|
89
|
+
elif isinstance(value, (float, np.floating)):
|
|
90
|
+
arg.float_value = float(value)
|
|
91
|
+
elif isinstance(value, str):
|
|
92
|
+
arg.string_value = value
|
|
93
|
+
elif isinstance(value, (list, tuple)):
|
|
94
|
+
try:
|
|
95
|
+
array = np.asarray(value)
|
|
96
|
+
except Exception:
|
|
97
|
+
arg.json_value = json.dumps(_json_safe(value), separators=(",", ":"))
|
|
98
|
+
return arg
|
|
99
|
+
if array.dtype == object:
|
|
100
|
+
try:
|
|
101
|
+
array = np.stack(value)
|
|
102
|
+
except Exception:
|
|
103
|
+
arg.json_value = json.dumps(_json_safe(value), separators=(",", ":"))
|
|
104
|
+
return arg
|
|
105
|
+
return map_arg(array)
|
|
106
|
+
elif isinstance(value, np.ndarray):
|
|
107
|
+
if value.dtype == object:
|
|
108
|
+
raise ValueError(f"Cannot map object-dtype array: {value!r}")
|
|
109
|
+
if np.iscomplexobj(value):
|
|
110
|
+
complex_array = arg.complex_array
|
|
111
|
+
complex_array.shape.dim.extend(value.shape)
|
|
112
|
+
for num in value.ravel():
|
|
113
|
+
complex_num = complex_array.data.add()
|
|
114
|
+
complex_num.real = float(num.real)
|
|
115
|
+
complex_num.imag = float(num.imag)
|
|
116
|
+
else:
|
|
117
|
+
float_array = arg.real_array
|
|
118
|
+
float_array.shape.dim.extend(value.shape)
|
|
119
|
+
float_array.data.extend(np.asarray(value, dtype=np.float32).ravel())
|
|
120
|
+
else:
|
|
121
|
+
raise ValueError(f"Unknown argument type during mapping: {value}")
|
|
122
|
+
return arg
|
|
123
|
+
|
|
124
|
+
def map_array_proto(np_array):
|
|
125
|
+
arg = grpc_pb2.Argument()
|
|
126
|
+
|
|
127
|
+
# Check if the array is complex
|
|
128
|
+
if np.iscomplexobj(np_array):
|
|
129
|
+
complex_array = grpc_pb2.ComplexArray()
|
|
130
|
+
for num in np_array.flat:
|
|
131
|
+
complex_number = complex_array.data.add()
|
|
132
|
+
complex_number.real = num.real
|
|
133
|
+
complex_number.imag = num.imag
|
|
134
|
+
arg.complex_array.CopyFrom(complex_array)
|
|
135
|
+
else:
|
|
136
|
+
# Handle as a regular float array
|
|
137
|
+
float_array = grpc_pb2.FloatArray()
|
|
138
|
+
float_array.data.extend(np_array.flat)
|
|
139
|
+
arg.float_array.CopyFrom(float_array)
|
|
140
|
+
|
|
141
|
+
return arg
|
|
142
|
+
|
|
143
|
+
def unmap_array_proto(arg):
|
|
144
|
+
# Check which type of array is available and convert appropriately
|
|
145
|
+
if arg.HasField('complex_array'):
|
|
146
|
+
# Convert ComplexArray to a numpy array of complex numbers
|
|
147
|
+
data = [complex(cn.real, cn.imag) for cn in arg.complex_array.data]
|
|
148
|
+
return np.array(data, dtype=np.complex64)
|
|
149
|
+
elif arg.HasField('float_array'):
|
|
150
|
+
# Convert FloatArray to a numpy array of floats
|
|
151
|
+
return np.array(arg.float_array.data, dtype=np.float32)
|
|
152
|
+
else:
|
|
153
|
+
raise ValueError("Argument does not contain a recognizable array.")
|
|
@@ -17,6 +17,7 @@ session = PromptSession()
|
|
|
17
17
|
DEFAULT_TIMEZONE_NOTE = "All times are in Pacific Time (Los Angeles)"
|
|
18
18
|
DEFAULT_TOS_URL = "https://remoterf.net/tos"
|
|
19
19
|
SERVER_TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
|
|
20
|
+
BANNER_CREDIT = "Created at UCLA by E. Ge and I. Roberts."
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
def _banner_note() -> str:
|
|
@@ -28,6 +29,10 @@ def _banner_note() -> str:
|
|
|
28
29
|
return text or DEFAULT_TIMEZONE_NOTE
|
|
29
30
|
|
|
30
31
|
|
|
32
|
+
def _banner_details() -> str:
|
|
33
|
+
return f"\n{BANNER_CREDIT}\nCurrent version: {print_my_version()}\n{_banner_note()}"
|
|
34
|
+
|
|
35
|
+
|
|
31
36
|
def _tos_notice_text() -> str:
|
|
32
37
|
notice_path = Path(__file__).resolve().parents[1] / "common" / "tos_notice.txt"
|
|
33
38
|
try:
|
|
@@ -66,7 +71,7 @@ def _format_reservation_range(start_time: datetime.datetime, end_time: datetime.
|
|
|
66
71
|
return f"{start} - {end}"
|
|
67
72
|
|
|
68
73
|
def welcome():
|
|
69
|
-
printf(f"Welcome to the RemoteRF Platform", (Sty.BOLD, Sty.BLUE),
|
|
74
|
+
printf(f"Welcome to the RemoteRF Platform", (Sty.BOLD, Sty.BLUE), _banner_details(), (Sty.GRAY))
|
|
70
75
|
try:
|
|
71
76
|
inpu = session.prompt(stylize("Please ", Sty.DEFAULT, "login", Sty.GREEN, " or ", Sty.DEFAULT, "register", Sty.RED, " to continue. (", Sty.DEFAULT, 'l', Sty.GREEN, "/", Sty.DEFAULT, 'r', Sty.RED, "): ", Sty.DEFAULT))
|
|
72
77
|
if inpu == 'r':
|
|
@@ -103,7 +108,7 @@ def welcome():
|
|
|
103
108
|
exit()
|
|
104
109
|
|
|
105
110
|
def title():
|
|
106
|
-
printf(f"Welcome to the RemoteRF Platform", (Sty.BOLD, Sty.BLUE),
|
|
111
|
+
printf(f"Welcome to the RemoteRF Platform", (Sty.BOLD, Sty.BLUE), _banner_details(), (Sty.GRAY))
|
|
107
112
|
printf("Terms of Service: ", Sty.BOLD, _tos_url(), Sty.DEFAULT)
|
|
108
113
|
# printf(f"Logged in as: ", Sty.DEFAULT, f'{account.username}', Sty.MAGENTA)
|
|
109
114
|
printf(f"Input ", Sty.DEFAULT, "'help' ", Sty.BRIGHT_GREEN, "for a list of avaliable commands.", Sty.DEFAULT)
|
|
@@ -9,12 +9,40 @@ from ..common.grpc import grpc_pb2
|
|
|
9
9
|
from ..common.grpc import grpc_pb2_grpc
|
|
10
10
|
from ..common.utils import *
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
_CONFIG_PATH = Path.home() / ".config" / "remoterf-client" / ".env"
|
|
13
|
+
_CONFIG_HELP = (
|
|
14
|
+
"Run:\n"
|
|
15
|
+
" remoterf --config --addr <host:port>\n"
|
|
16
|
+
"Example:\n"
|
|
17
|
+
" remoterf --config --addr 123.45.654.321:12321"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _load_client_config() -> tuple[str, str]:
|
|
22
|
+
load_dotenv(_CONFIG_PATH)
|
|
23
|
+
addr = (os.getenv("REMOTERF_ADDR") or "").strip().strip('"').strip("'")
|
|
24
|
+
ca_path = (os.getenv("REMOTERF_CA_CERT") or "").strip().strip('"').strip("'")
|
|
25
|
+
|
|
26
|
+
if not addr or not ca_path:
|
|
27
|
+
raise RuntimeError(
|
|
28
|
+
"RemoteRF client is not configured.\n"
|
|
29
|
+
f"Expected REMOTERF_ADDR and REMOTERF_CA_CERT in:\n {_CONFIG_PATH}\n"
|
|
30
|
+
f"{_CONFIG_HELP}"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
certs_path = Path(ca_path).expanduser()
|
|
34
|
+
if not certs_path.exists():
|
|
35
|
+
raise RuntimeError(
|
|
36
|
+
"RemoteRF client config points to a missing CA certificate.\n"
|
|
37
|
+
f"REMOTERF_CA_CERT={ca_path}\n"
|
|
38
|
+
"Re-run RemoteRF client config to fetch the certificate again.\n"
|
|
39
|
+
f"{_CONFIG_HELP}"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
return addr, ca_path
|
|
43
|
+
|
|
15
44
|
|
|
16
|
-
addr =
|
|
17
|
-
ca_path = ca_path.strip().strip('"').strip("'")
|
|
45
|
+
addr, ca_path = _load_client_config()
|
|
18
46
|
|
|
19
47
|
options = [
|
|
20
48
|
('grpc.max_send_message_length', 100 * 1024 * 1024),
|
|
@@ -115,6 +115,20 @@ def _try_call(prop, token, arg=_NO_ARG):
|
|
|
115
115
|
)
|
|
116
116
|
result = resp.results.get(prop)
|
|
117
117
|
return unmap_arg(result) if result is not None else None
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _try_calln(prop, token, kwargs):
|
|
121
|
+
payload = {'a': map_arg(token)}
|
|
122
|
+
for key, value in dict(kwargs).items():
|
|
123
|
+
if value is _NO_ARG:
|
|
124
|
+
continue
|
|
125
|
+
payload[str(key)] = map_arg(value)
|
|
126
|
+
resp = rpc_client(
|
|
127
|
+
function_name=f"{_PREFIX}:{prop}:CALLN",
|
|
128
|
+
args=payload,
|
|
129
|
+
)
|
|
130
|
+
result = resp.results.get(prop)
|
|
131
|
+
return unmap_arg(result) if result is not None else None
|
|
118
132
|
'''
|
|
119
133
|
|
|
120
134
|
|
|
@@ -188,10 +202,49 @@ def _codegen(schema: dict) -> str:
|
|
|
188
202
|
# ── Call methods ──────────────────────────────────────────────────
|
|
189
203
|
for method, info in sorted(call_map.items()):
|
|
190
204
|
doc = info.get("doc", "")
|
|
191
|
-
|
|
205
|
+
args_meta = info.get("args")
|
|
206
|
+
|
|
207
|
+
if args_meta is None:
|
|
208
|
+
lines.append(f" def {method}(self, _v=_NO_ARG):")
|
|
209
|
+
if doc:
|
|
210
|
+
lines.append(f' """{doc}"""')
|
|
211
|
+
lines.append(f' return _try_call("{method}", self.token, _v)')
|
|
212
|
+
lines.append("")
|
|
213
|
+
continue
|
|
214
|
+
|
|
215
|
+
params = []
|
|
216
|
+
inserted_keyword_marker = False
|
|
217
|
+
for arg in args_meta:
|
|
218
|
+
name = str(arg.get("name", "")).strip()
|
|
219
|
+
if not name:
|
|
220
|
+
continue
|
|
221
|
+
if arg.get("kind") == "keyword_only" and not inserted_keyword_marker:
|
|
222
|
+
params.append("*")
|
|
223
|
+
inserted_keyword_marker = True
|
|
224
|
+
if arg.get("required", True):
|
|
225
|
+
params.append(name)
|
|
226
|
+
else:
|
|
227
|
+
params.append(f"{name}=_NO_ARG")
|
|
228
|
+
|
|
229
|
+
signature = ", ".join(["self", *params])
|
|
230
|
+
lines.append(f" def {method}({signature}):")
|
|
192
231
|
if doc:
|
|
193
232
|
lines.append(f' """{doc}"""')
|
|
194
|
-
|
|
233
|
+
|
|
234
|
+
arg_names = [
|
|
235
|
+
str(arg.get("name", "")).strip()
|
|
236
|
+
for arg in args_meta
|
|
237
|
+
if str(arg.get("name", "")).strip()
|
|
238
|
+
]
|
|
239
|
+
if len(arg_names) == 0:
|
|
240
|
+
lines.append(f' return _try_call("{method}", self.token)')
|
|
241
|
+
elif len(arg_names) == 1 and args_meta[0].get("required", True):
|
|
242
|
+
lines.append(f' return _try_call("{method}", self.token, {arg_names[0]})')
|
|
243
|
+
else:
|
|
244
|
+
lines.append(f' return _try_calln("{method}", self.token, {{')
|
|
245
|
+
for arg_name in arg_names:
|
|
246
|
+
lines.append(f' "{arg_name}": {arg_name},')
|
|
247
|
+
lines.append(" })")
|
|
195
248
|
lines.append("")
|
|
196
249
|
|
|
197
250
|
return "\n".join(lines)
|
|
@@ -39,4 +39,6 @@ src/remoterf.egg-info/SOURCES.txt
|
|
|
39
39
|
src/remoterf.egg-info/dependency_links.txt
|
|
40
40
|
src/remoterf.egg-info/entry_points.txt
|
|
41
41
|
src/remoterf.egg-info/requires.txt
|
|
42
|
-
src/remoterf.egg-info/top_level.txt
|
|
42
|
+
src/remoterf.egg-info/top_level.txt
|
|
43
|
+
tests/test_dynamic_device_codegen.py
|
|
44
|
+
tests/test_process_arg.py
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
import sys
|
|
3
|
+
import types
|
|
4
|
+
|
|
5
|
+
fake_grpc_client = types.ModuleType("remoteRF.core.grpc_client")
|
|
6
|
+
fake_grpc_client.rpc_client = lambda *args, **kwargs: None
|
|
7
|
+
sys.modules.setdefault("remoteRF.core.grpc_client", fake_grpc_client)
|
|
8
|
+
from remoteRF.drivers.dynamic_device import _codegen
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DynamicDeviceCodegenTests(unittest.TestCase):
|
|
12
|
+
def test_codegen_emits_calln_for_named_optional_or_multi_arg_methods(self):
|
|
13
|
+
code = _codegen({
|
|
14
|
+
"device_type": "fake_device",
|
|
15
|
+
"client_class": "FakeDevice",
|
|
16
|
+
"driver_version": "0.0.0",
|
|
17
|
+
"schema_hash": "sha256:test",
|
|
18
|
+
"getters": {},
|
|
19
|
+
"setters": {},
|
|
20
|
+
"calls": {
|
|
21
|
+
"call_configure": {
|
|
22
|
+
"doc": "Configure",
|
|
23
|
+
"args": [
|
|
24
|
+
{"name": "freq", "required": True, "type": "float"},
|
|
25
|
+
{"name": "gain", "required": False, "type": "int", "default": 0},
|
|
26
|
+
{"name": "options", "required": False, "type": "dict", "default": None},
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
"call_rx": {"args": []},
|
|
30
|
+
"call_tx": {"args": [{"name": "samples", "required": True, "type": "any"}]},
|
|
31
|
+
"call_legacy": {},
|
|
32
|
+
"call_keyword": {
|
|
33
|
+
"args": [
|
|
34
|
+
{"name": "first", "required": False, "kind": "positional_or_keyword"},
|
|
35
|
+
{"name": "required", "required": True, "kind": "keyword_only"},
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
compile(code, "<generated>", "exec")
|
|
42
|
+
self.assertIn("def configure(self, freq, gain=_NO_ARG, options=_NO_ARG):", code)
|
|
43
|
+
self.assertIn('return _try_calln("configure", self.token, {', code)
|
|
44
|
+
self.assertIn('"options": options,', code)
|
|
45
|
+
self.assertIn("def rx(self):", code)
|
|
46
|
+
self.assertIn('return _try_call("rx", self.token)', code)
|
|
47
|
+
self.assertIn("def tx(self, samples):", code)
|
|
48
|
+
self.assertIn('return _try_call("tx", self.token, samples)', code)
|
|
49
|
+
self.assertIn("def legacy(self, _v=_NO_ARG):", code)
|
|
50
|
+
self.assertIn("def keyword(self, first=_NO_ARG, *, required):", code)
|
|
51
|
+
|
|
52
|
+
def test_generated_helpers_include_calln_and_omit_no_arg_sentinel(self):
|
|
53
|
+
code = _codegen({
|
|
54
|
+
"device_type": "fake_device",
|
|
55
|
+
"client_class": "FakeDevice",
|
|
56
|
+
"driver_version": "0.0.0",
|
|
57
|
+
"schema_hash": "sha256:test",
|
|
58
|
+
"getters": {},
|
|
59
|
+
"setters": {},
|
|
60
|
+
"calls": {},
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
self.assertIn("def _try_calln(prop, token, kwargs):", code)
|
|
64
|
+
self.assertIn("if value is _NO_ARG:", code)
|
|
65
|
+
self.assertIn('function_name=f"{_PREFIX}:{prop}:CALLN"', code)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
unittest.main()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from remoteRF.common.utils.process_arg import map_arg, unmap_arg
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ProcessArgTests(unittest.TestCase):
|
|
9
|
+
def test_json_values_round_trip_dict_and_none(self):
|
|
10
|
+
value = {"nested": {"x": 1}, "flag": True, "nothing": None}
|
|
11
|
+
|
|
12
|
+
self.assertEqual(unmap_arg(map_arg(value)), value)
|
|
13
|
+
self.assertIsNone(unmap_arg(map_arg(None)))
|
|
14
|
+
|
|
15
|
+
def test_existing_scalar_values_still_round_trip(self):
|
|
16
|
+
self.assertIs(unmap_arg(map_arg(True)), True)
|
|
17
|
+
self.assertEqual(unmap_arg(map_arg(12)), 12)
|
|
18
|
+
self.assertAlmostEqual(unmap_arg(map_arg(1.5)), 1.5)
|
|
19
|
+
self.assertEqual(unmap_arg(map_arg("abc")), "abc")
|
|
20
|
+
|
|
21
|
+
def test_arrays_and_list_values_still_round_trip_as_arrays(self):
|
|
22
|
+
real = np.array([[1.0, 2.0], [3.0, 4.0]], dtype=np.float32)
|
|
23
|
+
complex_value = np.array([1 + 2j, 3 + 4j], dtype=np.complex64)
|
|
24
|
+
|
|
25
|
+
np.testing.assert_allclose(unmap_arg(map_arg(real)), real)
|
|
26
|
+
np.testing.assert_allclose(unmap_arg(map_arg([1, 2, 3])), np.array([1, 2, 3]))
|
|
27
|
+
np.testing.assert_allclose(unmap_arg(map_arg(complex_value)), complex_value)
|
|
28
|
+
|
|
29
|
+
def test_json_values_can_contain_arrays_and_mixed_return_tuples(self):
|
|
30
|
+
samples = np.array([[1 + 2j, 3 + 4j]], dtype=np.complex64)
|
|
31
|
+
value = (2, samples, {"error_code": "none"})
|
|
32
|
+
|
|
33
|
+
count, restored_samples, metadata = unmap_arg(map_arg(value))
|
|
34
|
+
|
|
35
|
+
self.assertEqual(count, 2)
|
|
36
|
+
np.testing.assert_allclose(restored_samples, samples)
|
|
37
|
+
self.assertEqual(metadata, {"error_code": "none"})
|
|
38
|
+
|
|
39
|
+
nested = unmap_arg(map_arg({"samples": samples}))
|
|
40
|
+
np.testing.assert_allclose(nested["samples"], samples)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if __name__ == "__main__":
|
|
44
|
+
unittest.main()
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
from ..grpc import grpc_pb2, grpc_pb2_grpc
|
|
2
|
-
import numpy as np
|
|
3
|
-
|
|
4
|
-
def unmap_arg(arg):
|
|
5
|
-
if arg.HasField('int64_value'):
|
|
6
|
-
return arg.int64_value
|
|
7
|
-
elif arg.HasField('float_value'):
|
|
8
|
-
return arg.float_value
|
|
9
|
-
elif arg.HasField('string_value'):
|
|
10
|
-
return arg.string_value
|
|
11
|
-
elif arg.HasField('bool_value'):
|
|
12
|
-
return arg.bool_value
|
|
13
|
-
elif arg.HasField('real_array'):
|
|
14
|
-
shape = tuple(arg.real_array.shape.dim)
|
|
15
|
-
return np.array(arg.real_array.data, dtype=np.float64).reshape(shape)
|
|
16
|
-
elif arg.HasField('complex_array'):
|
|
17
|
-
shape = tuple(arg.complex_array.shape.dim)
|
|
18
|
-
data = [complex(c.real, c.imag) for c in arg.complex_array.data]
|
|
19
|
-
return np.array(data, dtype=np.complex64).reshape(shape)
|
|
20
|
-
else:
|
|
21
|
-
raise ValueError(f"Unknown argument type during unmapping: {arg}")
|
|
22
|
-
|
|
23
|
-
def map_arg(value):
|
|
24
|
-
arg = grpc_pb2.Argument()
|
|
25
|
-
|
|
26
|
-
if isinstance(value, int):
|
|
27
|
-
arg.int64_value = value
|
|
28
|
-
elif isinstance(value, float):
|
|
29
|
-
arg.float_value = value
|
|
30
|
-
elif isinstance(value, str):
|
|
31
|
-
arg.string_value = value
|
|
32
|
-
elif isinstance(value, bool):
|
|
33
|
-
arg.bool_value = value
|
|
34
|
-
elif isinstance(value, (list, tuple)):
|
|
35
|
-
return map_arg(np.array(value))
|
|
36
|
-
elif isinstance(value, np.ndarray):
|
|
37
|
-
if np.iscomplexobj(value):
|
|
38
|
-
complex_array = arg.complex_array
|
|
39
|
-
complex_array.shape.dim.extend(value.shape)
|
|
40
|
-
for num in value.ravel():
|
|
41
|
-
complex_num = complex_array.data.add()
|
|
42
|
-
complex_num.real = num.real
|
|
43
|
-
complex_num.imag = num.imag
|
|
44
|
-
else:
|
|
45
|
-
float_array = arg.real_array
|
|
46
|
-
float_array.shape.dim.extend(value.shape)
|
|
47
|
-
float_array.data.extend(value.ravel())
|
|
48
|
-
else:
|
|
49
|
-
raise ValueError(f"Unknown argument type during mapping: {value}")
|
|
50
|
-
return arg
|
|
51
|
-
|
|
52
|
-
def map_array_proto(np_array):
|
|
53
|
-
arg = grpc_pb2.Argument()
|
|
54
|
-
|
|
55
|
-
# Check if the array is complex
|
|
56
|
-
if np.iscomplexobj(np_array):
|
|
57
|
-
complex_array = grpc_pb2.ComplexArray()
|
|
58
|
-
for num in np_array.flat:
|
|
59
|
-
complex_number = complex_array.data.add()
|
|
60
|
-
complex_number.real = num.real
|
|
61
|
-
complex_number.imag = num.imag
|
|
62
|
-
arg.complex_array.CopyFrom(complex_array)
|
|
63
|
-
else:
|
|
64
|
-
# Handle as a regular float array
|
|
65
|
-
float_array = grpc_pb2.FloatArray()
|
|
66
|
-
float_array.data.extend(np_array.flat)
|
|
67
|
-
arg.float_array.CopyFrom(float_array)
|
|
68
|
-
|
|
69
|
-
return arg
|
|
70
|
-
|
|
71
|
-
def unmap_array_proto(arg):
|
|
72
|
-
# Check which type of array is available and convert appropriately
|
|
73
|
-
if arg.HasField('complex_array'):
|
|
74
|
-
# Convert ComplexArray to a numpy array of complex numbers
|
|
75
|
-
data = [complex(cn.real, cn.imag) for cn in arg.complex_array.data]
|
|
76
|
-
return np.array(data, dtype=np.complex64)
|
|
77
|
-
elif arg.HasField('float_array'):
|
|
78
|
-
# Convert FloatArray to a numpy array of floats
|
|
79
|
-
return np.array(arg.float_array.data, dtype=np.float32)
|
|
80
|
-
else:
|
|
81
|
-
raise ValueError("Argument does not contain a recognizable array.")
|
|
82
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|