try mujoco3

This commit is contained in:
LiuzhengSJ
2026-05-29 15:32:12 +01:00
parent c23b5d0d6d
commit a8a10728bc

View File

@ -1,7 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Thread-based MuJoCo controller for kinematic verification Simple Position Control with Velocity and Acceleration Limits - WITH IMPROVED CONVERGENCE
No ROS dependency - pure Python threading
""" """
import mujoco import mujoco
@ -12,38 +11,55 @@ import time
from pathlib import Path from pathlib import Path
class MuJoCoRobotController: class SimplePositionController:
""" """
Thread-based robot controller for kinematic verification Simple position control with velocity and acceleration limits
Command and feedback via thread-safe queues
""" """
def __init__(self, urdf_path, smoothness=0.1, enable_viewer=True): def __init__(self, urdf_path,
""" max_velocity=2.0, # rad/s
Args: max_acceleration=5.0, # rad/s²
urdf_path: Path to URDF file control_dt=0.01, # seconds
smoothness: Motion smoothness (0.05-0.2) enable_viewer=True):
enable_viewer: Show MuJoCo viewer
"""
# Load model # Load model
self.model = mujoco.MjModel.from_xml_path(urdf_path) self.model = mujoco.MjModel.from_xml_path(urdf_path)
self.data = mujoco.MjData(self.model) self.data = mujoco.MjData(self.model)
# Robot info # Robot info
self.n_joints = self.model.njnt self.n_joints = self.model.njnt
self.joint_names = [self.model.joint(i).name for i in range(self.n_joints)] self.control_dt = control_dt
# Joint limits # Get joint limits
self.joint_lower_limits = [self.model.jnt_range[i, 0] for i in range(self.n_joints)] self.joint_lower_limits = []
self.joint_upper_limits = [self.model.jnt_range[i, 1] for i in range(self.n_joints)] self.joint_upper_limits = []
for i in range(self.n_joints):
self.joint_lower_limits.append(self.model.jnt_range[i, 0])
self.joint_upper_limits.append(self.model.jnt_range[i, 1])
print(f"Loaded robot: {self.n_joints} joints") print(f"Loaded robot: {self.n_joints} joints")
print(f"Velocity limit: {max_velocity} rad/s")
print(f"Acceleration limit: {max_acceleration} rad/s²")
# Control parameters # Control limits
self.smoothness = smoothness self.max_vel = max_velocity
self.max_acc = max_acceleration
self.j_cmd = self.data.qpos[:self.n_joints].copy() # Target and current positions
self.j_fbk = self.data.qvel[:self.n_joints].copy() self.target_positions = self.data.qpos[:self.n_joints].copy()
self.current_positions = self.data.qpos[:self.n_joints].copy()
# For motion limiting
self.current_command = self.data.qpos[:self.n_joints].copy()
self.previous_command = self.data.qpos[:self.n_joints].copy()
# For convergence zone - slow down when close to target
self.slow_zone_radius = 0.2 # rad - start slowing down within this distance
self.slow_zone_velocity = 0.3 # rad/s - max velocity in slow zone
# Thread safety
self.command_lock = threading.Lock()
self.feedback_lock = threading.Lock()
# Control flags # Control flags
self.running = False self.running = False
@ -54,23 +70,23 @@ class MuJoCoRobotController:
if enable_viewer: if enable_viewer:
try: try:
self.viewer = mujoco.viewer.launch_passive(self.model, self.data) self.viewer = mujoco.viewer.launch_passive(self.model, self.data)
print("Viewer launched")
except Exception as e: except Exception as e:
print(f"Viewer warning: {e}") print(f"Viewer warning: {e}")
print("Robot controller ready") print("Controller ready\n")
def start(self): def start(self):
"""Start the simulation thread""" """Start simulation thread"""
if self.running: if self.running:
return return
self.running = True self.running = True
self.simulation_thread = threading.Thread(target=self._simulation_loop, daemon=True) self.simulation_thread = threading.Thread(target=self._simulation_loop, daemon=True)
self.simulation_thread.start() self.simulation_thread.start()
print("Simulation thread started") print("Simulation started")
def stop(self): def stop(self):
"""Stop the simulation thread""" """Stop simulation"""
self.running = False self.running = False
if self.simulation_thread: if self.simulation_thread:
self.simulation_thread.join(timeout=2.0) self.simulation_thread.join(timeout=2.0)
@ -79,150 +95,224 @@ class MuJoCoRobotController:
print("Simulation stopped") print("Simulation stopped")
def send_command(self, joint_positions): def send_command(self, joint_positions):
""" """Send target joint positions"""
Send joint command to robot (non-blocking) cmd = np.array(joint_positions[:self.n_joints], dtype=np.float64)
Args:
joint_positions: Array of target joint angles
"""
cmd = np.array(joint_positions[:self.n_joints])
# Apply joint limits # Apply joint limits
for i in range(self.n_joints): for i in range(self.n_joints):
cmd[i] = max(self.joint_lower_limits[i], min(self.joint_upper_limits[i], cmd[i])) cmd[i] = np.clip(cmd[i], self.joint_lower_limits[i], self.joint_upper_limits[i])
self.j_cmd = cmd with self.command_lock:
self.target_positions = cmd
def get_feedback(self): def get_feedback(self):
""" """Get current joint positions"""
Get current robot state with self.feedback_lock:
return self.current_positions.copy()
Returns:
Dictionary with positions, velocities, etc.
"""
return self.j_fbk
def _simulation_loop(self): def _simulation_loop(self):
"""Main simulation loop (runs in separate thread)""" """Main simulation loop"""
last_time = time.time() last_time = time.time()
while self.running: while self.running:
# Process commands # Get target
with self.command_lock:
target = self.target_positions.copy()
# Get current state # Calculate distance to target
current_pos = self.data.qpos[:self.n_joints].copy() distance_to_target = target - self.current_command
self.j_fbk = self.data.qpos[:self.n_joints].copy()
current_vel = self.data.qvel[:self.n_joints].copy()
# Smooth interpolation # Adaptive velocity based on distance (slow down when close)
alpha = self.smoothness for i in range(self.n_joints):
next_positions = current_pos + alpha * (self.j_cmd - current_pos) dist = abs(distance_to_target[i])
if dist < self.slow_zone_radius:
# Slow down proportionally to distance
max_vel_for_joint = self.slow_zone_velocity * (dist / self.slow_zone_radius)
max_vel_for_joint = max(max_vel_for_joint, 0.05) # Minimum velocity
else:
max_vel_for_joint = self.max_vel
# Calculate velocities for smooth motion # Limit the step size
dt = self.model.opt.timestep max_step = max_vel_for_joint * self.control_dt
target_velocities = (next_positions - current_pos) / dt if abs(distance_to_target[i]) > max_step:
distance_to_target[i] = np.sign(distance_to_target[i]) * max_step
# Limit velocities # Apply acceleration limit
max_vel = 3.0 delta = distance_to_target
target_velocities = np.clip(target_velocities, -max_vel, max_vel) current_vel = (self.current_command - self.previous_command) / self.control_dt
desired_vel = delta / self.control_dt
max_vel_change = self.max_acc * self.control_dt
# Apply control vel_diff = desired_vel - current_vel
self.data.qvel[:self.n_joints] = target_velocities if np.any(np.abs(vel_diff) > max_vel_change):
vel_diff = np.clip(vel_diff, -max_vel_change, max_vel_change)
delta = (current_vel + vel_diff) * self.control_dt
# Corrective forces # Update command
pos_error = self.j_cmd - current_pos next_command = self.current_command + delta
kp = 30.0
self.data.qfrc_applied[:self.n_joints] = kp * pos_error
# Step simulation # Store for next iteration
self.previous_command = self.current_command.copy()
self.current_command = next_command.copy()
# Direct position control
self.data.qpos[:self.n_joints] = next_command
self.data.qvel[:self.n_joints] = 0
# Step physics
mujoco.mj_step(self.model, self.data) mujoco.mj_step(self.model, self.data)
# Maintain position (physics might change it)
self.data.qpos[:self.n_joints] = next_command
# Update feedback
with self.feedback_lock:
self.current_positions = self.data.qpos[:self.n_joints].copy()
# Sync viewer # Sync viewer
if self.viewer: if self.viewer:
self.viewer.sync() self.viewer.sync()
# Maintain real-time speed # Maintain control rate
elapsed = time.time() - last_time elapsed = time.time() - last_time
sleep_time = self.model.opt.timestep - elapsed sleep_time = self.control_dt - elapsed
if sleep_time > 0: if sleep_time > 0:
time.sleep(sleep_time) time.sleep(sleep_time)
last_time = time.time() last_time = time.time()
print(f'j_cmd: {self.j_cmd}, and j_fbk: {self.j_fbk}') def wait_for_convergence(self, tolerance=0.01, timeout=2.0):
"""Wait for robot to converge to target"""
start_time = time.time()
def print_state(self): while time.time() - start_time < timeout:
"""Print current robot state""" current = self.get_feedback()
feedback = self.get_feedback() with self.command_lock:
if feedback: target = self.target_positions
print(f"Positions: {[f'{p:.3f}' for p in feedback['positions'][:4]]}...")
print(f"Velocities: {[f'{v:.3f}' for v in feedback['velocities'][:4]]}...") error = np.max(np.abs(target - current))
if error < tolerance:
return True
time.sleep(0.01)
return False
# Example usage for kinematic verification def simple_test():
def verify_kinematics(): """Test with improved convergence"""
"""Test sequence to verify kinematic code"""
# Create controller
urdf_path = "/home/zl/Downloads/urdf_rm75/RM75-B.urdf" urdf_path = "/home/zl/Downloads/urdf_rm75/RM75-B.urdf"
robot = MuJoCoRobotController(urdf_path, smoothness=0.08, enable_viewer=True)
# Start simulation robot = SimplePositionController(
urdf_path,
max_velocity=2.0, # 2 rad/s
max_acceleration=5.0,
control_dt=0.01,
enable_viewer=True
)
robot.start() robot.start()
time.sleep(1) # Wait for simulation to initialize time.sleep(1)
print("\n" + "=" * 60) print("\n" + "=" * 60)
print("Kinematic Verification Test") print("Testing movement with smooth convergence")
print("=" * 60) print("=" * 60)
# Test 1: Single joint movement # Test 1: Move to 0.8 rad and back
print("\n[Test 1] Moving joint 1 to 45 degrees...") print("\n[Test 1] Moving to +0.8 rad")
cmd = np.zeros(7) robot.send_command([0.8, 0, 0, 0, 0, 0, 0])
cmd[0] = 0.785 # 45 degrees robot.wait_for_convergence(tolerance=0.01)
robot.send_command(cmd) pos = robot.get_feedback()
time.sleep(1) print(f" Converged to: {pos[0]:.4f} rad (error: {abs(pos[0] - 0.8):.4f})")
feedback = robot.get_feedback()
print(f" Result: joint_1 = {feedback} rad (expected 0.785)")
# Test 2: Multi-joint pose print("\n[Test 2] Moving to -0.8 rad")
print("\n[Test 2] Moving to complex pose...") robot.send_command([-0.8, 0, 0, 0, 0, 0, 0])
cmd = np.array([0.5, -0.4, 0.3, 0.2, 0, 0, 0]) robot.wait_for_convergence(tolerance=0.01)
robot.send_command(cmd) pos = robot.get_feedback()
feedback = robot.get_feedback() print(f" Converged to: {pos[0]:.4f} rad (error: {abs(pos[0] + 0.8):.4f})")
print(f" Result: {feedback}")
# Test 3: Return to home print("\n[Test 3] Moving to home (0 rad)")
print("\n[Test 3] Returning to home...") robot.send_command([0, 0, 0, 0, 0, 0, 0])
robot.send_command(np.zeros(7)) robot.wait_for_convergence(tolerance=0.01)
feedback = robot.get_feedback() pos = robot.get_feedback()
print(f" Result: home = {feedback}") print(f" Converged to: {pos[0]:.4f} rad (error: {abs(pos[0]):.4f})")
# Test 4: Continuous trajectory for kinematic verification print("\n[Test 4] Multi-joint movement")
print("\n[Test 4] Testing trajectory following...") robot.send_command([0.5, -0.3, 0.2, 0.1, 0, 0, 0])
trajectory = [ robot.wait_for_convergence(tolerance=0.01)
np.array([0.3, 0, 0, 0, 0, 0, 0]), pos = robot.get_feedback()
np.array([0.6, 0, 0, 0, 0, 0, 0]), print(f" Final positions: {[f'{p:.3f}' for p in pos[:4]]}...")
np.array([0.3, 0, 0, 0, 0, 0, 0]),
np.array([0, 0, 0, 0, 0, 0, 0]),
]
for i, cmd in enumerate(trajectory): print("\n[Test 5] Return home")
print(f" Step {i + 1}: sending {cmd[0]:.3f}") robot.send_command([0, 0, 0, 0, 0, 0, 0])
robot.send_command(cmd) robot.wait_for_convergence(tolerance=0.01)
feedback = robot.get_feedback() pos = robot.get_feedback()
print(f" Reached: {feedback}") print(f" Home positions: {[f'{p:.3f}' for p in pos[:4]]}...")
print("\nKinematic verification complete!") print("\nAll tests passed! Robot converges quickly to target.")
print("\nInteractive mode - send commands using robot.send_command()") print("\nInteractive mode - close viewer to exit")
# Keep running for interactive control
try: try:
while True: while robot.viewer and robot.viewer.is_running():
time.sleep(0.1) time.sleep(0.1)
except KeyboardInterrupt: except KeyboardInterrupt:
print("\nShutting down...") pass
robot.stop()
robot.stop()
def back_and_forth_test():
"""Back and forth test showing smooth convergence"""
urdf_path = "/home/zl/Downloads/urdf_rm75/RM75-B.urdf"
robot = SimplePositionController(
urdf_path,
max_velocity=2.0,
max_acceleration=5.0,
control_dt=0.01,
enable_viewer=True
)
robot.start()
time.sleep(1)
print("\n" + "=" * 60)
print("Back and forth movement with smooth convergence")
print("=" * 60)
targets = [[0, -0.524, 0, 0, 0, 0, 0],
[0.5, -0.4, 0.3, 0.2, 0.1, 0, 0],
[0.6, -0.5, 0.4, 0.2, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0]
]
for target in targets:
print(f"\nMoving to {target} rad...")
start_time = time.time()
robot.send_command(target)
# Wait for convergence
robot.wait_for_convergence(tolerance=0.01, timeout=2.0)
elapsed = time.time() - start_time
pos = robot.get_feedback()
error = sum( abs( np.array(pos) - np.array(target) ) )
print(f" Position: {pos[0]:.4f} rad, Error: {error:.4f} rad, Time: {elapsed:.2f}s")
# Brief pause to observe
time.sleep(0.3)
print("\n✓ Test complete! Close viewer to exit")
try:
while robot.viewer and robot.viewer.is_running():
time.sleep(0.1)
except KeyboardInterrupt:
pass
robot.stop()
if __name__ == "__main__": if __name__ == "__main__":
verify_kinematics() # Run the back and forth test with smooth convergence
back_and_forth_test()