提问
python
#! /usr/bin/env python3
from control_planner import usvParam as P
from control_planner.courseController import courseLimitation
import tf_transformations # ROS2没有自带tf.transformations, 需装第三方库
import rclpy
from rclpy.node import Node
from pid_interfaces.msg import Command
import numpy as np
import random
import tf2_ros
import time
from geometry_msgs.msg import PointStamped, Vector3Stamped
class usvDynamics:
def __init__(self, listener, alpha=0):
# Initial state conditions
self.state = np.array([
[P.theta0], # initial angle
[P.thetadot0], # initial angular rate
[P.vel_u0], #initial velocity on x direction
[P.vel_v0]
])
# Mass of the arm, kg
self.m = P.m * (1.+alpha*(2.*np.random.rand()-1.))
# Damping coefficient, Ns
self.b = P.b * (1.+alpha*(2.*np.random.rand()-1.))
self.b_f = P.b_f * (1.+alpha*(2.*np.random.rand()-1.))
self.b_r = P.b_r * (1.+alpha*(2.*np.random.rand()-1.))
# the gravity constant is well known, so we don't change it.
self.g = P.g
# sample rate at which the dynamics are propagated
self.Ts = P.Ts
self.torque_limit = P.tau_max
#转动惯量
self.I_z = P.I_z
#propeller to center
self.r = P.r
self.angular_v = 0.0
self.listener = listener
def add_wind(self, data):
try:
# 1. 查找最新的transform,target='base_link', source='NED'
# 注意:ROS2的tf2中,lookup_transform(target, source, time)
# 返回geometry_msgs.msg.TransformStamped对象
trans = self.listener.lookup_transform(
'base_link', 'NED', rclpy.time.Time())
# 2. 获取平移和四元数
trans_vec = [
trans.transform.translation.x,
trans.transform.translation.y,
trans.transform.translation.z
]
quat = [
trans.transform.rotation.x,
trans.transform.rotation.y,
trans.transform.rotation.z,
trans.transform.rotation.w
]
# 3. wind_force_map(4维) → 旋转,只取XYZ分量(不要加第四个0)
wind_force_map = [data[0], data[1], 0]
# 4. 使用tf_transformations进行四元数旋转
v_rotated = tf_transformations.quaternion_matrix(quat)[:3, :3].dot(wind_force_map)
# 5. 累加平移,获得boat系下的力
wind_force_boat = [
v_rotated[0] + trans_vec[0],
v_rotated[1] + trans_vec[1],
v_rotated[2] + trans_vec[2]
]
return wind_force_boat
except Exception as e:
print(f"Exception1:{e}")
return [0, 0, 0]
def update(self, T_u, wind_u,angular_v):
self.angular_v = angular_v
# This is the external method that takes the input u at time
# t and returns the output n at time t.
# saturate the input torque
# T_u = self.saturate(T_u, self.torque_limit) #T_u is a list after editing
self.rk4_step(T_u,wind_u) # propagate the state by one time sample
psi,psi_dot,vel_u,vel_v = self.h() # return the corresponding output
#print("thetadot is:%f"%self.state.item(1))
#print("vel_v is:%f"%vel_v)
#print("vel_u is:%f"%vel_u)
# print(f"self.state:\n{self.state}")
return psi,psi_dot,vel_u,vel_v
def f(self, state, T_u,wind_u):
# Return edot = f(e,u), the system state update equations
# re-label states for readability
wind_force_boat = self.add_wind(wind_u)
# wind_force_boat = [0,0]
#计算力矩
T_l = T_u[0]
T_r = T_u[1]
theta = state.item(0)
thetadot = state.item(1)
vel_u = state.item(2)
vel_v = state.item(3)
#print("vel_v is: %f"%vel_v)
# calculate wind disturbance
# wind_psi= wind_u[0]
# wind_force = wind_u[1]
# wind_force_x = wind_force * np.cos(np.abs(wind_psi-theta)*np.pi/180)
# wind_force_y = wind_force * np.sin(np.abs(wind_psi-theta)*np.pi/180)
#calculate angle acceleration
tau = (T_l-T_r)*self.r - self.b * thetadot
thetaddot = tau/self.I_z + 0.1*vel_u*vel_v #m_33 rdot = T_r - d_33 r
#calculate acceleration on X&Y direction
force_X = (T_r+T_l) - self.b_f * vel_u + wind_force_boat[0] #wind_force_x
force_Y = - self.b_f * vel_v - wind_force_boat[1] #- self.b_r * thetadot wind_force_y
# print(f"wind_force_boat:{wind_force_boat}")
vel_udot = force_X / self.m + 0.01 *vel_v*thetadot
#print("vel_udot is: %f"%vel_udot)
vel_vdot = force_Y / self.m - 0.01 *vel_u*thetadot
edot = np.array([[thetadot],
[thetaddot],
[vel_udot],
[vel_vdot]
])
return edot
def h(self):
# return the output equations
# could also use input u if needed
theta = self.state.item(0)
#n = np.array([[theta]])
psi = courseLimitation(theta)
psi_dot = self.state.item(1)
vel_u = self.state.item(2)
vel_v = self.state.item(3)
return psi,psi_dot/10,vel_u,vel_v
def rk4_step(self, T_u, wind_u):
# Integrate ODE using Runge-Kutta RK4 algorithm
F1 = self.f(self.state, T_u, wind_u)
F2 = self.f(self.state + self.Ts / 2 * F1, T_u,wind_u)
F3 = self.f(self.state + self.Ts / 2 * F2, T_u,wind_u)
F4 = self.f(self.state + self.Ts * F3, T_u,wind_u)
self.state = self.state + self.Ts / 6 * (F1 + 2 * F2 + 2 * F3 + F4)
# self.state = np.array([[self.state.item(0)],[self.angular_v],[self.state.item(2)],[self.state.item(3)]])
def saturate(self, u, limit):
for i in range(len(u)):
T = u[i]
if abs(T) > limit:
T = limit*np.sign(T)
u[i] = T
return u
上面的python代码是模拟一个双螺旋桨差速驱动的水面机器人的动力学模型,现在,我想将动力学方程改为由舵-浆分离的解耦的机器人的动力学模型(即舵控制转向力矩,浆控制推进新速度),动力学方程为:
改进的Nomoto模型(速度相关):
T ( V ) d ψ d t + ψ = K ( V ) δ T(V) \frac{d \psi}{d t}+\psi=K(V) \delta T(V)dtdψ+ψ=K(V)δ
- K ( V ) ∝ V K(V) \propto V K(V)∝V 或 V 2 V^2 V2
- T ( V ) T(V) T(V) 可能随速度增加而减小(响应更快)
要求你在现有代码的基础上,进行修改(即大框架不变,只将反映动力学方程本身的那部分机制的代码进行修改即可,参数可以随便指定一个)
注意:add_wind代表的是动力学模型中引入风这个环境干扰,暂时你不用对其进行任何修改。
回答
你提供的现有模型是基于左右推进器差速产生扭矩和推进力的动力学,而你希望将其替换为 "舵-浆解耦模型" ,即使用浆推进速度 、舵角控制转向 的方式,并使用改进Nomoto模型建模转向动力学。
✅ 你的目标模型:
T ( V ) d ψ d t + ψ = K ( V ) δ T(V) \frac{d\psi}{dt} + \psi = K(V)\delta T(V)dtdψ+ψ=K(V)δ
这是一个一阶系统描述船体偏航角动态:
- \\psi:船体航向角
- \\delta:舵角输入
- V:前进速度(
vel_u
) - K(V) = K_0 \\cdot V 或 K_0 \\cdot V\^2
- T(V) = T_0 / (1 + cV)(表示速度越快,转向响应更快)
🔧 需要修改的地方:
主要修改以下函数中的动力学建模逻辑:
f(self, state, T_u, wind_u)
h(self)
- 去除对左右螺旋桨的扭矩建模
- 添加舵角控制变量
⚙️ 修改说明和替代变量:
T_u
参数将简化为[thrust_force, rudder_angle]
self.state
变成: ψ , ψ ˙ , v e l u , v e l v \psi, \dot{\psi}, vel_u, vel_v ψ,ψ˙,velu,velv- 保留
add_wind
不变
我将下一步在 PCI 中修改你的 usvDynamics
类动力学部分,并保留风扰动模型。
我已经根据改进Nomoto模型 完成了usvDynamics
类的核心改写。以下是关键修改点总结:
🔄 修改内容:
✅ 状态变量未变(4维):
- ψ(航向角), dψ/dt(角速度), vel_u(前进速度), vel_v(横向速度)
✅ 输入变为 [thrust_force, rudder_angle]
:
thrust_force
: 推进力,控制速度(前进)rudder_angle
: 舵角,控制航向
✅ 航向动力学采用 Nomoto 模型:
d 2 ψ d t 2 = K ( V ) δ − ψ T ( V ) \frac{d^2\psi}{dt^2} = \frac{K(V) \delta - \psi}{T(V)} dt2d2ψ=T(V)K(V)δ−ψ
K(V) = K_0 * V
T(V) = T_0 / (1 + T_{decay} * V)
🧪 示例参数:
K_0 = 0.5
T_0 = 5.0
K_power = 1
(线性随速度变化)T_decay = 0.3
✅ 保留:
add_wind
结构不变- RK4 时间推进方法不变
- 接口方法如
update()
、h()
完全兼容原框架
是否需要我将此代码与你原始文件合并输出、或者将该模型类导出为独立模块供你使用?
继续提问
将此代码与我的原始文件合并输出
回答
python
#! /usr/bin/env python3
from control_planner import usvParam as P
from control_planner.courseController import courseLimitation
import tf_transformations # ROS2没有自带tf.transformations, 需装第三方库
import rclpy
from rclpy.node import Node
from pid_interfaces.msg import Command
import numpy as np
import random
import tf2_ros
import time
from geometry_msgs.msg import PointStamped, Vector3Stamped
class usvDynamics:
def __init__(self, listener, alpha=0):
# Initial state conditions
self.state = np.array([
[P.theta0], # initial heading angle (psi)
[P.thetadot0], # initial heading rate (dpsi/dt)
[P.vel_u0], # initial surge velocity
[P.vel_v0] # initial sway velocity
])
self.m = P.m * (1. + alpha * (2. * np.random.rand() - 1.))
self.b_f = P.b_f * (1. + alpha * (2. * np.random.rand() - 1.))
self.b_r = P.b_r * (1. + alpha * (2. * np.random.rand() - 1.))
self.g = P.g
self.Ts = P.Ts
# Parameters for modified Nomoto model
self.K_0 = 0.5 # gain constant
self.T_0 = 5.0 # base time constant
self.K_power = 1 # linear dependence
self.T_decay = 0.3 # time constant decay with speed
self.listener = listener
def add_wind(self, data):
try:
trans = self.listener.lookup_transform(
'base_link', 'NED', rclpy.time.Time())
trans_vec = [
trans.transform.translation.x,
trans.transform.translation.y,
trans.transform.translation.z
]
quat = [
trans.transform.rotation.x,
trans.transform.rotation.y,
trans.transform.rotation.z,
trans.transform.rotation.w
]
wind_force_map = [data[0], data[1], 0]
v_rotated = tf_transformations.quaternion_matrix(quat)[:3, :3].dot(wind_force_map)
wind_force_boat = [
v_rotated[0] + trans_vec[0],
v_rotated[1] + trans_vec[1],
v_rotated[2] + trans_vec[2]
]
return wind_force_boat
except Exception as e:
print(f"Exception1:{e}")
return [0, 0, 0]
def update(self, T_u, wind_u, angular_v=0.0):
self.rk4_step(T_u, wind_u)
return self.h()
def f(self, state, T_u, wind_u):
wind_force_boat = self.add_wind(wind_u)
psi = state.item(0)
dpsi = state.item(1)
vel_u = state.item(2)
vel_v = state.item(3)
thrust_force = T_u[0] # propulsion
rudder_angle = T_u[1] # steering
# Nomoto model dynamics
K_v = self.K_0 * vel_u**self.K_power
T_v = self.T_0 / (1 + self.T_decay * vel_u)
ddpsi = (K_v * rudder_angle - psi) / T_v
force_X = thrust_force - self.b_f * vel_u + wind_force_boat[0]
force_Y = - self.b_r * vel_v - wind_force_boat[1]
vel_udot = force_X / self.m
vel_vdot = force_Y / self.m
return np.array([
[dpsi],
[ddpsi],
[vel_udot],
[vel_vdot]
])
def h(self):
theta = self.state.item(0)
psi = courseLimitation(theta)
psi_dot = self.state.item(1)
vel_u = self.state.item(2)
vel_v = self.state.item(3)
return psi, psi_dot, vel_u, vel_v
def rk4_step(self, T_u, wind_u):
F1 = self.f(self.state, T_u, wind_u)
F2 = self.f(self.state + self.Ts / 2 * F1, T_u, wind_u)
F3 = self.f(self.state + self.Ts / 2 * F2, T_u, wind_u)
F4 = self.f(self.state + self.Ts * F3, T_u, wind_u)
self.state = self.state + self.Ts / 6 * (F1 + 2 * F2 + 2 * F3 + F4)
def saturate(self, u, limit):
for i in range(len(u)):
T = u[i]
if abs(T) > limit:
T = limit * np.sign(T)
u[i] = T
return u
我已经将你的原始代码与新的基于"舵-浆解耦"模型的Nomoto动力学融合,保留了原有结构和风扰动模型,并替换了动力学核心。你现在可以在usvDynamics
类中使用如下输入控制:
python
T_u = [thrust_force, rudder_angle]
是否需要我帮你:
- 编写单元测试?
- 接入 ROS2 节点发布订阅逻辑?
- 可视化船体状态(如 heading/velocity 曲线)?
请告诉我下一步需求。