RemoteRF 1.0.7__tar.gz → 1.0.9__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-1.0.7/src/remoteRF.egg-info → remoterf-1.0.9}/PKG-INFO +1 -1
- {remoterf-1.0.7 → remoterf-1.0.9}/setup.py +1 -1
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/core/app.py +261 -20
- {remoterf-1.0.7 → remoterf-1.0.9/src/remoteRF.egg-info}/PKG-INFO +1 -1
- {remoterf-1.0.7 → remoterf-1.0.9}/LICENSE +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/MANIFEST.in +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/README.md +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/setup.cfg +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/__init__.py +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/common/__init__.py +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/common/grpc/__init__.py +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/common/grpc/grpc_pb2.py +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/common/grpc/grpc_pb2_grpc.py +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/common/utils/__init__.py +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/common/utils/ansi_codes.py +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/common/utils/api_token.py +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/common/utils/list_string.py +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/common/utils/process_arg.py +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/config/__init__.py +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/config/cert_fetcher.py +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/config/config.py +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/core/__init__.py +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/core/acc_login.py +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/core/grpc_acc.py +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/core/grpc_client.py +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/core/version.py +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/drivers/__init__.py +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/drivers/adalm_pluto/__init__.py +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/drivers/adalm_pluto/pluto_remote.py +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/remoterf_cli.py +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF.egg-info/SOURCES.txt +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF.egg-info/dependency_links.txt +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF.egg-info/entry_points.txt +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF.egg-info/requires.txt +0 -0
- {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF.egg-info/top_level.txt +0 -0
|
@@ -6,7 +6,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
|
|
|
6
6
|
|
|
7
7
|
setup(
|
|
8
8
|
name="remoterf",
|
|
9
|
-
version="1.0.
|
|
9
|
+
version="1.0.9",
|
|
10
10
|
author="Ethan Ge",
|
|
11
11
|
author_email="ethoGalaxy@gmail.com",
|
|
12
12
|
description="A python API to remotely access signal centric hardware. Client-side only! Courtesy of Wireless Lab @ UCLA & Prof. Ian Roberts.",
|
|
@@ -401,10 +401,6 @@ def fetch_all_reservations():
|
|
|
401
401
|
return entries
|
|
402
402
|
|
|
403
403
|
def fetch_reservations_for_range(start_day: datetime.date, end_day: datetime.date):
|
|
404
|
-
"""
|
|
405
|
-
Fetch all reservations (via fetch_all_reservations) and filter those whose start_time date falls between start_day and end_day (inclusive).
|
|
406
|
-
Returns a dictionary keyed by (device_id, day) (device_id as string, day as datetime.date) with a list of reservation tuples.
|
|
407
|
-
"""
|
|
408
404
|
all_res = fetch_all_reservations() # This calls the network only once.
|
|
409
405
|
res_dict = {}
|
|
410
406
|
for res in all_res:
|
|
@@ -415,7 +411,6 @@ def fetch_reservations_for_range(start_day: datetime.date, end_day: datetime.dat
|
|
|
415
411
|
return res_dict
|
|
416
412
|
|
|
417
413
|
def is_slot_conflicting(slot: tuple, reservations: list):
|
|
418
|
-
"""Return True if the slot overlaps with any reservation in the provided list."""
|
|
419
414
|
slot_start, slot_end = slot
|
|
420
415
|
for res_start, res_end in reservations:
|
|
421
416
|
if slot_start < res_end and slot_end > res_start:
|
|
@@ -423,16 +418,6 @@ def is_slot_conflicting(slot: tuple, reservations: list):
|
|
|
423
418
|
return False
|
|
424
419
|
|
|
425
420
|
def interactive_reserve_next_days(block_minutes=60):
|
|
426
|
-
"""
|
|
427
|
-
Interactive function that:
|
|
428
|
-
1) Displays a menu of all devices (0-based indexing).
|
|
429
|
-
2) Prompts the user for which device they want.
|
|
430
|
-
3) Prompts how many days (starting today) to check for available reservations.
|
|
431
|
-
4) Prompts for an optional block duration in minutes (default = 60).
|
|
432
|
-
5) Displays the free time slots (in 'block_minutes' increments) for that device,
|
|
433
|
-
over the indicated number of days, also 0-based indexed.
|
|
434
|
-
6) Reserves the chosen slot on that device, after confirmation.
|
|
435
|
-
"""
|
|
436
421
|
try:
|
|
437
422
|
# --- 1) Fetch and display all devices ---
|
|
438
423
|
data = account.get_devices()
|
|
@@ -580,6 +565,261 @@ def interactive_reserve_next_days(block_minutes=60):
|
|
|
580
565
|
|
|
581
566
|
except Exception as e:
|
|
582
567
|
print(f"Error: {e}")
|
|
568
|
+
|
|
569
|
+
def interactive_reserve_next_days_auto():
|
|
570
|
+
try:
|
|
571
|
+
import ast, json, datetime
|
|
572
|
+
|
|
573
|
+
# ---------------------------
|
|
574
|
+
# 1) Fetch perms + per-device caps
|
|
575
|
+
# ---------------------------
|
|
576
|
+
perms_resp = account.get_perms()
|
|
577
|
+
if "ace" in perms_resp.results:
|
|
578
|
+
print(f"Error: {unmap_arg(perms_resp.results['ace'])}")
|
|
579
|
+
return
|
|
580
|
+
|
|
581
|
+
uc_raw = unmap_arg(perms_resp.results.get("UC", map_arg("[]")))
|
|
582
|
+
try:
|
|
583
|
+
perm_row = ast.literal_eval(uc_raw)[0] # your server returns a list-of-rows
|
|
584
|
+
except Exception:
|
|
585
|
+
perm_row = []
|
|
586
|
+
|
|
587
|
+
perm_level = perm_row[0] if perm_row else ""
|
|
588
|
+
|
|
589
|
+
details = {}
|
|
590
|
+
if perm_level == "Normal User":
|
|
591
|
+
details_raw = unmap_arg(perms_resp.results.get("details", map_arg("{}")))
|
|
592
|
+
try:
|
|
593
|
+
details = json.loads(details_raw) if details_raw else {}
|
|
594
|
+
except Exception:
|
|
595
|
+
details = {}
|
|
596
|
+
|
|
597
|
+
caps = details.get("caps", {}) or {}
|
|
598
|
+
|
|
599
|
+
def _cap_for(dev_id: int) -> dict:
|
|
600
|
+
# tolerate server sending str keys, or int keys
|
|
601
|
+
return caps.get(str(dev_id)) or caps.get(dev_id) or {}
|
|
602
|
+
|
|
603
|
+
# Allowed devices filter from perms:
|
|
604
|
+
allowed_dev_ids: set[int] | None = None
|
|
605
|
+
|
|
606
|
+
if perm_level == "Normal User":
|
|
607
|
+
# server already computes allowed devices list for Normal User
|
|
608
|
+
devs = details.get("devices", []) or []
|
|
609
|
+
try:
|
|
610
|
+
allowed_dev_ids = {int(d) for d in devs}
|
|
611
|
+
except Exception:
|
|
612
|
+
allowed_dev_ids = set()
|
|
613
|
+
|
|
614
|
+
elif perm_level == "Power User":
|
|
615
|
+
# results[5] is the allowed device list (string encoded) in your system
|
|
616
|
+
try:
|
|
617
|
+
allowed_dev_ids = {int(x) for x in str_to_list(perm_row[5])}
|
|
618
|
+
except Exception:
|
|
619
|
+
allowed_dev_ids = None
|
|
620
|
+
|
|
621
|
+
elif perm_level == "Admin":
|
|
622
|
+
allowed_dev_ids = None # no filter
|
|
623
|
+
|
|
624
|
+
# For Power User, one max duration applies to all allowed devices
|
|
625
|
+
power_user_max_t_sec = None
|
|
626
|
+
if perm_level == "Power User":
|
|
627
|
+
try:
|
|
628
|
+
power_user_max_t_sec = int(perm_row[4])
|
|
629
|
+
except Exception:
|
|
630
|
+
power_user_max_t_sec = None
|
|
631
|
+
|
|
632
|
+
def _auto_block_minutes(max_t_sec: int) -> int:
|
|
633
|
+
try:
|
|
634
|
+
max_t_sec = int(max_t_sec)
|
|
635
|
+
except Exception:
|
|
636
|
+
return 30
|
|
637
|
+
|
|
638
|
+
if max_t_sec <= 0:
|
|
639
|
+
return 30
|
|
640
|
+
|
|
641
|
+
max_min = max_t_sec // 60
|
|
642
|
+
if max_min < 10:
|
|
643
|
+
# Can't make a valid reservation at all (server min = 10 minutes).
|
|
644
|
+
return max_min
|
|
645
|
+
|
|
646
|
+
# Pick the largest "nice" block <= max_min
|
|
647
|
+
candidates = [10, 15, 20, 30, 45, 60, 90, 120, 180, 240, 360, 480, 720]
|
|
648
|
+
best = 10
|
|
649
|
+
for c in candidates:
|
|
650
|
+
if c <= max_min:
|
|
651
|
+
best = c
|
|
652
|
+
else:
|
|
653
|
+
break
|
|
654
|
+
return best
|
|
655
|
+
|
|
656
|
+
dev_resp = account.get_devices()
|
|
657
|
+
if "ace" in dev_resp.results:
|
|
658
|
+
print(f"Error: {unmap_arg(dev_resp.results['ace'])}")
|
|
659
|
+
return
|
|
660
|
+
|
|
661
|
+
sorted_device_ids = sorted(dev_resp.results.keys(), key=int) # strings
|
|
662
|
+
|
|
663
|
+
if allowed_dev_ids is not None:
|
|
664
|
+
sorted_device_ids = [d for d in sorted_device_ids if int(d) in allowed_dev_ids]
|
|
665
|
+
|
|
666
|
+
if not sorted_device_ids:
|
|
667
|
+
print("No devices available for your permission level.")
|
|
668
|
+
return
|
|
669
|
+
|
|
670
|
+
print("Devices:")
|
|
671
|
+
block_by_dev: dict[str, int] = {}
|
|
672
|
+
max_by_dev: dict[str, int] = {}
|
|
673
|
+
|
|
674
|
+
for idx, dev_id in enumerate(sorted_device_ids):
|
|
675
|
+
dev_name = unmap_arg(dev_resp.results[dev_id])
|
|
676
|
+
|
|
677
|
+
max_t_sec = 0
|
|
678
|
+
if perm_level == "Normal User":
|
|
679
|
+
c = _cap_for(int(dev_id))
|
|
680
|
+
try:
|
|
681
|
+
max_t_sec = int(c.get("max_reservation_time_sec", 0) or 0)
|
|
682
|
+
except Exception:
|
|
683
|
+
max_t_sec = 0
|
|
684
|
+
elif perm_level == "Power User" and power_user_max_t_sec is not None:
|
|
685
|
+
max_t_sec = int(power_user_max_t_sec)
|
|
686
|
+
elif perm_level == "Admin":
|
|
687
|
+
# Admin has no cap; keep UX sane
|
|
688
|
+
max_t_sec = 60 * 60 # 60 min max display; only affects block sizing display
|
|
689
|
+
|
|
690
|
+
block_min = _auto_block_minutes(max_t_sec)
|
|
691
|
+
block_by_dev[str(dev_id)] = int(block_min)
|
|
692
|
+
max_by_dev[str(dev_id)] = int(max_t_sec)
|
|
693
|
+
|
|
694
|
+
# if max_t_sec > 0:
|
|
695
|
+
# print(f"{idx}. Device ID: {dev_id} Name: {dev_name} (max={max_t_sec//60} min, blocks={block_min} min)")
|
|
696
|
+
# else:
|
|
697
|
+
# print(f"{idx}. Device ID: {dev_id} Name: {dev_name} (blocks={block_min} min)")
|
|
698
|
+
|
|
699
|
+
print(f"{idx}. Device ID: {dev_id} Duration: {block_min} min. Name: {dev_name}")
|
|
700
|
+
|
|
701
|
+
sel = input("Which device do you want? (enter the 0-based index): ").strip()
|
|
702
|
+
try:
|
|
703
|
+
sel_i = int(sel)
|
|
704
|
+
if sel_i < 0 or sel_i >= len(sorted_device_ids):
|
|
705
|
+
print("Invalid selection.")
|
|
706
|
+
return
|
|
707
|
+
except ValueError:
|
|
708
|
+
print("Invalid input. Please enter a number.")
|
|
709
|
+
return
|
|
710
|
+
|
|
711
|
+
chosen_device_id = str(sorted_device_ids[sel_i])
|
|
712
|
+
block_minutes = int(block_by_dev.get(chosen_device_id, 30))
|
|
713
|
+
|
|
714
|
+
if block_minutes < 10:
|
|
715
|
+
print(
|
|
716
|
+
f"Your max reservation duration for device {chosen_device_id} is < 10 minutes "
|
|
717
|
+
f"(max_sec={max_by_dev.get(chosen_device_id, 0)}). Cannot create valid reservations."
|
|
718
|
+
)
|
|
719
|
+
return
|
|
720
|
+
|
|
721
|
+
num_days_s = input("Enter the number of days to check for available reservations (starting today): ").strip()
|
|
722
|
+
try:
|
|
723
|
+
num_days = int(num_days_s)
|
|
724
|
+
if num_days <= 0:
|
|
725
|
+
print("Invalid number of days.")
|
|
726
|
+
return
|
|
727
|
+
except ValueError:
|
|
728
|
+
print("Invalid input. Please enter a number.")
|
|
729
|
+
return
|
|
730
|
+
|
|
731
|
+
# ---------------------------
|
|
732
|
+
# 4) Compute availability (same logic you already have)
|
|
733
|
+
# ---------------------------
|
|
734
|
+
today = datetime.date.today()
|
|
735
|
+
end_day = today + datetime.timedelta(days=num_days - 1)
|
|
736
|
+
|
|
737
|
+
reservations_range = fetch_reservations_for_range(today, end_day)
|
|
738
|
+
available_slots = []
|
|
739
|
+
now = datetime.datetime.now()
|
|
740
|
+
|
|
741
|
+
def build_time_slots(date: datetime.date, block_size: int):
|
|
742
|
+
slots = []
|
|
743
|
+
start_of_day = datetime.datetime.combine(date, datetime.time(0, 0))
|
|
744
|
+
minutes_in_day = 24 * 60
|
|
745
|
+
current_offset = 0
|
|
746
|
+
while current_offset < minutes_in_day:
|
|
747
|
+
slot_start = start_of_day + datetime.timedelta(minutes=current_offset)
|
|
748
|
+
slot_end = slot_start + datetime.timedelta(minutes=block_size)
|
|
749
|
+
if slot_end.date() != date and slot_end.time() != datetime.time.min:
|
|
750
|
+
break
|
|
751
|
+
slots.append((slot_start, slot_end))
|
|
752
|
+
current_offset += block_size
|
|
753
|
+
return slots
|
|
754
|
+
|
|
755
|
+
for i in range(num_days):
|
|
756
|
+
day = today + datetime.timedelta(days=i)
|
|
757
|
+
all_slots = build_time_slots(day, block_minutes)
|
|
758
|
+
|
|
759
|
+
key = (str(chosen_device_id), day)
|
|
760
|
+
day_reservations = reservations_range.get(key, [])
|
|
761
|
+
|
|
762
|
+
for slot in all_slots:
|
|
763
|
+
slot_start, slot_end = slot
|
|
764
|
+
if day == today and slot_end <= now:
|
|
765
|
+
continue
|
|
766
|
+
if not is_slot_conflicting(slot, day_reservations):
|
|
767
|
+
available_slots.append((day, slot))
|
|
768
|
+
|
|
769
|
+
if not available_slots:
|
|
770
|
+
print(f"No available {block_minutes}-minute slots for device {chosen_device_id} in the next {num_days} days.")
|
|
771
|
+
return
|
|
772
|
+
|
|
773
|
+
available_slots.sort(key=lambda x: (x[0], x[1][0]))
|
|
774
|
+
|
|
775
|
+
print(f"\nAvailable time slots for device {chosen_device_id} ({block_minutes} min blocks) over the next {num_days} days:")
|
|
776
|
+
last_day = None
|
|
777
|
+
for idx, (day, slot) in enumerate(available_slots):
|
|
778
|
+
slot_start_str = slot[0].strftime("%I:%M %p")
|
|
779
|
+
slot_end_str = slot[1].strftime("%I:%M %p")
|
|
780
|
+
if day != last_day:
|
|
781
|
+
print("\n" + f"{day.strftime('%Y-%m-%d')} ({day.strftime('%a')})")
|
|
782
|
+
last_day = day
|
|
783
|
+
print(f" {idx}. {slot_start_str} - {slot_end_str}")
|
|
784
|
+
|
|
785
|
+
pick_s = input("Select a slot by index: ").strip()
|
|
786
|
+
try:
|
|
787
|
+
pick = int(pick_s)
|
|
788
|
+
if pick < 0 or pick >= len(available_slots):
|
|
789
|
+
print("Invalid selection.")
|
|
790
|
+
return
|
|
791
|
+
except ValueError:
|
|
792
|
+
print("Invalid input. Please enter a number.")
|
|
793
|
+
return
|
|
794
|
+
|
|
795
|
+
chosen_day, chosen_slot = available_slots[pick]
|
|
796
|
+
slot_start_str = chosen_slot[0].strftime("%I:%M %p")
|
|
797
|
+
slot_end_str = chosen_slot[1].strftime("%I:%M %p")
|
|
798
|
+
|
|
799
|
+
confirmation = input(
|
|
800
|
+
f"You have selected a reservation on {chosen_day.strftime('%Y-%m-%d')} "
|
|
801
|
+
f"from {slot_start_str} to {slot_end_str} on device {chosen_device_id}. "
|
|
802
|
+
f"Confirm reservation? (y/n): "
|
|
803
|
+
).strip().lower()
|
|
804
|
+
|
|
805
|
+
if confirmation != "y":
|
|
806
|
+
print("Reservation cancelled.")
|
|
807
|
+
return
|
|
808
|
+
|
|
809
|
+
# ---------------------------
|
|
810
|
+
# 5) Reserve
|
|
811
|
+
# ---------------------------
|
|
812
|
+
token = account.reserve_device(int(chosen_device_id), chosen_slot[0], chosen_slot[1])
|
|
813
|
+
if token:
|
|
814
|
+
print(
|
|
815
|
+
f"Reservation successful on device {chosen_device_id} for "
|
|
816
|
+
f"{chosen_day.strftime('%Y-%m-%d')} {slot_start_str}-{slot_end_str}."
|
|
817
|
+
)
|
|
818
|
+
print(f"Thy Token -> {token}")
|
|
819
|
+
print("Please keep this token safe, as it is not saved on server side and cannot be retrieved again.")
|
|
820
|
+
|
|
821
|
+
except Exception as e:
|
|
822
|
+
print(f"Error: {e}")
|
|
583
823
|
|
|
584
824
|
welcome()
|
|
585
825
|
clear()
|
|
@@ -606,14 +846,15 @@ while True:
|
|
|
606
846
|
# elif inpu == "resdev s":
|
|
607
847
|
# interactive_reserve_all()
|
|
608
848
|
elif inpu == "resdev":
|
|
609
|
-
interactive_reserve_next_days(block_minutes=30)
|
|
849
|
+
# interactive_reserve_next_days(block_minutes=30)
|
|
850
|
+
interactive_reserve_next_days_auto()
|
|
610
851
|
elif inpu == 'cancelres':
|
|
611
852
|
cancel_my_reservation()
|
|
612
853
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
854
|
+
elif inpu == 'resdev -n':
|
|
855
|
+
# check if user is admin
|
|
856
|
+
# if account.get_perms().results['UC'] == 'Admin':
|
|
857
|
+
reserve()
|
|
617
858
|
elif account.is_admin and inpu.strip().startswith("admin"):
|
|
618
859
|
handle_admin_command(inpu)
|
|
619
860
|
else:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|