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.
- remoteRF/common/grpc/v2_codec.py +1 -1
- remoteRF/core/dynamic_v2_transport.py +4 -91
- remoteRF/core/grpc_acc.py +1 -1
- remoteRF/core/secure_channel.py +0 -9
- remoteRF/drivers/adalm_pluto/adalm_pluto_remote.py +2 -14
- remoteRF/drivers/dynamic_device.py +21 -57
- remoteRF/drivers/dynamic_v2.py +38 -27
- remoteRF/drivers/support/uhd_v2.py +0 -9
- remoteRF/remoterf_cli.py +1 -15
- {remoterf-2.1.0.dist-info → remoterf-2.1.0.dev7.dist-info}/METADATA +16 -4
- {remoterf-2.1.0.dist-info → remoterf-2.1.0.dev7.dist-info}/RECORD +15 -16
- remoteRF.py +0 -39
- {remoterf-2.1.0.dist-info → remoterf-2.1.0.dev7.dist-info}/WHEEL +0 -0
- {remoterf-2.1.0.dist-info → remoterf-2.1.0.dev7.dist-info}/entry_points.txt +0 -0
- {remoterf-2.1.0.dist-info → remoterf-2.1.0.dev7.dist-info}/licenses/LICENSE +0 -0
- {remoterf-2.1.0.dist-info → remoterf-2.1.0.dev7.dist-info}/top_level.txt +0 -0
remoteRF/common/grpc/v2_codec.py
CHANGED
|
@@ -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
|
|
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
|
|
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=
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
remoteRF/core/secure_channel.py
CHANGED
|
@@ -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.
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1408
|
-
|
|
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
|
-
|
|
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 =
|
|
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=
|
|
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 =
|
|
1663
|
-
|
|
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"
|
remoteRF/drivers/dynamic_v2.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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"
|
|
681
|
-
f"
|
|
682
|
-
|
|
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 =
|
|
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
|
|
6
|
-
|
|
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.
|
|
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=
|
|
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=
|
|
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=
|
|
30
|
-
remoteRF/core/grpc_acc.py,sha256
|
|
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=
|
|
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=
|
|
43
|
-
remoteRF/drivers/dynamic_v2.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|