cob_footprint_observer.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2017 Fraunhofer Institute for Manufacturing Engineering and Automation (IPA)
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9 
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 
18 #include <cob_footprint_observer.h>
19 
20 // Constructor
22  times_warned_(0)
23 {
24  nh_ = ros::NodeHandle("~");
25 
26  m_mutex = PTHREAD_MUTEX_INITIALIZER;
27 
28  // publish footprint
29  topic_pub_footprint_ = nh_.advertise<geometry_msgs::PolygonStamped>("adjusted_footprint",1);
30 
31  // advertise service
33 
34  // read footprint_source parameter
35  std::string footprint_source;
36  if(!nh_.hasParam("footprint_source")) ROS_WARN("Checking default location (/local_costmap_node/costmap) for initial footprint parameter.");
37  nh_.param("footprint_source", footprint_source, std::string("/local_costmap_node/costmap"));
38 
39  // node handle to get footprint from parameter server
40  ros::NodeHandle footprint_source_nh_(footprint_source);
41 
42  // load the robot footprint from the parameter server if its available in the local costmap namespace
43  robot_footprint_ = loadRobotFootprint(footprint_source_nh_);
44  if(robot_footprint_.size() > 4)
45  ROS_WARN("You have set more than 4 points as robot_footprint, cob_footprint_observer can deal only with rectangular footprints so far!");
46 
47  // get parameter sepcifying minimal changes of footprint that are accepted. (smaller changes are neglected)
48  if(!nh_.hasParam("epsilon"))
49  ROS_WARN("No epsilon value specified. Changes in footprint smaller than epsilon are neglected. Using default [0.01m]!");
50  nh_.param("epsilon", epsilon_, 0.01);
51 
52  // get the frames for which to check the footprint
53  if(!nh_.hasParam("frames_to_check")) ROS_WARN("No frames to check for footprint observer. Only using initial footprint!");
54  nh_.param("frames_to_check", frames_to_check_, std::string(""));
55 
56  if(!nh_.hasParam("robot_base_frame")) ROS_WARN("No parameter robot_base_frame on parameter server. Using default [/base_link].");
57  nh_.param("robot_base_frame", robot_base_frame_, std::string("/base_link"));
58 
60 }
61 
62 // Destructor
64 {}
65 
66 // GetFootprint Service callback
67 bool FootprintObserver::getFootprintCB(cob_footprint_observer::GetFootprint::Request &req, cob_footprint_observer::GetFootprint::Response &resp)
68 {
69  // create Polygon message for service call
70  geometry_msgs::PolygonStamped footprint_poly;
71  footprint_poly.header.frame_id = robot_base_frame_;
72  footprint_poly.header.stamp = ros::Time::now();
73 
74  footprint_poly.polygon.points.resize(robot_footprint_.size());
75  for(unsigned int i=0; i<robot_footprint_.size(); ++i) {
76  footprint_poly.polygon.points[i].x = robot_footprint_[i].x;
77  footprint_poly.polygon.points[i].y = robot_footprint_[i].y;
78  footprint_poly.polygon.points[i].z = robot_footprint_[i].z;
79  }
80 
81  resp.footprint = footprint_poly;
82  resp.success.data = true;
83 
84  return true;
85 }
86 
87 // load robot footprint from costmap_2d_ros to keep same footprint format
88 std::vector<geometry_msgs::Point> FootprintObserver::loadRobotFootprint(ros::NodeHandle node){
89  std::vector<geometry_msgs::Point> footprint;
90  geometry_msgs::Point pt;
91  double padding;
92 
93  std::string padding_param, footprint_param;
94  if(!node.searchParam("footprint_padding", padding_param))
95  padding = 0.01;
96  else
97  node.param(padding_param, padding, 0.01);
98 
99  //grab the footprint from the parameter server if possible
100  XmlRpc::XmlRpcValue footprint_list;
101  std::string footprint_string;
102  std::vector<std::string> footstring_list;
103  if(node.searchParam("footprint", footprint_param)){
104  node.getParam(footprint_param, footprint_list);
105  if(footprint_list.getType() == XmlRpc::XmlRpcValue::TypeString) {
106  footprint_string = std::string(footprint_list);
107 
108  //if there's just an empty footprint up there, return
109  if(footprint_string == "[]" || footprint_string == "")
110  return footprint;
111 
112  boost::erase_all(footprint_string, " ");
113 
114  boost::char_separator<char> sep("[]");
115  boost::tokenizer<boost::char_separator<char> > tokens(footprint_string, sep);
116  footstring_list = std::vector<std::string>(tokens.begin(), tokens.end());
117  }
118  //make sure we have a list of lists
119  if(!(footprint_list.getType() == XmlRpc::XmlRpcValue::TypeArray && footprint_list.size() > 2)
120  && !(footprint_list.getType() == XmlRpc::XmlRpcValue::TypeString && footstring_list.size() > 5)){
121  ROS_FATAL("The footprint must be specified as list of lists on the parameter server, %s was specified as %s",
122  footprint_param.c_str(), std::string(footprint_list).c_str());
123  throw std::runtime_error("The footprint must be specified as list of lists on the parameter server with at least 3 points eg: [[x1, y1], [x2, y2], ..., [xn, yn]]");
124  }
125 
126  if(footprint_list.getType() == XmlRpc::XmlRpcValue::TypeArray) {
127  for(int i = 0; i < footprint_list.size(); ++i){
128  //make sure we have a list of lists of size 2
129  XmlRpc::XmlRpcValue point = footprint_list[i];
130  if(!(point.getType() == XmlRpc::XmlRpcValue::TypeArray && point.size() == 2)){
131  ROS_FATAL("The footprint must be specified as list of lists on the parameter server eg: [[x1, y1], [x2, y2], ..., [xn, yn]], but this spec is not of that form");
132  throw std::runtime_error("The footprint must be specified as list of lists on the parameter server eg: [[x1, y1], [x2, y2], ..., [xn, yn]], but this spec is not of that form");
133  }
134 
135  //make sure that the value we're looking at is either a double or an int
136  if(!(point[0].getType() == XmlRpc::XmlRpcValue::TypeInt || point[0].getType() == XmlRpc::XmlRpcValue::TypeDouble)){
137  ROS_FATAL("Values in the footprint specification must be numbers");
138  throw std::runtime_error("Values in the footprint specification must be numbers");
139  }
140  pt.x = point[0].getType() == XmlRpc::XmlRpcValue::TypeInt ? (int)(point[0]) : (double)(point[0]);
141  pt.x += sign(pt.x) * padding;
142 
143  //make sure that the value we're looking at is either a double or an int
144  if(!(point[1].getType() == XmlRpc::XmlRpcValue::TypeInt || point[1].getType() == XmlRpc::XmlRpcValue::TypeDouble)){
145  ROS_FATAL("Values in the footprint specification must be numbers");
146  throw std::runtime_error("Values in the footprint specification must be numbers");
147  }
148  pt.y = point[1].getType() == XmlRpc::XmlRpcValue::TypeInt ? (int)(point[1]) : (double)(point[1]);
149  pt.y += sign(pt.y) * padding;
150 
151  footprint.push_back(pt);
152 
153  node.deleteParam(footprint_param);
154  std::ostringstream oss;
155  bool first = true;
156  BOOST_FOREACH(geometry_msgs::Point p, footprint) {
157  if(first) {
158  oss << "[[" << p.x << "," << p.y << "]";
159  first = false;
160  }
161  else {
162  oss << ",[" << p.x << "," << p.y << "]";
163  }
164  }
165  oss << "]";
166  node.setParam(footprint_param, oss.str().c_str());
167  node.setParam("footprint", oss.str().c_str());
168  }
169  }
170 
171  else if(footprint_list.getType() == XmlRpc::XmlRpcValue::TypeString) {
172  std::vector<geometry_msgs::Point> footprint_spec;
173  bool valid_foot = true;
174  BOOST_FOREACH(std::string t, footstring_list) {
175  if( t != "," ) {
176  boost::erase_all(t, " ");
177  boost::char_separator<char> pt_sep(",");
178  boost::tokenizer<boost::char_separator<char> > pt_tokens(t, pt_sep);
179  std::vector<std::string> point(pt_tokens.begin(), pt_tokens.end());
180 
181  if(point.size() != 2) {
182  ROS_WARN("Each point must have exactly 2 coordinates");
183  valid_foot = false;
184  break;
185  }
186 
187  std::vector<double>tmp_pt;
188  BOOST_FOREACH(std::string p, point) {
189  std::istringstream iss(p);
190  double temp;
191  if(iss >> temp) {
192  tmp_pt.push_back(temp);
193  }
194  else {
195  ROS_WARN("Each coordinate must convert to a double.");
196  valid_foot = false;
197  break;
198  }
199  }
200  if(!valid_foot)
201  break;
202 
203  geometry_msgs::Point pt;
204  pt.x = tmp_pt[0];
205  pt.y = tmp_pt[1];
206 
207  footprint_spec.push_back(pt);
208  }
209  }
210  if (valid_foot) {
211  footprint = footprint_spec;
212  node.setParam("footprint", footprint_string);
213  }
214  else {
215  ROS_FATAL("This footprint is not vaid it must be specified as a list of lists with at least 3 points, you specified %s", footprint_string.c_str());
216  throw std::runtime_error("The footprint must be specified as list of lists on the parameter server with at least 3 points eg: [[x1, y1], [x2, y2], ..., [xn, yn]]");
217  }
218  }
219  }
220 
221  footprint_right_ = 0.0f; footprint_left_ = 0.0f; footprint_front_ = 0.0f; footprint_rear_ = 0.0f;
222  //extract rectangular borders for simplifying:
223  for(unsigned int i=0; i<footprint.size(); i++) {
224  if(footprint[i].x > footprint_front_) footprint_front_ = footprint[i].x;
225  if(footprint[i].x < footprint_rear_) footprint_rear_ = footprint[i].x;
226  if(footprint[i].y > footprint_left_) footprint_left_ = footprint[i].y;
227  if(footprint[i].y < footprint_right_) footprint_right_ = footprint[i].y;
228  }
229  ROS_DEBUG("Extracted rectangular footprint for cob_footprint_observer: Front: %f, Rear %f, Left: %f, Right %f",
231 
232  if ( fabs(footprint_front_ - footprint_rear_) == 0.0 || fabs(footprint_right_ - footprint_left_) == 0.0){
233  ROS_WARN("Footprint has no physical dimension!");
234  }
235 
236  // save initial footprint
241 
242  return footprint;
243 }
244 
245 // checks if footprint has to be adjusted and does so if necessary
247  // check each frame
248  std::string frame;
249  std::stringstream ss;
250  ss << frames_to_check_;
251 
252  double x_rear, x_front, y_left, y_right;
253  x_front = footprint_front_initial_;
254  x_rear = footprint_rear_initial_;
255  y_left = footprint_left_initial_;
256  y_right = footprint_right_initial_;
257 
258  bool missing_frame_exists = false;
259  while(ss >> frame){
260  // get transform between robot base frame and frame
262  tf::StampedTransform transform;
264 
265  tf::Vector3 frame_position = transform.getOrigin();
266 
267  // check if frame position is outside of current footprint
268  if(frame_position.x() > x_front) x_front = frame_position.x();
269  if(frame_position.x() < x_rear) x_rear = frame_position.x();
270  if(frame_position.y() > y_left) y_left = frame_position.y();
271  if(frame_position.y() < y_right) y_right = frame_position.y();
272  } else if ( (ros::Time::now() - last_tf_missing_).toSec() > 10.0
273  && times_warned_ < 3 ) {
274  ++times_warned_;
275  missing_frame_exists = true;
276  ROS_WARN("Footprint Observer: Transformation for %s not available! Frame %s not considered in adjusted footprint!",
277  frame.c_str(), frame.c_str());
278  }
279  }
280  if (missing_frame_exists)
282 
283  // check if footprint has changed
284  if ( fabs( footprint_front_ - x_front ) > epsilon_
285  || fabs( footprint_rear_ - x_rear ) > epsilon_
286  || fabs( footprint_left_ - y_left ) > epsilon_
287  || fabs( footprint_right_ - y_right ) > epsilon_ )
288  {
289  pthread_mutex_lock(&m_mutex);
290  // adjust footprint
291  footprint_front_ = x_front;
292  footprint_rear_ = x_rear;
293  footprint_left_ = y_left;
294  footprint_right_ = y_right;
295  pthread_mutex_unlock(&m_mutex);
296 
297  // create new footprint vector
298  geometry_msgs::Point point;
299  std::vector<geometry_msgs::Point> points;
300 
301  point.x = footprint_front_;
302  point.y = footprint_left_;
303  point.z = 0;
304  points.push_back(point);
305  point.y = footprint_right_;
306  points.push_back(point);
307  point.x = footprint_rear_;
308  points.push_back(point);
309  point.y = footprint_left_;
310  points.push_back(point);
311 
312  pthread_mutex_lock(&m_mutex);
313  robot_footprint_ = points;
314  pthread_mutex_unlock(&m_mutex);
315 
316  // publish the adjusted footprint
318  }
319 
320 }
321 
322 // publishes the adjusted footprint
324 
325  // create Polygon message
326  geometry_msgs::PolygonStamped footprint_poly;
327  footprint_poly.header.frame_id = robot_base_frame_;
328  footprint_poly.header.stamp = ros::Time::now();
329 
330  footprint_poly.polygon.points.resize(robot_footprint_.size());
331  for(unsigned int i=0; i<robot_footprint_.size(); ++i) {
332  footprint_poly.polygon.points[i].x = robot_footprint_[i].x;
333  footprint_poly.polygon.points[i].y = robot_footprint_[i].y;
334  footprint_poly.polygon.points[i].z = robot_footprint_[i].z;
335  }
336 
337  // publish adjusted footprint
338  topic_pub_footprint_.publish(footprint_poly);
339 
340 }
341 
342 // compute the sign of x
343 double FootprintObserver::sign(double x){
344  if(x >= 0.0f) return 1.0f;
345  else return -1.0f;
346 }
347 
348 //#######################
349 //#### main programm ####
350 int main(int argc, char** argv)
351 {
352  // initialize ROS, specify name of node
353  ros::init(argc,argv,"footprint_observer");
354 
355  // create observer
356  FootprintObserver footprintObserver;
357 
358  // run footprint observer periodically until node has been shut down
359  ros::Rate loop_rate(30); // Hz
360  while(footprintObserver.nh_.ok()){
361  footprintObserver.checkFootprint();
362  ros::spinOnce();
363  loop_rate.sleep();
364  }
365 
366  return 0;
367 };
#define ROS_FATAL(...)
void publish(const boost::shared_ptr< M > &message) const
ros::ServiceServer srv_get_footprint_
f
std::vector< geometry_msgs::Point > loadRobotFootprint(ros::NodeHandle node)
loads the robot footprint from the costmap node
int size() const
bool deleteParam(const std::string &key) const
tf::TransformListener tf_listener_
ROSCPP_DECL void init(int &argc, char **argv, const std::string &name, uint32_t options=0)
ServiceServer advertiseService(const std::string &service, bool(T::*srv_func)(MReq &, MRes &), T *obj)
checks the footprint of care-o-bot and advertises a service to get the adjusted footprint ...
Type const & getType() const
TFSIMD_FORCE_INLINE const tfScalar & y() const
#define ROS_WARN(...)
bool canTransform(const std::string &target_frame, const std::string &source_frame, const ros::Time &time, std::string *error_msg=NULL) const
TFSIMD_FORCE_INLINE const tfScalar & x() const
bool searchParam(const std::string &key, std::string &result) const
bool param(const std::string &param_name, T &param_val, const T &default_val) const
TFSIMD_FORCE_INLINE const tfScalar & y() const
TFSIMD_FORCE_INLINE const tfScalar & x() const
ros::Publisher topic_pub_footprint_
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
TFSIMD_FORCE_INLINE Vector3 & getOrigin()
void lookupTransform(const std::string &target_frame, const std::string &source_frame, const ros::Time &time, StampedTransform &transform) const
bool sleep()
void publishFootprint()
publishes the adjusted footprint as geometry_msgs::StampedPolygon message
double sign(double x)
computes the sign of x
bool getParam(const std::string &key, std::string &s) const
static Time now()
int main(int argc, char **argv)
bool ok() const
bool hasParam(const std::string &key) const
void checkFootprint()
checks the footprint of the robot if it needs to be enlarged due to arm or tray
ROSCPP_DECL void spinOnce()
void setParam(const std::string &key, const XmlRpc::XmlRpcValue &v) const
std::vector< geometry_msgs::Point > robot_footprint_
bool getFootprintCB(cob_footprint_observer::GetFootprint::Request &req, cob_footprint_observer::GetFootprint::Response &resp)
callback for GetFootprint service
#define ROS_DEBUG(...)


cob_footprint_observer
Author(s): Matthias Gruhler
autogenerated on Thu Apr 8 2021 02:39:33