"""
|
差速履带运动学/动力学模型。
|
"""
|
|
from __future__ import annotations
|
|
import dataclasses
|
import math
|
import random
|
from typing import Tuple
|
|
GRAVITY = 9.80665 # m/s^2
|
|
|
def _clamp(value: float, min_value: float, max_value: float) -> float:
|
return max(min_value, min(max_value, value))
|
|
|
def _wrap_angle(rad: float) -> float:
|
"""Wrap angle to [-pi, pi)."""
|
pi2 = math.tau # 2*pi
|
while rad >= math.pi:
|
rad -= pi2
|
while rad < -math.pi:
|
rad += pi2
|
return rad
|
|
|
@dataclasses.dataclass
|
class DifferentialDriveState:
|
east: float = 0.0
|
north: float = 0.0
|
up: float = 0.0
|
heading: float = 0.0 # rad, 数学坐标系 (东=0, CCW>0)
|
linear_velocity: float = 0.0 # m/s
|
angular_velocity: float = 0.0 # rad/s
|
pitch_deg: float = 0.0
|
roll_deg: float = 0.0
|
east_velocity: float = 0.0
|
north_velocity: float = 0.0
|
up_velocity: float = 0.0
|
body_accel_g: Tuple[float, float, float] = (0.0, 0.0, -1.0)
|
gyro_deg_s: Tuple[float, float, float] = (0.0, 0.0, 0.0)
|
temperature_c: float = 30.0
|
|
def copy(self) -> "DifferentialDriveState":
|
return dataclasses.replace(self)
|
|
|
class DifferentialDriveModel:
|
"""准静态差速履带模型,可模拟原地转向。"""
|
|
def __init__(
|
self,
|
track_width: float = 0.8,
|
max_linear_speed: float = 2.0,
|
max_linear_accel: float = 1.5,
|
max_angular_speed: float = math.radians(120.0),
|
max_angular_accel: float = math.radians(180.0),
|
):
|
self.track_width = track_width
|
self.max_linear_speed = max_linear_speed
|
self.max_linear_accel = max_linear_accel
|
self.max_angular_speed = max_angular_speed
|
self.max_angular_accel = max_angular_accel
|
self.state = DifferentialDriveState()
|
self._rng = random.Random(42)
|
|
def reset(self, east: float = 0.0, north: float = 0.0, up: float = 0.0, heading_deg: float = 0.0):
|
self.state = DifferentialDriveState(east=east, north=north, up=up, heading=math.radians(heading_deg))
|
|
def step(self, target_linear: float, target_angular: float, dt: float) -> DifferentialDriveState:
|
if dt <= 0.0:
|
return self.state
|
|
target_linear = _clamp(target_linear, -self.max_linear_speed, self.max_linear_speed)
|
target_angular = _clamp(target_angular, -self.max_angular_speed, self.max_angular_speed)
|
|
dv = target_linear - self.state.linear_velocity
|
max_dv = self.max_linear_accel * dt
|
dv = _clamp(dv, -max_dv, max_dv)
|
linear_acc = dv / dt
|
self.state.linear_velocity += dv
|
|
# 符号定义:
|
# - MCU已修复方向反转问题(去掉了错误的取反),现在直接使用MCU下发的角速度
|
# - 数学坐标系:东为0°,逆时针为正(CCW>0)
|
target_angular_math = target_angular # 直接使用,不再取反
|
|
dw = target_angular_math - self.state.angular_velocity
|
max_dw = self.max_angular_accel * dt
|
dw = _clamp(dw, -max_dw, max_dw)
|
angular_acc = dw / dt
|
self.state.angular_velocity += dw
|
|
# 更新姿态与位置
|
self.state.heading = _wrap_angle(self.state.heading + self.state.angular_velocity * dt)
|
cos_h = math.cos(self.state.heading)
|
sin_h = math.sin(self.state.heading)
|
self.state.east += self.state.linear_velocity * cos_h * dt
|
self.state.north += self.state.linear_velocity * sin_h * dt
|
self.state.up += 0.0 # 可扩展地形模型
|
|
# 速度 (ENU)
|
self.state.east_velocity = self.state.linear_velocity * cos_h
|
self.state.north_velocity = self.state.linear_velocity * sin_h
|
self.state.up_velocity = 0.0
|
|
# 体轴加速度:前向加速度 + 向心加速度
|
lateral_acc = self.state.linear_velocity * self.state.angular_velocity
|
ax_g = linear_acc / GRAVITY
|
ay_g = lateral_acc / GRAVITY
|
az_g = -1.0 # 假设水平地面
|
self.state.body_accel_g = (
|
ax_g + self._rng.gauss(0.0, 0.002),
|
ay_g + self._rng.gauss(0.0, 0.002),
|
az_g + self._rng.gauss(0.0, 0.0005),
|
)
|
|
# 姿态近似:由加速度分量映射成小角度
|
self.state.pitch_deg = math.degrees(math.atan2(ax_g, 1.0))
|
self.state.roll_deg = math.degrees(math.atan2(-ay_g, 1.0))
|
|
# 惯导(IMU)符号定义:与MCU方向定义保持一致,不再取反
|
# MCU已修复方向反转问题,现在直接使用angular_velocity
|
self.state.gyro_deg_s = (
|
self._rng.gauss(0.0, 0.01),
|
self._rng.gauss(0.0, 0.01),
|
math.degrees(self.state.angular_velocity) + self._rng.gauss(0.0, 0.1), # 直接使用,不再取反
|
)
|
|
temp_drift = self._rng.gauss(0.0, 0.0005)
|
self.state.temperature_c += temp_drift
|
|
return self.state
|