-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathmain_sim.py
More file actions
219 lines (180 loc) · 6.94 KB
/
main_sim.py
File metadata and controls
219 lines (180 loc) · 6.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
"""
OpenServoSim - Simulation Entry Point
Run a MuJoCo simulation of the servo biped robot with optional
servo physics modeling and controller selection.
Usage:
python main_sim.py # Headless breathing demo
python main_sim.py --render # With visualization
python main_sim.py --controller uvc # Use UVC controller
python main_sim.py --no-servo-model # Skip servo physics layer
Examples:
# Quick test: robot does sinusoidal knee bending ("breathing")
python main_sim.py --render
# UVC balance test: push the robot and watch it recover
python main_sim.py --controller uvc --render --duration 10
"""
import argparse
import time
import numpy as np
import os
from sim.mujoco_env import MuJoCoServoEnv
from sim.servo_model import ServoModel, ServoConfig
def create_controller(name: str):
"""Factory function to create a controller by name."""
if name == "breathing":
return None # Use built-in breathing demo
elif name == "uvc":
from controllers.uvc_controller import UVCController
return UVCController(control_freq=50.0)
else:
raise ValueError(f"Unknown controller: {name}")
def breathing_demo(t: float, num_actuators: int) -> np.ndarray:
"""
Simple sinusoidal "breathing" motion for testing.
Makes the robot's knees bend in a slow sine wave,
verifying that the simulation pipeline works end-to-end.
Args:
t: Current time in seconds
num_actuators: Number of actuators
Returns:
Target positions for all actuators
"""
targets = np.zeros(num_actuators)
# Sinusoidal knee bending (0.5 Hz, amplitude ±0.3 rad)
knee_angle = 0.5 + 0.3 * np.sin(2 * np.pi * 0.5 * t)
# Symmetric for both legs
# Joint order: hip_pitch, hip_roll, knee, ankle_pitch, ankle_roll
for leg_offset in [0, 5]: # Right leg (0-4), Left leg (5-9)
targets[leg_offset + 0] = knee_angle / 2 # Hip pitch
targets[leg_offset + 1] = 0.0 # Hip roll
targets[leg_offset + 2] = knee_angle # Knee
targets[leg_offset + 3] = knee_angle / 2 # Ankle pitch
targets[leg_offset + 4] = 0.0 # Ankle roll
return targets
def main():
parser = argparse.ArgumentParser(
description="OpenServoSim - Servo Biped Simulation",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
"--render", action="store_true", help="Enable real-time visualization"
)
parser.add_argument(
"--controller",
type=str,
default="breathing",
choices=["breathing", "uvc"],
help="Controller to use (default: breathing demo)",
)
parser.add_argument(
"--duration",
type=float,
default=5.0,
help="Simulation duration in seconds (default: 5)",
)
parser.add_argument(
"--control-freq",
type=float,
default=50.0,
help="Control loop frequency in Hz (default: 50)",
)
parser.add_argument(
"--no-servo-model",
action="store_true",
help="Disable servo physics simulation (ideal actuators)",
)
parser.add_argument(
"--model",
type=str,
default=None,
help="Path to MJCF model file (default: models/servo_biped/servo_biped.xml)",
)
args = parser.parse_args()
# Resolve model path
if args.model is None:
model_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"models", "servo_biped", "servo_biped.xml",
)
else:
model_path = args.model
print("=" * 60)
print(" OpenServoSim - Servo Biped Simulation")
print("=" * 60)
print(f" Model: {os.path.basename(model_path)}")
print(f" Controller: {args.controller}")
print(f" Duration: {args.duration}s")
print(f" Frequency: {args.control_freq} Hz")
print(f" Servo Model: {'OFF' if args.no_servo_model else 'ON'}")
print(f" Render: {'ON' if args.render else 'OFF'}")
print("=" * 60)
# --- Initialize environment ---
sim_timestep = 0.002 # 2ms = 500Hz physics
env = MuJoCoServoEnv(model_path, timestep=sim_timestep)
env.reset()
print(f"\n Model loaded: {env.model.nq} qpos, {env.num_actuators} actuators")
# --- Initialize servo model (optional) ---
servo_model = None
if not args.no_servo_model:
servo_model = ServoModel(
num_servos=env.num_actuators,
control_freq=args.control_freq,
sim_timestep=sim_timestep,
)
servo_model.reset()
# --- Initialize controller ---
controller = create_controller(args.controller)
if controller is not None:
controller.reset()
# --- Main simulation loop ---
control_period = 1.0 / args.control_freq
total_steps = int(args.duration / sim_timestep)
steps_per_control = int(control_period / sim_timestep)
print(f"\n Running {total_steps} simulation steps...")
print(f" Control update every {steps_per_control} steps ({control_period*1000:.1f}ms)")
print()
sim_time = 0.0
last_control_time = -control_period # Force first update
for step in range(total_steps):
sim_time = step * sim_timestep
# --- Control update (at control frequency) ---
if sim_time - last_control_time >= control_period:
last_control_time = sim_time
if controller is None:
# Breathing demo
targets = breathing_demo(sim_time, env.num_actuators)
else:
# Use controller
imu_data = env.get_imu_data()
joint_pos = env.get_joint_positions()
joint_vel = env.get_joint_velocities()
targets = controller.compute(imu_data, joint_pos, joint_vel)
# Apply servo model (delay, dead zone, filter)
if servo_model is not None:
targets = servo_model.apply(targets, sim_time)
env.set_actuator_targets(targets)
# --- Physics step ---
env.step()
# --- Render (if enabled) ---
if args.render:
env.render()
# Throttle to real-time
time.sleep(max(0, sim_timestep - 0.0001))
# --- Periodic info ---
if step > 0 and step % (steps_per_control * 10) == 0:
imu = env.get_imu_data()
height = env.get_torso_height()
contacts = env.get_foot_contacts()
print(
f" t={sim_time:5.2f}s | "
f"height={height*1000:.1f}mm | "
f"pitch={np.degrees(imu['pitch']):+5.1f}° | "
f"roll={np.degrees(imu['roll']):+5.1f}° | "
f"feet: R={'▓' if contacts['right'] else '░'} "
f"L={'▓' if contacts['left'] else '░'}"
)
print(f"\n Simulation complete ({args.duration}s)")
# --- Cleanup ---
env.close()
if __name__ == "__main__":
main()