Skip to content

Commit f8b9214

Browse files
authored
Update fixedwing dynamics (#22)
* add fixedwing sim and reformat Signed-off-by: wsribunm <worawissribunma@gmail.com> * update fixedwing model Signed-off-by: wsribunm <worawissribunma@gmail.com> * edit param for nvp sim Signed-off-by: wsribunm <worawissribunma@gmail.com> * fix fixedwing sim Signed-off-by: wsribunm <worawissribunma@gmail.com> * reformat Signed-off-by: wsribunm <worawissribunma@gmail.com> * remove orig file Signed-off-by: wsribunm <worawissribunma@gmail.com> --------- Signed-off-by: wsribunm <worawissribunma@gmail.com>
1 parent 78acfbe commit f8b9214

File tree

8 files changed

+3055
-99
lines changed

8 files changed

+3055
-99
lines changed

config/nvp_sim.config.rviz

Lines changed: 403 additions & 0 deletions
Large diffs are not rendered by default.

cyecca/models/fixedwing.py

Lines changed: 308 additions & 86 deletions
Large diffs are not rendered by default.

cyecca/models/fixedwing.py.orig

Lines changed: 598 additions & 0 deletions
Large diffs are not rendered by default.

cyecca/models/lookupTableFixedwing.py

Lines changed: 1092 additions & 0 deletions
Large diffs are not rendered by default.

launch/fixedwing_sim.xml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<launch>
2-
<node pkg="cyecca" exec="fixedwing_sim.py" output="screen"/>
3-
<node pkg="joy" exec="joy_node"/>
4-
</launch>
5-
<!-- vi: ts=4 sw=4 et-->
2+
<node pkg="cyecca" exec="fixedwing_sim.py" output="screen"/>
3+
<node pkg="joy" exec="joy_node"/>
4+
</launch>

launch/viewer_nvp.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<launch>
2+
<node pkg="rviz2" exec="rviz2" args="-d $(env HOME)/ros2_ws/src/cyecca/config/nvp_sim.config.rviz">
3+
<param name="use_sim_time" value="true"/>
4+
</node>
5+
</launch>
6+
<!-- vi: ts=4 sw=4 et-->

scripts/fixedwing_sim.py

Lines changed: 162 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env python3
2-
from cyecca.models import fixedwing
2+
from cyecca.models import fixedwing, lookupTableFixedwing
33

4+
# from cyecca.models.lookupTableFixedwing import build_tables
45
import casadi as ca
56
import numpy as np
67

@@ -9,12 +10,13 @@
910
from rclpy.node import Node
1011
from rclpy.parameter import Parameter
1112

12-
from geometry_msgs.msg import PoseWithCovarianceStamped, TransformStamped
13+
from geometry_msgs.msg import PoseWithCovarianceStamped, TransformStamped, Point
1314

1415
from rosgraph_msgs.msg import Clock
1516
from nav_msgs.msg import Odometry, Path
1617
from sensor_msgs.msg import Joy
1718
from tf2_ros import TransformBroadcaster
19+
from visualization_msgs.msg import Marker
1820

1921

2022
class Simulator(Node):
@@ -31,6 +33,12 @@ def __init__(self, x0=None, p=None):
3133
self.pub_pose = self.create_publisher(PoseWithCovarianceStamped, "pose", 1)
3234
self.pub_clock = self.create_publisher(Clock, "clock", 1)
3335
self.pub_odom = self.create_publisher(Odometry, "odom", 1)
36+
self.pub_lift = self.create_publisher(Marker, "lift", 1)
37+
self.pub_drag = self.create_publisher(Marker, "drag", 1)
38+
self.pub_weight = self.create_publisher(Marker, "weight", 1)
39+
self.pub_thrust = self.create_publisher(Marker, "thrust", 1)
40+
self.pub_side_force = self.create_publisher(Marker, "side_force", 1)
41+
self.pub_vel_b = self.create_publisher(Marker, "vel_b", 1)
3442

3543
self.tf_broadcaster = TransformBroadcaster(self)
3644

@@ -54,18 +62,34 @@ def __init__(self, x0=None, p=None):
5462
# ----------------------------------------------
5563
self.input_mode = "manual"
5664

65+
# -------------------------------------------------------
66+
# Lookup Table
67+
# -------------------------------------------------------
68+
69+
self.lookup_tab = lookupTableFixedwing.build_tables()
70+
self.coeff_data = {
71+
"CL": 0.0,
72+
"CD": 0.0,
73+
"Cy": 0.0,
74+
"Cl": 0.0,
75+
"Cm": 0.0,
76+
"Cn": 0.0,
77+
"Cmdr": 0.0,
78+
"Cmda": 0.0,
79+
}
5780
# -------------------------------------------------------
5881
# Dynamics
5982
# ----------------------------------------------
6083
dynamics = fixedwing
61-
self.model = dynamics.derive_model()
84+
self.model = dynamics.derive_model(coeff_data=self.coeff_data)
6285
self.x0_dict = self.model["x0_defaults"]
6386
if x0 is not None:
6487
for k in x0.keys():
6588
if not k in self.x0_dict.keys():
6689
raise KeyError(k)
6790
self.x0_dict[k] = x0[k]
6891
self.p_dict = self.model["p_defaults"]
92+
# print(self.p_dict)
6993
if p is not None:
7094
for k in p.keys():
7195
if not k in self.p_dict.keys():
@@ -74,9 +98,14 @@ def __init__(self, x0=None, p=None):
7498

7599
# init state (x), param(p), and input(u)
76100
self.state = np.array(list(self.x0_dict.values()), dtype=float)
77-
self.p = np.array(list(self.p_dict.values()), dtype=float)
101+
self.p = np.array(
102+
[
103+
self.p_dict[str(self.model["p"][i])]
104+
for i in range(self.model["p"].shape[0])
105+
],
106+
dtype=float,
107+
)
78108
self.u = np.zeros(4, dtype=float)
79-
80109
# start main loop on timer
81110
self.system_clock = rclpy.clock.Clock(
82111
clock_type=rclpy.clock.ClockType.SYSTEM_TIME
@@ -86,6 +115,7 @@ def __init__(self, x0=None, p=None):
86115
callback=self.timer_callback,
87116
clock=self.system_clock,
88117
)
118+
self.Info = None
89119

90120
# Manual Joy AETR
91121
def joy_callback(self, msg: Joy):
@@ -114,7 +144,7 @@ def auto_joy_callback(self, msg: Joy):
114144
self.input_auto = ca.vertcat(
115145
msg.axes[0], # thrust
116146
msg.axes[1], # aileron
117-
msg.axes[2], # elevator
147+
-msg.axes[2], # elevator -- positive = elevator down (pitch up)
118148
msg.axes[3], # rudder
119149
) # TAER
120150

@@ -139,9 +169,9 @@ def update_controller(self):
139169
elif self.input_mode == "auto":
140170
self.u = ca.vertcat( # TAER mode
141171
float(self.input_auto[0]),
142-
float(self.input_auto[1]), # scaled roll moment from rudder
172+
float(self.input_auto[1]),
143173
float(self.input_auto[2]),
144-
float(self.input_auto[3]), # Rudder directly affects yaw for nvp
174+
float(self.input_auto[3]),
145175
)
146176
else:
147177
self.get_logger().info("unhandled mode: %s" % self.input_mode)
@@ -151,14 +181,78 @@ def integrate_simulation(self):
151181
"""
152182
Integrate the simulation one step and calculate measurements
153183
"""
184+
RAD2DEG = 180 / ca.pi
185+
154186
try:
187+
# Grab from Look Up Table
188+
if self.Info == None:
189+
self.Info = {}
190+
self.Info["alpha"] = 0.0
191+
self.Info["beta"] = 0.0
192+
self.Info["ail"] = 0.0
193+
self.Info["elev"] = 0.0
194+
self.Info["rud"] = 0.0
195+
196+
else:
197+
self.coeff_data["CD"] = -1 * self.lookup_tab["Cx"](
198+
self.Info["alpha"] * RAD2DEG, self.Info["elev"] * RAD2DEG
199+
)
200+
self.coeff_data["Cmdr"] = -1 * self.lookup_tab["DnDr"](
201+
self.Info["alpha"] * RAD2DEG, self.Info["beta"] * RAD2DEG / 2
202+
)
203+
self.coeff_data["Cmda"] = -1 * self.lookup_tab["DlDa"](
204+
self.Info["alpha"] * RAD2DEG, self.Info["beta"] * RAD2DEG / 2
205+
)
206+
self.coeff_data["CL"] = self.lookup_tab["Cz"](
207+
self.Info["alpha"] * RAD2DEG,
208+
self.Info["beta"] * RAD2DEG,
209+
self.Info["elev"] * RAD2DEG,
210+
)
211+
self.coeff_data["Cy"] = self.lookup_tab["Cy"](
212+
self.Info["beta"] * RAD2DEG,
213+
self.Info["ail"] * RAD2DEG,
214+
self.Info["rud"] * RAD2DEG,
215+
)
216+
self.coeff_data["Cl"] = -1 * self.lookup_tab["Cl"](
217+
self.Info["alpha"] * RAD2DEG, self.Info["beta"] * RAD2DEG
218+
)
219+
self.coeff_data["Cm"] = -1 * self.lookup_tab["Cm"](
220+
self.Info["alpha"] * RAD2DEG, self.Info["elev"] * RAD2DEG
221+
)
222+
self.coeff_data["Cn"] = -1 * self.lookup_tab["Cn"](
223+
self.Info["alpha"] * RAD2DEG, self.Info["beta"] * RAD2DEG
224+
)
225+
226+
dynamics = fixedwing
227+
# print("Cm", self.coeff_data["Cm"])
228+
self.model = dynamics.derive_model(coeff_data=self.coeff_data)
229+
155230
# opts = {"abstol": 1e-9,"reltol":1e-9,"fsens_err_con": True,"calc_ic":True,"calc_icB":True}
231+
opts = {"abstol": 1e-2, "reltol": 1e-6, "fsens_err_con": True}
232+
xdot = self.model["f"](self.state, self.u, self.p)
233+
234+
# RK4
235+
# f_int = ca.integrator(
236+
# "test", "rk", self.model["dae"], {"t0": self.t, "tf": self.t + self.dt}
237+
# )
238+
# res = f_int(x0=self.state, z0=ca.DM.zeros(self.model["z"].size1()), p=self.p, u=self.u)
239+
240+
# IDAS
241+
# f_int = ca.integrator(
242+
# "test", "idas", self.model["dae"], {"t0": self.t, "tf": self.t + self.dt, **opts}
243+
# )
244+
# res = f_int(x0=self.state, z0=ca.DM.zeros(self.model["z"].size1()), p=self.p, u=self.u)
245+
246+
# #CVODES
156247
f_int = ca.integrator(
157-
"test", "cvodes", self.model["dae"], self.t, self.t + self.dt
248+
"test", "cvodes", self.model["dae"], self.t, self.t + self.dt, opts
158249
)
159250
res = f_int(x0=self.state, z0=0.0, p=self.p, u=self.u)
251+
160252
except RuntimeError as e:
161253
print(e)
254+
xdot = self.model["f"](x=self.state, u=self.u, p=self.p)
255+
print(xdot)
162256
raise e
163257

164258
x1 = np.array(res["xf"]).reshape(-1)
@@ -170,6 +264,7 @@ def integrate_simulation(self):
170264
# store states and measurements
171265
# ---------------------------------------------------------------------
172266
self.state = np.array(res["xf"]).reshape(-1)
267+
self.Info = self.model["Info"](x=self.state, u=self.u, p=self.p)
173268

174269
self.publish_state()
175270

@@ -222,6 +317,64 @@ def publish_state(self):
222317
tf.transform.rotation.z = qz
223318
self.tf_broadcaster.sendTransform(tf)
224319

320+
# ------------------------------------
321+
# publish tf2 transform vector lift
322+
# ------------------------------------
323+
def vector(v, name, color, scale):
324+
marker = Marker()
325+
marker.header.frame_id = "base_link"
326+
marker.ns = name
327+
marker.id = 0
328+
marker.type = Marker.ARROW
329+
marker.action = Marker.ADD
330+
marker.header.stamp = msg_clock.clock
331+
marker.pose.position.x = 0.0
332+
marker.pose.position.y = 0.0
333+
marker.pose.position.z = 0.0
334+
marker.pose.orientation.w = 1.0
335+
marker.pose.orientation.x = 0.0
336+
marker.pose.orientation.y = 0.0
337+
marker.pose.orientation.z = 0.0
338+
marker.scale.x = 0.05
339+
marker.scale.y = 0.1
340+
marker.scale.z = 0.1
341+
marker.color.r = color[0]
342+
marker.color.g = color[1]
343+
marker.color.b = color[2]
344+
marker.color.a = color[3]
345+
346+
p1 = Point()
347+
p1.x = 0.0
348+
p1.y = 0.0
349+
p1.z = 0.0
350+
p2 = Point()
351+
p2.x = float(v[0] * scale)
352+
p2.y = float(v[1] * scale)
353+
p2.z = float(v[2] * scale)
354+
marker.points.append(p1)
355+
marker.points.append(p2)
356+
return marker
357+
358+
self.pub_lift.publish(
359+
vector(self.Info["L_b"], "lift", [1.0, 0.0, 0.0, 1.0], 1.0)
360+
)
361+
self.pub_drag.publish(
362+
vector(self.Info["D_b"], "drag", [0.0, 0.0, 1.0, 1.0], 1.0)
363+
)
364+
self.pub_weight.publish(
365+
vector(self.Info["W_b"], "weight", [0.0, 1.0, 0.0, 1.0], 1.0)
366+
)
367+
self.pub_thrust.publish(
368+
vector(self.Info["T_b"], "thrust", [1.0, 0.0, 1.0, 1.0], 1.0)
369+
)
370+
self.pub_side_force.publish(
371+
vector(self.Info["S_b"], "side_force", [1.0, 1.0, 0.0, 1.0], 1.0)
372+
)
373+
374+
self.pub_vel_b.publish(
375+
vector(self.Info["v_b"], "vel_b", [0.0, 1.0, 1.0, 1.0], 0.1)
376+
)
377+
print("alpha", self.Info["alpha"], "CL", self.Info["CL"], "CD", self.Info["CD"])
225378
# ------------------------------------
226379
# publish pose with covariance stamped
227380
# ------------------------------------

0 commit comments

Comments
 (0)