remoteRF-server 1.0.3__tar.gz → 1.0.4__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 (73) hide show
  1. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/PKG-INFO +1 -1
  2. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/pyproject.toml +1 -1
  3. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/common/idl/schema.py +2 -2
  4. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/server/device_manager.py +6 -1
  5. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/server/grpc_server.py +13 -5
  6. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/serverrf_cli.py +14 -18
  7. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server.egg-info/PKG-INFO +1 -1
  8. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/tests/test_cert_provider.py +24 -0
  9. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/tests/test_dynamic_v2.py +68 -2
  10. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/tests/test_pluto_compat.py +15 -0
  11. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/README.md +0 -0
  12. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/setup.cfg +0 -0
  13. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/__init__.py +0 -0
  14. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/common/__init__.py +0 -0
  15. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/common/grpc/__init__.py +0 -0
  16. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/common/grpc/grpc_host_pb2.py +0 -0
  17. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/common/grpc/grpc_host_pb2_grpc.py +0 -0
  18. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/common/grpc/grpc_pb2.py +0 -0
  19. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/common/grpc/grpc_pb2_grpc.py +0 -0
  20. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/common/grpc/v2_codec.py +0 -0
  21. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/common/idl/__init__.py +0 -0
  22. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/common/idl/device_schema.py +0 -0
  23. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/common/idl/pluto_mimo_schema.py +0 -0
  24. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/common/idl/pluto_schema.py +0 -0
  25. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/common/idl/v2.py +0 -0
  26. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/common/utils/__init__.py +0 -0
  27. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/common/utils/ansi_codes.py +0 -0
  28. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/common/utils/api_token.py +0 -0
  29. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/common/utils/db_connection.py +0 -0
  30. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/common/utils/db_location.py +0 -0
  31. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/common/utils/list_string.py +0 -0
  32. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/common/utils/process_arg.py +0 -0
  33. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/drivers/__init__.py +0 -0
  34. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/drivers/adalm_pluto/__init__.py +0 -0
  35. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/drivers/adalm_pluto/pluto_mimo_schema.py +0 -0
  36. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/drivers/adalm_pluto/pluto_remote_server.py +0 -0
  37. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/drivers/adalm_pluto/pluto_schema.py +0 -0
  38. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/drivers/usrp/__init__.py +0 -0
  39. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/drivers/usrp/policy.py +0 -0
  40. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/drivers/usrp/uhd_manifest.py +0 -0
  41. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/drivers/usrp/usrp_schema.py +0 -0
  42. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/host/__init__.py +0 -0
  43. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/host/host_auth_token.py +0 -0
  44. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/host/host_directory_store.py +0 -0
  45. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/host/host_tunnel_server.py +0 -0
  46. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/server/__init__.py +0 -0
  47. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/server/acc_perms.py +0 -0
  48. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/server/cert_provider.py +0 -0
  49. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/server/dynamic_v2.py +0 -0
  50. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/server/reservation.py +0 -0
  51. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/server/rpc_manager.py +0 -0
  52. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/server/user_group_cli.py +0 -0
  53. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/server/user_group_handler.py +0 -0
  54. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/server/v2_errors.py +0 -0
  55. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/tools/__init__.py +0 -0
  56. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/tools/gen_certs.py +0 -0
  57. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/tools/gist_status.py +0 -0
  58. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/tools/gist_status_testing.py +0 -0
  59. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server/tools/introspect_uhd.py +0 -0
  60. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server.egg-info/SOURCES.txt +0 -0
  61. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server.egg-info/dependency_links.txt +0 -0
  62. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server.egg-info/entry_points.txt +0 -0
  63. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server.egg-info/requires.txt +0 -0
  64. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/src/remoteRF_server.egg-info/top_level.txt +0 -0
  65. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/tests/test_default_usrp_schema.py +0 -0
  66. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/tests/test_idl_schema.py +0 -0
  67. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/tests/test_process_arg.py +0 -0
  68. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/tests/test_reservation_timestamps.py +0 -0
  69. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/tests/test_rpc_manager_calln.py +0 -0
  70. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/tests/test_usrp_client_server_hardware.py +0 -0
  71. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/tests/test_usrp_client_server_localhost.py +0 -0
  72. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/tests/test_usrp_remoterf_rx_hardware.py +0 -0
  73. {remoterf_server-1.0.3 → remoterf_server-1.0.4}/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.3
3
+ Version: 1.0.4
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.3"
7
+ version = "1.0.4"
8
8
  description = "RemoteRF server-side control package"
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
@@ -6,7 +6,7 @@ Admin writes one .py per device TYPE, drops it in ~/.config/remoterf/drivers/,
6
6
  and the server auto-discovers it on startup via load_drivers().
7
7
 
8
8
  ~/.config/remoterf/
9
- devices.env ← "I have 3 plutos at these serials"
9
+ devices.yml ← "I have 3 devices at these serials"
10
10
  drivers/
11
11
  pluto_schema.py ← "here's what a pluto IS"
12
12
  hackrf_schema.py
@@ -531,7 +531,7 @@ class DeviceSchema:
531
531
  Base for all device schemas. Admin subclasses this once per device type.
532
532
 
533
533
  Class attributes (admin sets these):
534
- device_type str — must match the TYPE in devices.env
534
+ device_type str — must match device_type in devices.yml
535
535
  driver_version str — semver
536
536
 
537
537
  Admin implements:
@@ -314,6 +314,11 @@ def _load_device_records() -> Dict[int, Dict[str, str]]:
314
314
  def _connect_from_record(rec: Dict[str, Any]):
315
315
  """Returns (schema_instance_or_None, ident, dtype)."""
316
316
  dtype = (rec.get("TYPE") or "").strip().lower()
317
+ # Older serverrf builds serialized the --pluto shorthand as "pluto",
318
+ # while the packaged schema has always registered as "adalm_pluto".
319
+ # Keep those inventories usable while all new writes use the canonical type.
320
+ if dtype == "pluto":
321
+ dtype = "adalm_pluto"
317
322
  ident = (rec.get("IDENT") or "").strip()
318
323
  init_kwargs = rec.get("INIT") or {}
319
324
 
@@ -873,7 +878,7 @@ def acquire_device(api_token: str) -> Iterator[Tuple[int, object]]:
873
878
 
874
879
  def reload_devices() -> None:
875
880
  """
876
- Reload devices.env and reconnect local devices.
881
+ Reload devices.yml and reconnect local devices.
877
882
  Preserves existing salt/hash for devices that remain (same gid),
878
883
  and preserves host virtual devices + their auth state.
879
884
  """
@@ -214,6 +214,18 @@ def _load_server_credentials(cert_dir: Path):
214
214
  )
215
215
 
216
216
 
217
+ def _print_tls_credentials_error(cert_dir: Path, error: Exception) -> None:
218
+ messages = (
219
+ "TLS certs/keys not found or unreadable.",
220
+ f"Expected in: {cert_dir}",
221
+ f"Fix: run serverrf --gen-certs {local_ip} --force",
222
+ "Or set REMOTERF_CERT_DIR to your cert folder.",
223
+ f"Details: {error}",
224
+ )
225
+ for message in messages:
226
+ printf(message, Sty.MAGENTA)
227
+
228
+
217
229
  def start_server_runtime(
218
230
  *,
219
231
  bind_host: str | None = None,
@@ -258,11 +270,7 @@ def start_server_runtime(
258
270
  try:
259
271
  server_credentials = _load_server_credentials(cert_dir)
260
272
  except Exception as e:
261
- printf("TLS certs/keys not found or unreadable.", Sty.RED)
262
- printf("Expected in: ", Sty.DEFAULT, f"{cert_dir}", Sty.MAGENTA)
263
- printf("Fix: run ", Sty.DEFAULT, "RRRFcerts --ip ", Sty.GREEN, f"{local_ip}", Sty.MAGENTA, " --force", Sty.DEFAULT)
264
- printf("Or set REMOTERF_CERT_DIR to your cert folder.", Sty.GRAY)
265
- print(f"\nDetails: {e}\n", file=sys.stderr)
273
+ _print_tls_credentials_error(cert_dir, e)
266
274
  raise SystemExit(2)
267
275
 
268
276
  if include_host_tunnel:
@@ -135,7 +135,7 @@ def _server_env_path() -> Path:
135
135
 
136
136
 
137
137
  def _devices_env_path() -> Path:
138
- # Legacy mirror retained for older tooling.
138
+ # Read-only migration source for pre-YAML installations.
139
139
  return _cfg_dir() / "devices.env"
140
140
 
141
141
 
@@ -156,11 +156,11 @@ def _gen_certs_path() -> Path:
156
156
  return (_repo_root() / "tools" / "gen_certs.py").resolve()
157
157
 
158
158
  def _gist_env_path() -> Path:
159
- # Stored alongside server.env/devices.env under ~/.config/remoterf by default
159
+ # Stored alongside server.env/devices.yml under ~/.config/remoterf by default
160
160
  return _cfg_dir() / "gist.env"
161
161
 
162
162
  def _tos_ack_path() -> Path:
163
- # Stored alongside server.env/devices.env under ~/.config/remoterf by default
163
+ # Stored alongside server.env/devices.yml under ~/.config/remoterf by default
164
164
  return _cfg_dir() / "tos.env"
165
165
 
166
166
 
@@ -262,6 +262,7 @@ def print_help() -> None:
262
262
  "\n"
263
263
  "Devices:\n"
264
264
  " serverrf -d | --device [options]\n"
265
+ " Inventory: ~/.config/remoterf/devices.yml (canonical YAML)\n"
265
266
  "\n"
266
267
  "Device options:\n"
267
268
  " --add --pluto <id:name:iio_serial> Add an ADALM-Pluto\n"
@@ -811,7 +812,11 @@ def _read_device_records() -> Dict[int, Dict[str, Any]]:
811
812
  recs[gid] = {
812
813
  "TYPE": dtype,
813
814
  "NAME": str(item.get("name") or f"device-{gid}").strip(),
814
- "IDENT_KIND": "iio_serial" if dtype == "pluto" else "serial",
815
+ "IDENT_KIND": (
816
+ "iio_serial"
817
+ if dtype in {"pluto", "adalm_pluto"}
818
+ else "serial"
819
+ ),
815
820
  "IDENT": ident,
816
821
  "INIT": dict(init),
817
822
  }
@@ -833,7 +838,6 @@ def _read_device_records() -> Dict[int, Dict[str, Any]]:
833
838
 
834
839
  def _write_device_records(recs: Dict[int, Dict[str, Any]]) -> None:
835
840
  devices: List[Dict[str, Any]] = []
836
- out: Dict[str, str] = {}
837
841
  for gid in sorted(recs.keys()):
838
842
  fields = recs[gid]
839
843
  dtype = str(fields.get("TYPE") or "").strip().lower()
@@ -852,22 +856,14 @@ def _write_device_records(recs: Dict[int, Dict[str, Any]]) -> None:
852
856
  "init": init,
853
857
  }
854
858
  )
855
- order = ["TYPE", "NAME", "IDENT_KIND", "IDENT"]
856
- for f in order:
857
- if f in fields and fields[f] != "":
858
- out[f"DEVICE_{gid}_{f}"] = str(fields[f])
859
- for f in sorted(fields.keys()):
860
- if f in order or f == "INIT":
861
- continue
862
- if fields[f] != "":
863
- out[f"DEVICE_{gid}_{f}"] = str(fields[f])
864
859
  _cfg_dir().mkdir(parents=True, exist_ok=True)
865
860
  _devices_yaml_path().write_text(
866
861
  yaml.safe_dump({"devices": devices}, sort_keys=False),
867
862
  encoding="utf-8",
868
863
  )
869
- # Maintain the original env representation for older CLI consumers.
870
- _write_env_kv(_devices_env_path(), out)
864
+ # Once a legacy inventory has been migrated successfully, remove it so
865
+ # there is exactly one authoritative device manifest.
866
+ _devices_env_path().unlink(missing_ok=True)
871
867
 
872
868
 
873
869
  def _device_add_pluto(spec: str) -> int:
@@ -894,14 +890,14 @@ def _device_add_pluto(spec: str) -> int:
894
890
  return 2
895
891
 
896
892
  recs[gid] = {
897
- "TYPE": "pluto",
893
+ "TYPE": "adalm_pluto",
898
894
  "NAME": name,
899
895
  "IDENT_KIND": "iio_serial",
900
896
  "IDENT": iio_serial,
901
897
  }
902
898
  _write_device_records(recs)
903
899
 
904
- print(f"Added device: {gid} type=pluto name={name} iio_serial={iio_serial}")
900
+ print(f"Added device: {gid} type=adalm_pluto name={name} iio_serial={iio_serial}")
905
901
  return 0
906
902
 
907
903
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoteRF-server
3
- Version: 1.0.3
3
+ Version: 1.0.4
4
4
  Summary: RemoteRF server-side control package
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -59,6 +59,30 @@ class CertificateProviderTests(unittest.TestCase):
59
59
  self.assertEqual(served, ca_pem)
60
60
  self.assertNotEqual(served, leaf_pem)
61
61
 
62
+ def test_missing_tls_guidance_is_visible_and_uses_serverrf(self):
63
+ from remoteRF_server.server import grpc_server
64
+
65
+ cert_dir = Path("/tmp/remoterf-test-certs")
66
+ error = FileNotFoundError("missing server.key")
67
+ with patch.object(grpc_server, "printf") as mock_printf:
68
+ grpc_server._print_tls_credentials_error(cert_dir, error)
69
+
70
+ rendered = [call.args for call in mock_printf.call_args_list]
71
+ self.assertEqual(
72
+ [message for message, _style in rendered],
73
+ [
74
+ "TLS certs/keys not found or unreadable.",
75
+ f"Expected in: {cert_dir}",
76
+ f"Fix: run serverrf --gen-certs {grpc_server.local_ip} --force",
77
+ "Or set REMOTERF_CERT_DIR to your cert folder.",
78
+ "Details: missing server.key",
79
+ ],
80
+ )
81
+ self.assertTrue(
82
+ all(style is grpc_server.Sty.MAGENTA for _message, style in rendered)
83
+ )
84
+ self.assertNotIn("RRRFcerts", "\n".join(message for message, _style in rendered))
85
+
62
86
 
63
87
  if __name__ == "__main__":
64
88
  unittest.main()
@@ -7,6 +7,7 @@ from pathlib import Path
7
7
  from unittest.mock import patch
8
8
 
9
9
  import numpy as np
10
+ import yaml
10
11
 
11
12
  from remoteRF_server import serverrf_cli
12
13
  from remoteRF_server.common.grpc import grpc_pb2
@@ -875,7 +876,7 @@ class DynamicV2ServerTests(unittest.TestCase):
875
876
  entry.active_operation = ""
876
877
  manager.close_all()
877
878
 
878
- def test_usrp_cli_writes_runtime_yaml_inventory_and_legacy_mirror(self):
879
+ def test_usrp_cli_writes_only_canonical_yaml_inventory(self):
879
880
  with tempfile.TemporaryDirectory() as tmp:
880
881
  with patch.dict(
881
882
  os.environ,
@@ -894,7 +895,72 @@ class DynamicV2ServerTests(unittest.TestCase):
894
895
  records = serverrf_cli._read_device_records()
895
896
  self.assertEqual(records[7]["TYPE"], "usrp")
896
897
  self.assertEqual(records[7]["IDENT"], "31A2B3C")
897
- self.assertTrue(Path(tmp, "devices.env").exists())
898
+ self.assertFalse(Path(tmp, "devices.env").exists())
899
+
900
+ def test_pluto_cli_uses_registered_adalm_pluto_device_type(self):
901
+ with tempfile.TemporaryDirectory() as tmp:
902
+ with patch.dict(
903
+ os.environ,
904
+ {"REMOTERF_CONFIG_DIR": tmp},
905
+ clear=False,
906
+ ):
907
+ self.assertEqual(
908
+ serverrf_cli._device_add_pluto("1:Pluto A:104473f6"),
909
+ 0,
910
+ )
911
+ inventory = Path(tmp, "devices.yml")
912
+ text = inventory.read_text(encoding="utf-8")
913
+ self.assertIn("device_type: adalm_pluto", text)
914
+ self.assertIn("serial: 104473f6", text)
915
+ records = serverrf_cli._read_device_records()
916
+ self.assertEqual(records[1]["TYPE"], "adalm_pluto")
917
+ self.assertEqual(records[1]["IDENT"], "104473f6")
918
+ self.assertEqual(records[1]["IDENT_KIND"], "iio_serial")
919
+ self.assertFalse(Path(tmp, "devices.env").exists())
920
+
921
+ def test_device_cli_migrates_legacy_env_inventory_to_yaml(self):
922
+ with tempfile.TemporaryDirectory() as tmp:
923
+ with patch.dict(
924
+ os.environ,
925
+ {"REMOTERF_CONFIG_DIR": tmp},
926
+ clear=False,
927
+ ):
928
+ Path(tmp, "devices.env").write_text(
929
+ "DEVICE_1_TYPE=adalm_pluto\n"
930
+ "DEVICE_1_NAME=Pluto A\n"
931
+ "DEVICE_1_IDENT_KIND=iio_serial\n"
932
+ "DEVICE_1_IDENT=104473f6\n",
933
+ encoding="utf-8",
934
+ )
935
+
936
+ self.assertEqual(
937
+ serverrf_cli._device_add_usrp("7:lab_usrp:31A2B3C"),
938
+ 0,
939
+ )
940
+
941
+ inventory = yaml.safe_load(
942
+ Path(tmp, "devices.yml").read_text(encoding="utf-8")
943
+ )
944
+ self.assertEqual(
945
+ inventory,
946
+ {
947
+ "devices": [
948
+ {
949
+ "device_id": 1,
950
+ "device_type": "adalm_pluto",
951
+ "name": "Pluto A",
952
+ "init": {"serial": "104473f6"},
953
+ },
954
+ {
955
+ "device_id": 7,
956
+ "device_type": "usrp",
957
+ "name": "lab_usrp",
958
+ "init": {"serial": "31A2B3C"},
959
+ },
960
+ ]
961
+ },
962
+ )
963
+ self.assertFalse(Path(tmp, "devices.env").exists())
898
964
 
899
965
  def test_server_cli_exposes_headless_runtime(self):
900
966
  with (
@@ -12,6 +12,7 @@ sys.modules.setdefault("adi", types.ModuleType("adi"))
12
12
  from remoteRF_server.common.idl.pluto_schema import PlutoSchema
13
13
  from remoteRF_server.common.utils import map_arg, unmap_arg
14
14
  from remoteRF_server.drivers.adalm_pluto.pluto_remote_server import PlutoServer
15
+ from remoteRF_server.server import device_manager
15
16
 
16
17
 
17
18
  class FakePluto:
@@ -79,6 +80,20 @@ class PlutoCompatTests(unittest.TestCase):
79
80
  self.assertIsNone(schema.dispatch("call_tx", {"value": samples}))
80
81
  np.testing.assert_allclose(self.device.tx_samples, samples)
81
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
+
82
97
 
83
98
  if __name__ == "__main__":
84
99
  unittest.main()