2121// / \brief Implementation of the MPCController class.
2222
2323#include " easynav_mpc_controller/MPCController.hpp"
24+ #include " easynav_system/GoalManager.hpp"
2425
2526namespace 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)
114121void
115122MPCController::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 (¶ms, 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 (¶ms, 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 ();
0 commit comments