Skip to content

Commit 3d7bdb1

Browse files
tune controller
1 parent 601d37d commit 3d7bdb1

3 files changed

Lines changed: 137 additions & 31 deletions

File tree

GEMstack/knowledge/defaults/e4_gazebo.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ control:
1212
brake_amount : 0.5
1313
brake_speed : 2.0
1414
pure_pursuit:
15-
lookahead: 4.0
16-
lookahead_scale: 1.0
17-
crosstrack_gain: 1.0
15+
lookahead: 2.5
16+
lookahead_scale: 3.0
17+
crosstrack_gain: 0.5
1818
desired_speed: trajectory
1919
longitudinal_control:
20-
pid_p: 0.1
21-
pid_i: 0.5
22-
pid_d: 0.5
20+
pid_p: 1.5
21+
pid_i: 0.2
22+
pid_d: 0.0
2323

2424
#configure the simulator, if using
2525
simulator:

GEMstack/onboard/interface/gem_gazebo.py

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from .gem import *
22
from ...utils import settings
33
import math
4+
import time
45

56
# ROS Headers
67
import rospy
@@ -74,6 +75,7 @@ def __init__(self):
7475
self.ackermann_pub = rospy.Publisher(
7576
'/ackermann_cmd', AckermannDrive, queue_size=1)
7677
self.ackermann_cmd = AckermannDrive()
78+
self.last_command = None # Store the last command
7779

7880
# Subscribe to IMU topic by default
7981
self.imu_sub = rospy.Subscriber("/imu", Imu, self.imu_callback)
@@ -214,21 +216,64 @@ def hardware_faults(self) -> List[str]:
214216
return self.faults
215217

216218
def send_command(self, command : GEMVehicleCommand):
219+
# Throttle rate at which we send commands
220+
t = self.time()
221+
if t < self.last_command_time + 1.0/self.max_send_rate:
222+
# Skip command, similar to hardware interface
223+
return
224+
self.last_command_time = t
217225

218-
v = self.last_reading.speed
219-
# convert pedal to acceleration
220-
accelerator_pedal_position = np.clip(command.accelerator_pedal_position,0.0,1.0)
221-
brake_pedal_position = np.clip(command.brake_pedal_position,0.0,1.0)
222-
acceleration = pedal_positions_to_acceleration(accelerator_pedal_position,brake_pedal_position,v,0,1)
223-
acceleration = np.clip(acceleration,-2, 1)
224-
# convert wheel angle to steering angle
226+
# Get current speed
227+
v = self.last_reading.speed
228+
229+
# Convert pedal to acceleration
230+
accelerator_pedal_position = np.clip(command.accelerator_pedal_position, 0.0, 1.0)
231+
brake_pedal_position = np.clip(command.brake_pedal_position, 0.0, 1.0)
232+
233+
# Zero out accelerator if brake is active (just like hardware interface)
234+
if brake_pedal_position > 0.0:
235+
accelerator_pedal_position = 0.0
236+
237+
# Calculate acceleration from pedal positions
238+
acceleration = pedal_positions_to_acceleration(accelerator_pedal_position, brake_pedal_position, v, 0, 1)
239+
240+
# Apply reasonable limits to acceleration
241+
max_accel = settings.get('vehicle.limits.max_acceleration', 1.0)
242+
max_decel = settings.get('vehicle.limits.max_deceleration', -2.0)
243+
acceleration = np.clip(acceleration, max_decel, max_accel)
244+
245+
# Convert wheel angle to steering angle (front wheel angle)
225246
phides = steer2front(command.steering_wheel_angle)
226247

227-
#create and publish drive message
248+
# Apply steering angle limits
249+
min_wheel_angle = settings.get('vehicle.geometry.min_wheel_angle', -0.6)
250+
max_wheel_angle = settings.get('vehicle.geometry.max_wheel_angle', 0.6)
251+
phides = np.clip(phides, min_wheel_angle, max_wheel_angle)
252+
253+
# Calculate target speed based on acceleration
254+
# Don't use infinite speed, instead calculate a reasonable target speed
255+
current_speed = v
256+
target_speed = current_speed
257+
258+
if acceleration > 0:
259+
# Accelerating - set target speed to current speed plus some increment
260+
# This is more realistic than infinite speed
261+
max_speed = settings.get('vehicle.limits.max_speed', 10.0)
262+
target_speed = min(current_speed + acceleration * 0.5, max_speed)
263+
elif acceleration < 0:
264+
# Braking - set target speed to zero if deceleration is significant
265+
if brake_pedal_position > 0.1:
266+
target_speed = 0.0
267+
268+
# Create and publish drive message
228269
msg = AckermannDrive()
229270
msg.acceleration = acceleration
230-
msg.speed = float('inf') if acceleration >0 else 0 #acceleration * self.dt
271+
msg.speed = target_speed
231272
msg.steering_angle = phides
232-
273+
msg.steering_angle_velocity = command.steering_wheel_speed # Respect steering velocity limit
274+
275+
# Debug output
276+
print(f"[ACKERMANN] Speed: {msg.speed:.2f}, Accel: {msg.acceleration:.2f}, Steer: {msg.steering_angle:.2f}")
277+
233278
self.ackermann_pub.publish(msg)
234279
self.last_command = command

testing/test_comfort_metrics.py

Lines changed: 76 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import numpy as np
99
from matplotlib.collections import LineCollection
1010
from matplotlib.colors import Normalize
11+
from matplotlib import cm
1112
import os
1213

1314
CMAP = "RdYlGn"
@@ -104,13 +105,18 @@ def compute_derivative(times, values):
104105
"""
105106
dt = np.diff(times)
106107
dv = np.diff(values)
107-
derivative = dv / dt
108+
109+
# Avoid division by zero or very small values
110+
mask = np.abs(dt) > 1e-10
111+
derivative = np.zeros_like(dt)
112+
derivative[mask] = dv[mask] / dt[mask]
113+
108114
return times[1:], derivative
109115

110116
def add_safety_colorbar(figure):
111117
"""Adds a colorbar to the right of the figure"""
112118
cbar_ax = figure.add_axes([0.92, 0.2, 0.02, 0.6])
113-
sm = plt.cm.ScalarMappable(cmap=CMAP)
119+
sm = cm.ScalarMappable(cmap=CMAP)
114120
cbar = figure.colorbar(sm, cax=cbar_ax)
115121
cbar.set_label("Comfort/Safety Level")
116122

@@ -149,9 +155,18 @@ def plot_jerk(axis, time, jerk, safe_thresh=1.0, unsafe_thresh=2.5):
149155
lc.set_array(safety[:-1])
150156
axis.add_collection(lc)
151157

152-
# set limits & labels
158+
# Set axis limits safely, avoiding NaN/Inf values
159+
if len(jerk) > 0:
160+
valid_jerk = jerk[~np.isnan(jerk) & ~np.isinf(jerk)]
161+
if len(valid_jerk) > 0:
162+
ymin, ymax = valid_jerk.min(), valid_jerk.max()
163+
# Add small margin if min equals max
164+
if ymin == ymax:
165+
ymin -= 0.1
166+
ymax += 0.1
167+
axis.set_ylim(ymin, ymax)
168+
153169
axis.set_xlim(time.min(), time.max())
154-
axis.set_ylim(jerk.min(), jerk.max())
155170
axis.set_xlabel("Time (s)")
156171
axis.set_ylabel("Jerk (m/s³)")
157172
axis.set_title("Vehicle Jerk Over Time")
@@ -171,8 +186,17 @@ def plot_acceleration(axis, time, acceleration, safe_thresh=0.5, unsafe_thresh=1
171186
lc.set_array(safety[:-1])
172187
axis.add_collection(lc)
173188

189+
# Set axis limits safely
190+
if len(acceleration) > 0:
191+
valid_accel = acceleration[~np.isnan(acceleration) & ~np.isinf(acceleration)]
192+
if len(valid_accel) > 0:
193+
ymin, ymax = valid_accel.min(), valid_accel.max()
194+
if ymin == ymax:
195+
ymin -= 0.1
196+
ymax += 0.1
197+
axis.set_ylim(ymin, ymax)
198+
174199
axis.set_xlim(time.min(), time.max())
175-
axis.set_ylim(acceleration.min(), acceleration.max())
176200
axis.set_xlabel("Time (s)")
177201
axis.set_ylabel("Acceleration (m/s²)")
178202
axis.set_title("Vehicle Acceleration Over Time")
@@ -192,8 +216,17 @@ def plot_heading_acceleration(axis, time, heading_acc, safe_thresh=0.0075, unsaf
192216
lc.set_array(safety[:-1])
193217
axis.add_collection(lc)
194218

219+
# Set axis limits safely
220+
if len(heading_acc) > 0:
221+
valid_heading = heading_acc[~np.isnan(heading_acc) & ~np.isinf(heading_acc)]
222+
if len(valid_heading) > 0:
223+
ymin, ymax = valid_heading.min(), valid_heading.max()
224+
if ymin == ymax:
225+
ymin -= 0.1
226+
ymax += 0.1
227+
axis.set_ylim(ymin, ymax)
228+
195229
axis.set_xlim(time.min(), time.max())
196-
axis.set_ylim(heading_acc.min(), heading_acc.max())
197230
axis.set_xlabel("Time (s)")
198231
axis.set_ylabel("Heading Acceleration (rad/s²)")
199232
axis.set_title("Vehicle Heading Acceleration Over Time")
@@ -214,10 +247,17 @@ def plot_crosstrack_error(axis, time, cte, safe_thresh=0.1, unsafe_thresh=0.4):
214247
lc.set_linewidth(2.0)
215248
axis.add_collection(lc)
216249

217-
# set axis limits
250+
# Set axis limits safely
251+
if len(cte) > 0:
252+
valid_cte = cte[~np.isnan(cte) & ~np.isinf(cte)]
253+
if len(valid_cte) > 0:
254+
ymin, ymax = valid_cte.min(), valid_cte.max()
255+
if ymin == ymax:
256+
ymin -= 0.1
257+
ymax += 0.1
258+
axis.set_ylim(ymin, ymax)
259+
218260
axis.set_xlim(time.min(), time.max())
219-
axis.set_ylim(cte.min(), cte.max())
220-
221261
axis.set_xlabel("Time (s)")
222262
axis.set_ylabel("Cross Track Error")
223263
axis.set_title("Vehicle Cross Track Error Over Time")
@@ -286,17 +326,31 @@ def plot_pedestrian_dist(axis, pedestrian_times, pedestrian_distances, safe_thre
286326

287327
# Parse behavior log file and compute metrics
288328
times, accelerations, heading_rates, ped_times, ped_distances = parse_behavior_log(behavior_file)
289-
time_jerk, jerk = compute_derivative(times, accelerations)
290-
time_heading_acc, heading_acc = compute_derivative(times, heading_rates)
329+
330+
# Ensure we have valid data before computing derivatives
331+
if len(times) > 1 and len(accelerations) > 1 and len(heading_rates) > 1:
332+
time_jerk, jerk = compute_derivative(times, accelerations)
333+
time_heading_acc, heading_acc = compute_derivative(times, heading_rates)
334+
else:
335+
print("Warning: Not enough data points to compute derivatives.")
336+
time_jerk, jerk = np.array([]), np.array([])
337+
time_heading_acc, heading_acc = np.array([]), np.array([])
291338

292339
# Pure pursuit tracker file exists: parse and plot all metrics
293340
if os.path.exists(tracker_file):
294341
vehicle_time, cte, x_actual, y_actual, x_desired, y_desired = parse_tracker_csv(tracker_file)
295342
plot_metrics(time_jerk, jerk, time_heading_acc, heading_acc, times, accelerations, vehicle_time, cte,
296343
x_actual, y_actual, x_desired, y_desired, ped_times, ped_distances)
297344

298-
print("RMS Cross Track Error:", np.sqrt(np.mean(cte**2)), "m")
299-
print("RMS Position Error:", np.sqrt(np.mean((x_actual - x_desired)**2 + (y_actual - y_desired)**2)), 'm')
345+
# Calculate RMS values only for valid data points
346+
valid_cte = cte[~np.isnan(cte) & ~np.isinf(cte)]
347+
if len(valid_cte) > 0:
348+
print("RMS Cross Track Error:", np.sqrt(np.mean(valid_cte**2)), "m")
349+
350+
valid_pos_error = np.sqrt((x_actual - x_desired)**2 + (y_actual - y_desired)**2)
351+
valid_pos_error = valid_pos_error[~np.isnan(valid_pos_error) & ~np.isinf(valid_pos_error)]
352+
if len(valid_pos_error) > 0:
353+
print("RMS Position Error:", np.sqrt(np.mean(valid_pos_error**2)), 'm')
300354
# Pure pursuit tracker file is missing: plot only behavior.json metrics
301355
else:
302356
print("Tracker file is missing. Skipping cross track error and vehicle position plots.")
@@ -310,7 +364,14 @@ def plot_pedestrian_dist(axis, pedestrian_times, pedestrian_distances, safe_thre
310364
add_safety_colorbar(fig)
311365
plt.show()
312366

313-
print("RMS Jerk:", np.sqrt(np.mean(jerk**2)), "m/s³")
314-
print("RMS Heading Acceleration:", np.sqrt(np.mean(heading_acc**2)), "rad/s²")
367+
# Calculate RMS values only for valid data points
368+
valid_jerk = jerk[~np.isnan(jerk) & ~np.isinf(jerk)]
369+
if len(valid_jerk) > 0:
370+
print("RMS Jerk:", np.sqrt(np.mean(valid_jerk**2)), "m/s³")
371+
372+
valid_heading_acc = heading_acc[~np.isnan(heading_acc) & ~np.isinf(heading_acc)]
373+
if len(valid_heading_acc) > 0:
374+
print("RMS Heading Acceleration:", np.sqrt(np.mean(valid_heading_acc**2)), "rad/s²")
375+
315376
if len(ped_distances) > 0:
316377
print("Minimum Pedestrian Distance:", np.min(ped_distances), "m")

0 commit comments

Comments
 (0)