remoteRF-server-testing 0.0.2__tar.gz → 0.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 (49) hide show
  1. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/PKG-INFO +1 -1
  2. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/pyproject.toml +1 -1
  3. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/common/idl/pluto_schema.py +11 -4
  4. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/common/utils/process_arg.py +2 -0
  5. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/host/host_tunnel_server.py +45 -1
  6. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/server/device_manager.py +77 -3
  7. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/server/rpc_manager.py +9 -2
  8. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server_testing.egg-info/PKG-INFO +1 -1
  9. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/README.md +0 -0
  10. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/setup.cfg +0 -0
  11. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/__init__.py +0 -0
  12. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/common/__init__.py +0 -0
  13. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/common/grpc/__init__.py +0 -0
  14. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/common/grpc/grpc_host_pb2.py +0 -0
  15. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/common/grpc/grpc_host_pb2_grpc.py +0 -0
  16. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/common/grpc/grpc_pb2.py +0 -0
  17. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/common/grpc/grpc_pb2_grpc.py +0 -0
  18. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/common/idl/__init__.py +0 -0
  19. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/common/idl/device_schema.py +0 -0
  20. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/common/idl/schema.py +0 -0
  21. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/common/utils/__init__.py +0 -0
  22. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/common/utils/ansi_codes.py +0 -0
  23. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/common/utils/api_token.py +0 -0
  24. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/common/utils/db_connection.py +0 -0
  25. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/common/utils/db_location.py +0 -0
  26. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/common/utils/list_string.py +0 -0
  27. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/drivers/__init__.py +0 -0
  28. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/drivers/adalm_pluto/__init__.py +0 -0
  29. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/drivers/adalm_pluto/pluto_remote_server.py +0 -0
  30. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/host/__init__.py +0 -0
  31. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/host/host_auth_token.py +0 -0
  32. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/host/host_directory_store.py +0 -0
  33. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/server/__init__.py +0 -0
  34. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/server/acc_perms.py +0 -0
  35. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/server/cert_provider.py +0 -0
  36. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/server/grpc_server.py +0 -0
  37. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/server/reservation.py +0 -0
  38. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/server/user_group_cli.py +0 -0
  39. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/server/user_group_handler.py +0 -0
  40. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/serverrf_cli.py +0 -0
  41. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/tools/__init__.py +0 -0
  42. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/tools/gen_certs.py +0 -0
  43. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/tools/gist_status.py +0 -0
  44. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server/tools/gist_status_testing.py +0 -0
  45. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server_testing.egg-info/SOURCES.txt +0 -0
  46. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server_testing.egg-info/dependency_links.txt +0 -0
  47. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server_testing.egg-info/entry_points.txt +0 -0
  48. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server_testing.egg-info/requires.txt +0 -0
  49. {remoterf_server_testing-0.0.2 → remoterf_server_testing-0.0.4}/src/remoteRF_server_testing.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoteRF-server-testing
3
- Version: 0.0.2
3
+ Version: 0.0.4
4
4
  Summary: RemoteRF server-side control package
5
5
  Requires-Python: >=3.8
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-testing"
7
- version = "0.0.2"
7
+ version = "0.0.4"
8
8
  description = "RemoteRF server-side control package"
9
9
  requires-python = ">=3.8"
10
10
  dependencies = [
@@ -84,12 +84,14 @@ class PlutoSchema(DeviceSchema):
84
84
  self.device.gain_control_mode_chan0 = value
85
85
 
86
86
  @idl_expose(kind="get")
87
- def get_rx_hardwaregain_chan0(self):
88
- return self.device.rx_hardwaregain_chan0
87
+ def get_hardwaregain_chan0(self):
88
+ """RX hardware gain (dB). pyadi-iio attribute: hardwaregain_chan0"""
89
+ return self.device.hardwaregain_chan0
89
90
 
90
91
  @idl_expose(kind="set")
91
- def set_rx_hardwaregain_chan0(self, value):
92
- self.device.rx_hardwaregain_chan0 = value
92
+ def set_hardwaregain_chan0(self, value):
93
+ """RX hardware gain (dB). pyadi-iio attribute: hardwaregain_chan0"""
94
+ self.device.hardwaregain_chan0 = value
93
95
 
94
96
  @idl_expose(kind="get")
95
97
  def get_tx_hardwaregain_chan0(self):
@@ -173,6 +175,11 @@ class PlutoSchema(DeviceSchema):
173
175
  def call_tx_destroy_buffer(self):
174
176
  self.device.tx_destroy_buffer()
175
177
 
178
+ @idl_expose(kind="call")
179
+ def call_disable_dds(self):
180
+ """Disable the internal DDS tone generator."""
181
+ self.device.disable_dds()
182
+
176
183
  # endregion
177
184
 
178
185
  # region ip / repr
@@ -31,6 +31,8 @@ def map_arg(value):
31
31
  arg.string_value = value
32
32
  elif isinstance(value, bool):
33
33
  arg.bool_value = value
34
+ elif isinstance(value, (list, tuple)):
35
+ return map_arg(np.array(value))
34
36
  elif isinstance(value, np.ndarray):
35
37
  if np.iscomplexobj(value):
36
38
  complex_array = arg.complex_array
@@ -259,6 +259,12 @@ class HostTunnelRegistry:
259
259
  self._host_status: Dict[str, HostStatus] = {}
260
260
  self._device_status: Dict[str, DeviceStatus] = {}
261
261
 
262
+ # IDL capability strings received via HostMeta.kv from each host.
263
+ # Keyed by device_id (string gid). Value is the compact caps string
264
+ # emitted by the host's capabilities_string() helper, e.g.:
265
+ # "disable_dds:call0,filter:rw,hardwaregain_chan0:rw,..."
266
+ self._host_device_caps: Dict[str, str] = {}
267
+
262
268
  # persistence
263
269
  cfg = cfg_dir_from_file(__file__)
264
270
  cfg.mkdir(parents=True, exist_ok=True)
@@ -531,6 +537,26 @@ class HostTunnelRegistry:
531
537
 
532
538
  info(f"[registry] heartbeat: host_id={host_id!r} unix_ms={now}")
533
539
 
540
+ # ── IDL capability cache ────────────────────────────────────────
541
+
542
+ def store_host_device_caps(self, host_id: str, device_id: str, caps: str) -> None:
543
+ """
544
+ Store the IDL capability string for a host device.
545
+
546
+ Called when the server receives HostMeta.kv["device.<gid>.caps"] in a
547
+ MetaResponse. The caps string is the compact attr:access list emitted
548
+ by the host's capabilities_string() helper.
549
+ """
550
+ with self._lock:
551
+ self._host_device_caps[str(device_id)] = str(caps)
552
+
553
+ def get_host_device_caps(self, device_id: str) -> str:
554
+ """
555
+ Return the IDL capability string for *device_id*, or "" if not yet received.
556
+ """
557
+ with self._lock:
558
+ return self._host_device_caps.get(str(device_id), "")
559
+
534
560
  def announce_devices(self, host_id: str, devices: list) -> None:
535
561
  info(f"[registry] announce_devices: host_id={host_id!r} n={len(devices)}")
536
562
 
@@ -1147,7 +1173,7 @@ class HostTunnelServicer(host_tunnel_pb2_grpc.HostTunnelServicer):
1147
1173
  warn(f"[inbound] rpc_response has no inflight waiter req_id={rid} stream_sess={stream_uuid}")
1148
1174
 
1149
1175
  elif which == "meta_response":
1150
- # NEW proto: MetaResponse.meta.devices
1176
+ # NEW proto: MetaResponse.meta.devices + meta.kv (IDL caps)
1151
1177
  if host_id is None or session is None:
1152
1178
  warn(f"[inbound] meta_response before hello/session: ignoring peer={peer} stream_sess={stream_uuid}")
1153
1179
  continue
@@ -1186,6 +1212,24 @@ class HostTunnelServicer(host_tunnel_pb2_grpc.HostTunnelServicer):
1186
1212
  else:
1187
1213
  info("[inbound] META_RESPONSE had no meta.devices (or empty)")
1188
1214
 
1215
+ # Read per-device IDL capability strings from meta.kv.
1216
+ # Keys: "device.<gid>.caps" "device.<gid>.type"
1217
+ # These are emitted by the host's tunnel_agent._collect_meta().
1218
+ try:
1219
+ kv = dict(getattr(meta, "kv", {}) or {})
1220
+ for k, v in kv.items():
1221
+ # e.g. k="device.0.caps", v="disable_dds:call0,filter:rw,..."
1222
+ parts = k.split(".")
1223
+ if len(parts) == 3 and parts[0] == "device" and parts[2] == "caps":
1224
+ device_id = parts[1]
1225
+ self.registry.store_host_device_caps(host_id, device_id, str(v))
1226
+ info(
1227
+ f"[inbound] stored IDL caps device_id={device_id} "
1228
+ f"host_id={host_id!r} n_attrs={len(v.split(','))}"
1229
+ )
1230
+ except Exception:
1231
+ exception("[inbound] META_RESPONSE failed reading meta.kv caps")
1232
+
1189
1233
  elif which == "heartbeat":
1190
1234
  if session is not None and host_id is not None:
1191
1235
  ms = int(getattr(frame.heartbeat, "unix_ms", 0) or 0) or now_ms()
@@ -491,11 +491,28 @@ def get_device_by_id(device_id: int):
491
491
  return (st.dev, st.salt, st.hsh)
492
492
 
493
493
  def get_device_schema(device_id: int):
494
- """Return the DeviceSchema instance for a local device, or None if unavailable."""
494
+ """
495
+ Return a DeviceSchema instance for this device, or None if unavailable.
496
+
497
+ For local devices: returns the live bound schema instance.
498
+ For host devices: instantiates an unbound schema from the driver registry
499
+ using the device's dtype — sufficient for IDL/hash purposes since the
500
+ schema structure is defined by the class, not the live device.
501
+ """
495
502
  with _state_lock:
496
503
  st = _devices.get(int(device_id))
497
- if st and st.origin == "local" and st.dev is not None:
498
- return st.dev # DeviceSchema instance
504
+ if st is None:
505
+ return None
506
+
507
+ if st.origin == "local" and st.dev is not None:
508
+ return st.dev
509
+
510
+ if st.origin == "host" and st.dtype:
511
+ try:
512
+ return get_driver_class(st.dtype)()
513
+ except KeyError:
514
+ return None
515
+
499
516
  return None
500
517
 
501
518
  def get_schema_by_token(api_token: str):
@@ -542,6 +559,63 @@ def get_schema_by_token(api_token: str):
542
559
  return None
543
560
  return get_device_schema(gid)
544
561
 
562
+ def resolve_gid_for_token(api_token: str) -> Optional[int]:
563
+ """
564
+ Resolve an API token to its device gid regardless of device origin
565
+ (local or host/VirtualDevice).
566
+
567
+ Unlike get_schema_by_token — which returns None for host devices because
568
+ they have no salt/hash — this function also covers:
569
+ • parsed master-tokens ('{gid}:secret') → any device by gid
570
+ • raw master_token → first connected device
571
+ • regular per-device tokens (salt/hsh) → local devices
572
+
573
+ Used by IDL queries that need a schema even for host devices.
574
+ Does NOT acquire any device I/O lock.
575
+ """
576
+ if not api_token:
577
+ return None
578
+
579
+ _sync_host_devices()
580
+
581
+ # Parsed master-token: resolves any device directly by gid (local or host).
582
+ parsed = parse_mastertoken(api_token)
583
+ if parsed:
584
+ cand, _ = parsed
585
+ with _state_lock:
586
+ st = _devices.get(int(cand))
587
+ if st and _is_connected(st):
588
+ return int(cand)
589
+ return None
590
+
591
+ # Raw master token — prefer local, fall back to any connected device.
592
+ if api_token == master_token:
593
+ with _state_lock:
594
+ # local first
595
+ for k, st in _devices.items():
596
+ if st.origin == "local" and _is_connected(st) and st.salt == "" and st.hsh == "":
597
+ return k
598
+ # then host
599
+ for k, st in _devices.items():
600
+ if _is_connected(st) and st.salt == "" and st.hsh == "":
601
+ return k
602
+ return None
603
+
604
+ # Regular per-device token (salt/hsh). Host devices have empty salt/hsh
605
+ # so they won't match here — but this covers local devices correctly.
606
+ with _state_lock:
607
+ snapshot = [
608
+ (k, st.salt, st.hsh)
609
+ for k, st in _devices.items()
610
+ if _is_connected(st)
611
+ ]
612
+ for k, salt, hsh in snapshot:
613
+ if salt and hsh and validate_token(salt, hsh, api_token):
614
+ return k
615
+
616
+ return None
617
+
618
+
545
619
  def find_device_id_by_name(name: str) -> Optional[int]:
546
620
  """Return the gid of the first device whose name matches (case-insensitive), or None."""
547
621
  name_lower = name.strip().lower()
@@ -52,7 +52,7 @@
52
52
 
53
53
  from .reservation import reservation_handler
54
54
  from ..common.utils import *
55
- from .device_manager import acquire_device, VirtualDevice, get_device_schema, find_device_id_by_name, get_schema_by_token
55
+ from .device_manager import acquire_device, VirtualDevice, get_device_schema, find_device_id_by_name, get_schema_by_token, resolve_gid_for_token
56
56
  from ..host import host_tunnel_server as hts
57
57
 
58
58
  class RpcManager:
@@ -151,7 +151,14 @@ class RpcManager:
151
151
  api_token = str(unmap_arg(args['token']))
152
152
  schema = get_schema_by_token(api_token)
153
153
  if schema is None:
154
- return {'error': map_arg('No schema available for this token (invalid token or host device)')}
154
+ # get_schema_by_token returns None for host (VirtualDevice) entries
155
+ # because they carry no salt/hsh. Fall through via gid resolution
156
+ # which uses get_device_schema and handles host devices correctly.
157
+ gid = resolve_gid_for_token(api_token)
158
+ if gid is not None:
159
+ schema = get_device_schema(gid)
160
+ if schema is None:
161
+ return {'error': map_arg('No schema available for this token (invalid token or device offline)')}
155
162
 
156
163
  elif 'device_id' in args:
157
164
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoteRF-server-testing
3
- Version: 0.0.2
3
+ Version: 0.0.4
4
4
  Summary: RemoteRF server-side control package
5
5
  Requires-Python: >=3.8
6
6
  Description-Content-Type: text/markdown