RemoteRF 2.0.1__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.
Files changed (41) hide show
  1. {remoterf-2.0.1/src/remoteRF.egg-info → remoterf-2.0.2}/PKG-INFO +1 -1
  2. {remoterf-2.0.1 → remoterf-2.0.2}/setup.py +1 -1
  3. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF/common/grpc/grpc_pb2.py +7 -7
  4. remoterf-2.0.2/src/remoteRF/common/utils/process_arg.py +153 -0
  5. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF/core/app.py +7 -2
  6. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF/drivers/dynamic_device.py +55 -2
  7. {remoterf-2.0.1 → remoterf-2.0.2/src/remoteRF.egg-info}/PKG-INFO +1 -1
  8. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF.egg-info/SOURCES.txt +3 -1
  9. remoterf-2.0.2/tests/test_dynamic_device_codegen.py +69 -0
  10. remoterf-2.0.2/tests/test_process_arg.py +44 -0
  11. remoterf-2.0.1/src/remoteRF/common/utils/process_arg.py +0 -82
  12. {remoterf-2.0.1 → remoterf-2.0.2}/LICENSE +0 -0
  13. {remoterf-2.0.1 → remoterf-2.0.2}/MANIFEST.in +0 -0
  14. {remoterf-2.0.1 → remoterf-2.0.2}/README.md +0 -0
  15. {remoterf-2.0.1 → remoterf-2.0.2}/setup.cfg +0 -0
  16. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF/__init__.py +0 -0
  17. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF/common/__init__.py +0 -0
  18. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF/common/grpc/__init__.py +0 -0
  19. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF/common/grpc/grpc_pb2_grpc.py +0 -0
  20. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF/common/tos_notice.txt +0 -0
  21. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF/common/utils/__init__.py +0 -0
  22. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF/common/utils/ansi_codes.py +0 -0
  23. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF/common/utils/api_token.py +0 -0
  24. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF/common/utils/list_string.py +0 -0
  25. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF/config/__init__.py +0 -0
  26. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF/config/cert_fetcher.py +0 -0
  27. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF/config/config.py +0 -0
  28. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF/core/__init__.py +0 -0
  29. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF/core/acc_login.py +0 -0
  30. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF/core/banner_note.txt +0 -0
  31. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF/core/grpc_acc.py +0 -0
  32. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF/core/grpc_client.py +0 -0
  33. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF/drivers/__init__.py +0 -0
  34. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF/drivers/adalm_pluto/__init__.py +0 -0
  35. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF/drivers/adalm_pluto/pluto_remote.py +0 -0
  36. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF/remoterf_cli.py +0 -0
  37. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF/version.py +0 -0
  38. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF.egg-info/dependency_links.txt +0 -0
  39. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF.egg-info/entry_points.txt +0 -0
  40. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF.egg-info/requires.txt +0 -0
  41. {remoterf-2.0.1 → remoterf-2.0.2}/src/remoteRF.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoterf
3
- Version: 2.0.1
3
+ Version: 2.0.2
4
4
  Summary: A python API to remotely access signal centric hardware. Client-side only! Courtesy of Wireless Lab @ UCLA & Prof. Ian Roberts.
5
5
  Author: Ethan Ge
6
6
  Author-email: ethoGalaxy@gmail.com
@@ -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.1",
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.29.0
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
- 29,
16
- 0,
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\"\xd7\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\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')
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=800
57
- _globals['_GENERICRPC']._serialized_start=802
58
- _globals['_GENERICRPC']._serialized_end=883
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), f"\nCurrent version: {print_my_version()} \n{_banner_note()}", (Sty.GRAY))
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), f"\nCurrent version: {print_my_version()} \n{_banner_note()}", (Sty.GRAY))
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)
@@ -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
- lines.append(f" def {method}(self, _v=_NO_ARG):")
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
- lines.append(f' return _try_call("{method}", self.token, _v)')
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoterf
3
- Version: 2.0.1
3
+ Version: 2.0.2
4
4
  Summary: A python API to remotely access signal centric hardware. Client-side only! Courtesy of Wireless Lab @ UCLA & Prof. Ian Roberts.
5
5
  Author: Ethan Ge
6
6
  Author-email: ethoGalaxy@gmail.com
@@ -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