RemoteRF 2.2.5__tar.gz → 2.3.0__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.0}/PKG-INFO +1 -1
- {remoterf-2.2.5 → remoterf-2.3.0}/pyproject.toml +1 -1
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/core/app.py +139 -31
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/core/dynamic_v2_transport.py +36 -10
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/core/grpc_acc.py +8 -6
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/core/grpc_client.py +252 -6
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/deployment/backend.py +19 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/deployment/http.py +2 -1
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/drivers/dynamic_device.py +5 -3
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/drivers/dynamic_v2.py +6 -3
- {remoterf-2.2.5 → remoterf-2.3.0/src/remoterf.egg-info}/PKG-INFO +1 -1
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf.egg-info/SOURCES.txt +1 -0
- remoterf-2.3.0/tests/test_device_routes.py +661 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/tests/test_native_devices.py +7 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/LICENSE +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/README.md +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/setup.cfg +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/common/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/common/grpc/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/common/grpc/deployment_capabilities.proto +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/common/grpc/deployment_capabilities_pb2.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/common/grpc/deployment_capabilities_pb2_grpc.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/common/grpc/grpc.proto +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/common/grpc/grpc_pb2.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/common/grpc/grpc_pb2_grpc.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/common/grpc/v2_codec.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/common/tos_notice.txt +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/common/utils/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/common/utils/ansi_codes.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/common/utils/api_token.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/common/utils/banner.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/common/utils/list_string.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/common/utils/process_arg.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/config/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/config/cert_fetcher.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/config/config.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/core/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/core/acc_login.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/core/banner_note.txt +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/core/direct_path.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/core/secure_channel.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/core/v2_errors.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/deployment/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/deployment/direct.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/deployment/homes.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/deployment/state.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/drivers/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/drivers/_virtual.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/drivers/adalm_pluto/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/drivers/adalm_pluto/adalm_pluto_remote.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/drivers/hackrf/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/drivers/hackrf/hackrf_remote.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/drivers/rtl_sdr/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/drivers/rtl_sdr/rtl_sdr_remote.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/drivers/sim_sdr/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/drivers/sim_sdr/sim_sdr_remote.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/drivers/support/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/drivers/support/ti_mmwave.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/drivers/support/uhd.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/drivers/support/uhd_dsp.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/drivers/support/uhd_v2.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/drivers/ti_mmwave/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/drivers/ti_mmwave/ti_mmwave_remote.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/drivers/usrp/__init__.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/drivers/usrp/usrp_remote.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/drivers/usrp/usrp_remote.pyi +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/remoterf_cli.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf/version.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf.egg-info/dependency_links.txt +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf.egg-info/entry_points.txt +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf.egg-info/requires.txt +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf.egg-info/top_level.txt +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/src/remoterf.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/tests/test_banner.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/tests/test_cli_login_fresh.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/tests/test_config_address.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/tests/test_deployment_accounts.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/tests/test_direct_path_fixed.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/tests/test_direct_path_partner.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/tests/test_direct_path_race.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/tests/test_dynamic_device_codegen.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/tests/test_dynamic_v2.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/tests/test_federated_devices.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/tests/test_fetch_idl_fallback.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/tests/test_finish_prompt.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/tests/test_gate_i_direct_path.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/tests/test_grpc_client_target.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/tests/test_homes.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/tests/test_native_account_credentials.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/tests/test_partner_dynamic_v2.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/tests/test_pluto_virtual.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/tests/test_process_arg.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/tests/test_remoterf_cli.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/tests/test_ti_mmwave.py +0 -0
- {remoterf-2.2.5 → remoterf-2.3.0}/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.0
|
|
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.0"
|
|
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"
|
|
@@ -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,73 @@ 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, 'Pluto SDR [PlutoA]' -> ('Pluto SDR', 'PlutoA')."""
|
|
150
|
+
m = _HANDLE_TAG.match(name)
|
|
151
|
+
return (m["name"], m["handle"]) if m else (name, None)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _print_reserved(where, when, token, handle, logged_in_to, install, no_handle=_NO_HANDLE):
|
|
155
|
+
"""The token and, when the device has one, its handle (both @<deployment>).
|
|
156
|
+
Printed before the driver install, which can fail or stall; the usage
|
|
157
|
+
lines follow it, once the device type is known. `no_handle`: why no
|
|
158
|
+
handle is shown."""
|
|
159
|
+
printf("Reservation successful on ", (Sty.BOLD, Sty.GREEN), where, Sty.MAGENTA, " for ", Sty.DEFAULT, f"{when}.", Sty.CYAN)
|
|
160
|
+
printf("", Sty.DEFAULT)
|
|
161
|
+
width = len("Device handle") if handle else len("Token")
|
|
162
|
+
printf(f" {'Token':<{width}} ", Sty.BOLD, token, (Sty.BOLD, Sty.GREEN))
|
|
163
|
+
if handle:
|
|
164
|
+
printf(" Device handle ", Sty.BOLD, handle, (Sty.BOLD, Sty.GREEN))
|
|
165
|
+
printf("", Sty.DEFAULT)
|
|
166
|
+
if handle:
|
|
167
|
+
printf(" Token works anywhere, no login needed. Keep it safe: it can't be shown again.", Sty.DEFAULT)
|
|
168
|
+
printf(f" Handle works while you're logged in to {logged_in_to} and this reservation is active.", Sty.DEFAULT)
|
|
169
|
+
else:
|
|
170
|
+
printf(" Works anywhere, no login needed. Keep it safe: it can't be shown again.", Sty.DEFAULT)
|
|
171
|
+
printf(f" ({no_handle}. Lost the token? Cancel and reserve again.)", Sty.DEFAULT)
|
|
172
|
+
printf("", Sty.DEFAULT)
|
|
173
|
+
call = DRIVER_CALLS.get(install())
|
|
174
|
+
if call:
|
|
175
|
+
printf("Use either one as the token:" if handle else "Use it as the token:", Sty.DEFAULT)
|
|
176
|
+
for text in (token, handle) if handle else (token,):
|
|
177
|
+
printf(f' sdr = {call}(token="{text}")', Sty.CYAN)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _print_native_reserved(device_id, label, when, token):
|
|
181
|
+
"""`label` as the server lists the device: 'Pluto SDR [PlutoA] (online)',
|
|
182
|
+
or a partner lab's 'Pluto SDR @mit (online)' (partner handles: none yet)."""
|
|
183
|
+
partner = _PARTNER_LABEL.match(label)
|
|
184
|
+
local = None if partner else _LOCAL_LABEL.match(label)
|
|
185
|
+
name, handle = (partner["name"], None) if partner else _name_and_handle(local["name"] if local else label)
|
|
186
|
+
home = _token_lab()
|
|
187
|
+
lab = partner["lab"] if partner else home
|
|
188
|
+
name = handle or name or f"device {device_id}"
|
|
189
|
+
_print_reserved(f"{name} @{lab}" if lab else name, when, _routable(token),
|
|
190
|
+
f"{handle}@{home}" if handle and home else None, home,
|
|
191
|
+
lambda: account.install_reserved_driver(token, int(device_id)),
|
|
192
|
+
_UNROUTABLE.format(handle, "this lab has no saved name") if handle else _NO_HANDLE)
|
|
193
|
+
|
|
126
194
|
|
|
127
195
|
def _print_connected(name, home, route):
|
|
128
196
|
"""One line, same voice as the banner: what we reached and how."""
|
|
@@ -450,6 +518,10 @@ def _render_destinations(entries):
|
|
|
450
518
|
print()
|
|
451
519
|
|
|
452
520
|
|
|
521
|
+
def _handle_tag(dev):
|
|
522
|
+
return f" [{dev['handle']}]" if dev.get("handle") else ""
|
|
523
|
+
|
|
524
|
+
|
|
453
525
|
def _federated_devices():
|
|
454
526
|
data = account.get_devices()
|
|
455
527
|
_render_destinations(data["destinations"])
|
|
@@ -466,7 +538,7 @@ def _federated_devices():
|
|
|
466
538
|
deployment = dev["deployment_name"]
|
|
467
539
|
printf(f" @{deployment}", (Sty.BOLD, Sty.MAGENTA))
|
|
468
540
|
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)
|
|
541
|
+
printf(" Device Name: ", Sty.GRAY, f"{dev['display_name']}{_handle_tag(dev)}", Sty.DEFAULT, f" ({dev['device_type'] or 'unknown'}, {state})", Sty.GRAY)
|
|
470
542
|
if data.get("incomplete"):
|
|
471
543
|
printf(f"{data['omitted_count']} more deployment(s) were not queried.", Sty.WARNING)
|
|
472
544
|
return data
|
|
@@ -546,12 +618,38 @@ def _federated_reservations_for_range(start_day, end_day):
|
|
|
546
618
|
return res_dict
|
|
547
619
|
|
|
548
620
|
|
|
549
|
-
def
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
621
|
+
def _global_slug(device_id, deployment_name):
|
|
622
|
+
"""The lab's name when it is its own Global slug (checked by discovery),
|
|
623
|
+
else None: then the bare token, which works as is."""
|
|
624
|
+
from .grpc_client import _deployment_id
|
|
625
|
+
from ..deployment.homes import NAME_RE
|
|
626
|
+
|
|
627
|
+
name = str(deployment_name or "").lower()
|
|
628
|
+
try:
|
|
629
|
+
if NAME_RE.match(name) and _deployment_id(name) == str(device_id).split(":")[0]:
|
|
630
|
+
return name
|
|
631
|
+
except RuntimeError:
|
|
632
|
+
pass
|
|
633
|
+
return None
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
def _federated_reserve_slot(device_id, chosen_day, chosen_slot, slot_start_str, slot_end_str,
|
|
637
|
+
deployment_name=None, device_name=None, device_handle=None):
|
|
638
|
+
token = account.reserve_device(device_id, chosen_slot[0], chosen_slot[1])
|
|
639
|
+
when = f"{chosen_day.strftime('%Y-%m-%d')} {slot_start_str}-{slot_end_str}"
|
|
640
|
+
install = lambda: account.install_reserved_driver(token, device_id)
|
|
641
|
+
if not token.startswith("rfg_"):
|
|
642
|
+
# An older Global: the HOME reference goes where a token goes.
|
|
643
|
+
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)
|
|
644
|
+
printf("Your Token -> ", Sty.BOLD, token, (Sty.BOLD, Sty.GREEN))
|
|
645
|
+
install()
|
|
646
|
+
return
|
|
647
|
+
slug = _global_slug(device_id, deployment_name)
|
|
648
|
+
name = device_handle or device_name or str(device_id).rsplit(":", 1)[-1]
|
|
649
|
+
lab = slug or deployment_name
|
|
650
|
+
_print_reserved(f"{name} @{lab}" if lab else name, when, f"{token}@{slug}" if slug else token,
|
|
651
|
+
f"{device_handle}@{slug}" if device_handle and slug else None, "RemoteRF Global", install,
|
|
652
|
+
_UNROUTABLE.format(device_handle, "this lab's Global name is unknown") if device_handle else _NO_HANDLE)
|
|
555
653
|
|
|
556
654
|
|
|
557
655
|
def _federated_reserve():
|
|
@@ -572,7 +670,7 @@ def _federated_reserve():
|
|
|
572
670
|
printf(
|
|
573
671
|
f"{idx}.", Sty.CYAN,
|
|
574
672
|
" Name: ", Sty.DEFAULT,
|
|
575
|
-
f"{dev['display_name']}", Sty.GRAY,
|
|
673
|
+
f"{dev['display_name']}{_handle_tag(dev)}", Sty.GRAY,
|
|
576
674
|
" Duration: ", Sty.DEFAULT,
|
|
577
675
|
f"{dev['block_min']} min", Sty.GREEN,
|
|
578
676
|
)
|
|
@@ -594,7 +692,9 @@ def _federated_reserve():
|
|
|
594
692
|
f" is < 10 minutes (max_sec={dev['max_reservation_time_sec'] or 0}). Cannot create valid reservations.", Sty.DEFAULT,
|
|
595
693
|
)
|
|
596
694
|
return
|
|
597
|
-
|
|
695
|
+
reserve_slot = functools.partial(_federated_reserve_slot, deployment_name=dev["deployment_name"],
|
|
696
|
+
device_name=dev["display_name"], device_handle=dev.get("handle"))
|
|
697
|
+
_pick_slot_and_reserve(dev["device_id"], dev["display_name"], dev["block_min"], _federated_reservations_for_range, reserve_slot,
|
|
598
698
|
device_label=f"{dev['display_name']} @{dev['deployment_name']}",
|
|
599
699
|
max_days=-(-int(dev.get("max_horizon_sec") or 0) // 86400) or None)
|
|
600
700
|
|
|
@@ -747,7 +847,8 @@ def cancel_my_reservation():
|
|
|
747
847
|
print("Aborting. A non integer key was given.")
|
|
748
848
|
|
|
749
849
|
_PARTNER_LABEL = re.compile(r"^(?P<name>.*) @(?P<lab>.+?) \((?P<state>[^)]*)\)$")
|
|
750
|
-
|
|
850
|
+
# The state always starts with online/offline; a PoE fault after it can hold brackets.
|
|
851
|
+
_LOCAL_LABEL = re.compile(r"^(?P<name>.*?) \((?P<state>(?:online|offline)\b.*)\)$")
|
|
751
852
|
|
|
752
853
|
|
|
753
854
|
def _native_device_rows():
|
|
@@ -808,7 +909,7 @@ def _print_native_devices(rows, *, numbered=False):
|
|
|
808
909
|
if row["lab"] != lab:
|
|
809
910
|
lab = row["lab"]
|
|
810
911
|
printf(f" @{lab}", (Sty.BOLD, Sty.MAGENTA))
|
|
811
|
-
prefix = (f"{index}.", Sty.CYAN
|
|
912
|
+
prefix = (f"{index}. ", Sty.CYAN) if numbered else ()
|
|
812
913
|
printf(*prefix, " Device Name: " if not numbered else " Device Name: ", Sty.GRAY,
|
|
813
914
|
row["name"], Sty.DEFAULT, f" ({row['state']})" if row["state"] else "", Sty.GRAY)
|
|
814
915
|
|
|
@@ -852,11 +953,18 @@ def reserve():
|
|
|
852
953
|
if not pick.isdigit() or not 1 <= int(pick) <= len(rows):
|
|
853
954
|
printf("Pick a number from the list.", Sty.WARNING)
|
|
854
955
|
return
|
|
855
|
-
|
|
956
|
+
row = rows[int(pick) - 1]
|
|
957
|
+
start, end = get_datetime("Reserve Start Time"), get_datetime("Reserve End Time")
|
|
958
|
+
token = account.reserve_device(row["id"], start, end)
|
|
856
959
|
if token != '':
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
960
|
+
name, handle = (row["name"], None) if row["away"] else _name_and_handle(row["name"])
|
|
961
|
+
home = _token_lab()
|
|
962
|
+
lab = row["lab"] if row["away"] else home or row["lab"]
|
|
963
|
+
_print_reserved(f"{handle or name} @{lab}" if lab else handle or name,
|
|
964
|
+
f"{start:%Y-%m-%d %I:%M %p}-{end:%I:%M %p}", _routable(token),
|
|
965
|
+
f"{handle}@{home}" if handle and home else None, home,
|
|
966
|
+
lambda: account.install_reserved_driver(token, row["id"]),
|
|
967
|
+
_UNROUTABLE.format(handle, "this lab has no saved name") if handle else _NO_HANDLE)
|
|
860
968
|
except Exception as e:
|
|
861
969
|
printf(f"Error: {e}", Sty.BRIGHT_RED)
|
|
862
970
|
|
|
@@ -1293,11 +1401,9 @@ def interactive_reserve_next_days(block_minutes=60):
|
|
|
1293
1401
|
# --- 6) Reserve the chosen slot on the chosen device ---
|
|
1294
1402
|
token = account.reserve_device(int(chosen_device_id), chosen_slot[0], chosen_slot[1])
|
|
1295
1403
|
if token:
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
account.install_reserved_driver(token, int(chosen_device_id))
|
|
1300
|
-
|
|
1404
|
+
_print_native_reserved(chosen_device_id, str(unmap_arg(data.results[chosen_device_id])),
|
|
1405
|
+
f"{chosen_day.strftime('%Y-%m-%d')} {slot_start_str}-{slot_end_str}", token)
|
|
1406
|
+
|
|
1301
1407
|
except Exception as e:
|
|
1302
1408
|
print(f"Error: {e}")
|
|
1303
1409
|
|
|
@@ -1413,7 +1519,7 @@ def _pick_slot_and_reserve(chosen_device_id, chosen_device_name, block_minutes,
|
|
|
1413
1519
|
slot_end_str = chosen_slot[1].strftime("%I:%M %p")
|
|
1414
1520
|
|
|
1415
1521
|
confirmation = session.prompt(stylize(
|
|
1416
|
-
"
|
|
1522
|
+
"Reserve ", Sty.DEFAULT,
|
|
1417
1523
|
f"{chosen_day.strftime('%Y-%m-%d')}", (Sty.BOLD, Sty.BLUE),
|
|
1418
1524
|
" from ", Sty.DEFAULT,
|
|
1419
1525
|
f"{slot_start_str}", Sty.CYAN,
|
|
@@ -1421,7 +1527,7 @@ def _pick_slot_and_reserve(chosen_device_id, chosen_device_name, block_minutes,
|
|
|
1421
1527
|
f"{slot_end_str}", Sty.CYAN,
|
|
1422
1528
|
" for ", Sty.DEFAULT,
|
|
1423
1529
|
f"{chosen_device_name}", Sty.GRAY,
|
|
1424
|
-
"
|
|
1530
|
+
"?\nConfirm reservation? (y/n): ", (Sty.BOLD, Sty.GREEN),
|
|
1425
1531
|
)).strip().lower()
|
|
1426
1532
|
|
|
1427
1533
|
if confirmation != "y":
|
|
@@ -1434,13 +1540,11 @@ def _pick_slot_and_reserve(chosen_device_id, chosen_device_name, block_minutes,
|
|
|
1434
1540
|
do_reserve(chosen_device_id, chosen_day, chosen_slot, slot_start_str, slot_end_str)
|
|
1435
1541
|
|
|
1436
1542
|
|
|
1437
|
-
def _native_reserve_slot(chosen_device_id, chosen_day, chosen_slot, slot_start_str, slot_end_str):
|
|
1543
|
+
def _native_reserve_slot(chosen_device_id, chosen_day, chosen_slot, slot_start_str, slot_end_str, device_label=""):
|
|
1438
1544
|
token = account.reserve_device(int(chosen_device_id), chosen_slot[0], chosen_slot[1])
|
|
1439
1545
|
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))
|
|
1546
|
+
_print_native_reserved(chosen_device_id, device_label,
|
|
1547
|
+
f"{chosen_day.strftime('%Y-%m-%d')} {slot_start_str}-{slot_end_str}", token)
|
|
1444
1548
|
|
|
1445
1549
|
|
|
1446
1550
|
def interactive_reserve_next_days_auto():
|
|
@@ -1578,19 +1682,23 @@ def interactive_reserve_next_days_auto():
|
|
|
1578
1682
|
|
|
1579
1683
|
chosen_device_key = sorted_device_ids[sel_i]
|
|
1580
1684
|
chosen_device_id = str(chosen_device_key)
|
|
1581
|
-
chosen_device_name = unmap_arg(dev_resp.results[chosen_device_key])
|
|
1685
|
+
chosen_device_name = str(unmap_arg(dev_resp.results[chosen_device_key]))
|
|
1686
|
+
# 'Pluto SDR [PlutoA] (online)' -> 'Pluto SDR [PlutoA]', 'Pluto SDR @mit (online)' -> 'Pluto SDR @mit'
|
|
1687
|
+
shown = _LOCAL_LABEL.match(chosen_device_name)
|
|
1688
|
+
shown = shown["name"] if shown else chosen_device_name
|
|
1582
1689
|
block_minutes = int(block_by_dev.get(chosen_device_id, 30))
|
|
1583
1690
|
|
|
1584
1691
|
if block_minutes < 10:
|
|
1585
1692
|
printf(
|
|
1586
1693
|
"Your max reservation duration for device ", Sty.DEFAULT,
|
|
1587
|
-
|
|
1694
|
+
shown, Sty.MAGENTA,
|
|
1588
1695
|
f" is < 10 minutes (max_sec={max_by_dev.get(chosen_device_id, 0)}). Cannot create valid reservations.", Sty.DEFAULT,
|
|
1589
1696
|
)
|
|
1590
1697
|
return
|
|
1591
1698
|
|
|
1592
|
-
_pick_slot_and_reserve(chosen_device_id,
|
|
1593
|
-
|
|
1699
|
+
_pick_slot_and_reserve(chosen_device_id, shown, block_minutes, fetch_reservations_for_range,
|
|
1700
|
+
functools.partial(_native_reserve_slot, device_label=chosen_device_name),
|
|
1701
|
+
device_label=shown, max_days=days_by_dev.get(chosen_device_id) or None)
|
|
1594
1702
|
|
|
1595
1703
|
except Exception as e:
|
|
1596
1704
|
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
|
|