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.
Files changed (35) hide show
  1. {remoterf-1.0.7/src/remoteRF.egg-info → remoterf-1.0.9}/PKG-INFO +1 -1
  2. {remoterf-1.0.7 → remoterf-1.0.9}/setup.py +1 -1
  3. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/core/app.py +261 -20
  4. {remoterf-1.0.7 → remoterf-1.0.9/src/remoteRF.egg-info}/PKG-INFO +1 -1
  5. {remoterf-1.0.7 → remoterf-1.0.9}/LICENSE +0 -0
  6. {remoterf-1.0.7 → remoterf-1.0.9}/MANIFEST.in +0 -0
  7. {remoterf-1.0.7 → remoterf-1.0.9}/README.md +0 -0
  8. {remoterf-1.0.7 → remoterf-1.0.9}/setup.cfg +0 -0
  9. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/__init__.py +0 -0
  10. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/common/__init__.py +0 -0
  11. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/common/grpc/__init__.py +0 -0
  12. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/common/grpc/grpc_pb2.py +0 -0
  13. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/common/grpc/grpc_pb2_grpc.py +0 -0
  14. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/common/utils/__init__.py +0 -0
  15. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/common/utils/ansi_codes.py +0 -0
  16. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/common/utils/api_token.py +0 -0
  17. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/common/utils/list_string.py +0 -0
  18. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/common/utils/process_arg.py +0 -0
  19. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/config/__init__.py +0 -0
  20. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/config/cert_fetcher.py +0 -0
  21. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/config/config.py +0 -0
  22. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/core/__init__.py +0 -0
  23. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/core/acc_login.py +0 -0
  24. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/core/grpc_acc.py +0 -0
  25. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/core/grpc_client.py +0 -0
  26. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/core/version.py +0 -0
  27. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/drivers/__init__.py +0 -0
  28. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/drivers/adalm_pluto/__init__.py +0 -0
  29. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/drivers/adalm_pluto/pluto_remote.py +0 -0
  30. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF/remoterf_cli.py +0 -0
  31. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF.egg-info/SOURCES.txt +0 -0
  32. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF.egg-info/dependency_links.txt +0 -0
  33. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF.egg-info/entry_points.txt +0 -0
  34. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF.egg-info/requires.txt +0 -0
  35. {remoterf-1.0.7 → remoterf-1.0.9}/src/remoteRF.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoterf
3
- Version: 1.0.7
3
+ Version: 1.0.9
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: Ethan Ge
6
6
  Author-email: ethoGalaxy@gmail.com
@@ -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.7",
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
- # elif inpu == 'resdev -n':
614
- # # check if user is admin
615
- # # if account.get_perms().results['UC'] == 'Admin':
616
- # reserve()
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoterf
3
- Version: 1.0.7
3
+ Version: 1.0.9
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: Ethan Ge
6
6
  Author-email: ethoGalaxy@gmail.com
File without changes
File without changes
File without changes
File without changes