remoteRF-server 1.0.7__tar.gz → 1.0.9__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 (74) hide show
  1. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/PKG-INFO +1 -1
  2. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/pyproject.toml +1 -1
  3. {remoterf_server-1.0.7/src/remoteRF_server/drivers/adalm_pluto → remoterf_server-1.0.9/src/remoteRF_server/common/idl}/pluto_schema.py +128 -5
  4. {remoterf_server-1.0.7/src/remoteRF_server/common/idl → remoterf_server-1.0.9/src/remoteRF_server/drivers/adalm_pluto}/pluto_schema.py +128 -5
  5. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/drivers/usrp/usrp_schema.py +45 -3
  6. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server.egg-info/PKG-INFO +1 -1
  7. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/tests/test_idl_schema.py +47 -2
  8. remoterf_server-1.0.9/tests/test_pluto_compat.py +221 -0
  9. remoterf_server-1.0.7/tests/test_pluto_compat.py +0 -99
  10. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/README.md +0 -0
  11. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/setup.cfg +0 -0
  12. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/__init__.py +0 -0
  13. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/common/__init__.py +0 -0
  14. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/common/grpc/__init__.py +0 -0
  15. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/common/grpc/grpc_host_pb2.py +0 -0
  16. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/common/grpc/grpc_host_pb2_grpc.py +0 -0
  17. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/common/grpc/grpc_pb2.py +0 -0
  18. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/common/grpc/grpc_pb2_grpc.py +0 -0
  19. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/common/grpc/v2_codec.py +0 -0
  20. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/common/idl/__init__.py +0 -0
  21. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/common/idl/device_schema.py +0 -0
  22. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/common/idl/pluto_mimo_schema.py +0 -0
  23. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/common/idl/schema.py +0 -0
  24. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/common/idl/v2.py +0 -0
  25. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/common/utils/__init__.py +0 -0
  26. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/common/utils/ansi_codes.py +0 -0
  27. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/common/utils/api_token.py +0 -0
  28. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/common/utils/db_connection.py +0 -0
  29. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/common/utils/db_location.py +0 -0
  30. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/common/utils/list_string.py +0 -0
  31. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/common/utils/process_arg.py +0 -0
  32. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/drivers/__init__.py +0 -0
  33. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/drivers/adalm_pluto/__init__.py +0 -0
  34. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/drivers/adalm_pluto/pluto_mimo_schema.py +0 -0
  35. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/drivers/adalm_pluto/pluto_remote_server.py +0 -0
  36. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/drivers/usrp/__init__.py +0 -0
  37. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/drivers/usrp/policy.py +0 -0
  38. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/drivers/usrp/uhd_manifest.py +0 -0
  39. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/host/__init__.py +0 -0
  40. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/host/host_auth_token.py +0 -0
  41. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/host/host_directory_store.py +0 -0
  42. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/host/host_tunnel_server.py +0 -0
  43. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/server/__init__.py +0 -0
  44. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/server/acc_perms.py +0 -0
  45. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/server/cert_provider.py +0 -0
  46. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/server/device_manager.py +0 -0
  47. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/server/dynamic_v2.py +0 -0
  48. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/server/grpc_server.py +0 -0
  49. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/server/reservation.py +0 -0
  50. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/server/rpc_manager.py +0 -0
  51. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/server/user_group_cli.py +0 -0
  52. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/server/user_group_handler.py +0 -0
  53. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/server/v2_errors.py +0 -0
  54. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/serverrf_cli.py +0 -0
  55. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/tools/__init__.py +0 -0
  56. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/tools/gen_certs.py +0 -0
  57. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/tools/gist_status.py +0 -0
  58. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/tools/gist_status_testing.py +0 -0
  59. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server/tools/introspect_uhd.py +0 -0
  60. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server.egg-info/SOURCES.txt +0 -0
  61. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server.egg-info/dependency_links.txt +0 -0
  62. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server.egg-info/entry_points.txt +0 -0
  63. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server.egg-info/requires.txt +0 -0
  64. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/src/remoteRF_server.egg-info/top_level.txt +0 -0
  65. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/tests/test_cert_provider.py +0 -0
  66. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/tests/test_default_usrp_schema.py +0 -0
  67. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/tests/test_dynamic_v2.py +0 -0
  68. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/tests/test_process_arg.py +0 -0
  69. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/tests/test_reservation_timestamps.py +0 -0
  70. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/tests/test_rpc_manager_calln.py +0 -0
  71. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/tests/test_usrp_client_server_hardware.py +0 -0
  72. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/tests/test_usrp_client_server_localhost.py +0 -0
  73. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/tests/test_usrp_remoterf_rx_hardware.py +0 -0
  74. {remoterf_server-1.0.7 → remoterf_server-1.0.9}/tests/test_usrp_uhd410_hardware.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoteRF-server
3
- Version: 1.0.7
3
+ Version: 1.0.9
4
4
  Summary: RemoteRF server-side control package
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "remoteRF-server"
7
- version = "1.0.7"
7
+ version = "1.0.9"
8
8
  description = "RemoteRF server-side control package"
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
@@ -8,6 +8,26 @@ import subprocess
8
8
  import re
9
9
  import adi
10
10
 
11
+
12
+ def _normalize_channels(value):
13
+ """Restore channel lists after numeric lists cross the legacy RPC codec."""
14
+ if hasattr(value, "tolist"):
15
+ value = value.tolist()
16
+ if isinstance(value, tuple):
17
+ value = list(value)
18
+ if not isinstance(value, list):
19
+ return value
20
+
21
+ channels = []
22
+ for channel in value:
23
+ if not isinstance(channel, str):
24
+ integer = int(channel)
25
+ if channel == integer:
26
+ channel = integer
27
+ channels.append(channel)
28
+ return channels
29
+
30
+
11
31
  # == PLUTO Connection Logic ==
12
32
 
13
33
  def connect_pluto(*, serial: str):
@@ -50,7 +70,7 @@ class PlutoSchema(DeviceSchema):
50
70
 
51
71
  device_type = "adalm_pluto"
52
72
  client_class = "Pluto"
53
- driver_version = "0.0.1"
73
+ driver_version = "0.0.2"
54
74
 
55
75
  @staticmethod
56
76
  def make_device(**kwargs):
@@ -85,13 +105,13 @@ class PlutoSchema(DeviceSchema):
85
105
 
86
106
  @idl_expose(kind="get")
87
107
  def get_rx_hardwaregain_chan0(self):
88
- """RX hardware gain (dB). pyadi-iio attribute: hardwaregain_chan0"""
89
- return self.device.hardwaregain_chan0
108
+ """RX hardware gain (dB). pyadi-iio attribute: rx_hardwaregain_chan0"""
109
+ return self.device.rx_hardwaregain_chan0
90
110
 
91
111
  @idl_expose(kind="set")
92
112
  def set_rx_hardwaregain_chan0(self, value):
93
- """RX hardware gain (dB). pyadi-iio attribute: hardwaregain_chan0"""
94
- self.device.hardwaregain_chan0 = value
113
+ """RX hardware gain (dB). pyadi-iio attribute: rx_hardwaregain_chan0"""
114
+ self.device.rx_hardwaregain_chan0 = value
95
115
 
96
116
  @idl_expose(kind="get")
97
117
  def get_tx_hardwaregain_chan0(self):
@@ -180,6 +200,109 @@ class PlutoSchema(DeviceSchema):
180
200
  """Disable the internal DDS tone generator."""
181
201
  self.device.disable_dds()
182
202
 
203
+ # region DDS
204
+
205
+ @idl_expose(kind="call")
206
+ def call_dds_single_tone(self, frequency, scale, channel=0):
207
+ """Generate one complex DDS tone on the selected TX channel."""
208
+ self.device.dds_single_tone(frequency, scale, channel)
209
+
210
+ @idl_expose(kind="call")
211
+ def call_dds_dual_tone(
212
+ self,
213
+ frequency1,
214
+ scale1,
215
+ frequency2,
216
+ scale2,
217
+ channel=0,
218
+ ):
219
+ """Generate two complex DDS tones on the selected TX channel."""
220
+ self.device.dds_dual_tone(
221
+ frequency1,
222
+ scale1,
223
+ frequency2,
224
+ scale2,
225
+ channel,
226
+ )
227
+
228
+ @idl_expose(kind="get")
229
+ def get_dds_frequencies(self):
230
+ return self.device.dds_frequencies
231
+
232
+ @idl_expose(kind="set")
233
+ def set_dds_frequencies(self, value):
234
+ self.device.dds_frequencies = value
235
+
236
+ @idl_expose(kind="get")
237
+ def get_dds_scales(self):
238
+ return self.device.dds_scales
239
+
240
+ @idl_expose(kind="set")
241
+ def set_dds_scales(self, value):
242
+ self.device.dds_scales = value
243
+
244
+ @idl_expose(kind="get")
245
+ def get_dds_phases(self):
246
+ return self.device.dds_phases
247
+
248
+ @idl_expose(kind="set")
249
+ def set_dds_phases(self, value):
250
+ self.device.dds_phases = value
251
+
252
+ @idl_expose(kind="get")
253
+ def get_dds_enabled(self):
254
+ return self.device.dds_enabled
255
+
256
+ @idl_expose(kind="set")
257
+ def set_dds_enabled(self, value):
258
+ self.device.dds_enabled = value
259
+
260
+ # endregion
261
+
262
+ # region channel configuration
263
+
264
+ @idl_expose(kind="get")
265
+ def get_rx_enabled_channels(self):
266
+ return self.device.rx_enabled_channels
267
+
268
+ @idl_expose(kind="set")
269
+ def set_rx_enabled_channels(self, value):
270
+ self.device.rx_enabled_channels = _normalize_channels(value)
271
+
272
+ @idl_expose(kind="get")
273
+ def get_tx_enabled_channels(self):
274
+ return self.device.tx_enabled_channels
275
+
276
+ @idl_expose(kind="set")
277
+ def set_tx_enabled_channels(self, value):
278
+ self.device.tx_enabled_channels = _normalize_channels(value)
279
+
280
+ @idl_expose(kind="get")
281
+ def get_rx_annotated(self):
282
+ return self.device.rx_annotated
283
+
284
+ @idl_expose(kind="set")
285
+ def set_rx_annotated(self, value):
286
+ self.device.rx_annotated = value
287
+
288
+ @idl_expose(kind="get")
289
+ def get_rx_output_type(self):
290
+ return self.device.rx_output_type
291
+
292
+ @idl_expose(kind="set")
293
+ def set_rx_output_type(self, value):
294
+ self.device.rx_output_type = value
295
+
296
+ @idl_expose(kind="get")
297
+ def get_rx_channel_names(self):
298
+ return self.device.rx_channel_names
299
+
300
+ @idl_expose(kind="get")
301
+ def get_tx_channel_names(self):
302
+ return self.device.tx_channel_names
303
+
304
+ # endregion
305
+
183
306
  # endregion
184
307
 
185
308
  # region ip / repr
@@ -8,6 +8,26 @@ import subprocess
8
8
  import re
9
9
  import adi
10
10
 
11
+
12
+ def _normalize_channels(value):
13
+ """Restore channel lists after numeric lists cross the legacy RPC codec."""
14
+ if hasattr(value, "tolist"):
15
+ value = value.tolist()
16
+ if isinstance(value, tuple):
17
+ value = list(value)
18
+ if not isinstance(value, list):
19
+ return value
20
+
21
+ channels = []
22
+ for channel in value:
23
+ if not isinstance(channel, str):
24
+ integer = int(channel)
25
+ if channel == integer:
26
+ channel = integer
27
+ channels.append(channel)
28
+ return channels
29
+
30
+
11
31
  # == PLUTO Connection Logic ==
12
32
 
13
33
  def connect_pluto(*, serial: str):
@@ -50,7 +70,7 @@ class PlutoSchema(DeviceSchema):
50
70
 
51
71
  device_type = "adalm_pluto"
52
72
  client_class = "Pluto"
53
- driver_version = "0.0.1"
73
+ driver_version = "0.0.2"
54
74
 
55
75
  @staticmethod
56
76
  def make_device(**kwargs):
@@ -85,13 +105,13 @@ class PlutoSchema(DeviceSchema):
85
105
 
86
106
  @idl_expose(kind="get")
87
107
  def get_rx_hardwaregain_chan0(self):
88
- """RX hardware gain (dB). pyadi-iio attribute: hardwaregain_chan0"""
89
- return self.device.hardwaregain_chan0
108
+ """RX hardware gain (dB). pyadi-iio attribute: rx_hardwaregain_chan0"""
109
+ return self.device.rx_hardwaregain_chan0
90
110
 
91
111
  @idl_expose(kind="set")
92
112
  def set_rx_hardwaregain_chan0(self, value):
93
- """RX hardware gain (dB). pyadi-iio attribute: hardwaregain_chan0"""
94
- self.device.hardwaregain_chan0 = value
113
+ """RX hardware gain (dB). pyadi-iio attribute: rx_hardwaregain_chan0"""
114
+ self.device.rx_hardwaregain_chan0 = value
95
115
 
96
116
  @idl_expose(kind="get")
97
117
  def get_tx_hardwaregain_chan0(self):
@@ -180,6 +200,109 @@ class PlutoSchema(DeviceSchema):
180
200
  """Disable the internal DDS tone generator."""
181
201
  self.device.disable_dds()
182
202
 
203
+ # region DDS
204
+
205
+ @idl_expose(kind="call")
206
+ def call_dds_single_tone(self, frequency, scale, channel=0):
207
+ """Generate one complex DDS tone on the selected TX channel."""
208
+ self.device.dds_single_tone(frequency, scale, channel)
209
+
210
+ @idl_expose(kind="call")
211
+ def call_dds_dual_tone(
212
+ self,
213
+ frequency1,
214
+ scale1,
215
+ frequency2,
216
+ scale2,
217
+ channel=0,
218
+ ):
219
+ """Generate two complex DDS tones on the selected TX channel."""
220
+ self.device.dds_dual_tone(
221
+ frequency1,
222
+ scale1,
223
+ frequency2,
224
+ scale2,
225
+ channel,
226
+ )
227
+
228
+ @idl_expose(kind="get")
229
+ def get_dds_frequencies(self):
230
+ return self.device.dds_frequencies
231
+
232
+ @idl_expose(kind="set")
233
+ def set_dds_frequencies(self, value):
234
+ self.device.dds_frequencies = value
235
+
236
+ @idl_expose(kind="get")
237
+ def get_dds_scales(self):
238
+ return self.device.dds_scales
239
+
240
+ @idl_expose(kind="set")
241
+ def set_dds_scales(self, value):
242
+ self.device.dds_scales = value
243
+
244
+ @idl_expose(kind="get")
245
+ def get_dds_phases(self):
246
+ return self.device.dds_phases
247
+
248
+ @idl_expose(kind="set")
249
+ def set_dds_phases(self, value):
250
+ self.device.dds_phases = value
251
+
252
+ @idl_expose(kind="get")
253
+ def get_dds_enabled(self):
254
+ return self.device.dds_enabled
255
+
256
+ @idl_expose(kind="set")
257
+ def set_dds_enabled(self, value):
258
+ self.device.dds_enabled = value
259
+
260
+ # endregion
261
+
262
+ # region channel configuration
263
+
264
+ @idl_expose(kind="get")
265
+ def get_rx_enabled_channels(self):
266
+ return self.device.rx_enabled_channels
267
+
268
+ @idl_expose(kind="set")
269
+ def set_rx_enabled_channels(self, value):
270
+ self.device.rx_enabled_channels = _normalize_channels(value)
271
+
272
+ @idl_expose(kind="get")
273
+ def get_tx_enabled_channels(self):
274
+ return self.device.tx_enabled_channels
275
+
276
+ @idl_expose(kind="set")
277
+ def set_tx_enabled_channels(self, value):
278
+ self.device.tx_enabled_channels = _normalize_channels(value)
279
+
280
+ @idl_expose(kind="get")
281
+ def get_rx_annotated(self):
282
+ return self.device.rx_annotated
283
+
284
+ @idl_expose(kind="set")
285
+ def set_rx_annotated(self, value):
286
+ self.device.rx_annotated = value
287
+
288
+ @idl_expose(kind="get")
289
+ def get_rx_output_type(self):
290
+ return self.device.rx_output_type
291
+
292
+ @idl_expose(kind="set")
293
+ def set_rx_output_type(self, value):
294
+ self.device.rx_output_type = value
295
+
296
+ @idl_expose(kind="get")
297
+ def get_rx_channel_names(self):
298
+ return self.device.rx_channel_names
299
+
300
+ @idl_expose(kind="get")
301
+ def get_tx_channel_names(self):
302
+ return self.device.tx_channel_names
303
+
304
+ # endregion
305
+
183
306
  # endregion
184
307
 
185
308
  # region ip / repr
@@ -98,6 +98,31 @@ def _payload_type(value):
98
98
  return None
99
99
 
100
100
 
101
+ def _tune_request_policy(value, current):
102
+ """Translate a portable policy name back to UHD's native enum value."""
103
+ if not isinstance(value, str):
104
+ return value
105
+ name = value.rsplit(".", 1)[-1].upper()
106
+ policy_types = (
107
+ type(current),
108
+ getattr(getattr(uhd, "types", None), "TuneRequestPolicy", None),
109
+ getattr(getattr(uhd, "types", None), "tune_request_policy", None),
110
+ getattr(
111
+ getattr(getattr(uhd, "libpyuhd", None), "types", None),
112
+ "tune_request_policy",
113
+ None,
114
+ ),
115
+ )
116
+ for policy_type in policy_types:
117
+ if policy_type is None:
118
+ continue
119
+ for candidate in (name, name.lower(), f"POLICY_{name}"):
120
+ native = getattr(policy_type, candidate, None)
121
+ if native is not None:
122
+ return native
123
+ return value
124
+
125
+
101
126
  def _require_dict(value, method):
102
127
  if not isinstance(value, dict):
103
128
  raise TypeError(
@@ -159,7 +184,16 @@ def _construct(value):
159
184
  return uhd.usrp.SubdevSpec(value["spec"])
160
185
 
161
186
  if kind == "TuneRequest":
162
- args = [_construct(item) for item in value.get("args", ())]
187
+ # Older generated clients used ``args`` for constructor arguments.
188
+ # Current TuneRequest snapshots use that same key for UHD's DeviceAddr
189
+ # field, so only sequences may be interpreted as constructor arguments.
190
+ raw_args = value.get("args", ())
191
+ legacy_constructor_args = isinstance(raw_args, (list, tuple))
192
+ args = (
193
+ [_construct(item) for item in raw_args]
194
+ if legacy_constructor_args
195
+ else []
196
+ )
163
197
  kwargs = {
164
198
  key: _construct(item)
165
199
  for key, item in value.get("kwargs", {}).items()
@@ -174,10 +208,18 @@ def _construct(value):
174
208
  obj = constructor(*args, **kwargs)
175
209
  for field in (
176
210
  "rf_freq", "dsp_freq", "rf_freq_policy", "dsp_freq_policy",
177
- "target_freq", "lo_offset", "args",
211
+ "args",
178
212
  ):
213
+ if field == "args" and legacy_constructor_args:
214
+ continue
179
215
  if field in value and hasattr(obj, field):
180
- setattr(obj, field, _construct(value[field]))
216
+ converted = _construct(value[field])
217
+ if field in {"rf_freq_policy", "dsp_freq_policy"}:
218
+ converted = _tune_request_policy(
219
+ converted,
220
+ getattr(obj, field),
221
+ )
222
+ setattr(obj, field, converted)
181
223
  return obj
182
224
 
183
225
  if kind == "DeviceAddr":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoteRF-server
3
- Version: 1.0.7
3
+ Version: 1.0.9
4
4
  Summary: RemoteRF server-side control package
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -319,6 +319,25 @@ class DeviceSchemaTests(unittest.TestCase):
319
319
  def __init__(self, spec):
320
320
  self.spec = spec
321
321
 
322
+ class DeviceAddr:
323
+ def __init__(self, value=""):
324
+ self.value = value
325
+
326
+ class TuneRequestPolicy:
327
+ AUTO = object()
328
+ MANUAL = object()
329
+ NONE = object()
330
+
331
+ class TuneRequest:
332
+ def __init__(self, target_freq=0.0, lo_offset=None):
333
+ self.target_freq = float(target_freq)
334
+ self.lo_offset = lo_offset
335
+ self.rf_freq = 0.0
336
+ self.dsp_freq = 0.0
337
+ self.rf_freq_policy = TuneRequestPolicy.AUTO
338
+ self.dsp_freq_policy = TuneRequestPolicy.AUTO
339
+ self.args = DeviceAddr()
340
+
322
341
  class TXMetadata:
323
342
  def __init__(self):
324
343
  self.has_time_spec = False
@@ -334,6 +353,9 @@ class DeviceSchemaTests(unittest.TestCase):
334
353
 
335
354
  fake_uhd.types = types.SimpleNamespace(
336
355
  TimeSpec=TimeSpec,
356
+ DeviceAddr=DeviceAddr,
357
+ TuneRequest=TuneRequest,
358
+ TuneRequestPolicy=TuneRequestPolicy,
337
359
  TXMetadata=TXMetadata,
338
360
  StreamCMD=StreamCMD,
339
361
  StreamMode=types.SimpleNamespace(
@@ -403,9 +425,32 @@ class DeviceSchemaTests(unittest.TestCase):
403
425
  tune = usrp_schema._construct({
404
426
  "__uhd_type__": "TuneRequest",
405
427
  "args": [915e6],
406
- "kwargs": {"dsp_freq": 0.0},
428
+ "kwargs": {},
429
+ })
430
+ self.assertIsInstance(tune, TuneRequest)
431
+ self.assertEqual(tune.target_freq, 915e6)
432
+
433
+ tune = usrp_schema._construct({
434
+ "__uhd_type__": "TuneRequest",
435
+ "target_freq": 100e6,
436
+ "lo_offset": None,
437
+ "rf_freq": 99e6,
438
+ "dsp_freq": 1e6,
439
+ "rf_freq_policy": "MANUAL",
440
+ "dsp_freq_policy": "AUTO",
441
+ "args": {
442
+ "__uhd_type__": "DeviceAddr",
443
+ "items": {"mode_n": "integer"},
444
+ },
407
445
  })
408
- self.assertEqual(tune, {"args": (915e6,), "kwargs": {"dsp_freq": 0.0}})
446
+ self.assertIsInstance(tune, TuneRequest)
447
+ self.assertEqual(tune.target_freq, 100e6)
448
+ self.assertEqual(tune.rf_freq, 99e6)
449
+ self.assertEqual(tune.dsp_freq, 1e6)
450
+ self.assertIs(tune.rf_freq_policy, TuneRequestPolicy.MANUAL)
451
+ self.assertIs(tune.dsp_freq_policy, TuneRequestPolicy.AUTO)
452
+ self.assertIsInstance(tune.args, DeviceAddr)
453
+ self.assertEqual(tune.args.value, "mode_n=integer")
409
454
 
410
455
  subdev = usrp_schema._construct({"__uhd_type__": "SubdevSpec", "spec": "A:0"})
411
456
  self.assertIsInstance(subdev, SubdevSpec)
@@ -0,0 +1,221 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ import types
5
+ import unittest
6
+ from unittest.mock import patch
7
+
8
+ import numpy as np
9
+
10
+ sys.modules.setdefault("adi", types.ModuleType("adi"))
11
+
12
+ from remoteRF_server.common.idl.pluto_schema import PlutoSchema
13
+ from remoteRF_server.common.utils import map_arg, unmap_arg
14
+ from remoteRF_server.drivers.adalm_pluto.pluto_remote_server import PlutoServer
15
+ from remoteRF_server.server import device_manager
16
+
17
+
18
+ class FakePluto:
19
+ def __init__(self):
20
+ self.sample_rate = 1_000_000
21
+ self.rx_hardwaregain_chan0 = 12.5
22
+ self.dds_frequencies = [100_000, 100_000]
23
+ self.dds_scales = [0.25, 0.25]
24
+ self.dds_phases = [0, 90_000]
25
+ self.dds_enabled = [True, True]
26
+ self.rx_enabled_channels = [0]
27
+ self.tx_enabled_channels = [0]
28
+ self.rx_annotated = False
29
+ self.rx_output_type = "raw"
30
+ self.rx_channel_names = ["voltage0", "voltage1"]
31
+ self.tx_channel_names = ["voltage0", "voltage1"]
32
+ self.single_tone_args = None
33
+ self.dual_tone_args = None
34
+ self.tx_samples = None
35
+ self.rx_samples = np.array([1 + 2j, 3 + 4j], dtype=np.complex64)
36
+
37
+ def rx(self):
38
+ return self.rx_samples
39
+
40
+ def tx(self, samples):
41
+ self.tx_samples = samples
42
+
43
+ def dds_single_tone(self, frequency, scale, channel=0):
44
+ self.single_tone_args = (frequency, scale, channel)
45
+
46
+ def dds_dual_tone(
47
+ self,
48
+ frequency1,
49
+ scale1,
50
+ frequency2,
51
+ scale2,
52
+ channel=0,
53
+ ):
54
+ self.dual_tone_args = (
55
+ frequency1,
56
+ scale1,
57
+ frequency2,
58
+ scale2,
59
+ channel,
60
+ )
61
+
62
+
63
+ class PlutoCompatTests(unittest.TestCase):
64
+ def setUp(self) -> None:
65
+ self.device = FakePluto()
66
+
67
+ def handle(self, function_name: str, args: dict):
68
+ with patch(
69
+ "remoteRF_server.drivers.adalm_pluto.pluto_remote_server.device_pluto",
70
+ return_value=self.device,
71
+ ):
72
+ return PlutoServer.handle_call(function_name=function_name, args=args)
73
+
74
+ def test_get_and_set_still_use_legacy_token_paths(self) -> None:
75
+ response = self.handle("Pluto:sample_rate:GET", {"a": map_arg("token")})
76
+ self.assertEqual(unmap_arg(response["sample_rate"]), 1_000_000)
77
+
78
+ response = self.handle(
79
+ "Pluto:sample_rate:SET",
80
+ {"a": map_arg("token"), "sample_rate": map_arg(2_000_000)},
81
+ )
82
+ self.assertEqual(response, {})
83
+ self.assertEqual(self.device.sample_rate, 2_000_000)
84
+
85
+ def test_call0_and_call1_still_use_existing_wire_shape(self) -> None:
86
+ response = self.handle("Pluto:rx:CALL0", {"a": map_arg("token")})
87
+ np.testing.assert_allclose(unmap_arg(response["rx"]), self.device.rx_samples)
88
+
89
+ samples = np.array([5 + 6j, 7 + 8j], dtype=np.complex64)
90
+ response = self.handle(
91
+ "Pluto:tx:CALL1",
92
+ {"a": map_arg("token"), "arg1": map_arg(samples)},
93
+ )
94
+ self.assertEqual(unmap_arg(response["tx"]), "None")
95
+ np.testing.assert_allclose(self.device.tx_samples, samples)
96
+
97
+ def test_schema_dispatch_preserves_pluto_get_set_call0_and_call1(self) -> None:
98
+ schema = PlutoSchema()
99
+ schema.bind(self.device)
100
+
101
+ idl = schema.get_idl()
102
+ self.assertEqual(idl["calls"]["call_rx"]["args"], [])
103
+ self.assertEqual(idl["calls"]["call_tx"]["args"][0]["name"], "value")
104
+ self.assertTrue(idl["calls"]["call_tx"]["args"][0]["required"])
105
+
106
+ self.assertEqual(schema.dispatch("get_sample_rate", {}), 1_000_000)
107
+ schema.dispatch("set_sample_rate", {"value": 2_000_000})
108
+ self.assertEqual(self.device.sample_rate, 2_000_000)
109
+
110
+ np.testing.assert_allclose(schema.dispatch("call_rx", {}), self.device.rx_samples)
111
+ samples = np.array([9 + 1j, 10 + 2j], dtype=np.complex64)
112
+ self.assertIsNone(schema.dispatch("call_tx", {"value": samples}))
113
+ np.testing.assert_allclose(self.device.tx_samples, samples)
114
+
115
+ def test_schema_rx_hardware_gain_uses_pyadi_property_name(self) -> None:
116
+ schema = PlutoSchema()
117
+ schema.bind(self.device)
118
+
119
+ self.assertEqual(schema.dispatch("get_rx_hardwaregain_chan0", {}), 12.5)
120
+ schema.dispatch("set_rx_hardwaregain_chan0", {"value": 24.0})
121
+ self.assertEqual(self.device.rx_hardwaregain_chan0, 24.0)
122
+
123
+ def test_schema_exposes_dds_generation_and_controls(self) -> None:
124
+ schema = PlutoSchema()
125
+ schema.bind(self.device)
126
+
127
+ idl = schema.get_idl()
128
+ single_args = idl["calls"]["call_dds_single_tone"]["args"]
129
+ dual_args = idl["calls"]["call_dds_dual_tone"]["args"]
130
+ self.assertEqual(
131
+ [arg["name"] for arg in single_args],
132
+ ["frequency", "scale", "channel"],
133
+ )
134
+ self.assertEqual(single_args[-1]["default"], 0)
135
+ self.assertEqual(
136
+ [arg["name"] for arg in dual_args],
137
+ ["frequency1", "scale1", "frequency2", "scale2", "channel"],
138
+ )
139
+ self.assertEqual(dual_args[-1]["default"], 0)
140
+
141
+ schema.dispatch(
142
+ "call_dds_single_tone",
143
+ {"frequency": 125_000, "scale": 0.5},
144
+ )
145
+ self.assertEqual(self.device.single_tone_args, (125_000, 0.5, 0))
146
+ schema.dispatch(
147
+ "call_dds_dual_tone",
148
+ {
149
+ "frequency1": 100_000,
150
+ "scale1": 0.25,
151
+ "frequency2": -200_000,
152
+ "scale2": 0.4,
153
+ "channel": 1,
154
+ },
155
+ )
156
+ self.assertEqual(
157
+ self.device.dual_tone_args,
158
+ (100_000, 0.25, -200_000, 0.4, 1),
159
+ )
160
+
161
+ controls = {
162
+ "dds_frequencies": [200_000, 200_000],
163
+ "dds_scales": [0.5, 0.5],
164
+ "dds_phases": [90_000, 0],
165
+ "dds_enabled": [False, True],
166
+ }
167
+ for name, value in controls.items():
168
+ schema.dispatch(f"set_{name}", {"value": value})
169
+ self.assertEqual(schema.dispatch(f"get_{name}", {}), value)
170
+
171
+ def test_schema_exposes_channel_selection_and_receive_formatting(self) -> None:
172
+ schema = PlutoSchema()
173
+ schema.bind(self.device)
174
+
175
+ # Numeric lists use ndarray transport in the legacy codec. The schema
176
+ # restores the integer list shape required by pyadi-iio.
177
+ encoded_channels = unmap_arg(map_arg([0]))
178
+ schema.dispatch("set_rx_enabled_channels", {"value": encoded_channels})
179
+ self.assertEqual(self.device.rx_enabled_channels, [0])
180
+ self.assertIs(type(self.device.rx_enabled_channels[0]), int)
181
+
182
+ schema.dispatch("set_tx_enabled_channels", {"value": ["voltage0"]})
183
+ self.assertEqual(self.device.tx_enabled_channels, ["voltage0"])
184
+ schema.dispatch("set_rx_annotated", {"value": True})
185
+ schema.dispatch("set_rx_output_type", {"value": "SI"})
186
+ self.assertTrue(schema.dispatch("get_rx_annotated", {}))
187
+ self.assertEqual(schema.dispatch("get_rx_output_type", {}), "SI")
188
+
189
+ def test_schema_exposes_channel_metadata_as_read_only(self) -> None:
190
+ schema = PlutoSchema()
191
+ schema.bind(self.device)
192
+
193
+ idl = schema.get_idl()
194
+ self.assertEqual(
195
+ schema.dispatch("get_rx_channel_names", {}),
196
+ ["voltage0", "voltage1"],
197
+ )
198
+ self.assertEqual(
199
+ schema.dispatch("get_tx_channel_names", {}),
200
+ ["voltage0", "voltage1"],
201
+ )
202
+ self.assertNotIn("set_rx_channel_names", idl["setters"])
203
+ self.assertNotIn("set_tx_channel_names", idl["setters"])
204
+
205
+ def test_legacy_pluto_inventory_type_resolves_to_packaged_schema(self) -> None:
206
+ with patch.object(PlutoSchema, "make_device", return_value=self.device):
207
+ schema, ident, dtype = device_manager._connect_from_record(
208
+ {
209
+ "TYPE": "pluto",
210
+ "IDENT": "104473f6",
211
+ "INIT": {"serial": "104473f6"},
212
+ }
213
+ )
214
+
215
+ self.assertIsInstance(schema, PlutoSchema)
216
+ self.assertEqual(ident, "104473f6")
217
+ self.assertEqual(dtype, "adalm_pluto")
218
+
219
+
220
+ if __name__ == "__main__":
221
+ unittest.main()
@@ -1,99 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import sys
4
- import types
5
- import unittest
6
- from unittest.mock import patch
7
-
8
- import numpy as np
9
-
10
- sys.modules.setdefault("adi", types.ModuleType("adi"))
11
-
12
- from remoteRF_server.common.idl.pluto_schema import PlutoSchema
13
- from remoteRF_server.common.utils import map_arg, unmap_arg
14
- from remoteRF_server.drivers.adalm_pluto.pluto_remote_server import PlutoServer
15
- from remoteRF_server.server import device_manager
16
-
17
-
18
- class FakePluto:
19
- def __init__(self):
20
- self.sample_rate = 1_000_000
21
- self.tx_samples = None
22
- self.rx_samples = np.array([1 + 2j, 3 + 4j], dtype=np.complex64)
23
-
24
- def rx(self):
25
- return self.rx_samples
26
-
27
- def tx(self, samples):
28
- self.tx_samples = samples
29
-
30
-
31
- class PlutoCompatTests(unittest.TestCase):
32
- def setUp(self) -> None:
33
- self.device = FakePluto()
34
-
35
- def handle(self, function_name: str, args: dict):
36
- with patch(
37
- "remoteRF_server.drivers.adalm_pluto.pluto_remote_server.device_pluto",
38
- return_value=self.device,
39
- ):
40
- return PlutoServer.handle_call(function_name=function_name, args=args)
41
-
42
- def test_get_and_set_still_use_legacy_token_paths(self) -> None:
43
- response = self.handle("Pluto:sample_rate:GET", {"a": map_arg("token")})
44
- self.assertEqual(unmap_arg(response["sample_rate"]), 1_000_000)
45
-
46
- response = self.handle(
47
- "Pluto:sample_rate:SET",
48
- {"a": map_arg("token"), "sample_rate": map_arg(2_000_000)},
49
- )
50
- self.assertEqual(response, {})
51
- self.assertEqual(self.device.sample_rate, 2_000_000)
52
-
53
- def test_call0_and_call1_still_use_existing_wire_shape(self) -> None:
54
- response = self.handle("Pluto:rx:CALL0", {"a": map_arg("token")})
55
- np.testing.assert_allclose(unmap_arg(response["rx"]), self.device.rx_samples)
56
-
57
- samples = np.array([5 + 6j, 7 + 8j], dtype=np.complex64)
58
- response = self.handle(
59
- "Pluto:tx:CALL1",
60
- {"a": map_arg("token"), "arg1": map_arg(samples)},
61
- )
62
- self.assertEqual(unmap_arg(response["tx"]), "None")
63
- np.testing.assert_allclose(self.device.tx_samples, samples)
64
-
65
- def test_schema_dispatch_preserves_pluto_get_set_call0_and_call1(self) -> None:
66
- schema = PlutoSchema()
67
- schema.bind(self.device)
68
-
69
- idl = schema.get_idl()
70
- self.assertEqual(idl["calls"]["call_rx"]["args"], [])
71
- self.assertEqual(idl["calls"]["call_tx"]["args"][0]["name"], "value")
72
- self.assertTrue(idl["calls"]["call_tx"]["args"][0]["required"])
73
-
74
- self.assertEqual(schema.dispatch("get_sample_rate", {}), 1_000_000)
75
- schema.dispatch("set_sample_rate", {"value": 2_000_000})
76
- self.assertEqual(self.device.sample_rate, 2_000_000)
77
-
78
- np.testing.assert_allclose(schema.dispatch("call_rx", {}), self.device.rx_samples)
79
- samples = np.array([9 + 1j, 10 + 2j], dtype=np.complex64)
80
- self.assertIsNone(schema.dispatch("call_tx", {"value": samples}))
81
- np.testing.assert_allclose(self.device.tx_samples, samples)
82
-
83
- def test_legacy_pluto_inventory_type_resolves_to_packaged_schema(self) -> None:
84
- with patch.object(PlutoSchema, "make_device", return_value=self.device):
85
- schema, ident, dtype = device_manager._connect_from_record(
86
- {
87
- "TYPE": "pluto",
88
- "IDENT": "104473f6",
89
- "INIT": {"serial": "104473f6"},
90
- }
91
- )
92
-
93
- self.assertIsInstance(schema, PlutoSchema)
94
- self.assertEqual(ident, "104473f6")
95
- self.assertEqual(dtype, "adalm_pluto")
96
-
97
-
98
- if __name__ == "__main__":
99
- unittest.main()