crossing_escaper.cpp
Go to the documentation of this file.
00001 #include <nj_escape_crossing/crossing_escaper.h>
00002 
00003 namespace nj_escape_crossing
00004 {
00005 
00006 CrossingEscaper::CrossingEscaper(std::string name, double escape_distance) :
00007   lama_jockeys::NavigatingJockey(name),
00008   kp_v_(0.1),
00009   kp_w_(0.2),
00010   min_linear_velocity_(0),
00011   max_linear_velocity_(0),
00012   min_angular_velocity_(0),
00013   max_angular_velocity_(0),
00014   escape_distance_(escape_distance),
00015   distance_reached_(0.1),
00016   max_angle_turn_only_(1.0),
00017   max_odometry_age_(0.1),
00018   exit_angle_topic_name_("exit_angle"),
00019   start_reached_(false),
00020   angle_reached_(false),
00021   goal_reached_(false),
00022   has_odometry_(false),
00023   has_direction_(false),
00024   crossing_interface_name_("crossing"),
00025   exit_angle_interface_name_("exit_angle"),
00026   last_dtheta_(0)
00027 {
00028   ros::NodeHandle private_nh("~");
00029   private_nh.getParam("kp_v", kp_v_);
00030   private_nh.getParam("kp_w", kp_w_);
00031   private_nh.getParam("min_linear_velocity", min_linear_velocity_);
00032   private_nh.getParam("max_linear_velocity", max_linear_velocity_);
00033   private_nh.getParam("min_angular_velocity", min_angular_velocity_);
00034   private_nh.getParam("max_angular_velocity", max_angular_velocity_);
00035   private_nh.getParam("escape_distance", escape_distance_);
00036   private_nh.getParam("distance_reached", distance_reached_);
00037   private_nh.getParam("max_angle_turn_only", max_angle_turn_only_);
00038   private_nh.getParam("max_odometry_age", max_odometry_age_);
00039   private_nh.getParam("crossing_interface_name", crossing_interface_name_);
00040   private_nh.getParam("exit_angle_interface_name", exit_angle_interface_name_);
00041   private_nh.getParam("exit_angle_topic", exit_angle_topic_name_);
00042 
00043   crossing_getter_ = nh_.serviceClient<lama_msgs::GetCrossing>(crossing_interface_name_ + "_getter");
00044   exit_angle_getter_ = nh_.serviceClient<lama_interfaces::GetDouble>(exit_angle_interface_name_ + "_getter");
00045 }
00046 
00052 void CrossingEscaper::onTraverse()
00053 {
00054   // TODO: Add a mechanism to stop before distance_to_escape_ if
00055   // the robot sees only 2 crossing exits on a certain distance.
00056   
00057   ROS_DEBUG_STREAM(jockey_name_ << ": received action TRAVERSE");
00058   
00059   if (!getDistanceToEscape())
00060   {
00061     server_.setAborted();
00062     return;
00063   }
00064   else if (distance_to_escape_ < 1e-10)
00065   {
00066     result_.final_state = lama_jockeys::NavigateResult::DONE;
00067     result_.completion_time = ros::Duration(0.0);
00068     server_.setSucceeded(result_);
00069     return;
00070   }
00071 
00072   twist_publisher_ = private_nh_.advertise<geometry_msgs::Twist>("cmd_vel", 1);
00073   odometry_subscriber_ = private_nh_.subscribe("odom", 1, &CrossingEscaper::odometry_callback, this);
00074 
00075   getExitDirection();
00076 
00077   start_position_ = odometry_;
00078   feedback_.completion = 0.01;
00079   server_.publishFeedback(feedback_);
00080 
00081   geometry_msgs::Twist twist;
00082 
00083   ros::Rate r(50);
00084   while (ros::ok())
00085   {
00086     if (server_.isPreemptRequested() && !ros::ok())
00087     {
00088       ROS_INFO_STREAM(jockey_name_ << ": Preempted");
00089       // set the action state to preempted
00090       server_.setPreempted();
00091       twist_publisher_.publish(geometry_msgs::Twist());
00092       break;
00093     }
00094 
00095     // Odometry timeout mechanism.
00096     if ((ros::Time::now() - odometry_.header.stamp) > ros::Duration(max_odometry_age_))
00097     {
00098       ROS_WARN("No Odometry received within %.3f s, setting Twist to 0 (odometry age: %.3f s)",
00099           max_odometry_age_, (ros::Time::now() - odometry_.header.stamp).toSec());
00100         twist_publisher_.publish(geometry_msgs::Twist());
00101       continue;
00102     }
00103 
00104     if (!start_reached_)
00105     {
00106       geometry_msgs::Point goal = goalFromOdometry(
00107           goal_.relative_edge_start.position.x,
00108           goal_.relative_edge_start.position.y);
00109       start_reached_ = goToGoal(goal, twist);
00110       twist_publisher_.publish(twist);
00111       // Reset the odometry
00112       if (start_reached_)
00113       {
00114         start_position_ = odometry_;
00115       }
00116     }
00117     else if (!angle_reached_)
00118     {
00119       angle_reached_ = turnToAngle(direction_, twist);
00120       twist_publisher_.publish(twist);
00121       if (angle_reached_)
00122       {
00123         ROS_DEBUG_STREAM(jockey_name_ << ": angle reached");
00124         last_dtheta_ = 0;
00125         feedback_.completion = 0.25;
00126         server_.publishFeedback(feedback_);
00127       }
00128     }
00129     else if (!goal_reached_)
00130     {
00131       geometry_msgs::Point goal = goalFromOdometry(
00132           (distance_to_escape_ + distance_reached_) * std::cos(direction_),
00133           (distance_to_escape_ + distance_reached_) * std::sin(direction_));
00134       goal_reached_ = goToGoal(goal, twist);
00135       twist_publisher_.publish(twist);
00136     }
00137     else
00138     {
00139       // The robot reached its goal.
00140       feedback_.completion = 1;
00141       server_.publishFeedback(feedback_);
00142       // As for now, the jockey is pretty stupid and does not stop before the
00143       // distance is traveled. In the future, it should be able to recognize if
00144       // the current place (Crossing-type) is the same as the place it started
00145       // from.
00146       ROS_DEBUG("%s: goal reached", jockey_name_.c_str());
00147       result_.final_state = lama_jockeys::NavigateResult::DONE;
00148       result_.completion_time = ros::Time::now() - getStartTime() - getInterruptionsDuration();
00149       server_.setSucceeded(result_);
00150       break;
00151     }
00152     ros::spinOnce();
00153     r.sleep();
00154   }
00155   angle_reached_ = false;
00156   goal_reached_ = false;
00157 }
00158 
00159 void CrossingEscaper::onStop()
00160 {
00161   twist_publisher_.publish(geometry_msgs::Twist());
00162   result_.final_state = lama_jockeys::NavigateResult::DONE;
00163   result_.completion_time = ros::Duration(0);
00164   server_.setSucceeded(result_);
00165   start_reached_ = false;
00166   angle_reached_ = false;
00167   goal_reached_ = false;
00168   has_odometry_ = false;
00169   has_direction_ = false;
00170 }
00171 
00172 void CrossingEscaper::odometry_callback(const nav_msgs::Odometry& odometry)
00173 {
00174   odometry_ = odometry;
00175   has_odometry_ = true;
00176 }
00177 
00178 void CrossingEscaper::direction_callback(const std_msgs::Float32& direction)
00179 {
00180   direction_ = direction.data;
00181   has_direction_ = true;
00182 }
00183 
00191 bool CrossingEscaper::getDistanceToEscape()
00192 {
00193   if (escape_distance_ < 1e-10)
00194   {
00195     // escape_distance_ not set, try to get escape distance from crossing_.
00196     if (!getCrossing())
00197     {
00198       ROS_ERROR_STREAM(jockey_name_ <<
00199           ": escape_distance not set and no Crossing found, aborting...");
00200       return false;
00201     }
00202     if (crossing_.radius == 0)
00203     {
00204       // crossing_.radius set to 0, don't need to travel very far.
00205       ROS_WARN("Crossing descriptor to be used as escape distance but radius is 0, DONE");
00206       return true;
00207     }
00208     distance_to_escape_ = crossing_.radius;
00209   }
00210   else
00211   {
00212     distance_to_escape_ = escape_distance_;
00213   }
00214   ROS_DEBUG("Distance to escape: %.3f m", distance_to_escape_);
00215   return true;
00216 }
00217 
00218 /* Set crossing_ to the descriptor found in the databse from information in goal.
00219  *
00220  * Set crossing_ to the descriptor associated with the first vertex of
00221  * goal_.edge, if goal._edge.id non-null. If goal._edge.id is null, get the
00222  * crossing with id goal_.descriptor_links[0].descriptor_id.
00223  *
00224  * Return false if edge does not exist or if no Crossing descriptor was found,
00225  * true otherwise.
00226  */
00227 bool CrossingEscaper::getCrossing()
00228 {
00229   if (goal_.edge.id != 0)
00230   {
00231     lama_interfaces::ActOnMap map_action;
00232     map_action.request.action = lama_interfaces::ActOnMapRequest::GET_DESCRIPTOR_LINKS;
00233     map_action.request.interface_name = crossing_interface_name_;
00234     if (goal_.edge.references.empty())
00235     {
00236       ROS_INFO("goal.edge (id: %d) should be have references, probably not an edge",
00237           goal_.edge.id);
00238       return false;
00239     }
00240     map_action.request.object.id = goal_.edge.references[0];
00241     map_action.request.interface_name = crossing_interface_name_;
00242     if (!map_agent_.call(map_action))
00243     {
00244       ROS_ERROR("Failed to call map agent");
00245       return false;
00246     }
00247     if (map_action.response.descriptor_links.empty())
00248     {
00249       ROS_DEBUG("No crossing descriptor for vertex %d", map_action.request.object.id);
00250       return false;
00251     }
00252     if (map_action.response.descriptor_links.size() > 1)
00253     {
00254       ROS_WARN("More than one crossing descriptor for vertex %d, taking the first one (id %d)",
00255           map_action.request.object.id, map_action.response.descriptor_links[0].descriptor_id);
00256     }
00257     return retrieveCrossingFromMap(map_action.response.descriptor_links[0].descriptor_id);
00258   }
00259   else
00260   {
00261     ROS_DEBUG("Goal.edge.id not set, getting descriptor id from goal.descriptor_link (id %d)",
00262         goal_.descriptor_link.descriptor_id);
00263     return retrieveCrossingFromMap(goal_.descriptor_link.descriptor_id);
00264   }
00265 }
00266 
00267 /* Retrieve a Crossing message from the map from its id.
00268  *
00269  * The message will be saved in crossing_.
00270  * Return false if the message in not found in the database.
00271  *
00272  * descriptor_id[in] id of the Crossing message in the database.
00273  */
00274 bool CrossingEscaper::retrieveCrossingFromMap(const int32_t descriptor_id)
00275 {
00276   lama_msgs::GetCrossing crossing_srv;
00277   crossing_srv.request.id = descriptor_id;
00278   if (!crossing_getter_.call(crossing_srv))
00279   {
00280     ROS_ERROR_STREAM(jockey_name_ << ": failed to get Crossing with id " <<
00281         descriptor_id << " and interface " << crossing_interface_name_ <<
00282         " (service " << crossing_getter_.getService() << ")");
00283     return false;
00284   }
00285   crossing_ = crossing_srv.response.descriptor;
00286   return true;
00287 }
00288 
00298 void CrossingEscaper::getExitDirection()
00299 {
00300   if (!getExitAngleFromMap())
00301   {
00302     // No exit angle descriptor available, get the travel direction from topic exit_angle_topic_name_.
00303     ros::Subscriber direction_subscriber = private_nh_.subscribe(exit_angle_topic_name_, 1,
00304         &CrossingEscaper::direction_callback, this);
00305 
00306     // Wait for odometry (for the start position) and exit_angle data.
00307     ros::Duration(0.2).sleep();
00308     ros::spinOnce();
00309     ros::Duration(0.1).sleep();
00310     while (!(has_odometry_ && has_direction_))
00311     {
00312       ros::spinOnce();
00313       ROS_WARN_STREAM_THROTTLE(5, "Waiting for odometry on topic " << odometry_subscriber_.getTopic() <<
00314           " and exit_angle on topic " << direction_subscriber.getTopic());
00315       ros::Duration(0.01).sleep();
00316     }
00317     has_odometry_ = false;
00318     has_direction_ = false;
00319     // Do not allow update of direction_ because we can't handle it.
00320     // shutdown is not necessary because direction_subscriber goes out of scope.
00321   }
00322   else
00323   {
00324     // Wait for odometry data (no exit_angle topic necessary).
00325     // TODO simplify with ROS_WARN_STREAM_DELAYED_THROTTLE (as of rosconsole v. 1.11.11).
00326     ros::Duration(0.2).sleep();
00327     ros::spinOnce();
00328     ros::Duration(0.1).sleep();
00329     while (!has_odometry_)
00330     {
00331       ros::spinOnce();
00332       ROS_WARN_STREAM_THROTTLE(5, "Waiting for odometry on topic " << odometry_subscriber_.getTopic());
00333       ros::Duration(0.01).sleep();
00334     }
00335     has_odometry_ = false;
00336   }
00337   
00338   // Correct the direction with data from goal_.relative_edge_start.
00339   /* ROS_INFO("relative_edge_start.orientation: %.3f deg", tf::getYaw(goal_.relative_edge_start.orientation) * 180 / M_PI); // DEBUG */
00340   direction_ += tf::getYaw(goal_.relative_edge_start.orientation);
00341 }
00342 
00347 bool CrossingEscaper::getExitAngleFromMap()
00348 {
00349   if (goal_.edge.id == 0)
00350   {
00351     // goal_.edge not set.
00352     return false;
00353   }
00354 
00355   lama_interfaces::ActOnMap map_action;
00356   map_action.request.action = lama_interfaces::ActOnMapRequest::GET_DESCRIPTOR_LINKS;
00357   map_action.request.object.id = goal_.edge.id;
00358   map_action.request.interface_name = exit_angle_interface_name_;
00359   map_agent_.call(map_action);
00360   if (map_action.response.descriptor_links.empty())
00361   {
00362     ROS_WARN_STREAM("No exit angle descriptor for edge " << map_action.request.object.id <<
00363         " on interface " << exit_angle_interface_name_);
00364     return false;
00365   }
00366   if (map_action.response.descriptor_links.size() > 1)
00367   {
00368     ROS_WARN("More than one exit angle descriptor for edge %d, taking the first one",
00369         map_action.request.object.id);
00370   }
00371   lama_interfaces::GetDouble exit_angle_srv;
00372   exit_angle_srv.request.id = map_action.response.descriptor_links[0].descriptor_id;
00373   if (!exit_angle_getter_.call(exit_angle_srv))
00374   {
00375     ROS_ERROR_STREAM(jockey_name_ << ": failed to get exit_angle with id " <<
00376         exit_angle_srv.request.id << " and interface " << exit_angle_interface_name_ <<
00377         " (service " << exit_angle_getter_.getService() << ")");
00378     return false;
00379   }
00380   direction_ = exit_angle_srv.response.descriptor;
00381   return true;
00382 }
00383 
00390 bool CrossingEscaper::turnToAngle(double direction, geometry_msgs::Twist& twist)
00391 {
00392   const double yaw_now= tf::getYaw(odometry_.pose.pose.orientation);
00393   const double dtheta = angles::shortest_angular_distance(yaw_now, direction);
00394   ROS_DEBUG("dtheta to goal: %.3f", dtheta);
00395 
00396   double wz = kp_w_ * dtheta;
00397 
00398   // Dead-zone management.
00399   if ((wz > 0) && (wz < min_angular_velocity_))
00400   {
00401     wz = min_angular_velocity_;
00402   }
00403   else if ((wz < 0) && (wz > -min_angular_velocity_))
00404   {
00405     wz = -min_angular_velocity_;
00406   }
00407   twist.linear.x = 0;
00408   twist.angular.z = wz;
00409 
00410   // The goal (0) is reached is the sign of dtheta changes.
00411   bool angle_reached = (dtheta * last_dtheta_ < -1e-10) && (dtheta < M_PI_2);
00412   last_dtheta_ = dtheta;
00413   return angle_reached;
00414 }
00415 
00422 bool CrossingEscaper::goToGoal(const geometry_msgs::Point& goal, geometry_msgs::Twist& twist)
00423 {
00424   // TODO: change parameters to dynamic_reconfigure.
00425   ros::NodeHandle private_nh("~");
00426   private_nh.getParamCached("kp_v", kp_v_);
00427   private_nh.getParamCached("kp_w", kp_w_);
00428   private_nh.getParamCached("min_linear_velocity", min_linear_velocity_);
00429   private_nh.getParamCached("max_linear_velocity", max_linear_velocity_);
00430   private_nh.getParamCached("min_angular_velocity", min_angular_velocity_);
00431   private_nh.getParamCached("max_angular_velocity", max_angular_velocity_);
00432 
00433   ROS_DEBUG("goal: (%.3f, %.3f)", goal.x, goal.y);
00434   double distance = std::sqrt(goal.x * goal.x + goal.y * goal.y);
00435 
00436   if (distance < distance_reached_)
00437   {
00438     // Goal reached.
00439     twist = geometry_msgs::Twist();
00440     return true;
00441   }
00442 
00443   double dtheta = std::atan2(goal.y, goal.x);
00444 
00445   if (std::fabs(dtheta) > max_angle_turn_only_)
00446   {
00447     // Do not go forward because the goal is not well in front of the robot.
00448     distance = 0.0;
00449   }
00450 
00451   // TODO: add a parameter "allow_backward" so that we don't need to turn 180 deg.
00452   
00453   double vx = kp_v_ * distance; 
00454   double wz = kp_w_ * dtheta;
00455 
00456   // Dead-zone management.
00457   if ((vx < min_linear_velocity_) && (std::abs(distance) > 1e-10) && (std::abs(wz) < min_angular_velocity_))
00458   {
00459     vx = min_linear_velocity_;
00460   }
00461   if ((wz > 0) && (wz < min_angular_velocity_) && (vx < min_linear_velocity_))
00462   {
00463     wz = min_angular_velocity_;
00464   }
00465   else if ((wz < 0) && (wz > -min_angular_velocity_) && (vx < min_linear_velocity_))
00466   {
00467     wz = -min_angular_velocity_;
00468   }
00469 
00470   // velocity throttle.
00471   if (max_linear_velocity_ > 1e-10)
00472   {
00473     // Only throttle if max_linear_velocity_ is set.
00474     if (vx > max_linear_velocity_)
00475     {
00476       vx = max_linear_velocity_;
00477     }
00478   }
00479   if (max_angular_velocity_ > 1e-10)
00480   {
00481     // Only throttle if max_angular_velocity_ is set.
00482     if (wz > max_angular_velocity_)
00483     {
00484       wz = max_angular_velocity_;
00485     }
00486     else if (wz < -max_angular_velocity_)
00487     {
00488       wz = -max_angular_velocity_;
00489     }
00490   }
00491 
00492   ROS_DEBUG("Distance to goal: %f, dtheta to goal: %f, vx: %f, wz: %f", distance, dtheta, vx, wz);
00493 
00494   twist.linear.x = vx;
00495   twist.angular.z = wz;
00496 
00497   return false;
00498 }
00499 
00505 geometry_msgs::Point CrossingEscaper::goalFromOdometry(double dx, double dy)
00506 {
00507   geometry_msgs::Point abs_goal;
00508   abs_goal.x = start_position_.pose.pose.position.x + dx;
00509   abs_goal.y = start_position_.pose.pose.position.y + dy;
00510 
00511   geometry_msgs::Point rel_goal_abs_frame;
00512   rel_goal_abs_frame.x = abs_goal.x - odometry_.pose.pose.position.x;
00513   rel_goal_abs_frame.y = abs_goal.y - odometry_.pose.pose.position.y;
00514 
00515   const double yaw = tf::getYaw(odometry_.pose.pose.orientation);
00516   const double cos_yaw = std::cos(yaw);
00517   const double sin_yaw = std::sin(yaw);
00518   geometry_msgs::Point rel_goal;
00519   rel_goal.x = rel_goal_abs_frame.x * cos_yaw + rel_goal_abs_frame.y * sin_yaw;
00520   rel_goal.y = -rel_goal_abs_frame.x * sin_yaw + rel_goal_abs_frame.y * cos_yaw;
00521 
00522   return rel_goal;
00523 }
00524 
00525 } // namespace nj_escape_crossing


nj_escape_crossing
Author(s): Gaƫl Ecorchard
autogenerated on Thu Jun 6 2019 22:02:11