point_frame.cpp
Go to the documentation of this file.
00001 /*********************************************************************
00002  * Software License Agreement (BSD License)
00003  *
00004  *  Copyright (c) 2014, PAL Robotics, S.L.
00005  *  Copyright (c) 2008, Willow Garage, Inc.
00006  *  All rights reserved.
00007  *
00008  *  Redistribution and use in source and binary forms, with or without
00009  *  modification, are permitted provided that the following conditions
00010  *  are met:
00011  *
00012  *   * Redistributions of source code must retain the above copyright
00013  *     notice, this list of conditions and the following disclaimer.
00014  *   * Redistributions in binary form must reproduce the above
00015  *     copyright notice, this list of conditions and the following
00016  *     disclaimer in the documentation and/or other materials provided
00017  *     with the distribution.
00018  *   * Neither the name of PAL Robotics, S.L. nor the names of its
00019  *     contributors may be used to endorse or promote products derived
00020  *     from this software without specific prior written permission.
00021  *
00022  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00023  *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00024  *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00025  *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00026  *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00027  *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00028  *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00029  *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00030  *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00031  *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00032  *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00033  *  POSSIBILITY OF SUCH DAMAGE.
00034  *********************************************************************/
00035 
00036 #include <ros/ros.h>
00037 #include <boost/scoped_ptr.hpp>
00038 #include <cmath>
00039 
00040 #include <actionlib/server/action_server.h>
00041 
00042 #include <kdl/chainfksolver.hpp>
00043 #include <kdl/chain.hpp>
00044 #include <kdl/chainjnttojacsolver.hpp>
00045 #include <kdl/chainfksolverpos_recursive.hpp>
00046 #include <kdl_parser/kdl_parser.hpp>
00047 
00048 #include <tf_conversions/tf_kdl.h>
00049 #include <tf/transform_datatypes.h>
00050 #include <tf/transform_listener.h>
00051 
00052 #include <urdf/model.h>
00053 
00054 #include <trajectory_msgs/JointTrajectory.h>
00055 #include <control_msgs/PointHeadAction.h>
00056 #include <control_msgs/QueryTrajectoryState.h>
00057 #include <control_msgs/JointTrajectoryControllerState.h>
00058 
00059 class ControlHead
00060 {
00061 private:
00062   typedef actionlib::ActionServer<control_msgs::PointHeadAction> PHAS;
00063   typedef PHAS::GoalHandle GoalHandle;
00064 
00065   const std::string action_name_;
00066   std::string root_;
00067   std::string tip_;
00068   std::string pan_link_;
00069   std::string default_pointing_frame_;
00070   std::string pointing_frame_;
00071   tf::Vector3 pointing_axis_;
00072   std::vector< std::string> joint_names_;
00073 
00074   ros::NodeHandle nh_, pnh_;
00075   ros::Publisher pub_controller_command_;
00076   ros::Subscriber sub_controller_state_;
00077   ros::ServiceClient cli_query_traj_;
00078   ros::Timer watchdog_timer_;
00079 
00080   PHAS action_server_;
00081   bool has_active_goal_;
00082   GoalHandle active_goal_;
00083   double success_angle_threshold_; //one of the stop conditions in the iterative solver
00084 
00085   KDL::Tree tree_;
00086   KDL::Chain chain_;
00087   tf::Point target_in_root_;
00088   tf::Vector3 desired_pointing_axis_in_frame_;
00089   double goal_error_; //this may be larger than success_angle_threshold_
00090 
00091   boost::scoped_ptr<KDL::ChainFkSolverPos> pose_solver_;
00092   boost::scoped_ptr<KDL::ChainJntToJacSolver> jac_solver_;
00093 
00094   tf::TransformListener tfl_;
00095   urdf::Model urdf_model_;
00096 
00097   control_msgs::JointTrajectoryControllerStateConstPtr last_controller_state_;
00098 
00099 public:
00100   ControlHead(const ros::NodeHandle &node)
00101       : action_name_("point_head_action")
00102       , nh_(node)
00103       , pnh_("~")
00104       , action_server_(nh_, action_name_.c_str(),
00105                        boost::bind(&ControlHead::goalCB, this, _1),
00106                        boost::bind(&ControlHead::cancelCB, this, _1), false)
00107       , has_active_goal_(false)
00108   {
00109     pnh_.param("pan_link", pan_link_, std::string("head_pan_link"));
00110     pnh_.param("default_pointing_frame", default_pointing_frame_, std::string("head_tilt_link"));
00111     pnh_.param("success_angle_threshold", success_angle_threshold_, 0.1);
00112 
00113     if (pan_link_[0] == '/') pan_link_.erase(0, 1);
00114     if (default_pointing_frame_[0] == '/') default_pointing_frame_.erase(0, 1);
00115 
00116     // Connects to the controller
00117     pub_controller_command_ =
00118       nh_.advertise<trajectory_msgs::JointTrajectory>("command", 2);
00119     sub_controller_state_ =
00120       nh_.subscribe("state", 1, &ControlHead::controllerStateCB, this);
00121     cli_query_traj_ =
00122         nh_.serviceClient<control_msgs::QueryTrajectoryState>("query_state");
00123 
00124     if (tree_.getNrOfJoints() == 0)
00125     {
00126       std::string robot_desc_string;
00127       std::string robot_desc_key;
00128       if(nh_.searchParam("robot_description", robot_desc_key))
00129       {
00130         ROS_INFO_STREAM("Load description from: " << robot_desc_key);
00131         nh_.param(robot_desc_key, robot_desc_string, std::string());
00132       }
00133       else
00134       {
00135         nh_.param("/robot_description", robot_desc_string, std::string());
00136       }
00137       
00138       ROS_DEBUG("Reading tree from robot_description...");
00139       if (!kdl_parser::treeFromString(robot_desc_string, tree_))
00140       {
00141          ROS_ERROR("Failed to construct kdl tree");
00142          exit(-1);
00143       }
00144       if (!urdf_model_.initString(robot_desc_string))
00145       {
00146         ROS_ERROR("Failed to parse urdf string for urdf::Model.");
00147         exit(-2);
00148       }
00149     }
00150 
00151     ROS_DEBUG("Tree has %d joints and %d segments.", tree_.getNrOfJoints(), tree_.getNrOfSegments());
00152     action_server_.start();
00153     watchdog_timer_ = nh_.createTimer(ros::Duration(1.0), &ControlHead::watchdog, this);
00154   }
00155 
00156   void goalCB(GoalHandle gh)
00157   {
00158     // Before we do anything, we need to know that name of the pan_link's parent, which we will treat as the root.
00159     if (root_.empty())
00160     {
00161       for (int i = 0; i < 10; ++i)
00162       {
00163         try
00164         {
00165           tfl_.getParent(pan_link_, ros::Time(), root_);
00166           break;
00167         }
00168         catch (const tf::TransformException &ex) {}
00169         ros::Duration(0.5).sleep();
00170       }
00171       if (root_.empty())
00172       {
00173         ROS_ERROR("Could not get parent of %s in the TF tree", pan_link_.c_str());
00174         gh.setRejected();
00175         return;
00176       }
00177     }
00178     if (root_[0] == '/') root_.erase(0, 1);
00179 
00180     ROS_DEBUG("Got point head goal!");
00181 
00182     // Process pointing frame and axis
00183     const geometry_msgs::PointStamped &target = gh.getGoal()->target;
00184     pointing_frame_ = gh.getGoal()->pointing_frame;
00185     if (pointing_frame_.length() == 0)
00186     {
00187       ROS_WARN("Pointing frame not specified, using %s [1, 0, 0] by default.", default_pointing_frame_.c_str());
00188       pointing_frame_ = default_pointing_frame_;
00189       pointing_axis_ = tf::Vector3(1.0, 0.0, 0.0);
00190     }
00191     else
00192     {
00193       if (pointing_frame_[0] == '/') pointing_frame_.erase(0, 1);
00194       bool ret1 = false;
00195       try
00196       {
00197         ret1 = tfl_.waitForTransform(pan_link_, pointing_frame_, target.header.stamp,
00198                                      ros::Duration(5.0), ros::Duration(0.01));
00199 
00200         tf::vector3MsgToTF(gh.getGoal()->pointing_axis, pointing_axis_);
00201         if (pointing_axis_.length() < 0.1)
00202         {
00203           size_t found = pointing_frame_.find("optical_frame");
00204           if (found != std::string::npos)
00205           {
00206             ROS_WARN("Pointing axis appears to be zero-length. Using [0, 0, 1] as default for an optical frame.");
00207             pointing_axis_ = tf::Vector3(0, 0, 1);
00208           }
00209           else
00210           {
00211             ROS_WARN("Pointing axis appears to be zero-length. Using [1, 0, 0] as default for a non-optical frame.");
00212             pointing_axis_ = tf::Vector3(1, 0, 0);
00213           }
00214         }
00215         else
00216         {
00217           pointing_axis_.normalize();
00218         }
00219       }
00220       catch (const tf::TransformException &ex)
00221       {
00222         ROS_ERROR("Transform failure (%d): %s", ret1, ex.what());
00223         gh.setRejected();
00224         return;
00225       }
00226     }
00227 
00228     //Put the target point in the root frame (usually torso_lift_link).
00229     bool ret1 = false;
00230     try
00231     {
00232       std::string error_msg;
00233       ret1 = tfl_.waitForTransform(root_.c_str(), target.header.frame_id, target.header.stamp,
00234                                        ros::Duration(5.0), ros::Duration(0.01), &error_msg);
00235 
00236       geometry_msgs::PointStamped target_in_root_msg;
00237       tfl_.transformPoint(root_.c_str(), target, target_in_root_msg );
00238       tf::pointMsgToTF(target_in_root_msg.point, target_in_root_);
00239       ROS_DEBUG_STREAM("Target point in base frame: (" << target_in_root_[0] << ", " << target_in_root_[1] << ", " << target_in_root_[2] << ")");
00240     }
00241     catch (const tf::TransformException &ex)
00242     {
00243       ROS_ERROR("Transform failure (%d): %s", ret1, ex.what());
00244       gh.setRejected();
00245       return;
00246     }
00247 
00248     if (tip_.compare(pointing_frame_) != 0)
00249     {
00250       bool success = tree_.getChain(root_.c_str(), pointing_frame_.c_str(), chain_);
00251       if (!success)
00252       {
00253         ROS_ERROR("Couldn't create chain from %s to %s.", root_.c_str(), pointing_frame_.c_str());
00254         gh.setRejected();
00255         return;
00256       }
00257       tip_ = pointing_frame_;
00258 
00259       pose_solver_.reset(new KDL::ChainFkSolverPos_recursive(chain_));
00260       jac_solver_.reset(new KDL::ChainJntToJacSolver(chain_));
00261       joint_names_.resize(chain_.getNrOfJoints());
00262     }
00263 
00264     const unsigned int joints = chain_.getNrOfJoints();
00265 
00266     KDL::JntArray jnt_pos(joints), jnt_eff(joints);
00267     KDL::Jacobian jacobian(joints);
00268 
00269     control_msgs::QueryTrajectoryState traj_state;
00270     traj_state.request.time = ros::Time::now() + ros::Duration(0.01);
00271     if (!cli_query_traj_.call(traj_state))
00272     {
00273       ROS_ERROR("Service call to query controller trajectory failed.");
00274       gh.setRejected();
00275       return;
00276     }
00277     if (traj_state.response.name.size() != joints)
00278     {
00279       ROS_ERROR("Number of joints mismatch: urdf chain vs. trajectory controller state.");
00280       gh.setRejected();
00281       return;
00282     }
00283     std::vector<urdf::JointLimits> limits_(joints);
00284 
00285     // Get initial joint positions and joint limits.
00286     for (unsigned int i = 0; i < joints; ++i)
00287     {
00288       joint_names_[i] = traj_state.response.name[i];
00289       limits_[i] = *(urdf_model_.joints_[joint_names_[i].c_str()]->limits);
00290       ROS_DEBUG("Joint %d %s: %f, limits: %f %f", i, traj_state.response.name[i].c_str(), traj_state.response.position[i], limits_[i].lower, limits_[i].upper);
00291       jnt_pos(i) = 0;
00292     }
00293 
00294     int count = 0;
00295     int limit_flips = 0;
00296     float correction_angle = 2*M_PI;
00297     float correction_delta = 2*M_PI;
00298     const int MAX_ITERATIONS = 15;
00299     while( ros::ok() &&
00300            fabs(correction_delta) > 0.001 &&
00301            count < MAX_ITERATIONS) //limit the iterations
00302     {
00303       //get the pose and jacobian for the current joint positions
00304       KDL::Frame pose;
00305       pose_solver_->JntToCart(jnt_pos, pose);
00306       jac_solver_->JntToJac(jnt_pos, jacobian);
00307 
00308       tf::Transform frame_in_root;
00309       tf::poseKDLToTF(pose, frame_in_root);
00310 
00311       tf::Vector3 axis_in_frame = pointing_axis_.normalized();
00312       tf::Vector3 target_from_frame = (target_in_root_ - frame_in_root.getOrigin()).normalized();
00313       tf::Vector3 current_in_frame = frame_in_root.getBasis().inverse()*target_from_frame;
00314       float prev_correction = correction_angle;
00315       correction_angle = current_in_frame.angle(axis_in_frame);
00316       correction_delta = correction_angle - prev_correction;
00317 
00318       ROS_DEBUG("At step %d, joint poses are %.4f and %.4f, angle error is %f radians", count, jnt_pos(0), jnt_pos(1), correction_angle);
00319       goal_error_ = correction_angle; //expected error after this iteration
00320       if ( correction_angle < 0.5*success_angle_threshold_ )
00321       {
00322         ROS_DEBUG_STREAM("Accepting solution as estimated error is: " << correction_angle*180.0/M_PI << "degrees and stopping condition is half of " << success_angle_threshold_*180.0/M_PI);
00323         break;
00324       }
00325       tf::Vector3 correction_axis = frame_in_root.getBasis()*(axis_in_frame.cross(current_in_frame).normalized());
00326       tf::Transform correction_tf(tf::Quaternion(correction_axis, 0.5*correction_angle), tf::Vector3(0,0,0));
00327       KDL::Frame correction_kdl;
00328       tf::transformTFToKDL(correction_tf, correction_kdl);
00329 
00330       // We apply a "wrench" proportional to the desired correction
00331       KDL::Frame identity_kdl;
00332       KDL::Twist twist = diff(correction_kdl, identity_kdl);
00333       KDL::Wrench wrench_desi;
00334       for (unsigned int i=0; i<6; ++i)
00335         wrench_desi(i) = -1.0*twist(i);
00336 
00337       // Converts the "wrench" into "joint corrections" with a jacbobian-transpose
00338       for (unsigned int i = 0; i < joints; ++i)
00339       {
00340         jnt_eff(i) = 0;
00341         for (unsigned int j=0; j<6; ++j)
00342           jnt_eff(i) += (jacobian(j,i) * wrench_desi(j));
00343         jnt_pos(i) += jnt_eff(i);
00344       }
00345 
00346      // account for pan_link joint limit in back.
00347      // if(jnt_pos(0) < limits_[0].lower && limit_flips++ == 0){ jnt_pos(0) += 1.5*M_PI; }
00348      // if(jnt_pos(0) > limits_[0].upper && limit_flips++ == 0){ jnt_pos(0) -= 1.5*M_PI; }
00349 
00350       // Move both joins to a position between the upper and lower joint limits
00351       jnt_pos(0) = std::max(limits_[0].lower, jnt_pos(0));
00352       jnt_pos(0) = std::min(limits_[0].upper, jnt_pos(0));
00353       jnt_pos(1) = std::max(limits_[1].lower, jnt_pos(1));
00354       jnt_pos(1) = std::min(limits_[1].upper, jnt_pos(1));
00355 
00356       count++;
00357 
00358       if (limit_flips > 1)
00359       {
00360         ROS_ERROR("Goal is out of joint limits, trying to point there anyway... \n");
00361         break;
00362       }
00363     }
00364     ROS_DEBUG_STREAM("Iterative solver took " << count << " steps. Expected error: " << correction_angle << " radians");
00365     if ( count == MAX_ITERATIONS )
00366       ROS_WARN("Aborted because maximum number of iterations was reached");    
00367 
00368     std::vector<double> q_goal(joints);
00369 
00370     //saturate joint positions considering the joint limits
00371     for(unsigned int i = 0; i < joints; i++)
00372     {
00373       jnt_pos(i) = std::max(limits_[i].lower, jnt_pos(i));
00374       jnt_pos(i) = std::min(limits_[i].upper, jnt_pos(i));
00375       q_goal[i] = jnt_pos(i);
00376       ROS_DEBUG("Joint %d %s: %f", i, joint_names_[i].c_str(), jnt_pos(i));
00377     }
00378 
00379     //re-compute desired pointing axis from desired joint positions 
00380     //(to take the case in which joint limits have been enforced into account)
00381     KDL::Frame pose;
00382     pose_solver_->JntToCart(jnt_pos, pose);
00383 
00384     tf::Transform frame_in_root;
00385     tf::poseKDLToTF(pose, frame_in_root);
00386     tf::Vector3 target_from_frame = target_in_root_ - frame_in_root.getOrigin();
00387     target_from_frame.normalize();
00388     ROS_DEBUG_STREAM("BEFORE applying joint limits => desired pointing axis = (" <<
00389                      pointing_axis_[0] << ", " <<
00390                      pointing_axis_[1] << ", " <<
00391                      pointing_axis_[2] << ")");
00392     desired_pointing_axis_in_frame_ = frame_in_root.getBasis().inverse()*target_from_frame;
00393     ROS_DEBUG_STREAM("AFTER applying joint limits => desired pointing axis = (" <<
00394                      desired_pointing_axis_in_frame_[0] << ", " <<
00395                      desired_pointing_axis_in_frame_[1] << ", " <<
00396                      desired_pointing_axis_in_frame_[2] << ")");
00397 
00398     //the goal will end when the angular error of the pointing axis
00399     //is lower than goal_error_. This variable is assigned with the maximum
00400     //between the ros param success_angle_threshold_ and the estimated error
00401     //from the iterative solver last iteration, i.e. goal_error_ current value
00402     goal_error_ = std::max(goal_error_, success_angle_threshold_);
00403     ROS_DEBUG_STREAM("the goal will terminate when error is: " << goal_error_*180.0/M_PI << " degrees => " << goal_error_ << " radians");
00404 
00405     if (has_active_goal_)
00406     {
00407       active_goal_.setCanceled();
00408       has_active_goal_ = false;
00409     }
00410 
00411     gh.setAccepted();
00412     active_goal_ = gh;
00413     has_active_goal_ = true;
00414 
00415     // Computes the duration of the movement.
00416     ros::Duration min_duration(0.01);
00417 
00418     if (gh.getGoal()->min_duration > min_duration)
00419         min_duration = gh.getGoal()->min_duration;
00420 
00421     // Determines if we need to increase the duration of the movement in order to enforce a maximum velocity.
00422     if (gh.getGoal()->max_velocity > 0)
00423     {
00424       // compute the largest required rotation among all the joints
00425       double largest_rotation = 0;
00426       for(unsigned int i = 0; i < joints; i++)
00427       {
00428         double required_rotation = fabs(q_goal[i] - traj_state.response.position[i]);
00429         if ( required_rotation > largest_rotation )
00430           largest_rotation = required_rotation;
00431       }
00432 
00433       ros::Duration limit_from_velocity(largest_rotation / gh.getGoal()->max_velocity);
00434       if (limit_from_velocity > min_duration)
00435         min_duration = limit_from_velocity;
00436     }
00437 
00438     // Computes the command to send to the trajectory controller.
00439     trajectory_msgs::JointTrajectory traj;
00440     traj.header.stamp = traj_state.request.time;
00441 
00442     traj.joint_names.push_back(traj_state.response.name[0]);
00443     traj.joint_names.push_back(traj_state.response.name[1]);
00444 
00445     traj.points.resize(1);
00446     traj.points[0].positions = q_goal;
00447     traj.points[0].velocities.push_back(0);
00448     traj.points[0].velocities.push_back(0);
00449     traj.points[0].time_from_start = ros::Duration(min_duration);
00450 
00451 
00452     pub_controller_command_.publish(traj);
00453   }
00454   
00455   void watchdog(const ros::TimerEvent &e)
00456   {
00457     const ros::Time now = ros::Time::now();
00458 
00459     // Aborts the active goal if the controller does not appear to be active.
00460     if (has_active_goal_)
00461     {
00462       bool should_abort = false;
00463       if (!last_controller_state_)
00464       {
00465         should_abort = true;
00466         ROS_WARN("Aborting goal because we have never heard a controller state message.");
00467       }
00468       else if ((now - last_controller_state_->header.stamp) > ros::Duration(5.0))
00469       {
00470         should_abort = true;
00471         ROS_WARN("Aborting goal because we haven't heard from the controller in %.3lf seconds",
00472                  (now - last_controller_state_->header.stamp).toSec());
00473       }
00474 
00475       if (should_abort)
00476       {
00477         // Stops the controller.
00478         trajectory_msgs::JointTrajectory empty;
00479         empty.joint_names = joint_names_;
00480         pub_controller_command_.publish(empty);
00481 
00482         // Marks the current goal as aborted.
00483         active_goal_.setAborted();
00484         has_active_goal_ = false;
00485       }
00486     }
00487   }
00488 
00489   void cancelCB(GoalHandle gh)
00490   {
00491     if (active_goal_ == gh)
00492     {
00493       // Stops the controller.
00494       trajectory_msgs::JointTrajectory empty;
00495       empty.joint_names = joint_names_;
00496       pub_controller_command_.publish(empty);
00497 
00498       // Marks the current goal as canceled.
00499       active_goal_.setCanceled();
00500       has_active_goal_ = false;
00501     }
00502   }
00503 
00504   void controllerStateCB(const control_msgs::JointTrajectoryControllerStateConstPtr &msg)
00505   {
00506     last_controller_state_ = msg;
00507     const ros::Time now = ros::Time::now();
00508 
00509     if (!has_active_goal_)
00510       return;
00511 
00513     try
00514     {     
00515       //now compute the current pointing axis from actual joint positions       
00516       KDL::JntArray jnt_pos(msg->joint_names.size());
00517       for (size_t i = 0; i < msg->joint_names.size(); ++i)
00518       {
00519         jnt_pos(i) = msg->actual.positions[i];
00520         ROS_DEBUG_STREAM("current state of joint " << i << ": " << jnt_pos(i));
00521       }
00522 
00523       KDL::Frame pose;
00524       pose_solver_->JntToCart(jnt_pos, pose);
00525 
00526       tf::Transform frame_in_root;
00527       tf::poseKDLToTF(pose, frame_in_root);
00528 
00529       tf::Vector3 axis_in_frame = pointing_axis_.normalized();
00530       tf::Vector3 target_from_frame = target_in_root_ - frame_in_root.getOrigin();
00531 
00532       target_from_frame.normalize();
00533       tf::Vector3 current_in_frame = frame_in_root.getBasis().inverse()*target_from_frame;
00534 
00535       control_msgs::PointHeadFeedback feedback;
00536       feedback.pointing_angle_error = current_in_frame.angle(desired_pointing_axis_in_frame_);
00537 
00538       ROS_DEBUG_STREAM("current error is: " << feedback.pointing_angle_error << " radians");
00539 
00540       active_goal_.publishFeedback(feedback);
00541 
00542           //the computed solution by the iterative solver can provide larger errors
00543           //due to MAX_ITERATIONS and correction_delta stop conditions
00544       if (feedback.pointing_angle_error <= goal_error_)
00545       {        
00546         ROS_DEBUG_STREAM("goal succeeded with error: " << feedback.pointing_angle_error << " radians");
00547         active_goal_.setSucceeded();
00548         has_active_goal_ = false;
00549       }
00550     }
00551     catch (const tf::TransformException &ex)
00552     {
00553       ROS_ERROR("Could not transform: %s", ex.what());
00554     }
00555   }
00556 };
00557 
00558 int main(int argc, char** argv)
00559 {
00560   ros::init(argc, argv, "point_head_action");
00561   ros::NodeHandle node;
00562   ControlHead ch(node);
00563   ros::spin();
00564   return 0;
00565 }


head_action
Author(s): Stuart Glaser
autogenerated on Thu Jun 6 2019 18:11:04