Formula Student Autonomous Systems
The code for the main driverless system
Loading...
Searching...
No Matches
vehicle_dynamics_node.py
Go to the documentation of this file.
1import rclpy
2from rclpy.node import Node
3from custom_interfaces.msg import (
4 Velocities,
5 WheelRPM,
6 SteeringAngle,
7)
8from std_msgs.msg import Float64MultiArray
9from geometry_msgs.msg import Vector3Stamped
10import message_filters
11import math
12
13
15 def __init__(self):
16 super().__init__("vehicle_dynamics_publisher")
17 self.get_logger().info("Vehicle Dynamics Node started")
18
19 velocity_sub = message_filters.Subscriber(
20 self, Velocities, "/state_estimation/velocities"
21 )
22 yaw_rate_sub = message_filters.Subscriber(
23 self, Vector3Stamped, "/imu/acceleration"
24 )
25 steering_angle_sub = message_filters.Subscriber(
26 self, SteeringAngle, "/vehicle/bosch_steering_angle"
27 )
28 angle_sync = message_filters.ApproximateTimeSynchronizer(
29 [velocity_sub, yaw_rate_sub, steering_angle_sub], queue_size=10, slop=0.05
30 )
31 angle_sync.registerCallback(self.slip_angle_callbackslip_angle_callback)
32
33 rl_rpm_sub = message_filters.Subscriber(self, WheelRPM, "/vehicle/rl_rpm")
34 rr_rpm_sub = message_filters.Subscriber(self, WheelRPM, "/vehicle/rr_rpm")
35 fl_rpm_sub = message_filters.Subscriber(self, WheelRPM, "/vehicle/fl_rpm")
36 fr_rpm_sub = message_filters.Subscriber(self, WheelRPM, "/vehicle/fr_rpm")
37 rpm_sync = message_filters.ApproximateTimeSynchronizer(
38 [rl_rpm_sub, rr_rpm_sub, fl_rpm_sub, fr_rpm_sub], queue_size=10, slop=0.05
39 )
40 rpm_sync.registerCallback(self.slip_ratio_callbackslip_ratio_callback)
41
42 self.slip_angle_pub = self.create_publisher(
43 Float64MultiArray, "/vehicle/slip_angles", 10
44 )
45 self.slip_ratio_pub = self.create_publisher(
46 Float64MultiArray, "/vehicle/slip_ratio", 10
47 )
48
49 self.distance_pub = self.create_publisher(
50 Float64MultiArray, "/vehicle/wheels_distance", 10
51 )
52
53 self.distance_cg_pub = self.create_publisher(
54 Float64MultiArray, "/vehicle/cg_distance", 10
55 )
56
57 self.path_cuvatures_pub = self.create_publisher(
58 Float64MultiArray, "/vehicle/path_curvatures", 10
59 )
60
61 self.last_rpm_time = None
62 self.last_est_time = None
64 "distance_fl": 0.0,
65 "distance_fr": 0.0,
66 "distance_rl": 0.0,
67 "distance_rr": 0.0,
68 }
69 self.last_distance_est = {"distance_cg": 0.0}
70 self.latest_vx = None
72 self.latest_yaw_rate = None
73
74 def slip_angle_callback(self, velocities_msg, yaw_rate_msg, steering_angle_msg):
75 """
76 Callback function for synchronized velocity, yaw_rate, and steering angle data.
77 Computes slip angles for the vehicle's center of gravity and all four wheels.
78
79 Args:
80 velocities_msg (Velocities): Estimated vehicle velocity data.
81 yaw_rate_msg (Vector3Stamped): IMU acc with yaw_rate component.
82 steering_angle_msg (SteeringAngle): Steering angle input from the vehicle.
83
84 Returns:
85 None
86 """
87 vx = velocities_msg.velocity_x
88 vy = velocities_msg.velocity_y
89 steering_angle = steering_angle_msg.steering_angle
90 yaw_rate = yaw_rate_msg.vector.x
91
92 self.latest_vx = vx
93 self.latest_steering_angle = steering_angle
94 self.latest_yaw_rate = yaw_rate
95
96 L = 1.53 # wheelbase
97 T = 1.2 # track width
98 wf = 0.47 # front weight distribution
99
100 slip_angle_beta = math.atan2(vy, vx)
101
102 v_FL_x = vx - yaw_rate * (T / 2)
103 v_FL_y = vy + yaw_rate * (L * wf)
104 alpha_FL = abs(steering_angle - math.atan2(v_FL_y, v_FL_x))
105
106 v_FR_x = vx + yaw_rate * (T / 2)
107 v_FR_y = vy + yaw_rate * (L * wf)
108 alpha_FR = abs(steering_angle - math.atan2(v_FR_y, v_FR_x))
109
110 v_RL_x = vx - yaw_rate * (T / 2)
111 v_RL_y = vy - yaw_rate * (L * (1 - wf))
112 alpha_RL = abs(math.atan2(v_RL_y, v_RL_x))
113
114 v_RR_x = vx + yaw_rate * (T / 2)
115 v_RR_y = vy - yaw_rate * (L * (1 - wf))
116 alpha_RR = abs(math.atan2(v_RR_y, v_RR_x))
117
118 slip_angles_msg = Float64MultiArray()
119 slip_angles_msg.data = [
120 math.degrees(slip_angle_beta),
121 math.degrees(alpha_FL),
122 math.degrees(alpha_FR),
123 math.degrees(alpha_RL),
124 math.degrees(alpha_RR),
125 ]
126
127 self.slip_angle_pub.publish(slip_angles_msg)
128
129 self.get_logger().info(
130 f"Published Slip Angles (deg): CG: {math.degrees(slip_angle_beta):.2f}, FL: {math.degrees(alpha_FL):.2f}, FR: {math.degrees(alpha_FR):.2f}, RL: {math.degrees(alpha_RL):.2f}, RR: {math.degrees(alpha_RR):.2f}, Yaw Rate: {yaw_rate:.3f}"
131 )
132
133 # Calculate and publish distance traveled (m) since last message using average speed
134
135 current_time = self.get_clock().now().nanoseconds / 1e9
136 if self.last_est_time is not None:
137 dt = current_time - self.last_est_time
138 v_total = math.sqrt(vx**2 + vy**2)
139 distance_cg = v_total * dt
140 self.last_est_time = current_time
141 self.last_distance_est["distance_cg"] += distance_cg
142 distance_cg_msg = Float64MultiArray()
143 distance_cg_msg.data = [distance_cg, self.last_distance_est["distance_cg"]]
144 self.distance_cg_pub.publish(distance_cg_msg)
145 self.get_logger().info(
146 f"Published Distance CG: {distance_cg:.3f} m, Time Delta: {dt:.3f} s"
147 )
148
149 def slip_ratio_callback(self, rl_rpm_msg, rr_rpm_msg, fl_rpm_msg, fr_rpm_msg):
150 """
151 Callback function for synchronized wheel speed sensor (WSS) data.
152 Computes the left and right slip ratio using front and rear wheel velocities.
153
154 Args:
155 rl_rpm_msg (WheelRPM): Rear left wheel RPM message.
156 rr_rpm_msg (WheelRPM): Rear right wheel RPM message.
157 fl_rpm_msg (WheelRPM): Front left wheel RPM message.
158 fr_rpm_msg (WheelRPM): Front right wheel RPM message.
159
160 Returns:
161 None
162 """
163 wheel_radius = 0.203
164
165 V_RL = (rl_rpm_msg.rl_rpm * 2 * math.pi * wheel_radius) / 60
166 V_RR = (rr_rpm_msg.rr_rpm * 2 * math.pi * wheel_radius) / 60
167 V_FL = (fl_rpm_msg.fl_rpm * 2 * math.pi * wheel_radius) / 60
168 V_FR = (fr_rpm_msg.fr_rpm * 2 * math.pi * wheel_radius) / 60
169
170 avg_rear_velocity = 0.5 * (V_RR + V_RL)
171 slip_ratio_left = (avg_rear_velocity / V_FL) - 1 if V_FL != 0 else float("nan")
172 slip_ratio_right = (avg_rear_velocity / V_FR) - 1 if V_FR != 0 else float("nan")
173
174 slip_ratios_msg = Float64MultiArray()
175 slip_ratios_msg.data = [slip_ratio_left, slip_ratio_right]
176
177 self.slip_ratio_pub.publish(slip_ratios_msg)
178
179 # Calculate and publish distance traveled (m) since last message using average rear speed
180 current_time = (
181 rl_rpm_msg.header.stamp.sec + rl_rpm_msg.header.stamp.nanosec * 1e-9
182 )
183 if self.last_rpm_time is not None:
184 dt = current_time - self.last_rpm_time
185 distance_fr = V_FR * dt
186 distance_fl = V_FL * dt
187 distance_rl = V_RL * dt
188 distance_rr = V_RR * dt
189 self.last_distance_rpm["distance_fr"] += distance_fr
190 self.last_distance_rpm["distance_fl"] += distance_fl
191 self.last_distance_rpm["distance_rl"] += distance_rl
192 self.last_distance_rpm["distance_rr"] += distance_rr
193 distance_msg = Float64MultiArray()
194 distance_msg.data = [
195 distance_fr,
196 distance_fl,
197 distance_rl,
198 distance_rr,
199 self.last_distance_rpm["distance_fr"],
200 self.last_distance_rpm["distance_fl"],
201 self.last_distance_rpm["distance_rl"],
202 self.last_distance_rpm["distance_rr"],
203 ]
204 self.distance_pub.publish(distance_msg)
205 else:
206 self.get_logger().info("First RPM sync received, skipping distance calc.")
207
208 self.last_rpm_time = current_time
209
210 # Calculate and publish path curvature (1/m) using average rear speed
211
212 if self.latest_vx is not None and self.latest_steering_angle is not None:
213 cinematic = math.tan(self.latest_steering_angle) / 1.53
214 dynamic = (
215 self.latest_yaw_rate / self.latest_vx
216 if self.latest_vx != 0
217 else float("nan")
218 )
219
220 curvature_msg = Float64MultiArray()
221 curvature_msg.data = [cinematic, dynamic]
222 self.path_curvature_pub.publish(curvature_msg)
223
224 self.get_logger().info(
225 f"Published Path Curvature: Cinematic = {cinematic:.4f}, Dynamic = {dynamic:.4f}"
226 )
227
228 self.get_logger().info(
229 f"Published Slip Ratio: Left: {slip_ratio_left:.3f}, Right: {slip_ratio_right:.3f}"
230 )
231
232
233def main(args=None):
234 """
235 Main function for the Vehicle Dynamics module.
236 Args:
237 args (list): Optional command-line arguments.
238 """
239 rclpy.init(args=args)
241 rclpy.spin(node)
242 node.destroy_node()
243 rclpy.shutdown()
244
245
246if __name__ == "__main__":
247 main()
slip_ratio_callback(self, rl_rpm_msg, rr_rpm_msg, fl_rpm_msg, fr_rpm_msg)
slip_angle_callback(self, velocities_msg, yaw_rate_msg, steering_angle_msg)
Definition main.py:1