Skip to content

Commit 8237511

Browse files
authored
Merge pull request #38 from fmrico/fix_patgh_planning
Fix path planner and controllers
2 parents 39c780c + 925c3df commit 8237511

9 files changed

Lines changed: 250 additions & 53 deletions

File tree

controllers/easynav_mpc_controller/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ set(CMAKE_CXX_EXTENSIONS OFF)
1212
find_package(ament_cmake REQUIRED)
1313
find_package(easynav_common REQUIRED)
1414
find_package(easynav_core REQUIRED)
15+
find_package(easynav_system REQUIRED)
1516
find_package(pluginlib REQUIRED)
1617
find_package(geometry_msgs REQUIRED)
1718
find_package(tf2_ros REQUIRED)
@@ -44,6 +45,7 @@ target_include_directories(${PROJECT_NAME} PUBLIC
4445
target_link_libraries(${PROJECT_NAME} PUBLIC
4546
easynav_common::easynav_common
4647
easynav_core::easynav_core
48+
easynav_system::easynav_system
4749
tf2_ros::tf2_ros
4850
pluginlib::pluginlib
4951
Eigen3::Eigen

controllers/easynav_mpc_controller/include/easynav_mpc_controller/MPCController.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ class MPCController : public ControllerMethodBase
7373
double max_ang_vel_{1.5}; ///< Maximum angular velocity for MPC.
7474
bool verbose_{false}; ///< Value to debug on terminal
7575

76+
// Fallback goal tolerances if GoalManager does not publish them
77+
double fallback_goal_pos_tol_{0.05}; ///< Default positional tolerance (meters).
78+
double fallback_goal_yaw_tol_{0.05}; ///< Default angular tolerance (radians).
79+
7680
std::unique_ptr<MPCOptimizer> optimizer_; ///< MPC optimizer
7781

7882
rclcpp::Publisher<nav_msgs::msg::Path>::SharedPtr mpc_path_pub_; ///< Publisher for MPC path.

controllers/easynav_mpc_controller/package.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
<depend>easynav_common</depend>
1616
<depend>easynav_core</depend>
17+
<depend>easynav_system</depend>
1718
<depend>pluginlib</depend>
1819
<depend>tf2_ros</depend>
1920
<depend>geometry_msgs</depend>

controllers/easynav_mpc_controller/src/easynav_mpc_controller/MPCController.cpp

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
/// \brief Implementation of the MPCController class.
2222

2323
#include "easynav_mpc_controller/MPCController.hpp"
24+
#include "easynav_system/GoalManager.hpp"
2425

2526
namespace easynav
2627
{
@@ -42,13 +43,19 @@ MPCController::on_initialize()
4243
node->declare_parameter<double>(plugin_name + ".max_angular_velocity", max_ang_vel_);
4344
node->declare_parameter<bool>(plugin_name + ".verbose", verbose_);
4445

46+
node->declare_parameter<double>(plugin_name + ".fallback_goal_pos_tol", fallback_goal_pos_tol_);
47+
node->declare_parameter<double>(plugin_name + ".fallback_goal_yaw_tol", fallback_goal_yaw_tol_);
48+
4549
node->get_parameter<int>(plugin_name + ".horizon_steps", horizon_steps_);
4650
node->get_parameter<double>(plugin_name + ".dt", dt_);
4751
node->get_parameter<double>(plugin_name + ".safety_radius", safety_radius_);
4852
node->get_parameter<double>(plugin_name + ".max_linear_velocity", max_lin_vel_);
4953
node->get_parameter<double>(plugin_name + ".max_angular_velocity", max_ang_vel_);
5054
node->get_parameter<bool>(plugin_name + ".verbose", verbose_);
5155

56+
node->get_parameter<double>(plugin_name + ".fallback_goal_pos_tol", fallback_goal_pos_tol_);
57+
node->get_parameter<double>(plugin_name + ".fallback_goal_yaw_tol", fallback_goal_yaw_tol_);
58+
5259
optimizer_ = std::make_unique<MPCOptimizer>();
5360

5461
mpc_path_pub_ =
@@ -114,14 +121,26 @@ MPCController::collision_checker(void *data, std::vector<double> & u)
114121
void
115122
MPCController::update_rt(NavState & nav_state)
116123
{
124+
// If navigation is IDLE, force zero velocity
125+
if (nav_state.has("navigation_state")) {
126+
const auto nav_state_val = nav_state.get<easynav::GoalManager::State>("navigation_state");
127+
if (nav_state_val == easynav::GoalManager::State::IDLE) {
128+
cmd_vel_.header.stamp = get_node()->now();
129+
cmd_vel_.twist.linear.x = 0.0;
130+
cmd_vel_.twist.angular.z = 0.0;
131+
nav_state.set("cmd_vel", cmd_vel_);
132+
return;
133+
}
134+
}
135+
117136
if (!nav_state.has("path") || !nav_state.has("robot_pose") || !nav_state.has("points")) {
118137
if(verbose_) {
119138
std::cout << "No Path, No Points or No Robot Pose" << std::endl;
120139
}
121140
return;
122141
}
123142

124-
const auto path = nav_state.get<nav_msgs::msg::Path>("path");
143+
nav_msgs::msg::Path path = nav_state.get<nav_msgs::msg::Path>("path");
125144
if (path.poses.empty()) {
126145
// If the path is empty, stop the robot
127146
cmd_vel_.header.frame_id = path.header.frame_id;
@@ -132,6 +151,46 @@ MPCController::update_rt(NavState & nav_state)
132151
return;
133152
}
134153

154+
// Build a local path that:
155+
// 1) keeps only the segment that brings the robot closer to the goal, and
156+
// 2) prepends a short straight segment from the robot pose to that segment.
157+
const auto & robot_pose_msg = nav_state.get<nav_msgs::msg::Odometry>("robot_pose");
158+
const auto & robot_p = robot_pose_msg.pose.pose.position;
159+
160+
// Goal is the last point of the planner path
161+
const auto & goal_p = path.poses.back().pose.position;
162+
163+
nav_msgs::msg::Path local_path;
164+
local_path.header = path.header;
165+
local_path.poses.clear();
166+
167+
// 1) Find the first index that actually brings us closer to the goal than our current pose
168+
const double d_robot_goal = std::hypot(goal_p.x - robot_p.x, goal_p.y - robot_p.y);
169+
std::size_t start_idx = 0;
170+
for (std::size_t i = 0; i < path.poses.size(); ++i) {
171+
const auto & pi = path.poses[i].pose.position;
172+
const double d_pi_goal = std::hypot(goal_p.x - pi.x, goal_p.y - pi.y);
173+
if (d_pi_goal <= d_robot_goal) {
174+
start_idx = i;
175+
break;
176+
}
177+
}
178+
179+
// 2) Prepend a point at the robot position to ensure continuity from the current pose
180+
geometry_msgs::msg::PoseStamped robot_ps;
181+
robot_ps.header = path.header;
182+
robot_ps.pose.position = robot_p;
183+
robot_ps.pose.orientation = path.poses[start_idx].pose.orientation;
184+
local_path.poses.push_back(robot_ps);
185+
186+
// 3) Copy the remaining points from start_idx to the goal
187+
for (std::size_t i = start_idx; i < path.poses.size(); ++i) {
188+
local_path.poses.push_back(path.poses[i]);
189+
}
190+
191+
// Use the local path from now on
192+
path = local_path;
193+
135194
int num_elements = path.poses.size();
136195
size_t local_horizon;
137196
if (num_elements > horizon_steps_) {
@@ -214,6 +273,62 @@ MPCController::update_rt(NavState & nav_state)
214273

215274
collision_checker(&params, u);
216275

276+
// Final alignment phase with hysteresis on distance:
277+
// - Enter when dist_to_goal <= 0.5 * pos_tol
278+
// - Stay in this phase (even if dist grows slightly) until dist_to_goal > pos_tol
279+
{
280+
const auto & goal_pose = path.poses.back().pose;
281+
282+
double pos_tol = fallback_goal_pos_tol_;
283+
double yaw_tol = fallback_goal_yaw_tol_;
284+
285+
if (nav_state.has("goal_tolerance.position")) {
286+
pos_tol = nav_state.get<double>("goal_tolerance.position");
287+
}
288+
if (nav_state.has("goal_tolerance.yaw")) {
289+
yaw_tol = nav_state.get<double>("goal_tolerance.yaw");
290+
}
291+
292+
const double dx_g = goal_pose.position.x - pose.position.x;
293+
const double dy_g = goal_pose.position.y - pose.position.y;
294+
const double dist_to_goal = std::hypot(dx_g, dy_g);
295+
296+
const double yaw_goal = std::atan2(
297+
2.0 * (goal_pose.orientation.w * goal_pose.orientation.z +
298+
goal_pose.orientation.x * goal_pose.orientation.y),
299+
1.0 - 2.0 * (goal_pose.orientation.y * goal_pose.orientation.y +
300+
goal_pose.orientation.z * goal_pose.orientation.z));
301+
double e_theta_goal = std::atan2(std::sin(yaw_ - yaw_goal), std::cos(yaw_ - yaw_goal));
302+
303+
const double enter_dist = 0.5 * pos_tol;
304+
305+
// Hysteresis based on distance to goal: start aligning when we are
306+
// well inside the goal radius (enter_dist) and keep aligning until
307+
// we move clearly outside (dist_to_goal > pos_tol).
308+
const bool inside_hysteresis_band = (dist_to_goal <= pos_tol);
309+
const bool should_enter_alignment = (dist_to_goal <= enter_dist);
310+
311+
if ((inside_hysteresis_band && std::fabs(e_theta_goal) > yaw_tol) ||
312+
should_enter_alignment)
313+
{
314+
// Stay in place and rotate towards the goal orientation using a simple P controller
315+
const double k_align = 1.0;
316+
double vlin = 0.0;
317+
double vrot = -k_align * e_theta_goal;
318+
319+
vrot = std::clamp(vrot, -max_ang_vel_, max_ang_vel_);
320+
321+
cmd_vel_.header.frame_id = path.header.frame_id;
322+
cmd_vel_.header.stamp = get_node()->now();
323+
cmd_vel_.twist.linear.x = vlin;
324+
cmd_vel_.twist.angular.z = vrot;
325+
326+
nav_state.set("cmd_vel", cmd_vel_);
327+
publish_mpc_path(&params, u, path);
328+
return;
329+
}
330+
}
331+
217332
// Publish the computed velocity command
218333
cmd_vel_.header.frame_id = path.header.frame_id;
219334
cmd_vel_.header.stamp = get_node()->now();

controllers/easynav_mppi_controller/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ set(CMAKE_CXX_EXTENSIONS OFF)
1212
find_package(ament_cmake REQUIRED)
1313
find_package(easynav_common REQUIRED)
1414
find_package(easynav_core REQUIRED)
15+
find_package(easynav_system REQUIRED)
1516
find_package(pluginlib REQUIRED)
1617
find_package(geometry_msgs REQUIRED)
1718
find_package(tf2_ros REQUIRED)
@@ -32,6 +33,7 @@ target_include_directories(${PROJECT_NAME} PUBLIC
3233
target_link_libraries(${PROJECT_NAME} PUBLIC
3334
easynav_common::easynav_common
3435
easynav_core::easynav_core
36+
easynav_system::easynav_system
3537
tf2_ros::tf2_ros
3638
pluginlib::pluginlib
3739
${geometry_msgs_TARGETS}

controllers/easynav_mppi_controller/package.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
<depend>easynav_common</depend>
1313
<depend>easynav_core</depend>
14+
<depend>easynav_system</depend>
1415
<depend>pluginlib</depend>
1516
<depend>tf2_ros</depend>
1617
<depend>geometry_msgs</depend>

controllers/easynav_mppi_controller/src/easynav_mppi_controller/MPPIController.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
#include "easynav_mppi_controller/MPPIController.hpp"
2626
#include "easynav_common/types/PointPerception.hpp"
27+
#include "easynav_system/GoalManager.hpp"
2728

2829
#include "nav_msgs/msg/odometry.hpp"
2930

@@ -140,6 +141,27 @@ void MPPIController::publish_mppi_markers(
140141
void
141142
MPPIController::update_rt(NavState & nav_state)
142143
{
144+
// If navigation is IDLE, force zero velocity
145+
if (nav_state.has("navigation_state")) {
146+
const auto nav_state_val = nav_state.get<easynav::GoalManager::State>("navigation_state");
147+
if (nav_state_val == easynav::GoalManager::State::IDLE) {
148+
twist_stamped_.header.stamp = get_node()->now();
149+
twist_stamped_.twist.linear.x = 0.0;
150+
twist_stamped_.twist.angular.z = 0.0;
151+
nav_state.set("cmd_vel", twist_stamped_);
152+
153+
// Also clear visualization markers when idle
154+
visualization_msgs::msg::MarkerArray clear_markers;
155+
visualization_msgs::msg::Marker delete_all;
156+
delete_all.action = visualization_msgs::msg::Marker::DELETEALL;
157+
clear_markers.markers.push_back(delete_all);
158+
159+
mppi_candidates_pub_->publish(clear_markers);
160+
mppi_optimal_pub_->publish(clear_markers);
161+
return;
162+
}
163+
}
164+
143165
if (!nav_state.has("path") || !nav_state.has("robot_pose")) {
144166
return;
145167
}

0 commit comments

Comments
 (0)