From b9d874e35a74271bce6023b9a9ec70d9ce8604a2 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Sat, 5 Jul 2025 19:03:52 -0400 Subject: [PATCH 1/2] Code needed to run our opmode --- server_python_scripts/opmode.py | 9 ++ server_python_scripts/run_opmode.py | 211 ++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 server_python_scripts/opmode.py create mode 100644 server_python_scripts/run_opmode.py diff --git a/server_python_scripts/opmode.py b/server_python_scripts/opmode.py new file mode 100644 index 00000000..75b5b421 --- /dev/null +++ b/server_python_scripts/opmode.py @@ -0,0 +1,9 @@ +class Opmode: + def __init__(self, robot): + self.robot = robot + def start(): + pass + def loop(): + pass + def stop(): + pass \ No newline at end of file diff --git a/server_python_scripts/run_opmode.py b/server_python_scripts/run_opmode.py new file mode 100644 index 00000000..10e4448e --- /dev/null +++ b/server_python_scripts/run_opmode.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python3 +""" +Script to run an opmode class derived from the Opmode base class. + +Usage: + python run_opmode.py + +The opmode file should contain a class that inherits from Opmode. +""" + +import sys +import time +import importlib.util +import inspect +import argparse +from pathlib import Path + +# Add the current directory to Python path to import local modules +sys.path.insert(0, str(Path(__file__).parent)) + +from opmode import Opmode +from robot import Robot + + +def find_opmode_class(module): + """ + Find the first class in the module that inherits from Opmode. + + Args: + module: The imported Python module + + Returns: + The Opmode-derived class, or None if not found + """ + for name, obj in inspect.getmembers(module, inspect.isclass): + if obj != Opmode and issubclass(obj, Opmode): + return obj + return None + + +def load_opmode_from_file(file_path): + """ + Dynamically load an opmode class from a Python file. + + Args: + file_path: Path to the Python file containing the opmode class + + Returns: + The Opmode-derived class + + Raises: + FileNotFoundError: If the file doesn't exist + ImportError: If the file can't be imported + ValueError: If no Opmode-derived class is found + """ + file_path = Path(file_path) + + if not file_path.exists(): + raise FileNotFoundError(f"Opmode file not found: {file_path}") + + if not file_path.suffix == '.py': + raise ValueError(f"File must be a Python file (.py): {file_path}") + + # Create module spec and load the module + spec = importlib.util.spec_from_file_location("opmode_module", file_path) + if spec is None or spec.loader is None: + raise ImportError(f"Could not load module from {file_path}") + + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + # Find the Opmode-derived class + opmode_class = find_opmode_class(module) + if opmode_class is None: + raise ValueError(f"No class derived from Opmode found in {file_path}") + + return opmode_class + + +def run_opmode(opmode_file, duration=None, loop_frequency=50): + """ + Run the opmode with the specified parameters. + + Args: + opmode_file: Path to the opmode Python file + duration: How long to run in seconds (None for infinite) + loop_frequency: Loops per second (default: 50 Hz) + """ + print(f"Loading opmode from: {opmode_file}") + + try: + # Load the opmode class + OpModeClass = load_opmode_from_file(opmode_file) + print(f"Found opmode class: {OpModeClass.__name__}") + + # Create robot instance + print("Creating robot instance...") + robot = Robot() + + # Create opmode instance + print("Initializing opmode...") + opmode = OpModeClass(robot) + + # Call start method + print("Starting opmode...") + opmode.start() + + # Calculate loop timing + loop_period = 1.0 / loop_frequency + print(f"Running main loop at {loop_frequency} Hz...") + + # Main loop + loop_count = 0 + start_time = time.time() + + try: + while True: + loop_start = time.time() + + # Call the loop method + opmode.loop() + loop_count += 1 + + # Check if we should stop based on duration + if duration is not None and (time.time() - start_time) >= duration: + break + + # Sleep to maintain loop frequency + elapsed = time.time() - loop_start + sleep_time = max(0, loop_period - elapsed) + + if sleep_time > 0: + time.sleep(sleep_time) + elif elapsed > loop_period * 1.1: # Warn if loop is running slow + print(f"Warning: Loop {loop_count} took {elapsed:.3f}s " + f"(target: {loop_period:.3f}s)") + + except KeyboardInterrupt: + print("\nReceived interrupt signal...") + + # Call stop method + print("Stopping opmode...") + opmode.stop() + + # Print statistics + total_time = time.time() - start_time + actual_frequency = loop_count / total_time if total_time > 0 else 0 + + print(f"\nOpmode completed:") + print(f" Total runtime: {total_time:.2f} seconds") + print(f" Total loops: {loop_count}") + print(f" Average frequency: {actual_frequency:.1f} Hz") + + except Exception as e: + print(f"Error running opmode: {e}") + sys.exit(1) + + +def main(): + """Main entry point.""" + parser = argparse.ArgumentParser( + description="Run an opmode class derived from Opmode", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + python run_opmode.py my_opmode.py + python run_opmode.py my_opmode.py --duration 30 + python run_opmode.py my_opmode.py --frequency 60 + """ + ) + + parser.add_argument( + 'opmode_file', + help='Python file containing the opmode class' + ) + + parser.add_argument( + '--duration', '-d', + type=float, + help='Duration to run in seconds (default: run until interrupted)' + ) + + parser.add_argument( + '--frequency', '-f', + type=int, + default=50, + help='Loop frequency in Hz (default: 50)' + ) + + parser.add_argument( + '--verbose', '-v', + action='store_true', + help='Enable verbose output' + ) + + args = parser.parse_args() + + if args.frequency <= 0: + print("Error: Frequency must be positive") + sys.exit(1) + + # Run the opmode + run_opmode( + args.opmode_file, + duration=args.duration, + loop_frequency=args.frequency + ) + + +if __name__ == '__main__': + main() \ No newline at end of file From 1464c70d3c2a65ff42e220dc947780be75620ed2 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Sun, 6 Jul 2025 08:07:11 -0400 Subject: [PATCH 2/2] Change Opmode to OpMode --- server_python_scripts/opmode.py | 2 +- server_python_scripts/run_opmode.py | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/server_python_scripts/opmode.py b/server_python_scripts/opmode.py index 75b5b421..b4ce7bf4 100644 --- a/server_python_scripts/opmode.py +++ b/server_python_scripts/opmode.py @@ -1,4 +1,4 @@ -class Opmode: +class OpMode: def __init__(self, robot): self.robot = robot def start(): diff --git a/server_python_scripts/run_opmode.py b/server_python_scripts/run_opmode.py index 10e4448e..f663c9e8 100644 --- a/server_python_scripts/run_opmode.py +++ b/server_python_scripts/run_opmode.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 """ -Script to run an opmode class derived from the Opmode base class. +Script to run an opmode class derived from the OpMode base class. Usage: python run_opmode.py -The opmode file should contain a class that inherits from Opmode. +The opmode file should contain a class that inherits from OpMode. """ import sys @@ -18,22 +18,22 @@ # Add the current directory to Python path to import local modules sys.path.insert(0, str(Path(__file__).parent)) -from opmode import Opmode +from opmode import OpMode from robot import Robot def find_opmode_class(module): """ - Find the first class in the module that inherits from Opmode. + Find the first class in the module that inherits from OpMode. Args: module: The imported Python module Returns: - The Opmode-derived class, or None if not found + The OpMode-derived class, or None if not found """ for name, obj in inspect.getmembers(module, inspect.isclass): - if obj != Opmode and issubclass(obj, Opmode): + if obj != OpMode and issubclass(obj, OpMode): return obj return None @@ -46,17 +46,17 @@ def load_opmode_from_file(file_path): file_path: Path to the Python file containing the opmode class Returns: - The Opmode-derived class + The OpMode-derived class Raises: FileNotFoundError: If the file doesn't exist ImportError: If the file can't be imported - ValueError: If no Opmode-derived class is found + ValueError: If no OpMode-derived class is found """ file_path = Path(file_path) if not file_path.exists(): - raise FileNotFoundError(f"Opmode file not found: {file_path}") + raise FileNotFoundError(f"OpMode file not found: {file_path}") if not file_path.suffix == '.py': raise ValueError(f"File must be a Python file (.py): {file_path}") @@ -69,10 +69,10 @@ def load_opmode_from_file(file_path): module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) - # Find the Opmode-derived class + # Find the OpMode-derived class opmode_class = find_opmode_class(module) if opmode_class is None: - raise ValueError(f"No class derived from Opmode found in {file_path}") + raise ValueError(f"No class derived from OpMode found in {file_path}") return opmode_class @@ -146,7 +146,7 @@ def run_opmode(opmode_file, duration=None, loop_frequency=50): total_time = time.time() - start_time actual_frequency = loop_count / total_time if total_time > 0 else 0 - print(f"\nOpmode completed:") + print(f"\nOpMode completed:") print(f" Total runtime: {total_time:.2f} seconds") print(f" Total loops: {loop_count}") print(f" Average frequency: {actual_frequency:.1f} Hz") @@ -159,7 +159,7 @@ def run_opmode(opmode_file, duration=None, loop_frequency=50): def main(): """Main entry point.""" parser = argparse.ArgumentParser( - description="Run an opmode class derived from Opmode", + description="Run an opmode class derived from OpMode", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: