waypoints_navi.cpp
Go to the documentation of this file.
00001 /*
00002  * Copyright (c) 2013, Jorge Santos, Yujin Robot.
00003  * All rights reserved.
00004  *
00005  * Redistribution and use in source and binary forms, with or without
00006  * modification, are permitted provided that the following conditions are met:
00007  *
00008  *     * Redistributions of source code must retain the above copyright
00009  *       notice, this list of conditions and the following disclaimer.
00010  *     * Redistributions in binary form must reproduce the above copyright
00011  *       notice, this list of conditions and the following disclaimer in the
00012  *       documentation and/or other materials provided with the distribution.
00013  *     * Neither the name of Yujin Robot nor the names of its
00014  *       contributors may be used to endorse or promote products derived from
00015  *       this software without specific prior written permission.
00016  *
00017  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00018  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00019  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00020  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
00021  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
00022  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
00023  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
00024  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
00025  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
00026  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00027  * POSSIBILITY OF SUCH DAMAGE.
00028  */
00029 
00030 #include <fstream>
00031 #include <yaml-cpp/yaml.h>
00032 
00033 #include <tf/tf.h>
00034 #include <tf/transform_listener.h>
00035 #include <ros/ros.h>
00036 #include <geometry_msgs/PoseStamped.h>
00037 #include <geometry_msgs/PointStamped.h>
00038 #include <move_base_msgs/MoveBaseAction.h>
00039 #include <actionlib/client/simple_action_client.h>
00040 #include <visualization_msgs/MarkerArray.h>
00041 
00042 #include <yocs_math_toolkit/common.hpp>
00043 #include <yocs_math_toolkit/geometry.hpp>
00044 
00045 #ifdef HAVE_NEW_YAMLCPP
00046 // The >> operator disappeared in yaml-cpp 0.5, so this function is
00047 // added to provide support for code written under the yaml-cpp 0.3 API.
00048 template<typename T>
00049 void operator >> (const YAML::Node& node, T& i)
00050 {
00051   i = node.as<T>();
00052 }
00053 #endif
00054 
00055 class WaypointsGoalNode
00056 {
00057 public:
00058   WaypointsGoalNode() : mode_(NONE), state_(IDLE), move_base_ac_("move_base", true)
00059   {
00060   }
00061 
00062   bool init()
00063   {
00064     ros::NodeHandle nh;
00065     ros::NodeHandle pnh("~");
00066 
00067     pnh.param("frequency",      frequency_,     1.0);
00068     pnh.param("close_enough",   close_enough_,  0.3);  // close enough to next waypoint
00069     pnh.param("goal_timeout",   goal_timeout_, 30.0);  // maximum time to reach a waypoint
00070     pnh.param("waypoints_file", waypoints_file_, std::string());
00071     pnh.param("robot_frame",    robot_frame_,    std::string("/base_footprint"));
00072     pnh.param("world_frame",    world_frame_,    std::string("/map"));
00073 
00074     // Wait for the move_base action servers to come up
00075     ros::Time t0 = ros::Time::now();
00076     double timeout = 10.0;
00077 
00078     while ((move_base_ac_.waitForServer(ros::Duration(1.0)) == false) && (ros::ok() == true))
00079     {
00080       if ((ros::Time::now() - t0).toSec() > timeout/2.0)
00081         ROS_WARN_THROTTLE(3, "Waiting for move_base action server to come up...");
00082 
00083       if ((ros::Time::now() - t0).toSec() > timeout)
00084       {
00085         ROS_ERROR("Timeout while waiting for move_base action server to come up");
00086         return false;
00087       }
00088     }
00089 
00090     wp_markers_pub_ = nh.advertise<visualization_msgs::MarkerArray>("waypoint_marker", 1, true);
00091 
00092     final_goal_sub_ = nh.subscribe("final_goal", 1, &WaypointsGoalNode::finalGoalCB, this);
00093     waypoints_sub_  = nh.subscribe("waypoints",  1, &WaypointsGoalNode::waypointCB, this);
00094 
00095     return true;
00096   }
00097 
00098   void finalGoalCB(const geometry_msgs::PoseStamped::ConstPtr& goal)
00099   {
00100     if ((state_ > IDLE) && (state_ < COMPLETED))
00101     {
00102       ROS_INFO("Already have a task; cancelling...");
00103       resetWaypoints();
00104       cancelAllGoals();
00105     }
00106     else
00107     {
00108       ROS_INFO("Goal received with %lu waypoints; lets' go!", waypoints_.size());
00109 
00110       goal_  = *goal;
00111       mode_  = GOAL;
00112       state_ = START;
00113 
00114       waypoints_it_ = waypoints_.begin();
00115     }
00116   }
00117 
00118   void waypointCB(const geometry_msgs::PointStamped::ConstPtr& point)
00119   {
00120     if ((state_ > IDLE) && (state_ < COMPLETED))
00121     {
00122       ROS_DEBUG("Already have a task; ignoring additional points");
00123     }
00124     else if ((waypoints_.size() > 1) && (mtk::distance2D(point->point, waypoints_.front().point) < 0.2))
00125     {
00126       // Waypoints loop closed; assume we are in loop mode and start moving
00127       ROS_INFO("Waypoints loop closed with %lu points; lets' go!", waypoints_.size());
00128 
00129       mode_  = LOOP;
00130       state_ = START;
00131 
00132       waypoints_it_ = waypoints_.begin();
00133     }
00134     else
00135     {
00136       ROS_DEBUG("Waypoint added: %s", mtk::point2str(point->point));
00137       waypoints_.push_back(*point);
00138     }
00139   }
00140 
00141   bool cancelAllGoals(double timeout = 2.0)
00142   {
00143     actionlib::SimpleClientGoalState goal_state = move_base_ac_.getState();
00144     if ((goal_state != actionlib::SimpleClientGoalState::ACTIVE) &&
00145         (goal_state != actionlib::SimpleClientGoalState::PENDING) &&
00146         (goal_state != actionlib::SimpleClientGoalState::RECALLED) &&
00147         (goal_state != actionlib::SimpleClientGoalState::PREEMPTED))
00148     {
00149       // We cannot cancel a REJECTED, ABORTED, SUCCEEDED or LOST goal
00150       ROS_WARN("Cannot cancel move base goal, as it has %s state!", goal_state.toString().c_str());
00151       return true;
00152     }
00153 
00154     ROS_INFO("Canceling move base goal with %s state...", goal_state.toString().c_str());
00155     move_base_ac_.cancelAllGoals();
00156     if (move_base_ac_.waitForResult(ros::Duration(timeout)) == false)
00157     {
00158       ROS_WARN("Cancel move base goal didn't finish after %.2f seconds: %s",
00159                timeout, goal_state.toString().c_str());
00160       return false;
00161     }
00162 
00163     ROS_INFO("Cancel move base goal succeed. New state is %s", goal_state.toString().c_str());
00164     return true;
00165   }
00166 
00167   void resetWaypoints()
00168   {
00169     ROS_DEBUG("Full reset: clear markers, delete waypoints and goal and set state to IDLE");
00170 
00171     publishMarkers(true);  // clear all markers
00172     waypoints_.clear();
00173     waypoints_it_ = waypoints_.end();
00174     goal_  = NOWHERE;
00175     mode_  = NONE;
00176     state_ = IDLE;
00177   }
00178 
00179   void publishMarkers(bool clear = false)
00180   {
00181     if ((state_ == IDLE) || (wp_markers_pub_.getNumSubscribers() == 0))
00182       return;
00183 
00184     visualization_msgs::MarkerArray markers_array;
00185     visualization_msgs::Marker marker, label;
00186 
00187     marker.header.frame_id = world_frame_;
00188     marker.header.stamp = ros::Time::now();
00189     marker.scale.x = 0.08;  // scale in meters
00190     marker.scale.y = 0.08;
00191     marker.scale.z = 0.01;
00192     marker.pose.position.z = marker.scale.z/2.0;
00193     marker.color.r = 0.8f;
00194     marker.color.g = 0.2f;
00195     marker.color.b = 0.2f;
00196 
00197     int index = 0;
00198     std::vector<geometry_msgs::PointStamped>::iterator it;
00199     for (it = waypoints_.begin(); it != waypoints_.end(); it++)
00200     {
00201       std::stringstream name;
00202       name << "WP " << index;
00203       marker.ns = name.str();
00204       marker.id = index;
00205       marker.pose.position.x = it->point.x;
00206       marker.pose.position.y = it->point.y;
00207       marker.type = visualization_msgs::Marker::CYLINDER;
00208       if ((clear == true) || ((mode_ == GOAL) && (it < waypoints_it_)))  // We are fully reseting waypoints list
00209         marker.action = visualization_msgs::Marker::DELETE;              // or this waypoint has being reached
00210       else
00211         marker.action = visualization_msgs::Marker::ADD;
00212       marker.color.a = (it == waypoints_it_)?1.0f:0.6f; // only next waypoint is solid
00213 
00214       label = marker;
00215       label.id = marker.id + 1000000;  // marker id must be unique
00216       label.type = visualization_msgs::Marker::TEXT_VIEW_FACING;
00217       label.pose.position.z = marker.pose.position.z + marker.scale.z/2.0 + 0.05; // just above the marker
00218       label.text = marker.ns.substr(3);  // i.e. strlen("WP ")
00219       label.scale.x = 0.1;
00220       label.scale.y = 0.1;
00221       label.scale.z = 0.1;
00222 
00223       markers_array.markers.push_back(marker);
00224       markers_array.markers.push_back(label);
00225 
00226       index++;
00227     }
00228 
00229     if (mode_ == GOAL)
00230     {
00231       marker.ns = "GOAL";
00232       marker.id = 666666;
00233       marker.type = visualization_msgs::Marker::ARROW;
00234       if (clear == true)
00235         marker.action = visualization_msgs::Marker::DELETE;
00236       else
00237         marker.action = visualization_msgs::Marker::ADD;
00238       marker.scale.x = 0.5;   // scale in metres
00239       marker.scale.y = 0.08;  // planar short and wide arrow
00240       marker.scale.z = 0.001;
00241       marker.pose.orientation = goal_.pose.orientation;
00242       marker.pose.position.x = goal_.pose.position.x;
00243       marker.pose.position.y = goal_.pose.position.y;
00244       marker.pose.position.z = 0.0005;
00245       marker.color.r = 0.8f;
00246       marker.color.g = 0.2f;
00247       marker.color.b = 0.2f;
00248       marker.color.a = 1.0f;
00249 
00250       markers_array.markers.push_back(marker);
00251     }
00252 
00253     wp_markers_pub_.publish(markers_array);
00254   }
00255 
00256   void spin()
00257   {
00258     move_base_msgs::MoveBaseGoal mb_goal;
00259 
00260     ros::Rate rate(frequency_);
00261 
00262     while (ros::ok())
00263     {
00264       ros::spinOnce();
00265       rate.sleep();
00266 
00267       publishMarkers();
00268 
00269       if ((state_ <= READY) || (state_ >= COMPLETED))
00270         continue;
00271 
00272       if (state_ == ACTIVE)
00273       {
00274         actionlib::SimpleClientGoalState goal_state = move_base_ac_.getState();
00275         if ((goal_state == actionlib::SimpleClientGoalState::ACTIVE) ||
00276             (goal_state == actionlib::SimpleClientGoalState::PENDING) ||
00277             (goal_state == actionlib::SimpleClientGoalState::RECALLED) ||
00278             (goal_state == actionlib::SimpleClientGoalState::PREEMPTED))
00279         {
00280           // We are still pursuing a goal...
00281 
00282           if ((ros::Time::now() - mb_goal.target_pose.header.stamp).toSec() >= goal_timeout_)
00283           {
00284             ROS_WARN("Cannot reach goal after %.2f seconds; request a new one (current state is %s)",
00285                       goal_timeout_, move_base_ac_.getState().toString().c_str());
00286           }
00287           else if (equals(mb_goal.target_pose, goal_) == false)
00288           {
00289             // When close enough to current goal (except for the final one!), go for the
00290             // next waypoint, so we avoid the final slow approach and subgoal obsession
00291             tf::StampedTransform robot_gb, goal_gb;
00292             try
00293             {
00294               tf_listener_.lookupTransform(world_frame_, robot_frame_, ros::Time(0.0), robot_gb);
00295             }
00296             catch (tf::TransformException& e)
00297             {
00298               ROS_WARN("Cannot get tf %s -> %s: %s", world_frame_.c_str(), robot_frame_.c_str(), e.what());
00299               continue;
00300             }
00301 
00302             mtk::pose2tf(mb_goal.target_pose, goal_gb);
00303             double distance = mtk::distance2D(robot_gb, goal_gb);
00304             if (distance > close_enough_)
00305             {
00306               continue;
00307             }
00308             else
00309             {
00310               ROS_DEBUG("Close enough to current goal (%.2f <= %.2f m); request a new one", distance, close_enough_);
00311             }
00312           }
00313           else
00314           {
00315             // Keep going until reaching the final goal
00316             continue;
00317           }
00318         }
00319         else if (equals(mb_goal.target_pose, goal_) == true)
00320         {
00321           resetWaypoints();
00322 
00323           // Final goal achieved or failed; check what happened
00324           if (move_base_ac_.getState() != actionlib::SimpleClientGoalState::SUCCEEDED)
00325           {
00326             ROS_ERROR("Go to goal failed: %s", move_base_ac_.getState().toString().c_str());
00327           }
00328           else
00329           {
00330             ROS_INFO("Go to goal successfully completed: %.2f, %.2f, %.2f",
00331                      mb_goal.target_pose.pose.position.x, mb_goal.target_pose.pose.position.y,
00332                      tf::getYaw(mb_goal.target_pose.pose.orientation));
00333           }
00334 
00335           continue;
00336         }
00337       } // if (state_ == ACTIVE)
00338 
00339       // If we are here is because we need a new goal (or the initial one!)
00340 
00341 
00343       if ((waypoints_.size() > 0) && (equals(mb_goal.target_pose.pose.position, waypoints_it_->point) == true))
00344       {
00345         waypoints_it_++;
00346 
00347         if (mode_ == LOOP)
00348         {
00349           if (waypoints_it_ == waypoints_.end())
00350             waypoints_it_ = waypoints_.begin();
00351         }
00352       }
00353 
00354       if (waypoints_it_ < waypoints_.end())
00355       {
00356         mb_goal.target_pose.header.stamp = ros::Time::now();
00357         mb_goal.target_pose.header.frame_id = world_frame_;
00358         mb_goal.target_pose.pose.position = waypoints_it_->point;
00359         mb_goal.target_pose.pose.orientation = tf::createQuaternionMsgFromYaw(0.0);  // TODO use the heading from robot loc to next (front)
00360       }
00361       else if (mode_ == GOAL)
00362       {
00363         mb_goal.target_pose = goal_;
00364         mb_goal.target_pose.header.stamp = ros::Time::now();
00365       }
00366       else
00367       {
00368         ROS_ERROR("Impossible situation.  M: %d  S: %d", mode_,  state_);
00369         break;
00370       }
00371 
00372       ROS_INFO("New goal: %.2f, %.2f, %.2f",
00373                mb_goal.target_pose.pose.position.x, mb_goal.target_pose.pose.position.y,
00374                tf::getYaw(mb_goal.target_pose.pose.orientation));
00375 
00376       // TODO This is a horrible workaround for a problem I cannot solve: send a new goal
00377       // when the previous one has been cancelled return immediately with succeeded state
00378       int times_sent = 0;
00379       do
00380       {
00381         move_base_ac_.sendGoal(mb_goal);
00382         times_sent++;
00383       } while ((move_base_ac_.waitForResult(ros::Duration(0.1)) == true) &&
00384                (move_base_ac_.getState() == actionlib::SimpleClientGoalState::SUCCEEDED));
00385 
00386       if (times_sent > 1)
00387         ROS_WARN("Again the strange case of instantaneous goals... (goal sent %d times)", times_sent);
00388 
00389       state_ = ACTIVE;
00390     }
00391   }
00392 
00393 private:
00394   const geometry_msgs::PoseStamped NOWHERE;
00395 
00396   enum { NONE = 0,
00397          GOAL,
00398          LOOP
00399        } mode_;
00400 
00401   enum { IDLE = 0,
00402          READY,
00403          START,
00404          ACTIVE,
00405          COMPLETED
00406        } state_;
00407 
00408   double      frequency_;
00409   double      close_enough_;
00410   double      goal_timeout_;
00411   std::string robot_frame_;
00412   std::string world_frame_;
00413 
00414   std::string waypoints_file_;
00415 
00416   std::vector<geometry_msgs::PointStamped>           waypoints_;
00417   std::vector<geometry_msgs::PointStamped>::iterator waypoints_it_;
00418   geometry_msgs::PoseStamped goal_;
00419 
00420   tf::TransformListener tf_listener_;
00421   ros::Publisher     wp_markers_pub_;
00422   ros::Subscriber    final_goal_sub_;
00423   ros::Subscriber    waypoints_sub_;
00424 
00425   actionlib::SimpleActionClient<move_base_msgs::MoveBaseAction> move_base_ac_;
00426 
00427 
00428   bool readWaypointsFile(std::string file)
00429   {
00430     waypoints_.clear();
00431     try
00432     {
00433       /*********************
00434       ** Yaml File Parsing
00435       **********************/
00436       std::ifstream ifs(file.c_str(), std::ifstream::in);
00437       if (ifs.good() == false)
00438       {
00439         ROS_ERROR("Waypoints file not found [%s]", file.c_str());
00440         return false;
00441       }
00442 
00443       YAML::Node doc;
00444 #ifdef HAVE_NEW_YAMLCPP
00445       doc = YAML::Load(ifs);
00446 
00447       const YAML::Node &wp_node_tmp = doc["waypoints"];
00448       const YAML::Node *wp_node = wp_node_tmp ? &wp_node_tmp : NULL;
00449 #else
00450       YAML::Parser parser(ifs);
00451       parser.GetNextDocument(doc);
00452 
00453       const YAML::Node *wp_node = doc.FindValue("waypoints");
00454 #endif
00455       if (wp_node != NULL)
00456       {
00457         for (unsigned int i = 0; i < wp_node->size(); i++)
00458         {
00459           // Parse waypoint entries on YAML
00460           geometry_msgs::PointStamped point;
00461 
00462           (*wp_node)[i]["point"]["x"]          >> point.point.x;
00463           (*wp_node)[i]["point"]["y"]          >> point.point.y;
00464           (*wp_node)[i]["point"]["z"]          >> point.point.z;
00465 
00466           waypoints_.push_back(point);
00467         }
00468       }
00469       else
00470       {
00471         ROS_DEBUG("No waypoints in file; assuming go to goal mode");  // still a valid file in goal mode
00472       }
00473 
00474 #ifdef HAVE_NEW_YAMLCPP
00475       const YAML::Node &fg_node_tmp = doc["final_goal"];
00476       const YAML::Node *fg_node = fg_node_tmp ? &fg_node_tmp : NULL;
00477 #else
00478       const YAML::Node *fg_node = doc.FindValue("final_goal");
00479 #endif
00480       if ((wp_node == NULL) && (fg_node == NULL))
00481       {
00482         ROS_ERROR("Missing required key: final_goal");
00483         return false;
00484       }
00485 
00486       if (fg_node != NULL)
00487       {
00488         // Parse goal pose
00489         geometry_msgs::PoseStamped goal;
00490 
00491         (*fg_node)["header"]["frame_id"]       >> goal.header.frame_id;
00492 
00493         (*fg_node)["pose"]["position"]["x"]    >> goal.pose.position.x;
00494         (*fg_node)["pose"]["position"]["y"]    >> goal.pose.position.y;
00495         (*fg_node)["pose"]["position"]["z"]    >> goal.pose.position.z;
00496 
00497         (*fg_node)["pose"]["orientation"]["x"] >> goal.pose.orientation.x;
00498         (*fg_node)["pose"]["orientation"]["y"] >> goal.pose.orientation.y;
00499         (*fg_node)["pose"]["orientation"]["z"] >> goal.pose.orientation.z;
00500         (*fg_node)["pose"]["orientation"]["w"] >> goal.pose.orientation.w;
00501 
00502         goal_ = goal;
00503         mode_ = GOAL;
00504       }
00505       else
00506       {
00507         ROS_DEBUG("No final goal in file; assuming loop mode");  // still a valid file in loop mode
00508         waypoints_it_ = waypoints_.begin();
00509         mode_ = LOOP;
00510       }
00511 
00512       state_ = READY;
00513 
00514       ROS_DEBUG("Waypoints file successfully parsed: %s", waypoints_file_.c_str());
00515     }
00516     catch(YAML::ParserException& e)
00517     {
00518       ROS_ERROR("Parse waypoints file failed: %s", e.what());
00519       return false;
00520     }
00521     catch(YAML::RepresentationException& e)
00522     {
00523       ROS_ERROR("Parse waypoints file failed: %s", e.what());
00524       return false;
00525     }
00526 
00527     return true;
00528   }
00529 
00530   bool equals(const geometry_msgs::PoseStamped& a, const geometry_msgs::PoseStamped& b)
00531   {
00532     return ((a.pose.position.x == b.pose.position.x) &&
00533             (a.pose.position.y == b.pose.position.y) &&
00534             (a.pose.position.z == b.pose.position.z));
00535     // TODO make decent, with rotation (tk::minAngle, I think) and frame_id and put in math toolkit
00536   }
00537 
00538   bool equals(const geometry_msgs::Point& a, const geometry_msgs::Point& b)
00539   {
00540     return ((a.x == b.x) && (a.y == b.y) && (a.z == b.z));
00541   }
00542 };
00543 
00544 int main(int argc, char **argv)
00545 {
00546   ros::init(argc, argv, ROS_PACKAGE_NAME);
00547 
00548   WaypointsGoalNode eg;
00549   if (eg.init() == false)
00550   {
00551     ROS_ERROR("%s initialization failed", ros::this_node::getName().c_str());
00552     return -1;
00553   }
00554   ROS_INFO("%s initialized", ros::this_node::getName().c_str());
00555   eg.spin();
00556   return 0;
00557 }


yocs_waypoints_navi
Author(s): Jorge Santos Simon
autogenerated on Fri Aug 28 2015 13:44:57