RemoteRF 2.1.0__py3-none-any.whl → 2.1.0.dev7__py3-none-any.whl

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.
@@ -48,7 +48,7 @@ def _json_safe(value):
48
48
  if hasattr(value, "as_payload"):
49
49
  return _json_safe(value.as_payload())
50
50
  if isinstance(value, np.generic):
51
- return _json_safe(value.item())
51
+ return value.item()
52
52
  if isinstance(value, complex):
53
53
  return {
54
54
  _JSON_TYPE_KEY: "complex",
@@ -28,7 +28,6 @@ from ..common.grpc.v2_codec import decode_value, encode_value
28
28
  from .v2_errors import (
29
29
  RemoteRFError,
30
30
  RemoteRFProtocolError,
31
- RemoteRFReservationError,
32
31
  RemoteRFTransportError,
33
32
  raise_for_envelope,
34
33
  )
@@ -76,55 +75,6 @@ def _payload(value):
76
75
  return value
77
76
 
78
77
 
79
- class _DevicePlaneStub:
80
- """DynamicControlV2 and SampleDataV1 for a partner lab's device: each
81
- request is one ``DynamicV2:<Operation>:WIRE`` device RPC, routed like any
82
- call on that token (the direct path to the owner when it is up, else the
83
- HOME, which relays it). The owner runs it through its own servicers."""
84
-
85
- def __init__(self, token: str):
86
- self._token = str(token)
87
-
88
- def _rpc(self, operation, request, response_type):
89
- from ..common.utils import map_arg, unmap_arg
90
- from .grpc_client import rpc_client
91
-
92
- try:
93
- response = rpc_client(
94
- function_name=f"DynamicV2:{operation}:WIRE",
95
- args={"a": map_arg(self._token), "wire": map_arg(request.SerializeToString())},
96
- )
97
- except RuntimeError as exc:
98
- # A refusal the HOME or the owner carried in "a": no live
99
- # reservation, a revoked agreement, a device no longer visible.
100
- raise RemoteRFReservationError(f"partner device: {exc}") from exc
101
- wire = unmap_arg(response.results["wire"]) if "wire" in response.results else None
102
- if not isinstance(wire, bytes):
103
- raise RemoteRFProtocolError("partner device plane returned no Dynamic v2 reply")
104
- return response_type.FromString(wire)
105
-
106
- def Negotiate(self, request, timeout=None):
107
- return self._rpc("Negotiate", request, grpc_pb2.NegotiateResponse)
108
-
109
- def GetSchema(self, request, timeout=None):
110
- return self._rpc("GetSchema", request, grpc_pb2.GetSchemaResponse)
111
-
112
- def OpenSession(self, request, timeout=None):
113
- return self._rpc("OpenSession", request, grpc_pb2.OpenSessionResponse)
114
-
115
- def Invoke(self, request, timeout=None):
116
- return self._rpc("Invoke", request, grpc_pb2.InvokeResponse)
117
-
118
- def CloseHandle(self, request, timeout=None):
119
- return self._rpc("CloseHandle", request, grpc_pb2.CloseResponse)
120
-
121
- def CloseSession(self, request, timeout=None):
122
- return self._rpc("CloseSession", request, grpc_pb2.CloseResponse)
123
-
124
- def SampleStream(self, frames, timeout=None):
125
- return [self._rpc("SampleFrame", frame, grpc_pb2.SampleFrame) for frame in frames]
126
-
127
-
128
78
  class DynamicV2Transport:
129
79
  def __init__(
130
80
  self,
@@ -132,21 +82,15 @@ class DynamicV2Transport:
132
82
  control_stub=None,
133
83
  sample_stub=None,
134
84
  control_timeout_sec: float = 30.0,
135
- open_timeout_sec: float = 120.0,
136
85
  stream_timeout_margin_sec: float = 5.0,
137
86
  ):
138
87
  self.control_timeout_sec = float(control_timeout_sec)
139
- # A cold USRP open loads firmware/FPGA (B2xx re-enumerates on USB),
140
- # which can outlast the normal control deadline.
141
- self.open_timeout_sec = float(open_timeout_sec)
142
88
  self.stream_timeout_margin_sec = float(stream_timeout_margin_sec)
143
89
  if (
144
90
  not math.isfinite(self.control_timeout_sec)
145
91
  or self.control_timeout_sec <= 0
146
92
  ):
147
93
  raise ValueError("control_timeout_sec must be finite and positive")
148
- if not math.isfinite(self.open_timeout_sec) or self.open_timeout_sec <= 0:
149
- raise ValueError("open_timeout_sec must be finite and positive")
150
94
  if (
151
95
  not math.isfinite(self.stream_timeout_margin_sec)
152
96
  or self.stream_timeout_margin_sec <= 0
@@ -163,35 +107,9 @@ class DynamicV2Transport:
163
107
  self.control = control_stub
164
108
  self.samples = sample_stub
165
109
 
166
- def _to_owner(self, token: str, owner: str | None = None) -> bool:
167
- """Carry this transport over the device plane when ``token`` drives a
168
- partner lab's device (named by the HOME's refusal, or already known)."""
169
- from . import direct_path
170
-
171
- if isinstance(self.control, _DevicePlaneStub):
172
- return False
173
- owner = owner or direct_path.partner_of(str(token))
174
- if not owner:
175
- return False
176
- # Marked, the token's device calls take the direct path to the owner.
177
- direct_path.mark_partner(str(token), str(owner))
178
- self.control = self.samples = _DevicePlaneStub(token)
179
- return True
180
-
181
- def _checked(self, response, token: str) -> bool:
182
- """raise_for_envelope, except for the refusal of a partner's token:
183
- True when the call should be repeated over the device plane."""
184
- try:
185
- raise_for_envelope(response.error)
186
- except RemoteRFProtocolError as exc:
187
- if self._to_owner(token, exc.details.get("federated")):
188
- return True
189
- raise
190
- return False
191
-
192
- def _call(self, fn, request, *, timeout: float | None = None):
110
+ def _call(self, fn, request):
193
111
  try:
194
- return fn(request, timeout=timeout or self.control_timeout_sec)
112
+ return fn(request, timeout=self.control_timeout_sec)
195
113
  except grpc.RpcError as exc:
196
114
  raise RemoteRFTransportError(
197
115
  f"Dynamic v2 RPC failed: {exc.code().name}: {exc.details()}",
@@ -223,7 +141,6 @@ class DynamicV2Transport:
223
141
  return response
224
142
 
225
143
  def get_schema(self, token: str) -> dict:
226
- self._to_owner(token)
227
144
  self.negotiate()
228
145
  response = self._call(
229
146
  self.control.GetSchema,
@@ -232,8 +149,7 @@ class DynamicV2Transport:
232
149
  schema_versions=[SCHEMA_VERSION],
233
150
  ),
234
151
  )
235
- if self._checked(response, token):
236
- return self.get_schema(token)
152
+ raise_for_envelope(response.error)
237
153
  try:
238
154
  schema = json.loads(response.schema_json)
239
155
  except (TypeError, json.JSONDecodeError) as exc:
@@ -249,7 +165,6 @@ class DynamicV2Transport:
249
165
  return schema
250
166
 
251
167
  def open_session(self, token: str, schema_hash: str):
252
- self._to_owner(token)
253
168
  response = self._call(
254
169
  self.control.OpenSession,
255
170
  grpc_pb2.OpenSessionRequest(
@@ -258,10 +173,8 @@ class DynamicV2Transport:
258
173
  control_protocol_version=CONTROL_PROTOCOL_VERSION,
259
174
  streaming_protocol_version=STREAMING_PROTOCOL_VERSION,
260
175
  ),
261
- timeout=self.open_timeout_sec,
262
176
  )
263
- if self._checked(response, token):
264
- return self.open_session(token, schema_hash)
177
+ raise_for_envelope(response.error)
265
178
  try:
266
179
  schema = json.loads(response.schema_json)
267
180
  capabilities = json.loads(response.capabilities_json or "{}")
remoteRF/core/grpc_acc.py CHANGED
@@ -110,7 +110,7 @@ class RemoteRFAccount:
110
110
  token = unmap_arg(response.results["Token"])
111
111
  try:
112
112
  from ..drivers.dynamic_device import install_driver
113
- install_driver(token=token, device_id=device_id)
113
+ install_driver(device_id=device_id)
114
114
  except Exception as e:
115
115
  print(f"Warning: could not install driver for device {device_id}: {e}")
116
116
  return token
@@ -36,15 +36,6 @@ import grpc
36
36
  DEFAULT_OPTIONS: Tuple[Tuple[str, int], ...] = (
37
37
  ("grpc.max_send_message_length", 100 * 1024 * 1024),
38
38
  ("grpc.max_receive_message_length", 100 * 1024 * 1024),
39
- # A call in flight when the network changes under it (a laptop roaming to
40
- # another access point) fails within ~30 s instead of waiting out TCP's
41
- # own retransmission timeout, ~9 minutes on macOS. Pings go out only while
42
- # a call is open, at most two per quiet stretch (gRPC's default), which
43
- # even a Server that never raised its ping allowance accepts.
44
- ("grpc.keepalive_time_ms", 20_000),
45
- ("grpc.keepalive_timeout_ms", 10_000),
46
- # The ack deadline a keepalive ping actually waits on (default 60 s).
47
- ("grpc.http2.ping_timeout_ms", 10_000),
48
39
  )
49
40
 
50
41
 
@@ -14,10 +14,10 @@
14
14
  # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
15
 
16
16
  # Auto-generated from IDL schema — do not edit by hand.
17
- # device_type: adalm_pluto driver_version: 0.0.3 schema_hash: sha256:35b66a1e4383a344046be27ff80d40aff3ebd147e7aa5dd0513303fd3c0cfab4
17
+ # device_type: adalm_pluto driver_version: 0.0.2 schema_hash: sha256:fa4603755fdfde9ff8286c95b2f30996df0d6253fdec608976b1aaae9f48354e
18
18
 
19
19
  _PREFIX = "Adalm_pluto"
20
- _SCHEMA_HASH = "sha256:35b66a1e4383a344046be27ff80d40aff3ebd147e7aa5dd0513303fd3c0cfab4"
20
+ _SCHEMA_HASH = "sha256:fa4603755fdfde9ff8286c95b2f30996df0d6253fdec608976b1aaae9f48354e"
21
21
  _CLIENT_MODULES = {}
22
22
  _CLIENT_OBJECTS = {}
23
23
 
@@ -375,18 +375,6 @@ class Pluto:
375
375
  'Return a connection identifier for this device (ping / verify).'
376
376
  return _try_call("ip", self.token)
377
377
 
378
- def power_cycle(self):
379
- 'PoE Plutos: power off, then on, and wait for boot. No-op on other Plutos.'
380
- return _try_call("power_cycle", self.token)
381
-
382
- def power_down(self):
383
- 'PoE Plutos: cut power; the next command powers it back up. No-op on other Plutos.'
384
- return _try_call("power_down", self.token)
385
-
386
- def power_up(self):
387
- 'PoE Plutos: power on and wait for boot (any command does this when needed). No-op on other Plutos.'
388
- return _try_call("power_up", self.token)
389
-
390
378
  def rx(self):
391
379
  return _try_call("rx", self.token)
392
380
 
@@ -36,7 +36,6 @@ from __future__ import annotations
36
36
 
37
37
  import json
38
38
  import keyword
39
- import sys
40
39
  from pathlib import Path
41
40
 
42
41
  from ..common.utils import map_arg, unmap_arg
@@ -95,7 +94,7 @@ def fetch_idl(
95
94
  # A HOME-minted device reference has no direct v2 control channel; the
96
95
  # schema is fetched through the same relayed IDL RPC every driver uses.
97
96
  federated = is_global_ref(token) or is_global_ref(device_id)
98
- if token is not None and device_id is None and prefer_v2 and not federated:
97
+ if token is not None and prefer_v2 and not federated:
99
98
  from .dynamic_v2 import fetch_schema_v2
100
99
  from ..core.v2_errors import RemoteRFProtocolError, RemoteRFReservationError, RemoteRFTransportError
101
100
 
@@ -112,19 +111,14 @@ def fetch_idl(
112
111
  raise
113
112
 
114
113
  args: dict = {}
115
- if device_id is not None:
116
- args['device_id'] = map_arg(device_id if is_global_ref(device_id) else int(device_id))
117
- elif token is not None:
114
+ if token is not None:
118
115
  args['token'] = map_arg(str(token))
116
+ elif device_id is not None:
117
+ args['device_id'] = map_arg(device_id if is_global_ref(device_id) else int(device_id))
119
118
  elif device_name is not None:
120
119
  args['device_name'] = map_arg(str(device_name))
121
120
  else:
122
121
  raise ValueError("fetch_idl requires token, device_id, or device_name")
123
- if prefer_v2 and 'token' not in args and not federated:
124
- # Servers that know v2 answer with it when the device publishes one
125
- # (by device_id, which needs no active reservation); v1-only devices
126
- # and older servers answer v1.
127
- args['schema_versions'] = map_arg(["2.0"])
128
122
 
129
123
  from ..core.grpc_client import rpc_client
130
124
 
@@ -132,18 +126,13 @@ def fetch_idl(
132
126
 
133
127
  if 'error' in resp.results:
134
128
  raise RuntimeError(f"IDL fetch failed: {unmap_arg(resp.results['error'])}")
135
- schema = json.loads(unmap_arg(resp.results['schema']))
136
- if 'federated' in resp.results:
137
- if token is not None:
138
- # A partner lab answered: device calls with this token can punch to it.
139
- from ..core import direct_path
129
+ if token is not None and 'federated' in resp.results:
130
+ # A partner lab answered: device calls with this token can punch to it.
131
+ from ..core import direct_path
132
+
133
+ direct_path.mark_partner(str(token), str(unmap_arg(resp.results['federated'])))
140
134
 
141
- direct_path.mark_partner(str(token), str(unmap_arg(resp.results['federated'])))
142
- if str(schema.get("schema_version")) != "2.0":
143
- # A partner's v1 answer may replace an installed v2 driver (see
144
- # _write_v1_driver_files); a v2 schema is hashed, so left as is.
145
- schema["federated"] = True
146
- return schema
135
+ return json.loads(unmap_arg(resp.results['schema']))
147
136
 
148
137
 
149
138
  # ─────────────────────────────────────────────────────────────────────────────
@@ -1404,14 +1393,8 @@ _DRIVERS_DIR = Path(__file__).parent
1404
1393
 
1405
1394
 
1406
1395
  def _write_driver_files(schema: dict) -> Path:
1407
- pkg_dir = _write_v2_driver_files(schema) if str(schema.get("schema_version")) == "2.0" else _write_v1_driver_files(schema)
1408
- stale = f"remoteRF.drivers.{pkg_dir.name}"
1409
- for name in [m for m in sys.modules if m == stale or m.startswith(stale + ".")]:
1410
- del sys.modules[name]
1411
- return pkg_dir
1412
-
1413
-
1414
- def _write_v1_driver_files(schema: dict) -> Path:
1396
+ if str(schema.get("schema_version")) == "2.0":
1397
+ return _write_v2_driver_files(schema)
1415
1398
 
1416
1399
  (
1417
1400
  device_type,
@@ -1426,14 +1409,8 @@ def _write_v1_driver_files(schema: dict) -> Path:
1426
1409
  _call_map,
1427
1410
  ) = _schema_maps(schema)
1428
1411
  pkg_dir = _DRIVERS_DIR / device_type
1429
- remote_path = pkg_dir / f"{device_type}_remote.py"
1430
- if remote_path.exists() and _is_v2_driver(remote_path) and not schema.get("federated"):
1431
- # The server answered v1 (older server, or a device that no longer
1432
- # publishes v2); don't trade an installed Dynamic v2 driver for it.
1433
- # A partner's device has only the v1 plane, so its answer does.
1434
- return pkg_dir
1435
1412
  pkg_dir.mkdir(exist_ok=True)
1436
- remote_path.write_text(_codegen(schema), encoding="utf-8")
1413
+ (pkg_dir / f"{device_type}_remote.py").write_text(_codegen(schema), encoding="utf-8")
1437
1414
  init_lines = [
1438
1415
  *_GPL_NOTICE_LINES,
1439
1416
  "from importlib import import_module as _import_module",
@@ -1585,7 +1562,11 @@ def ensure_driver(*, token: str = None, device_id: int = None, device_name: str
1585
1562
  device_name=device_name,
1586
1563
  prefer_v2=True,
1587
1564
  )
1588
- device_type = _schema_device_type(schema)
1565
+ device_type = (
1566
+ _require_package_name(schema.get("device_type"), field="device_type")
1567
+ if str(schema.get("schema_version")) == "2.0"
1568
+ else _schema_maps(schema)[0]
1569
+ )
1589
1570
  remote_path = _DRIVERS_DIR / device_type / f"{device_type}_remote.py"
1590
1571
  init_path = remote_path.with_name("__init__.py")
1591
1572
 
@@ -1605,8 +1586,6 @@ def ensure_driver(*, token: str = None, device_id: int = None, device_name: str
1605
1586
  current_hash = _read_schema_hash(remote_path)
1606
1587
  if current_hash != schema.get("schema_hash"):
1607
1588
  _write_driver_files(schema)
1608
- if _read_schema_hash(remote_path) == current_hash:
1609
- return
1610
1589
  print(
1611
1590
  f"Driver for '{device_type}' updated "
1612
1591
  f"(was {(current_hash or '?')[:16]}…, "
@@ -1615,19 +1594,6 @@ def ensure_driver(*, token: str = None, device_id: int = None, device_name: str
1615
1594
  )
1616
1595
 
1617
1596
 
1618
- def _schema_device_type(schema: dict) -> str:
1619
- if str(schema.get("schema_version")) == "2.0":
1620
- return _require_package_name(schema.get("device_type"), field="device_type")
1621
- return _schema_maps(schema)[0]
1622
-
1623
-
1624
- def _is_v2_driver(path: Path) -> bool:
1625
- try:
1626
- return "build_uhd_bindings" in path.read_text(encoding="utf-8")
1627
- except OSError:
1628
- return False
1629
-
1630
-
1631
1597
  def _read_schema_hash(path: Path) -> str | None:
1632
1598
  """Extract _SCHEMA_HASH value from a generated driver file without importing it."""
1633
1599
  try:
@@ -1655,14 +1621,12 @@ def install_driver_if_stale(*, token: str, current_hash: str) -> bool:
1655
1621
 
1656
1622
  from remoteRF.drivers.pluto import *
1657
1623
  """
1658
- schema = fetch_idl(token=token, prefer_v2=True)
1624
+ schema = fetch_idl(token=token, prefer_v2=False)
1659
1625
  if schema.get("schema_hash") == current_hash:
1660
1626
  return False
1661
1627
 
1662
- device_type = _schema_device_type(schema)
1663
- pkg_dir = _write_driver_files(schema)
1664
- if _read_schema_hash(pkg_dir / f"{device_type}_remote.py") == current_hash:
1665
- return False
1628
+ device_type = _schema_maps(schema)[0]
1629
+ _write_driver_files(schema)
1666
1630
  print(
1667
1631
  f"Driver for '{device_type}' updated "
1668
1632
  f"(was {current_hash[:16]}…, now {schema.get('schema_hash','?')[:16]}…).\n"
@@ -21,6 +21,7 @@ import hashlib
21
21
  import json
22
22
  import keyword
23
23
  import numbers
24
+ import re
24
25
  import types
25
26
  import weakref
26
27
 
@@ -82,6 +83,24 @@ _PARAMETER_KINDS = {
82
83
  _PARAMETER_DIRECTIONS = {"in", "out", "inout"}
83
84
 
84
85
 
86
+ def _canonical_native_version(value) -> str:
87
+ """Strip distro/build metadata while retaining the pinned upstream API."""
88
+ text = str(value or "").strip()
89
+ if text.lower().startswith("uhd "):
90
+ text = text[4:].strip()
91
+ match = re.match(
92
+ r"^(\d+)\.(\d+)\.(\d+)(?:\.(\d+|main|head))?",
93
+ text,
94
+ flags=re.IGNORECASE,
95
+ )
96
+ if match is None:
97
+ return text.partition("-")[0].strip()
98
+ major, minor, patch, build = match.groups()
99
+ if build is None or build.lower() in {"main", "head"}:
100
+ build = "0"
101
+ return ".".join((major, minor, patch, build))
102
+
103
+
85
104
  def validate_schema_v2(schema: dict) -> dict:
86
105
  if not isinstance(schema, dict) or schema.get("schema_version") != "2.0":
87
106
  raise RemoteRFProtocolError("Dynamic schema_version must be '2.0'")
@@ -204,13 +223,9 @@ def _matches_type(value, wire_type: str) -> bool:
204
223
  return True
205
224
  if choice == "bytes" and isinstance(value, (bytes, bytearray, memoryview)):
206
225
  return True
207
- if (
208
- choice == "complex"
209
- and isinstance(value, (numbers.Complex, np.number))
210
- and not isinstance(value, bool)
211
- ):
226
+ if choice == "complex" and isinstance(value, (complex, np.complexfloating)):
212
227
  return True
213
- if choice.startswith("list") and isinstance(value, (list, tuple, np.ndarray)):
228
+ if choice.startswith("list") and isinstance(value, (list, tuple)):
214
229
  if "[" not in choice:
215
230
  return True
216
231
  item_type = choice.partition("[")[2].rpartition("]")[0]
@@ -280,11 +295,7 @@ class OverloadBinder:
280
295
  else:
281
296
  raise TypeError(f"missing required argument {name!r}")
282
297
 
283
- if (
284
- supplied
285
- and not (value is None and not spec.get("required", True))
286
- and not _matches_type(value, spec.get("type", "any"))
287
- ):
298
+ if supplied and not _matches_type(value, spec.get("type", "any")):
288
299
  raise TypeError(
289
300
  f"argument {name!r} must match {spec.get('type')}, "
290
301
  f"not {type(value).__name__}"
@@ -667,21 +678,23 @@ def build_uhd_bindings(schema: dict, *, transport_factory=DynamicV2Transport):
667
678
  self.token = str(token)
668
679
  self._transport = transport_factory()
669
680
  self._transport.negotiate()
670
- try:
671
- opened = self._transport.open_session(self.token, schema["schema_hash"])
672
- except RemoteRFProtocolError as exc:
673
- if not exc.details.get("current"):
674
- raise
675
- from .dynamic_device import install_driver_if_stale
676
-
677
- device_type = schema.get("device_type", "usrp")
678
- if install_driver_if_stale(token=self.token, current_hash=schema["schema_hash"]):
681
+ opened = self._transport.open_session(self.token, schema["schema_hash"])
682
+ required_uhd = str(
683
+ schema.get("native_api", {}).get("version") or ""
684
+ )
685
+ actual_uhd = str(opened.get("uhd_version") or "")
686
+ if (
687
+ required_uhd
688
+ and _canonical_native_version(actual_uhd)
689
+ != _canonical_native_version(required_uhd)
690
+ ):
691
+ try:
692
+ self._transport.close_session(opened["session_id"])
693
+ finally:
679
694
  raise RemoteRFProtocolError(
680
- f"the server's {device_type} driver changed and the local copy "
681
- f"was updated; reimport remoteRF.drivers.{device_type} and retry",
682
- details=dict(exc.details),
683
- ) from exc
684
- raise
695
+ f"client requires UHD {required_uhd}; "
696
+ f"server reports {actual_uhd or 'unknown'}"
697
+ )
685
698
  self._session_id = opened["session_id"]
686
699
  self._handle = opened["device_handle"]
687
700
  self._generation = 0
@@ -820,7 +833,6 @@ def build_uhd_bindings(schema: dict, *, transport_factory=DynamicV2Transport):
820
833
  Range=uhd_v2.Range,
821
834
  MetaRange=uhd_v2.MetaRange,
822
835
  TuneRequest=uhd_v2.TuneRequest,
823
- TuneRequestPolicy=uhd_v2.TuneRequestPolicy,
824
836
  TuneResult=uhd_v2.TuneResult,
825
837
  StreamCMD=uhd_v2.StreamCMD,
826
838
  StreamMode=uhd_v2.StreamMode,
@@ -852,7 +864,6 @@ def build_uhd_bindings(schema: dict, *, transport_factory=DynamicV2Transport):
852
864
  time_spec=uhd_v2.TimeSpec,
853
865
  device_addr=uhd_v2.DeviceAddr,
854
866
  tune_request=uhd_v2.TuneRequest,
855
- tune_request_policy=uhd_v2.TuneRequestPolicy,
856
867
  tune_result=uhd_v2.TuneResult,
857
868
  )
858
869
  )
@@ -230,10 +230,6 @@ class TuneRequestPolicy(str, Enum):
230
230
  NONE = "NONE"
231
231
  AUTO = "AUTO"
232
232
  MANUAL = "MANUAL"
233
- # UHD's own spellings: uhd.types.TuneRequestPolicy.manual
234
- none = "NONE"
235
- auto = "AUTO"
236
- manual = "MANUAL"
237
233
 
238
234
 
239
235
  class TuneRequest(_ValueObject):
@@ -250,11 +246,6 @@ class TuneRequest(_ValueObject):
250
246
  self.dsp_freq = 0.0
251
247
  self.rf_freq_policy = TuneRequestPolicy.AUTO
252
248
  self.dsp_freq_policy = TuneRequestPolicy.AUTO
253
- if self.lo_offset is not None:
254
- # As UHD's tune_request_t(target, lo_off): the RF LO sits manually
255
- # at target + offset and the DSP takes up the difference.
256
- self.rf_freq_policy = TuneRequestPolicy.MANUAL
257
- self.rf_freq = self.target_freq + self.lo_offset
258
249
  self.args = DeviceAddr()
259
250
 
260
251
  def as_payload(self):
remoteRF/remoterf_cli.py CHANGED
@@ -158,7 +158,6 @@ def print_help() -> None:
158
158
  printf("Config:", (Sty.BOLD, Sty.MAGENTA))
159
159
  printf(" remoterf -c | --config [options]", Sty.CYAN)
160
160
  printf(" -a, --addr, -addr <host:port>", Sty.CYAN, " Set target server", Sty.DEFAULT)
161
- printf(" --cert-port <port>", Sty.CYAN, " Cert server port (default: grpc port + 1)", Sty.DEFAULT)
162
161
  printf(" -w, --wipe, -wipe", Sty.CYAN, " Delete all local config", Sty.DEFAULT)
163
162
  printf(" -y, --yes, -yes", Sty.CYAN, " Skip wipe confirmation", Sty.DEFAULT)
164
163
  printf(" -r, --register", Sty.CYAN, " Register right after configuring", Sty.DEFAULT)
@@ -461,7 +460,6 @@ def main() -> int:
461
460
  yes = False
462
461
  register = False
463
462
  account_transport = None
464
- cert_port_arg = None
465
463
 
466
464
  i = 1
467
465
  while i < len(argv):
@@ -483,18 +481,6 @@ def main() -> int:
483
481
  i += 2
484
482
  continue
485
483
 
486
- if tok in ("--cert-port", "-cert-port"):
487
- if i + 1 >= len(argv):
488
- print("ERROR: missing required argument after --cert-port")
489
- return 2
490
- try:
491
- cert_port_arg = int(argv[i + 1].strip())
492
- except Exception:
493
- print("ERROR: --cert-port must be an integer")
494
- return 2
495
- i += 2
496
- continue
497
-
498
484
  if tok in ("--wipe", "-w", "-wipe"):
499
485
  wipe = True
500
486
  i += 1
@@ -563,7 +549,7 @@ def main() -> int:
563
549
  return 2
564
550
 
565
551
  # configure returns the proper exit code
566
- cert_port = cert_port_arg if cert_port_arg is not None else port + 1
552
+ cert_port = port + 1
567
553
  result = configure(host, port, cert_port)
568
554
  if result not in (None, 0):
569
555
  return int(result)
@@ -1,27 +1,39 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoterf
3
- Version: 2.1.0
3
+ Version: 2.1.0.dev7
4
4
  Summary: A python API to remotely access signal centric hardware. Client-side only! Courtesy of Wireless Lab @ UCLA & Prof. Ian Roberts.
5
- Author-email: Ethan Ge <ethoGalaxy@gmail.com>
6
- License-Expression: GPL-3.0-or-later
5
+ Author: Ethan Ge
6
+ Author-email: ethoGalaxy@gmail.com
7
+ License: GPL-3.0-or-later
7
8
  Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
8
10
  Classifier: Operating System :: OS Independent
9
11
  Requires-Python: >=3.10
10
12
  Description-Content-Type: text/markdown
11
13
  License-File: LICENSE
12
- Requires-Dist: remoterf-federation-core==0.16.0
14
+ Requires-Dist: remoterf-federation-core==0.13.0
13
15
  Requires-Dist: grpcio<2.0.0,>=1.78.1
14
16
  Requires-Dist: protobuf<7.0.0,>=6.31.1
15
17
  Requires-Dist: numpy
16
18
  Requires-Dist: prompt_toolkit
17
19
  Requires-Dist: python-dotenv
20
+ Requires-Dist: prompt-toolkit
18
21
  Requires-Dist: httpx<1.0,>=0.27
19
22
  Requires-Dist: certifi
20
23
  Requires-Dist: pydantic<3.0,>=2.6
21
24
  Requires-Dist: keyring<26,>=24
22
25
  Requires-Dist: aioice<0.11,>=0.10
23
26
  Requires-Dist: aioquic<2.0,>=1.2
27
+ Dynamic: author
28
+ Dynamic: author-email
29
+ Dynamic: classifier
30
+ Dynamic: description
31
+ Dynamic: description-content-type
32
+ Dynamic: license
24
33
  Dynamic: license-file
34
+ Dynamic: requires-dist
35
+ Dynamic: requires-python
36
+ Dynamic: summary
25
37
 
26
38
  # Remote RF
27
39
 
@@ -1,6 +1,5 @@
1
- remoteRF.py,sha256=ANpEl1Hg7HpGji6UZP6SzHORqE1v4S7SzsZzWFiG6Es,1853
2
1
  remoteRF/__init__.py,sha256=-1h_xQjiE66oiWRO-vnLGJ4smpgUNhFuV-ZreW9_tD4,667
3
- remoteRF/remoterf_cli.py,sha256=fuLMH6ts-h5Ii_MlOB14-lgmkWis-WRP4wwWJy8jeIA,21407
2
+ remoteRF/remoterf_cli.py,sha256=ShiYgQfrjYxoegQD432XVJ9MrZrTn7iq9yLGVW-uPbE,20749
4
3
  remoteRF/version.py,sha256=NWzcPOAdd567IvC8ei1yyPdeS7w0kt99oKwL6oWAQSc,850
5
4
  remoteRF/common/__init__.py,sha256=ey1IjkAaXFL1sZBnBwuWVlHhA_n0bu_uxgkDV_KOxYg,708
6
5
  remoteRF/common/tos_notice.txt,sha256=fUHXV6U9uhZDvVs8BcRi4kWbU1PeU9mkxML0n5DDQp8,334
@@ -11,7 +10,7 @@ remoteRF/common/grpc/deployment_capabilities_pb2_grpc.py,sha256=Vy_7zGsMNy_UwbGR
11
10
  remoteRF/common/grpc/grpc.proto,sha256=X85kGh3DgypPsb_sDjtARCkdb91XXNOr6GiTr2axEcY,5853
12
11
  remoteRF/common/grpc/grpc_pb2.py,sha256=8aVMR9vsLGWtdpvK1iAMspgGQN1_Jn7EdHzdz_YcJ3M,12736
13
12
  remoteRF/common/grpc/grpc_pb2_grpc.py,sha256=1vbqOxzDXt0bi8mjTJLAS742CsSUQcaZTw0N4cxRVyE,17731
14
- remoteRF/common/grpc/v2_codec.py,sha256=kgj_WXBOKw--J7ZgE7TBfrUbF5AmjMg_VNMx9tDNHTQ,5191
13
+ remoteRF/common/grpc/v2_codec.py,sha256=a9_xpxAnJAvAssfkElbGaDitkUpd1ntoK6a9h-xx7Og,5179
15
14
  remoteRF/common/utils/__init__.py,sha256=mRw79YhvGlA5m1wsejx6n4uNcGwusUizDquSkoU1nFY,1128
16
15
  remoteRF/common/utils/ansi_codes.py,sha256=jzSri_BqezN5yMBBLT9cjU2LoJZiQ4qBxhk5Y0KQPSI,5275
17
16
  remoteRF/common/utils/api_token.py,sha256=G0kratdJk1PUs4svF6qjRC0do8osV9mntj7UgqzRySI,1928
@@ -26,10 +25,10 @@ remoteRF/core/acc_login.py,sha256=Qct7vEsc0OIdVwmikOv2mVv9-ehH4CMVw7QWHe2yOsg,10
26
25
  remoteRF/core/app.py,sha256=uh4UtLpN9Qx88EfeKMP4J9XU9mKS4HD942xh3zD1PJg,71823
27
26
  remoteRF/core/banner_note.txt,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
27
  remoteRF/core/direct_path.py,sha256=O-DsMt198ywRuiszBxjINA_2sP3XHmj-ow9s6dSl9E0,28813
29
- remoteRF/core/dynamic_v2_transport.py,sha256=sOXrgvo020u_Mu8lGp1v0SI-MvMY3A43ezTbJRzY5aY,21998
30
- remoteRF/core/grpc_acc.py,sha256=XzE4uTSQ6ZX_4jcgK9pbS96_9_7oDqWEqDTTi07Z6P0,7636
28
+ remoteRF/core/dynamic_v2_transport.py,sha256=MLTi920dFeYeAxeOTuNZtw4mY-zZIq_ZYLDW992To3c,18029
29
+ remoteRF/core/grpc_acc.py,sha256=-4eFiYcE8R1A6LN4KuqJG6QYlpEMoLcH6WmNH_VPwKI,7623
31
30
  remoteRF/core/grpc_client.py,sha256=34bNG5s65eatP91h0EYsLXrtRDCqykXDdRQbSI3Uw2M,18023
32
- remoteRF/core/secure_channel.py,sha256=_avJBcvtLv0_9MgT6dpM-6bNH8DGph2Gls6DJZMkIXo,2859
31
+ remoteRF/core/secure_channel.py,sha256=ffPpc5o-3YAh7jMIT4izRbC-Iq9D-biSwpXwGBVlvUU,2283
33
32
  remoteRF/core/v2_errors.py,sha256=Xmuta24cfQEy4FGG7HZk7B9hD5g4pGT5Hz8Eguy42lU,2956
34
33
  remoteRF/deployment/__init__.py,sha256=aEl9z32KMUBfRRm6Ho0-fM-VUtiKIRq4bQBU2_u29-c,69
35
34
  remoteRF/deployment/backend.py,sha256=ULrHn31w-v8tN5BU4HktuPKG4vEGIteqPYo0hUWVjfI,16135
@@ -39,10 +38,10 @@ remoteRF/deployment/http.py,sha256=8Ar1-GOIyCJrPWURZyOl969e7FZqjdQuOkA5Km2IMGw,4
39
38
  remoteRF/deployment/state.py,sha256=rXHEMf-kJP1sYCJn-VD84ZNalkYUaNAiYaoaVu4uo3U,6541
40
39
  remoteRF/drivers/__init__.py,sha256=DXjon8E0R1X27fDQDy6IrwzqTFwrT6-suC5VSDXUP8Y,1250
41
40
  remoteRF/drivers/_virtual.py,sha256=J6uy0G1VCpr3AeTKWiPUZienNTlO9eBC3TVb2uBdcvc,4402
42
- remoteRF/drivers/dynamic_device.py,sha256=lSdDgGl6022G2rUyo2WD0zMImwi9Q-uIzqX-T60MYeY,62180
43
- remoteRF/drivers/dynamic_v2.py,sha256=CIJ6SW0jptT0Kp93Rly7WlYbeDoTdZVoP2CfHCaSiAo,33666
41
+ remoteRF/drivers/dynamic_device.py,sha256=mGbRLh92QuWcktHNm_ZMej7AFWUvHM9ttJtsGDOGm9I,60504
42
+ remoteRF/drivers/dynamic_v2.py,sha256=vF45cgH4rpKo8Gf67yJ4Hw6lKY1JhP-LGdyd68Fz-PI,33919
44
43
  remoteRF/drivers/adalm_pluto/__init__.py,sha256=QmFIGRC9MG9GMOc277Hkwd2CG-3wI59UmuV0k2XsvY8,829
45
- remoteRF/drivers/adalm_pluto/adalm_pluto_remote.py,sha256=yIf_VkgMsDPsz5W54MK0emRJz22UujydMpWGFV41jtU,12186
44
+ remoteRF/drivers/adalm_pluto/adalm_pluto_remote.py,sha256=uvaJmmd8IK7THqjKn6xjLwnXKIXNO_MNGoQz1KJCZf8,11669
46
45
  remoteRF/drivers/adalm_pluto/pluto_remote.py,sha256=6HRc94yZlM9FDt0_XcPIo_kVcG6ELgRahW0T25BIfb0,9134
47
46
  remoteRF/drivers/hackrf/__init__.py,sha256=BlpF0ZyAu8IcfpxyQlPXbI4PATdisFT1lR0cM8Gk6O0,821
48
47
  remoteRF/drivers/hackrf/hackrf_remote.py,sha256=UMwBsKGTUjq6lp0s3axwbdel9YtUUMoik0VtpsjGN-Y,8956
@@ -54,15 +53,15 @@ remoteRF/drivers/support/__init__.py,sha256=-1h_xQjiE66oiWRO-vnLGJ4smpgUNhFuV-Zr
54
53
  remoteRF/drivers/support/ti_mmwave.py,sha256=a7ThvnYjdGzoqJjoG1l7KOdOGAbhVWXBYFjW3UE2MDI,10085
55
54
  remoteRF/drivers/support/uhd.py,sha256=t0X2wG_xHSSVYedGhKSN-GIuvmkmd346bM7hx1KHm6s,11722
56
55
  remoteRF/drivers/support/uhd_dsp.py,sha256=s1_PsBYao16wv8Gq2mHBo8tTBfHzc_cyd1DmRATNhAg,3223
57
- remoteRF/drivers/support/uhd_v2.py,sha256=A66MK7FqfyT8AeNqS4pnCIwBxTTszMmT02YoZF2kqGk,23028
56
+ remoteRF/drivers/support/uhd_v2.py,sha256=5Ho-ryyC86y-vitBsbFPouvzrVylNI0pdNzwXMANfXg,22600
58
57
  remoteRF/drivers/ti_mmwave/__init__.py,sha256=-ZhvekQi9UCgtZUhezu17s-wUoxc_FZO1-5ATLRzBAI,1189
59
58
  remoteRF/drivers/ti_mmwave/ti_mmwave_remote.py,sha256=sQiRCstSoi73T7TkE2WXp1YYp3GhCIeJavq50qKsllU,5696
60
59
  remoteRF/drivers/usrp/__init__.py,sha256=y79nl50_4dV9-AI3cfDptOAWeR58fAaihHObqJI9HJM,780
61
60
  remoteRF/drivers/usrp/usrp_remote.py,sha256=hBh_CmGOAoEkkdQQ8uPFAsBA6LVeZsPSlpPGgo1c7sM,115018
62
61
  remoteRF/drivers/usrp/usrp_remote.pyi,sha256=MG7rMJdjyT0hLLoJmnS1emDQjgepWz695GbFED3v-T4,11810
63
- remoterf-2.1.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
64
- remoterf-2.1.0.dist-info/METADATA,sha256=bc71IrZ885tKY1vCyv-BfBONqq5aGmfyD5kJh9ZrId0,12778
65
- remoterf-2.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
66
- remoterf-2.1.0.dist-info/entry_points.txt,sha256=La_TjAqCfqdcVTVjB2WrsJNn5rfgcuIYroe2v-CMwRQ,56
67
- remoterf-2.1.0.dist-info/top_level.txt,sha256=XQJoVTmAOsHV9qtPSP_QSh0ma4jKxsDkxn4IjM_ZuZk,9
68
- remoterf-2.1.0.dist-info/RECORD,,
62
+ remoterf-2.1.0.dev7.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
63
+ remoterf-2.1.0.dev7.dist-info/METADATA,sha256=p6qsKMi09w4yItYpIHhlSSylztT45QLMAzbFvKxV92c,13090
64
+ remoterf-2.1.0.dev7.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
65
+ remoterf-2.1.0.dev7.dist-info/entry_points.txt,sha256=La_TjAqCfqdcVTVjB2WrsJNn5rfgcuIYroe2v-CMwRQ,56
66
+ remoterf-2.1.0.dev7.dist-info/top_level.txt,sha256=XQJoVTmAOsHV9qtPSP_QSh0ma4jKxsDkxn4IjM_ZuZk,9
67
+ remoterf-2.1.0.dev7.dist-info/RECORD,,
remoteRF.py DELETED
@@ -1,39 +0,0 @@
1
- # Copyright (C) 2026 RemoteRF
2
- #
3
- # This program is free software: you can redistribute it and/or modify
4
- # it under the terms of the GNU General Public License as published by
5
- # the Free Software Foundation, either version 3 of the License, or
6
- # (at your option) any later version.
7
- #
8
- # This program is distributed in the hope that it will be useful,
9
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
- # GNU General Public License for more details.
12
- #
13
- # You should have received a copy of the GNU General Public License
14
- # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
-
16
- """Rescue for an upgrade from remoterf 2.0.7-2.0.13 on Windows and macOS.
17
-
18
- Those releases installed the package in a lowercase ``remoterf`` folder. When
19
- that folder survives an upgrade (it holds the generated drivers pip does not
20
- know about), the new files land inside it, and on a case-insensitive disk
21
- ``import remoteRF`` no longer finds the package. Python only reaches this
22
- module then: a correctly named ``remoteRF`` folder always takes precedence.
23
- """
24
-
25
- import importlib.util
26
- import os
27
- import sys
28
-
29
- _FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), "remoterf")
30
- _SPEC = importlib.util.spec_from_file_location(
31
- __name__, os.path.join(_FOLDER, "__init__.py"), submodule_search_locations=[_FOLDER]
32
- )
33
- if _SPEC is None or not os.path.isfile(os.path.join(_FOLDER, "__init__.py")):
34
- raise ImportError("the remoteRF package folder is missing; reinstall with: pip install --force-reinstall remoterf")
35
- _MODULE = importlib.util.module_from_spec(_SPEC)
36
- # The import system returns whatever this name maps to once this module has
37
- # run: the package itself, so ``remoteRF.<submodule>`` resolves inside it.
38
- sys.modules[__name__] = _MODULE
39
- _SPEC.loader.exec_module(_MODULE)