Skip to content

Commit b9d874e

Browse files
committed
Code needed to run our opmode
1 parent 3af13c1 commit b9d874e

File tree

2 files changed

+220
-0
lines changed

2 files changed

+220
-0
lines changed

server_python_scripts/opmode.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class Opmode:
2+
def __init__(self, robot):
3+
self.robot = robot
4+
def start():
5+
pass
6+
def loop():
7+
pass
8+
def stop():
9+
pass
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Script to run an opmode class derived from the Opmode base class.
4+
5+
Usage:
6+
python run_opmode.py <opmode_file.py>
7+
8+
The opmode file should contain a class that inherits from Opmode.
9+
"""
10+
11+
import sys
12+
import time
13+
import importlib.util
14+
import inspect
15+
import argparse
16+
from pathlib import Path
17+
18+
# Add the current directory to Python path to import local modules
19+
sys.path.insert(0, str(Path(__file__).parent))
20+
21+
from opmode import Opmode
22+
from robot import Robot
23+
24+
25+
def find_opmode_class(module):
26+
"""
27+
Find the first class in the module that inherits from Opmode.
28+
29+
Args:
30+
module: The imported Python module
31+
32+
Returns:
33+
The Opmode-derived class, or None if not found
34+
"""
35+
for name, obj in inspect.getmembers(module, inspect.isclass):
36+
if obj != Opmode and issubclass(obj, Opmode):
37+
return obj
38+
return None
39+
40+
41+
def load_opmode_from_file(file_path):
42+
"""
43+
Dynamically load an opmode class from a Python file.
44+
45+
Args:
46+
file_path: Path to the Python file containing the opmode class
47+
48+
Returns:
49+
The Opmode-derived class
50+
51+
Raises:
52+
FileNotFoundError: If the file doesn't exist
53+
ImportError: If the file can't be imported
54+
ValueError: If no Opmode-derived class is found
55+
"""
56+
file_path = Path(file_path)
57+
58+
if not file_path.exists():
59+
raise FileNotFoundError(f"Opmode file not found: {file_path}")
60+
61+
if not file_path.suffix == '.py':
62+
raise ValueError(f"File must be a Python file (.py): {file_path}")
63+
64+
# Create module spec and load the module
65+
spec = importlib.util.spec_from_file_location("opmode_module", file_path)
66+
if spec is None or spec.loader is None:
67+
raise ImportError(f"Could not load module from {file_path}")
68+
69+
module = importlib.util.module_from_spec(spec)
70+
spec.loader.exec_module(module)
71+
72+
# Find the Opmode-derived class
73+
opmode_class = find_opmode_class(module)
74+
if opmode_class is None:
75+
raise ValueError(f"No class derived from Opmode found in {file_path}")
76+
77+
return opmode_class
78+
79+
80+
def run_opmode(opmode_file, duration=None, loop_frequency=50):
81+
"""
82+
Run the opmode with the specified parameters.
83+
84+
Args:
85+
opmode_file: Path to the opmode Python file
86+
duration: How long to run in seconds (None for infinite)
87+
loop_frequency: Loops per second (default: 50 Hz)
88+
"""
89+
print(f"Loading opmode from: {opmode_file}")
90+
91+
try:
92+
# Load the opmode class
93+
OpModeClass = load_opmode_from_file(opmode_file)
94+
print(f"Found opmode class: {OpModeClass.__name__}")
95+
96+
# Create robot instance
97+
print("Creating robot instance...")
98+
robot = Robot()
99+
100+
# Create opmode instance
101+
print("Initializing opmode...")
102+
opmode = OpModeClass(robot)
103+
104+
# Call start method
105+
print("Starting opmode...")
106+
opmode.start()
107+
108+
# Calculate loop timing
109+
loop_period = 1.0 / loop_frequency
110+
print(f"Running main loop at {loop_frequency} Hz...")
111+
112+
# Main loop
113+
loop_count = 0
114+
start_time = time.time()
115+
116+
try:
117+
while True:
118+
loop_start = time.time()
119+
120+
# Call the loop method
121+
opmode.loop()
122+
loop_count += 1
123+
124+
# Check if we should stop based on duration
125+
if duration is not None and (time.time() - start_time) >= duration:
126+
break
127+
128+
# Sleep to maintain loop frequency
129+
elapsed = time.time() - loop_start
130+
sleep_time = max(0, loop_period - elapsed)
131+
132+
if sleep_time > 0:
133+
time.sleep(sleep_time)
134+
elif elapsed > loop_period * 1.1: # Warn if loop is running slow
135+
print(f"Warning: Loop {loop_count} took {elapsed:.3f}s "
136+
f"(target: {loop_period:.3f}s)")
137+
138+
except KeyboardInterrupt:
139+
print("\nReceived interrupt signal...")
140+
141+
# Call stop method
142+
print("Stopping opmode...")
143+
opmode.stop()
144+
145+
# Print statistics
146+
total_time = time.time() - start_time
147+
actual_frequency = loop_count / total_time if total_time > 0 else 0
148+
149+
print(f"\nOpmode completed:")
150+
print(f" Total runtime: {total_time:.2f} seconds")
151+
print(f" Total loops: {loop_count}")
152+
print(f" Average frequency: {actual_frequency:.1f} Hz")
153+
154+
except Exception as e:
155+
print(f"Error running opmode: {e}")
156+
sys.exit(1)
157+
158+
159+
def main():
160+
"""Main entry point."""
161+
parser = argparse.ArgumentParser(
162+
description="Run an opmode class derived from Opmode",
163+
formatter_class=argparse.RawDescriptionHelpFormatter,
164+
epilog="""
165+
Examples:
166+
python run_opmode.py my_opmode.py
167+
python run_opmode.py my_opmode.py --duration 30
168+
python run_opmode.py my_opmode.py --frequency 60
169+
"""
170+
)
171+
172+
parser.add_argument(
173+
'opmode_file',
174+
help='Python file containing the opmode class'
175+
)
176+
177+
parser.add_argument(
178+
'--duration', '-d',
179+
type=float,
180+
help='Duration to run in seconds (default: run until interrupted)'
181+
)
182+
183+
parser.add_argument(
184+
'--frequency', '-f',
185+
type=int,
186+
default=50,
187+
help='Loop frequency in Hz (default: 50)'
188+
)
189+
190+
parser.add_argument(
191+
'--verbose', '-v',
192+
action='store_true',
193+
help='Enable verbose output'
194+
)
195+
196+
args = parser.parse_args()
197+
198+
if args.frequency <= 0:
199+
print("Error: Frequency must be positive")
200+
sys.exit(1)
201+
202+
# Run the opmode
203+
run_opmode(
204+
args.opmode_file,
205+
duration=args.duration,
206+
loop_frequency=args.frequency
207+
)
208+
209+
210+
if __name__ == '__main__':
211+
main()

0 commit comments

Comments
 (0)