| """Trot Gait Controller for Spot robot.""" |
| import numpy as np |
| import os |
| import importlib.util |
|
|
| |
| _controllers_dir = os.path.dirname(os.path.abspath(__file__)) |
| _base_path = os.path.join(_controllers_dir, "base.py") |
| _spec = importlib.util.spec_from_file_location("spot_base", _base_path) |
| _base_module = importlib.util.module_from_spec(_spec) |
| _spec.loader.exec_module(_base_module) |
| BaseController = _base_module.BaseController |
|
|
|
|
| class TrotGaitController(BaseController): |
| """ |
| Classical trot gait controller for Spot quadruped. |
| |
| Trot gait: Diagonal legs move together (FL+HR, FR+HL). |
| Uses simple sinusoidal trajectories for leg motion. |
| """ |
|
|
| |
| STANDING_POSE = np.array([ |
| 0.0, 1.04, -1.8, |
| 0.0, 1.04, -1.8, |
| 0.0, 1.04, -1.8, |
| 0.0, 1.04, -1.8, |
| ], dtype=np.float32) |
|
|
| def __init__(self, num_joints: int = 12): |
| super().__init__(num_joints) |
|
|
| |
| self.cmd = np.array([0.0, 0.0, 0.0], dtype=np.float32) |
|
|
| |
| self.gait_freq = 2.0 |
| self.swing_height = 0.3 |
| self.stride_length = 0.25 |
|
|
| |
| |
| self.phase_offsets = np.array([0.0, 0.5, 0.5, 0.0]) |
|
|
| def set_command(self, vx: float = 0.0, vy: float = 0.0, vyaw: float = 0.0): |
| """Set velocity command for the robot.""" |
| self.cmd[0] = np.clip(vx, -1.0, 1.0) |
| self.cmd[1] = np.clip(vy, -0.5, 0.5) |
| self.cmd[2] = np.clip(vyaw, -1.5, 1.5) |
|
|
| def get_command(self): |
| """Get current velocity command.""" |
| return self.cmd.copy() |
|
|
| def compute_action(self, obs: np.ndarray, data) -> np.ndarray: |
| """Compute target joint positions for trot gait.""" |
| vx, vy, vyaw = self.cmd |
|
|
| |
| speed = np.sqrt(vx**2 + vy**2 + vyaw**2) |
| if speed < 0.05: |
| |
| return self.STANDING_POSE.copy() |
|
|
| |
| phase = (self.time * self.gait_freq) % 1.0 |
|
|
| |
| hy_stand = 1.04 |
| kn_stand = -1.8 |
|
|
| |
| target = np.zeros(12, dtype=np.float32) |
|
|
| for leg_idx in range(4): |
| |
| leg_phase = (phase + self.phase_offsets[leg_idx]) % 1.0 |
|
|
| |
| in_swing = leg_phase < 0.5 |
|
|
| |
| if in_swing: |
| p = leg_phase / 0.5 |
| else: |
| p = (leg_phase - 0.5) / 0.5 |
|
|
| |
| |
| |
| if in_swing: |
| |
| hy_offset = self.stride_length * vx * (p - 0.5) |
| else: |
| |
| hy_offset = self.stride_length * vx * (0.5 - p) |
|
|
| |
| if in_swing: |
| |
| kn_offset = -self.swing_height * np.sin(np.pi * p) |
| else: |
| kn_offset = 0.0 |
|
|
| |
| is_left = leg_idx in [0, 2] |
| hx_offset = 0.0 |
| if abs(vyaw) > 0.01: |
| |
| sign = 1.0 if is_left else -1.0 |
| if in_swing: |
| hx_offset = sign * 0.1 * vyaw * np.sin(np.pi * p) |
|
|
| |
| if abs(vy) > 0.01: |
| sign = 1.0 if is_left else -1.0 |
| if in_swing: |
| hx_offset += sign * 0.2 * vy * (p - 0.5) |
| else: |
| hx_offset += sign * 0.2 * vy * (0.5 - p) |
|
|
| |
| base_idx = leg_idx * 3 |
| target[base_idx + 0] = hx_offset |
| target[base_idx + 1] = hy_stand + hy_offset |
| target[base_idx + 2] = kn_stand + kn_offset |
|
|
| return target |
|
|
| def reset(self): |
| super().reset() |
| self.cmd = np.array([0.0, 0.0, 0.0], dtype=np.float32) |
|
|
| @property |
| def name(self) -> str: |
| cmd = self.cmd |
| return f"Trot Gait (cmd: {cmd[0]:.1f}, {cmd[1]:.1f}, {cmd[2]:.1f})" |
|
|