remoteRF-server 1.0.6__tar.gz → 1.0.8__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.6 → remoterf_server-1.0.8}/PKG-INFO +5 -5
  2. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/README.md +4 -4
  3. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/pyproject.toml +1 -1
  4. {remoterf_server-1.0.6/src/remoteRF_server/drivers/adalm_pluto → remoterf_server-1.0.8/src/remoteRF_server/common/idl}/pluto_schema.py +128 -5
  5. {remoterf_server-1.0.6/src/remoteRF_server/common/idl → remoterf_server-1.0.8/src/remoteRF_server/drivers/adalm_pluto}/pluto_schema.py +128 -5
  6. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/drivers/usrp/usrp_schema.py +49 -4
  7. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/server/device_manager.py +69 -3
  8. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/serverrf_cli.py +7 -2
  9. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server.egg-info/PKG-INFO +5 -5
  10. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/tests/test_default_usrp_schema.py +50 -1
  11. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/tests/test_dynamic_v2.py +7 -3
  12. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/tests/test_idl_schema.py +47 -2
  13. remoterf_server-1.0.8/tests/test_pluto_compat.py +221 -0
  14. remoterf_server-1.0.6/tests/test_pluto_compat.py +0 -99
  15. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/setup.cfg +0 -0
  16. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/__init__.py +0 -0
  17. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/common/__init__.py +0 -0
  18. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/common/grpc/__init__.py +0 -0
  19. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/common/grpc/grpc_host_pb2.py +0 -0
  20. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/common/grpc/grpc_host_pb2_grpc.py +0 -0
  21. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/common/grpc/grpc_pb2.py +0 -0
  22. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/common/grpc/grpc_pb2_grpc.py +0 -0
  23. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/common/grpc/v2_codec.py +0 -0
  24. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/common/idl/__init__.py +0 -0
  25. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/common/idl/device_schema.py +0 -0
  26. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/common/idl/pluto_mimo_schema.py +0 -0
  27. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/common/idl/schema.py +0 -0
  28. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/common/idl/v2.py +0 -0
  29. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/common/utils/__init__.py +0 -0
  30. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/common/utils/ansi_codes.py +0 -0
  31. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/common/utils/api_token.py +0 -0
  32. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/common/utils/db_connection.py +0 -0
  33. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/common/utils/db_location.py +0 -0
  34. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/common/utils/list_string.py +0 -0
  35. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/common/utils/process_arg.py +0 -0
  36. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/drivers/__init__.py +0 -0
  37. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/drivers/adalm_pluto/__init__.py +0 -0
  38. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/drivers/adalm_pluto/pluto_mimo_schema.py +0 -0
  39. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/drivers/adalm_pluto/pluto_remote_server.py +0 -0
  40. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/drivers/usrp/__init__.py +0 -0
  41. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/drivers/usrp/policy.py +0 -0
  42. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/drivers/usrp/uhd_manifest.py +0 -0
  43. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/host/__init__.py +0 -0
  44. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/host/host_auth_token.py +0 -0
  45. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/host/host_directory_store.py +0 -0
  46. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/host/host_tunnel_server.py +0 -0
  47. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/server/__init__.py +0 -0
  48. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/server/acc_perms.py +0 -0
  49. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/server/cert_provider.py +0 -0
  50. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/server/dynamic_v2.py +0 -0
  51. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/server/grpc_server.py +0 -0
  52. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/server/reservation.py +0 -0
  53. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/server/rpc_manager.py +0 -0
  54. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/server/user_group_cli.py +0 -0
  55. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/server/user_group_handler.py +0 -0
  56. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/server/v2_errors.py +0 -0
  57. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/tools/__init__.py +0 -0
  58. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/tools/gen_certs.py +0 -0
  59. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/tools/gist_status.py +0 -0
  60. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/tools/gist_status_testing.py +0 -0
  61. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server/tools/introspect_uhd.py +0 -0
  62. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server.egg-info/SOURCES.txt +0 -0
  63. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server.egg-info/dependency_links.txt +0 -0
  64. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server.egg-info/entry_points.txt +0 -0
  65. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server.egg-info/requires.txt +0 -0
  66. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/src/remoteRF_server.egg-info/top_level.txt +0 -0
  67. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/tests/test_cert_provider.py +0 -0
  68. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/tests/test_process_arg.py +0 -0
  69. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/tests/test_reservation_timestamps.py +0 -0
  70. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/tests/test_rpc_manager_calln.py +0 -0
  71. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/tests/test_usrp_client_server_hardware.py +0 -0
  72. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/tests/test_usrp_client_server_localhost.py +0 -0
  73. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/tests/test_usrp_remoterf_rx_hardware.py +0 -0
  74. {remoterf_server-1.0.6 → remoterf_server-1.0.8}/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.6
3
+ Version: 1.0.8
4
4
  Summary: RemoteRF server-side control package
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -334,13 +334,13 @@ separate policy file. This includes transmit controls, transmit streaming,
334
334
  GPIO/register methods, and property-tree writes. `usrp_policy.yml` is not read
335
335
  or required by the current server.
336
336
 
337
- RemoteRF sets `UHD_LOG_CONSOLE_LEVEL=off` before importing UHD so native UHD
337
+ RemoteRF forces `UHD_LOG_CONSOLE_LEVEL=off` before importing UHD so native UHD
338
338
  messages do not overwrite the interactive `serverrf` prompt. To temporarily
339
- restore UHD diagnostics, set a level such as `warning`, `info`, or `debug`
340
- before starting the server:
339
+ restore UHD diagnostics, explicitly set `REMOTERF_UHD_CONSOLE_LEVEL` to a level
340
+ such as `warning`, `info`, or `debug` before starting the server:
341
341
 
342
342
  ```bash
343
- UHD_LOG_CONSOLE_LEVEL=warning serverrf -s
343
+ REMOTERF_UHD_CONSOLE_LEVEL=warning serverrf -s
344
344
  ```
345
345
 
346
346
  On every qualified target, generate the introspection record and enforce the
@@ -319,13 +319,13 @@ separate policy file. This includes transmit controls, transmit streaming,
319
319
  GPIO/register methods, and property-tree writes. `usrp_policy.yml` is not read
320
320
  or required by the current server.
321
321
 
322
- RemoteRF sets `UHD_LOG_CONSOLE_LEVEL=off` before importing UHD so native UHD
322
+ RemoteRF forces `UHD_LOG_CONSOLE_LEVEL=off` before importing UHD so native UHD
323
323
  messages do not overwrite the interactive `serverrf` prompt. To temporarily
324
- restore UHD diagnostics, set a level such as `warning`, `info`, or `debug`
325
- before starting the server:
324
+ restore UHD diagnostics, explicitly set `REMOTERF_UHD_CONSOLE_LEVEL` to a level
325
+ such as `warning`, `info`, or `debug` before starting the server:
326
326
 
327
327
  ```bash
328
- UHD_LOG_CONSOLE_LEVEL=warning serverrf -s
328
+ REMOTERF_UHD_CONSOLE_LEVEL=warning serverrf -s
329
329
  ```
330
330
 
331
331
  On every qualified target, generate the introspection record and enforce the
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "remoteRF-server"
7
- version = "1.0.6"
7
+ version = "1.0.8"
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
@@ -11,7 +11,10 @@ import os
11
11
 
12
12
  def _configure_uhd_console_logging():
13
13
  """Keep native UHD logs out of the interactive server console by default."""
14
- os.environ.setdefault("UHD_LOG_CONSOLE_LEVEL", "off")
14
+ os.environ["UHD_LOG_CONSOLE_LEVEL"] = os.getenv(
15
+ "REMOTERF_UHD_CONSOLE_LEVEL",
16
+ "off",
17
+ )
15
18
 
16
19
 
17
20
  _configure_uhd_console_logging()
@@ -95,6 +98,31 @@ def _payload_type(value):
95
98
  return None
96
99
 
97
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
+
98
126
  def _require_dict(value, method):
99
127
  if not isinstance(value, dict):
100
128
  raise TypeError(
@@ -156,7 +184,16 @@ def _construct(value):
156
184
  return uhd.usrp.SubdevSpec(value["spec"])
157
185
 
158
186
  if kind == "TuneRequest":
159
- 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
+ )
160
197
  kwargs = {
161
198
  key: _construct(item)
162
199
  for key, item in value.get("kwargs", {}).items()
@@ -171,10 +208,18 @@ def _construct(value):
171
208
  obj = constructor(*args, **kwargs)
172
209
  for field in (
173
210
  "rf_freq", "dsp_freq", "rf_freq_policy", "dsp_freq_policy",
174
- "target_freq", "lo_offset", "args",
211
+ "args",
175
212
  ):
213
+ if field == "args" and legacy_constructor_args:
214
+ continue
176
215
  if field in value and hasattr(obj, field):
177
- 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)
178
223
  return obj
179
224
 
180
225
  if kind == "DeviceAddr":
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import os
6
6
  import re
7
7
  import shutil
8
+ import hashlib
8
9
  import time
9
10
  import yaml
10
11
  import threading
@@ -419,10 +420,60 @@ def _driver_registers_type(path: Path, device_type: str) -> bool:
419
420
  pattern = rf"(?:idl_register|register_driver)\(\s*['\"]{re.escape(device_type)}['\"]\s*\)"
420
421
  return re.search(pattern, text) is not None
421
422
 
422
- def _seed_default_driver(filename: str, *device_types: str) -> None:
423
+ def _driver_sha256(path: Path) -> str:
424
+ digest = hashlib.sha256()
425
+ with path.open("rb") as handle:
426
+ for chunk in iter(lambda: handle.read(1024 * 1024), b""):
427
+ digest.update(chunk)
428
+ return digest.hexdigest()
429
+
430
+
431
+ def _managed_driver_marker(target: Path) -> Path:
432
+ return target.with_name(f".{target.name}.remoterf-managed")
433
+
434
+
435
+ def _refresh_managed_driver(
436
+ target: Path,
437
+ source: Path,
438
+ *,
439
+ legacy_hashes: tuple[str, ...] = (),
440
+ ) -> bool:
441
+ """Refresh an unmodified RemoteRF-managed schema and preserve user edits."""
442
+ marker = _managed_driver_marker(target)
443
+ try:
444
+ target_hash = _driver_sha256(target)
445
+ marker_hash = marker.read_text(encoding="utf-8").strip() if marker.exists() else ""
446
+ except OSError:
447
+ return False
448
+
449
+ if target_hash != marker_hash and target_hash not in legacy_hashes:
450
+ return False
451
+
452
+ try:
453
+ source_hash = _driver_sha256(source)
454
+ if target_hash != source_hash:
455
+ shutil.copy2(source, target)
456
+ marker.write_text(source_hash + "\n", encoding="utf-8")
457
+ except OSError:
458
+ return False
459
+ return True
460
+
461
+
462
+ def _seed_default_driver(
463
+ filename: str,
464
+ *device_types: str,
465
+ legacy_hashes: tuple[str, ...] = (),
466
+ ) -> None:
423
467
  drivers_dir = get_drivers_dir()
424
468
  target = drivers_dir / filename
469
+ source = _packaged_driver_schema_path(filename)
425
470
  if target.exists() or target.is_symlink():
471
+ if target.is_file() and source.exists():
472
+ _refresh_managed_driver(
473
+ target,
474
+ source,
475
+ legacy_hashes=legacy_hashes,
476
+ )
426
477
  return
427
478
 
428
479
  try:
@@ -437,13 +488,16 @@ def _seed_default_driver(filename: str, *device_types: str) -> None:
437
488
  if any(_driver_registers_type(driver_file, device_type) for device_type in device_types):
438
489
  return
439
490
 
440
- source = _packaged_driver_schema_path(filename)
441
491
  if not source.exists():
442
492
  print(f"Warning: default driver schema source is missing: {source}")
443
493
  return
444
494
 
445
495
  try:
446
496
  shutil.copy2(source, target)
497
+ _managed_driver_marker(target).write_text(
498
+ _driver_sha256(source) + "\n",
499
+ encoding="utf-8",
500
+ )
447
501
  print(f"Created default driver config at {target}")
448
502
  except OSError as e:
449
503
  print(f"Warning: could not create default driver config {target}: {e}")
@@ -453,7 +507,19 @@ def _seed_default_pluto_drivers() -> None:
453
507
  _seed_default_driver("pluto_mimo_schema.py", "adalm_pluto_mimo")
454
508
 
455
509
  def _seed_default_usrp_driver() -> None:
456
- _seed_default_driver("usrp_schema.py", "usrp")
510
+ _seed_default_driver(
511
+ "usrp_schema.py",
512
+ "usrp",
513
+ legacy_hashes=(
514
+ # Previously shipped built-in copies. These are safe to refresh;
515
+ # any edited/custom schema has a different digest and is preserved.
516
+ "ec30aaadb1e8edec289de01e4f997aa3c8b59d3fd0a69c5ac94b37b05dac7a65",
517
+ "baf29cd0f22e84891b546c30c963cddb02bc93db0efb50fe32cc743c8600c227",
518
+ "01360d017359d6cb65761b78b5c2bb570b8f2f55e8ab280ba23d6813deddb233",
519
+ "ca32b958759a5f7933e10426a320ca74e7546734524749ea510de83cf5084397",
520
+ "0d185cd24571bae2d5f75d6c28164694918a86b20854b96fe9b77c475ca28ed1",
521
+ ),
522
+ )
457
523
 
458
524
  def _load_all_drivers() -> None:
459
525
  _seed_default_pluto_drivers()
@@ -339,8 +339,13 @@ def _serve(*, headless: bool = False) -> int:
339
339
 
340
340
  # UHD writes native log messages to stderr/std::clog. Set this before the
341
341
  # server imports device drivers so those messages cannot corrupt the
342
- # interactive prompt. An explicit administrator setting still wins.
343
- os.environ.setdefault("UHD_LOG_CONSOLE_LEVEL", "off")
342
+ # interactive prompt. RemoteRF keeps this off even when the surrounding
343
+ # shell has a noisy UHD default; diagnostics require an explicit RemoteRF
344
+ # opt-in.
345
+ os.environ["UHD_LOG_CONSOLE_LEVEL"] = os.getenv(
346
+ "REMOTERF_UHD_CONSOLE_LEVEL",
347
+ "off",
348
+ )
344
349
 
345
350
  from remoteRF_server.server.grpc_server import (
346
351
  headless_main,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoteRF-server
3
- Version: 1.0.6
3
+ Version: 1.0.8
4
4
  Summary: RemoteRF server-side control package
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -334,13 +334,13 @@ separate policy file. This includes transmit controls, transmit streaming,
334
334
  GPIO/register methods, and property-tree writes. `usrp_policy.yml` is not read
335
335
  or required by the current server.
336
336
 
337
- RemoteRF sets `UHD_LOG_CONSOLE_LEVEL=off` before importing UHD so native UHD
337
+ RemoteRF forces `UHD_LOG_CONSOLE_LEVEL=off` before importing UHD so native UHD
338
338
  messages do not overwrite the interactive `serverrf` prompt. To temporarily
339
- restore UHD diagnostics, set a level such as `warning`, `info`, or `debug`
340
- before starting the server:
339
+ restore UHD diagnostics, explicitly set `REMOTERF_UHD_CONSOLE_LEVEL` to a level
340
+ such as `warning`, `info`, or `debug` before starting the server:
341
341
 
342
342
  ```bash
343
- UHD_LOG_CONSOLE_LEVEL=warning serverrf -s
343
+ REMOTERF_UHD_CONSOLE_LEVEL=warning serverrf -s
344
344
  ```
345
345
 
346
346
  On every qualified target, generate the introspection record and enforce the