RemoteRF 2.2.5__tar.gz → 2.3.1__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.
- {remoterf-2.2.5/src/remoterf.egg-info → remoterf-2.3.1}/PKG-INFO +1 -1
- {remoterf-2.2.5 → remoterf-2.3.1}/pyproject.toml +1 -1
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/core/app.py +149 -32
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/core/dynamic_v2_transport.py +36 -10
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/core/grpc_acc.py +8 -6
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/core/grpc_client.py +260 -6
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/deployment/backend.py +19 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/deployment/http.py +2 -1
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/drivers/dynamic_device.py +5 -3
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/drivers/dynamic_v2.py +6 -3
- {remoterf-2.2.5 → remoterf-2.3.1/src/remoterf.egg-info}/PKG-INFO +1 -1
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf.egg-info/SOURCES.txt +1 -0
- remoterf-2.3.1/tests/test_device_routes.py +671 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/tests/test_native_devices.py +7 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/LICENSE +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/README.md +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/setup.cfg +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/common/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/common/grpc/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/common/grpc/deployment_capabilities.proto +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/common/grpc/deployment_capabilities_pb2.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/common/grpc/deployment_capabilities_pb2_grpc.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/common/grpc/grpc.proto +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/common/grpc/grpc_pb2.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/common/grpc/grpc_pb2_grpc.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/common/grpc/v2_codec.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/common/tos_notice.txt +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/common/utils/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/common/utils/ansi_codes.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/common/utils/api_token.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/common/utils/banner.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/common/utils/list_string.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/common/utils/process_arg.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/config/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/config/cert_fetcher.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/config/config.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/core/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/core/acc_login.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/core/banner_note.txt +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/core/direct_path.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/core/secure_channel.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/core/v2_errors.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/deployment/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/deployment/direct.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/deployment/homes.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/deployment/state.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/drivers/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/drivers/_virtual.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/drivers/adalm_pluto/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/drivers/adalm_pluto/adalm_pluto_remote.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/drivers/hackrf/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/drivers/hackrf/hackrf_remote.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/drivers/rtl_sdr/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/drivers/rtl_sdr/rtl_sdr_remote.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/drivers/sim_sdr/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/drivers/sim_sdr/sim_sdr_remote.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/drivers/support/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/drivers/support/ti_mmwave.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/drivers/support/uhd.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/drivers/support/uhd_dsp.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/drivers/support/uhd_v2.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/drivers/ti_mmwave/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/drivers/ti_mmwave/ti_mmwave_remote.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/drivers/usrp/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/drivers/usrp/usrp_remote.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/drivers/usrp/usrp_remote.pyi +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/remoterf_cli.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf/version.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf.egg-info/dependency_links.txt +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf.egg-info/entry_points.txt +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf.egg-info/requires.txt +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf.egg-info/top_level.txt +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/src/remoterf.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/tests/test_banner.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/tests/test_cli_login_fresh.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/tests/test_config_address.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/tests/test_deployment_accounts.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/tests/test_direct_path_fixed.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/tests/test_direct_path_partner.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/tests/test_direct_path_race.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/tests/test_dynamic_device_codegen.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/tests/test_dynamic_v2.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/tests/test_federated_devices.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/tests/test_fetch_idl_fallback.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/tests/test_finish_prompt.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/tests/test_gate_i_direct_path.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/tests/test_grpc_client_target.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/tests/test_homes.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/tests/test_native_account_credentials.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/tests/test_partner_dynamic_v2.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/tests/test_pluto_virtual.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/tests/test_process_arg.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/tests/test_remoterf_cli.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/tests/test_ti_mmwave.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.1}/tests/test_upgrade_rescue.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: remoterf
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.1
|
|
4
4
|
Summary: A python API to remotely access signal centric hardware. Client-side only! Courtesy of Wireless Lab @ UCLA & Prof. Ian Roberts.
|
|
5
5
|
Author-email: Ethan Ge <ethoGalaxy@gmail.com>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "remoterf"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.3.1"
|
|
8
8
|
description = "A python API to remotely access signal centric hardware. Client-side only! Courtesy of Wireless Lab @ UCLA & Prof. Ian Roberts."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "GPL-3.0-or-later"
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
# You should have received a copy of the GNU General Public License
|
|
14
14
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
15
15
|
|
|
16
|
-
from remoterf.core.grpc_client import addr as server_addr, handle_admin_command
|
|
16
|
+
from remoterf.core.grpc_client import addr as server_addr, handle_admin_command, _key
|
|
17
17
|
from . import *
|
|
18
18
|
from ..common.utils import *
|
|
19
19
|
|
|
@@ -21,6 +21,7 @@ import getpass
|
|
|
21
21
|
import os
|
|
22
22
|
import re
|
|
23
23
|
import datetime
|
|
24
|
+
import functools
|
|
24
25
|
import time
|
|
25
26
|
import ast
|
|
26
27
|
from pathlib import Path
|
|
@@ -123,6 +124,80 @@ home_name = None
|
|
|
123
124
|
# The route it was reached over (None on a bare direct profile or an HTTPS home).
|
|
124
125
|
current_route = None
|
|
125
126
|
|
|
127
|
+
# What each generated driver package exposes, for the usage lines after a reservation.
|
|
128
|
+
DRIVER_CALLS = {"pluto": "adi.Pluto", "adalm_pluto": "adi.Pluto", "usrp": "uhd.usrp.MultiUSRP"}
|
|
129
|
+
_HANDLE_TAG = re.compile(r"^(?P<name>.*) \[(?P<handle>\S+)\]$") # handles have no whitespace
|
|
130
|
+
_NO_HANDLE = "This device has no handle"
|
|
131
|
+
_UNROUTABLE = "Its handle {} can't be used from here: {}"
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _token_lab():
|
|
135
|
+
"""The lab `<token>@<lab>` routes to from here: this shell's home, else the
|
|
136
|
+
direct profile's (a shell started by --register has no home yet)."""
|
|
137
|
+
from .grpc_client import _active_home_name
|
|
138
|
+
|
|
139
|
+
return home_name or _active_home_name()
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _routable(token):
|
|
143
|
+
"""The token as a driver takes it: @<this lab>, so it is never sent elsewhere."""
|
|
144
|
+
lab = _token_lab()
|
|
145
|
+
return f"{token}@{lab}" if lab else f"{token}"
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _name_and_handle(name):
|
|
149
|
+
"""A listed device name -> (name, handle). The handle is the name keyed,
|
|
150
|
+
'Pluto SDR' -> 'Pluto_SDR' (None when the lab can't address it), unless a
|
|
151
|
+
1.2.0 lab tags it: 'Pluto SDR [PlutoA]'."""
|
|
152
|
+
m = _HANDLE_TAG.match(name)
|
|
153
|
+
if m:
|
|
154
|
+
return m["name"], m["handle"]
|
|
155
|
+
h = _key(name)
|
|
156
|
+
# The lab's is_device_handle (core 0.19.0; this client pins 0.16.0).
|
|
157
|
+
ok = h and not h.startswith(("rfg_", "bridge-")) and not any(c in "@:" or c.isspace() or not c.isprintable() for c in h)
|
|
158
|
+
return name, h if ok else None
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _print_reserved(where, when, token, handle, logged_in_to, install, no_handle=_NO_HANDLE):
|
|
162
|
+
"""The token and, when the device has one, its handle (both @<deployment>).
|
|
163
|
+
Printed before the driver install, which can fail or stall; the usage
|
|
164
|
+
lines follow it, once the device type is known. `no_handle`: why no
|
|
165
|
+
handle is shown."""
|
|
166
|
+
printf("Reservation successful on ", (Sty.BOLD, Sty.GREEN), where, Sty.MAGENTA, " for ", Sty.DEFAULT, f"{when}.", Sty.CYAN)
|
|
167
|
+
printf("", Sty.DEFAULT)
|
|
168
|
+
width = len("Device handle") if handle else len("Token")
|
|
169
|
+
printf(f" {'Token':<{width}} ", Sty.BOLD, token, (Sty.BOLD, Sty.GREEN))
|
|
170
|
+
if handle:
|
|
171
|
+
printf(" Device handle ", Sty.BOLD, handle, (Sty.BOLD, Sty.GREEN))
|
|
172
|
+
printf("", Sty.DEFAULT)
|
|
173
|
+
if handle:
|
|
174
|
+
printf(" Token works anywhere, no login needed. Keep it safe: it can't be shown again.", Sty.DEFAULT)
|
|
175
|
+
printf(f" Handle works while you're logged in to {logged_in_to} and this reservation is active.", Sty.DEFAULT)
|
|
176
|
+
else:
|
|
177
|
+
printf(" Works anywhere, no login needed. Keep it safe: it can't be shown again.", Sty.DEFAULT)
|
|
178
|
+
printf(f" ({no_handle}. Lost the token? Cancel and reserve again.)", Sty.DEFAULT)
|
|
179
|
+
printf("", Sty.DEFAULT)
|
|
180
|
+
call = DRIVER_CALLS.get(install())
|
|
181
|
+
if call:
|
|
182
|
+
printf("Use either one as the token:" if handle else "Use it as the token:", Sty.DEFAULT)
|
|
183
|
+
for text in (token, handle) if handle else (token,):
|
|
184
|
+
printf(f' sdr = {call}(token="{text}")', Sty.CYAN)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _print_native_reserved(device_id, label, when, token):
|
|
188
|
+
"""`label` as the server lists the device: 'Pluto SDR [PlutoA] (online)',
|
|
189
|
+
or a partner lab's 'Pluto SDR @mit (online)' (partner handles: none yet)."""
|
|
190
|
+
partner = _PARTNER_LABEL.match(label)
|
|
191
|
+
local = None if partner else _LOCAL_LABEL.match(label)
|
|
192
|
+
name, handle = (partner["name"], None) if partner else _name_and_handle(local["name"] if local else label)
|
|
193
|
+
home = _token_lab()
|
|
194
|
+
lab = partner["lab"] if partner else home
|
|
195
|
+
name = handle or name or f"device {device_id}"
|
|
196
|
+
_print_reserved(f"{name} @{lab}" if lab else name, when, _routable(token),
|
|
197
|
+
f"{handle}@{home}" if handle and home else None, home,
|
|
198
|
+
lambda: account.install_reserved_driver(token, int(device_id)),
|
|
199
|
+
_UNROUTABLE.format(handle, "this lab has no saved name") if handle else _NO_HANDLE)
|
|
200
|
+
|
|
126
201
|
|
|
127
202
|
def _print_connected(name, home, route):
|
|
128
203
|
"""One line, same voice as the banner: what we reached and how."""
|
|
@@ -450,6 +525,12 @@ def _render_destinations(entries):
|
|
|
450
525
|
print()
|
|
451
526
|
|
|
452
527
|
|
|
528
|
+
def _handle_tag(dev):
|
|
529
|
+
"""' [handle]' only when it isn't just the name."""
|
|
530
|
+
h = dev.get("handle")
|
|
531
|
+
return f" [{h}]" if h and _key(dev.get("display_name") or "") != _key(h) else ""
|
|
532
|
+
|
|
533
|
+
|
|
453
534
|
def _federated_devices():
|
|
454
535
|
data = account.get_devices()
|
|
455
536
|
_render_destinations(data["destinations"])
|
|
@@ -466,7 +547,7 @@ def _federated_devices():
|
|
|
466
547
|
deployment = dev["deployment_name"]
|
|
467
548
|
printf(f" @{deployment}", (Sty.BOLD, Sty.MAGENTA))
|
|
468
549
|
state = "online" if dev["online"] else "offline"
|
|
469
|
-
printf(" Device Name: ", Sty.GRAY, f"{dev['display_name']}", Sty.DEFAULT, f" ({dev['device_type'] or 'unknown'}, {state})", Sty.GRAY)
|
|
550
|
+
printf(" Device Name: ", Sty.GRAY, f"{dev['display_name']}{_handle_tag(dev)}", Sty.DEFAULT, f" ({dev['device_type'] or 'unknown'}, {state})", Sty.GRAY)
|
|
470
551
|
if data.get("incomplete"):
|
|
471
552
|
printf(f"{data['omitted_count']} more deployment(s) were not queried.", Sty.WARNING)
|
|
472
553
|
return data
|
|
@@ -546,12 +627,38 @@ def _federated_reservations_for_range(start_day, end_day):
|
|
|
546
627
|
return res_dict
|
|
547
628
|
|
|
548
629
|
|
|
549
|
-
def
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
630
|
+
def _global_slug(device_id, deployment_name):
|
|
631
|
+
"""The lab's name when it is its own Global slug (checked by discovery),
|
|
632
|
+
else None: then the bare token, which works as is."""
|
|
633
|
+
from .grpc_client import _deployment_id
|
|
634
|
+
from ..deployment.homes import NAME_RE
|
|
635
|
+
|
|
636
|
+
name = str(deployment_name or "").lower()
|
|
637
|
+
try:
|
|
638
|
+
if NAME_RE.match(name) and _deployment_id(name) == str(device_id).split(":")[0]:
|
|
639
|
+
return name
|
|
640
|
+
except RuntimeError:
|
|
641
|
+
pass
|
|
642
|
+
return None
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
def _federated_reserve_slot(device_id, chosen_day, chosen_slot, slot_start_str, slot_end_str,
|
|
646
|
+
deployment_name=None, device_name=None, device_handle=None):
|
|
647
|
+
token = account.reserve_device(device_id, chosen_slot[0], chosen_slot[1])
|
|
648
|
+
when = f"{chosen_day.strftime('%Y-%m-%d')} {slot_start_str}-{slot_end_str}"
|
|
649
|
+
install = lambda: account.install_reserved_driver(token, device_id)
|
|
650
|
+
if not token.startswith("rfg_"):
|
|
651
|
+
# An older Global: the HOME reference goes where a token goes.
|
|
652
|
+
printf("Reservation successful on device ", (Sty.BOLD, Sty.GREEN), f"{str(device_id).rsplit(':', 1)[-1]}", Sty.MAGENTA, " for ", Sty.DEFAULT, f"{when}.", Sty.CYAN)
|
|
653
|
+
printf("Your Token -> ", Sty.BOLD, token, (Sty.BOLD, Sty.GREEN))
|
|
654
|
+
install()
|
|
655
|
+
return
|
|
656
|
+
slug = _global_slug(device_id, deployment_name)
|
|
657
|
+
name = device_handle or device_name or str(device_id).rsplit(":", 1)[-1]
|
|
658
|
+
lab = slug or deployment_name
|
|
659
|
+
_print_reserved(f"{name} @{lab}" if lab else name, when, f"{token}@{slug}" if slug else token,
|
|
660
|
+
f"{device_handle}@{slug}" if device_handle and slug else None, "RemoteRF Global", install,
|
|
661
|
+
_UNROUTABLE.format(device_handle, "this lab's Global name is unknown") if device_handle else _NO_HANDLE)
|
|
555
662
|
|
|
556
663
|
|
|
557
664
|
def _federated_reserve():
|
|
@@ -572,7 +679,7 @@ def _federated_reserve():
|
|
|
572
679
|
printf(
|
|
573
680
|
f"{idx}.", Sty.CYAN,
|
|
574
681
|
" Name: ", Sty.DEFAULT,
|
|
575
|
-
f"{dev['display_name']}", Sty.GRAY,
|
|
682
|
+
f"{dev['display_name']}{_handle_tag(dev)}", Sty.GRAY,
|
|
576
683
|
" Duration: ", Sty.DEFAULT,
|
|
577
684
|
f"{dev['block_min']} min", Sty.GREEN,
|
|
578
685
|
)
|
|
@@ -594,7 +701,9 @@ def _federated_reserve():
|
|
|
594
701
|
f" is < 10 minutes (max_sec={dev['max_reservation_time_sec'] or 0}). Cannot create valid reservations.", Sty.DEFAULT,
|
|
595
702
|
)
|
|
596
703
|
return
|
|
597
|
-
|
|
704
|
+
reserve_slot = functools.partial(_federated_reserve_slot, deployment_name=dev["deployment_name"],
|
|
705
|
+
device_name=dev["display_name"], device_handle=dev.get("handle"))
|
|
706
|
+
_pick_slot_and_reserve(dev["device_id"], dev["display_name"], dev["block_min"], _federated_reservations_for_range, reserve_slot,
|
|
598
707
|
device_label=f"{dev['display_name']} @{dev['deployment_name']}",
|
|
599
708
|
max_days=-(-int(dev.get("max_horizon_sec") or 0) // 86400) or None)
|
|
600
709
|
|
|
@@ -747,7 +856,8 @@ def cancel_my_reservation():
|
|
|
747
856
|
print("Aborting. A non integer key was given.")
|
|
748
857
|
|
|
749
858
|
_PARTNER_LABEL = re.compile(r"^(?P<name>.*) @(?P<lab>.+?) \((?P<state>[^)]*)\)$")
|
|
750
|
-
|
|
859
|
+
# The state always starts with online/offline; a PoE fault after it can hold brackets.
|
|
860
|
+
_LOCAL_LABEL = re.compile(r"^(?P<name>.*?) \((?P<state>(?:online|offline)\b.*)\)$")
|
|
751
861
|
|
|
752
862
|
|
|
753
863
|
def _native_device_rows():
|
|
@@ -808,7 +918,7 @@ def _print_native_devices(rows, *, numbered=False):
|
|
|
808
918
|
if row["lab"] != lab:
|
|
809
919
|
lab = row["lab"]
|
|
810
920
|
printf(f" @{lab}", (Sty.BOLD, Sty.MAGENTA))
|
|
811
|
-
prefix = (f"{index}.", Sty.CYAN
|
|
921
|
+
prefix = (f"{index}. ", Sty.CYAN) if numbered else ()
|
|
812
922
|
printf(*prefix, " Device Name: " if not numbered else " Device Name: ", Sty.GRAY,
|
|
813
923
|
row["name"], Sty.DEFAULT, f" ({row['state']})" if row["state"] else "", Sty.GRAY)
|
|
814
924
|
|
|
@@ -852,11 +962,18 @@ def reserve():
|
|
|
852
962
|
if not pick.isdigit() or not 1 <= int(pick) <= len(rows):
|
|
853
963
|
printf("Pick a number from the list.", Sty.WARNING)
|
|
854
964
|
return
|
|
855
|
-
|
|
965
|
+
row = rows[int(pick) - 1]
|
|
966
|
+
start, end = get_datetime("Reserve Start Time"), get_datetime("Reserve End Time")
|
|
967
|
+
token = account.reserve_device(row["id"], start, end)
|
|
856
968
|
if token != '':
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
969
|
+
name, handle = (row["name"], None) if row["away"] else _name_and_handle(row["name"])
|
|
970
|
+
home = _token_lab()
|
|
971
|
+
lab = row["lab"] if row["away"] else home or row["lab"]
|
|
972
|
+
_print_reserved(f"{handle or name} @{lab}" if lab else handle or name,
|
|
973
|
+
f"{start:%Y-%m-%d %I:%M %p}-{end:%I:%M %p}", _routable(token),
|
|
974
|
+
f"{handle}@{home}" if handle and home else None, home,
|
|
975
|
+
lambda: account.install_reserved_driver(token, row["id"]),
|
|
976
|
+
_UNROUTABLE.format(handle, "this lab has no saved name") if handle else _NO_HANDLE)
|
|
860
977
|
except Exception as e:
|
|
861
978
|
printf(f"Error: {e}", Sty.BRIGHT_RED)
|
|
862
979
|
|
|
@@ -1293,11 +1410,9 @@ def interactive_reserve_next_days(block_minutes=60):
|
|
|
1293
1410
|
# --- 6) Reserve the chosen slot on the chosen device ---
|
|
1294
1411
|
token = account.reserve_device(int(chosen_device_id), chosen_slot[0], chosen_slot[1])
|
|
1295
1412
|
if token:
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
account.install_reserved_driver(token, int(chosen_device_id))
|
|
1300
|
-
|
|
1413
|
+
_print_native_reserved(chosen_device_id, str(unmap_arg(data.results[chosen_device_id])),
|
|
1414
|
+
f"{chosen_day.strftime('%Y-%m-%d')} {slot_start_str}-{slot_end_str}", token)
|
|
1415
|
+
|
|
1301
1416
|
except Exception as e:
|
|
1302
1417
|
print(f"Error: {e}")
|
|
1303
1418
|
|
|
@@ -1413,7 +1528,7 @@ def _pick_slot_and_reserve(chosen_device_id, chosen_device_name, block_minutes,
|
|
|
1413
1528
|
slot_end_str = chosen_slot[1].strftime("%I:%M %p")
|
|
1414
1529
|
|
|
1415
1530
|
confirmation = session.prompt(stylize(
|
|
1416
|
-
"
|
|
1531
|
+
"Reserve ", Sty.DEFAULT,
|
|
1417
1532
|
f"{chosen_day.strftime('%Y-%m-%d')}", (Sty.BOLD, Sty.BLUE),
|
|
1418
1533
|
" from ", Sty.DEFAULT,
|
|
1419
1534
|
f"{slot_start_str}", Sty.CYAN,
|
|
@@ -1421,7 +1536,7 @@ def _pick_slot_and_reserve(chosen_device_id, chosen_device_name, block_minutes,
|
|
|
1421
1536
|
f"{slot_end_str}", Sty.CYAN,
|
|
1422
1537
|
" for ", Sty.DEFAULT,
|
|
1423
1538
|
f"{chosen_device_name}", Sty.GRAY,
|
|
1424
|
-
"
|
|
1539
|
+
"?\nConfirm reservation? (y/n): ", (Sty.BOLD, Sty.GREEN),
|
|
1425
1540
|
)).strip().lower()
|
|
1426
1541
|
|
|
1427
1542
|
if confirmation != "y":
|
|
@@ -1434,13 +1549,11 @@ def _pick_slot_and_reserve(chosen_device_id, chosen_device_name, block_minutes,
|
|
|
1434
1549
|
do_reserve(chosen_device_id, chosen_day, chosen_slot, slot_start_str, slot_end_str)
|
|
1435
1550
|
|
|
1436
1551
|
|
|
1437
|
-
def _native_reserve_slot(chosen_device_id, chosen_day, chosen_slot, slot_start_str, slot_end_str):
|
|
1552
|
+
def _native_reserve_slot(chosen_device_id, chosen_day, chosen_slot, slot_start_str, slot_end_str, device_label=""):
|
|
1438
1553
|
token = account.reserve_device(int(chosen_device_id), chosen_slot[0], chosen_slot[1])
|
|
1439
1554
|
if token:
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
printf("Please keep this token safe, as it is not saved on the server and cannot be retrieved again. If you lose it, cancel your reservation and make a new one.", Sty.DEFAULT)
|
|
1443
|
-
account.install_reserved_driver(token, int(chosen_device_id))
|
|
1555
|
+
_print_native_reserved(chosen_device_id, device_label,
|
|
1556
|
+
f"{chosen_day.strftime('%Y-%m-%d')} {slot_start_str}-{slot_end_str}", token)
|
|
1444
1557
|
|
|
1445
1558
|
|
|
1446
1559
|
def interactive_reserve_next_days_auto():
|
|
@@ -1578,19 +1691,23 @@ def interactive_reserve_next_days_auto():
|
|
|
1578
1691
|
|
|
1579
1692
|
chosen_device_key = sorted_device_ids[sel_i]
|
|
1580
1693
|
chosen_device_id = str(chosen_device_key)
|
|
1581
|
-
chosen_device_name = unmap_arg(dev_resp.results[chosen_device_key])
|
|
1694
|
+
chosen_device_name = str(unmap_arg(dev_resp.results[chosen_device_key]))
|
|
1695
|
+
# 'Pluto SDR [PlutoA] (online)' -> 'Pluto SDR [PlutoA]', 'Pluto SDR @mit (online)' -> 'Pluto SDR @mit'
|
|
1696
|
+
shown = _LOCAL_LABEL.match(chosen_device_name)
|
|
1697
|
+
shown = shown["name"] if shown else chosen_device_name
|
|
1582
1698
|
block_minutes = int(block_by_dev.get(chosen_device_id, 30))
|
|
1583
1699
|
|
|
1584
1700
|
if block_minutes < 10:
|
|
1585
1701
|
printf(
|
|
1586
1702
|
"Your max reservation duration for device ", Sty.DEFAULT,
|
|
1587
|
-
|
|
1703
|
+
shown, Sty.MAGENTA,
|
|
1588
1704
|
f" is < 10 minutes (max_sec={max_by_dev.get(chosen_device_id, 0)}). Cannot create valid reservations.", Sty.DEFAULT,
|
|
1589
1705
|
)
|
|
1590
1706
|
return
|
|
1591
1707
|
|
|
1592
|
-
_pick_slot_and_reserve(chosen_device_id,
|
|
1593
|
-
|
|
1708
|
+
_pick_slot_and_reserve(chosen_device_id, shown, block_minutes, fetch_reservations_for_range,
|
|
1709
|
+
functools.partial(_native_reserve_slot, device_label=chosen_device_name),
|
|
1710
|
+
device_label=shown, max_days=days_by_dev.get(chosen_device_id) or None)
|
|
1594
1711
|
|
|
1595
1712
|
except Exception as e:
|
|
1596
1713
|
print(f"Error: {e}")
|
|
@@ -82,8 +82,9 @@ class _DevicePlaneStub:
|
|
|
82
82
|
call on that token (the direct path to the owner when it is up, else the
|
|
83
83
|
HOME, which relays it). The owner runs it through its own servicers."""
|
|
84
84
|
|
|
85
|
-
def __init__(self, token: str):
|
|
85
|
+
def __init__(self, token: str, label: str = "partner device: "):
|
|
86
86
|
self._token = str(token)
|
|
87
|
+
self._label = label
|
|
87
88
|
|
|
88
89
|
def _rpc(self, operation, request, response_type):
|
|
89
90
|
from ..common.utils import map_arg, unmap_arg
|
|
@@ -97,7 +98,7 @@ class _DevicePlaneStub:
|
|
|
97
98
|
except RuntimeError as exc:
|
|
98
99
|
# A refusal the HOME or the owner carried in "a": no live
|
|
99
100
|
# reservation, a revoked agreement, a device no longer visible.
|
|
100
|
-
raise RemoteRFReservationError(f"
|
|
101
|
+
raise RemoteRFReservationError(f"{self._label}{exc}") from exc
|
|
101
102
|
wire = unmap_arg(response.results["wire"]) if "wire" in response.results else None
|
|
102
103
|
if not isinstance(wire, bytes):
|
|
103
104
|
raise RemoteRFProtocolError("partner device plane returned no Dynamic v2 reply")
|
|
@@ -131,6 +132,7 @@ class DynamicV2Transport:
|
|
|
131
132
|
*,
|
|
132
133
|
control_stub=None,
|
|
133
134
|
sample_stub=None,
|
|
135
|
+
token=None,
|
|
134
136
|
control_timeout_sec: float = 30.0,
|
|
135
137
|
open_timeout_sec: float = 120.0,
|
|
136
138
|
stream_timeout_margin_sec: float = 5.0,
|
|
@@ -156,11 +158,18 @@ class DynamicV2Transport:
|
|
|
156
158
|
)
|
|
157
159
|
self._stream_poisoned = False
|
|
158
160
|
self._stream_failure_details = {}
|
|
161
|
+
self._renewed = False
|
|
159
162
|
if control_stub is None or sample_stub is None:
|
|
160
|
-
from .grpc_client import get_active_channel
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
163
|
+
from .grpc_client import device_route, get_active_channel
|
|
164
|
+
route = device_route(token) if token is not None else None
|
|
165
|
+
if route is not None and route.kind == "global":
|
|
166
|
+
# No device channel through Global: each request is a relayed RPC.
|
|
167
|
+
plane = _DevicePlaneStub(token, label="")
|
|
168
|
+
control_stub, sample_stub = control_stub or plane, sample_stub or plane
|
|
169
|
+
else:
|
|
170
|
+
channel = route.connection.channel if route and route.connection else get_active_channel()
|
|
171
|
+
control_stub = control_stub or grpc_pb2_grpc.DynamicControlV2Stub(channel)
|
|
172
|
+
sample_stub = sample_stub or grpc_pb2_grpc.SampleDataV1Stub(channel)
|
|
164
173
|
self.control = control_stub
|
|
165
174
|
self.samples = sample_stub
|
|
166
175
|
|
|
@@ -168,17 +177,26 @@ class DynamicV2Transport:
|
|
|
168
177
|
"""Carry this transport over the device plane when ``token`` drives a
|
|
169
178
|
partner lab's device (named by the HOME's refusal, or already known)."""
|
|
170
179
|
from . import direct_path
|
|
180
|
+
from .grpc_client import wire_token
|
|
171
181
|
|
|
172
182
|
if isinstance(self.control, _DevicePlaneStub):
|
|
173
183
|
return False
|
|
174
|
-
owner = owner or direct_path.partner_of(str(token))
|
|
184
|
+
owner = owner or direct_path.partner_of(wire_token(str(token)))
|
|
175
185
|
if not owner:
|
|
176
186
|
return False
|
|
177
187
|
# Marked, the token's device calls take the direct path to the owner.
|
|
178
|
-
direct_path.mark_partner(str(token), str(owner))
|
|
188
|
+
direct_path.mark_partner(wire_token(str(token)), str(owner))
|
|
179
189
|
self.control = self.samples = _DevicePlaneStub(token)
|
|
180
190
|
return True
|
|
181
191
|
|
|
192
|
+
def _inner_token(self, token: str) -> str:
|
|
193
|
+
# Over the device plane the owner substitutes its own bridge token, so
|
|
194
|
+
# no bearer secret ever rides inside a frame.
|
|
195
|
+
if isinstance(self.control, _DevicePlaneStub):
|
|
196
|
+
return ""
|
|
197
|
+
from .grpc_client import wire_token
|
|
198
|
+
return wire_token(str(token))
|
|
199
|
+
|
|
182
200
|
def _checked(self, response, token: str) -> bool:
|
|
183
201
|
"""raise_for_envelope, except for the refusal of a partner's token:
|
|
184
202
|
True when the call should be repeated over the device plane."""
|
|
@@ -188,6 +206,14 @@ class DynamicV2Transport:
|
|
|
188
206
|
if self._to_owner(token, exc.details.get("federated")):
|
|
189
207
|
return True
|
|
190
208
|
raise
|
|
209
|
+
except RemoteRFReservationError as exc:
|
|
210
|
+
# A dead bridge token behind x@lab: ask the lab again, once.
|
|
211
|
+
from .grpc_client import device_route, renewed_route
|
|
212
|
+
|
|
213
|
+
if self._renewed or renewed_route(str(token), device_route(str(token)), str(exc)) is None:
|
|
214
|
+
raise
|
|
215
|
+
self._renewed = True
|
|
216
|
+
return True
|
|
191
217
|
return False
|
|
192
218
|
|
|
193
219
|
def _call(self, fn, request, *, timeout: float | None = None):
|
|
@@ -229,7 +255,7 @@ class DynamicV2Transport:
|
|
|
229
255
|
response = self._call(
|
|
230
256
|
self.control.GetSchema,
|
|
231
257
|
grpc_pb2.GetSchemaRequest(
|
|
232
|
-
token=
|
|
258
|
+
token=self._inner_token(token),
|
|
233
259
|
schema_versions=[SCHEMA_VERSION],
|
|
234
260
|
),
|
|
235
261
|
)
|
|
@@ -254,7 +280,7 @@ class DynamicV2Transport:
|
|
|
254
280
|
response = self._call(
|
|
255
281
|
self.control.OpenSession,
|
|
256
282
|
grpc_pb2.OpenSessionRequest(
|
|
257
|
-
token=
|
|
283
|
+
token=self._inner_token(token),
|
|
258
284
|
requested_schema_hash=str(schema_hash),
|
|
259
285
|
control_protocol_version=CONTROL_PROTOCOL_VERSION,
|
|
260
286
|
streaming_protocol_version=STREAMING_PROTOCOL_VERSION,
|
|
@@ -101,8 +101,9 @@ class RemoteRFAccount:
|
|
|
101
101
|
if self.is_https_home:
|
|
102
102
|
with _busy("Reserving…"):
|
|
103
103
|
reservation = self.backend.reserve(device_id, int(start_time.timestamp()), int(end_time.timestamp()))
|
|
104
|
-
#
|
|
105
|
-
|
|
104
|
+
# Global's device API token (rfg_...), else (an older Global) the
|
|
105
|
+
# HOME reference, which also drives: adi.Pluto("<deployment>:<id>")
|
|
106
|
+
return reservation.get('token') or reservation['device_id']
|
|
106
107
|
username, credential_secret = self._rpc_credentials()
|
|
107
108
|
with _busy("Reserving…"):
|
|
108
109
|
response = self._call(function_name="ACC:reserve_device", args={"un":map_arg(username), "pw":map_arg(credential_secret), "dd":map_arg(device_id), "st":map_arg(int(start_time.timestamp())), "et":map_arg(int(end_time.timestamp()))})
|
|
@@ -114,13 +115,14 @@ class RemoteRFAccount:
|
|
|
114
115
|
|
|
115
116
|
def install_reserved_driver(self, handle, device_id):
|
|
116
117
|
"""Separate from reserve_device: the token is shown before this network
|
|
117
|
-
step runs, so a failed or stuck driver fetch cannot lose it.
|
|
118
|
+
step runs, so a failed or stuck driver fetch cannot lose it. Returns
|
|
119
|
+
the installed device type, or None."""
|
|
118
120
|
try:
|
|
119
121
|
from ..drivers.dynamic_device import install_driver
|
|
120
122
|
if self.is_https_home:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
123
|
+
# By the ref, which needs no live slot (an rfg_ token does).
|
|
124
|
+
return install_driver(device_id=device_id).name
|
|
125
|
+
return install_driver(token=handle, device_id=device_id).name
|
|
124
126
|
except Exception as e:
|
|
125
127
|
print(f"Warning: could not install driver for device {device_id}: {e}")
|
|
126
128
|
|