test_navigate.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2017, the neonavigation authors
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  * * Redistributions in binary form must reproduce the above copyright
11  * notice, this list of conditions and the following disclaimer in the
12  * documentation and/or other materials provided with the distribution.
13  * * Neither the name of the copyright holder nor the names of its
14  * contributors may be used to endorse or promote products derived from
15  * this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27  * POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include <cmath>
31 #include <cstddef>
32 #include <string>
33 #include <unistd.h>
34 #include <vector>
35 
36 #include <ros/ros.h>
37 
38 #include <costmap_cspace_msgs/CSpace3D.h>
39 #include <nav_msgs/GetPlan.h>
40 #include <nav_msgs/OccupancyGrid.h>
41 #include <nav_msgs/Path.h>
42 #include <planner_cspace_msgs/PlannerStatus.h>
43 #include <std_srvs/Empty.h>
44 #include <tf2/utils.h>
47 
48 #include <gtest/gtest.h>
49 
51 {
52 std::ostream& operator<<(std::ostream& os, const PlannerStatus::ConstPtr& msg)
53 {
54  if (!msg)
55  {
56  os << "nullptr";
57  }
58  else
59  {
60  os << std::endl
61  << " header: " << msg->header.stamp << " " << msg->header.frame_id << std::endl
62  << " status: " << static_cast<int>(msg->status) << std::endl
63  << " error: " << static_cast<int>(msg->error);
64  }
65  return os;
66 }
67 } // namespace planner_cspace_msgs
68 
69 class Navigate : public ::testing::Test
70 {
71 protected:
75  nav_msgs::OccupancyGrid::ConstPtr map_;
76  nav_msgs::OccupancyGrid::ConstPtr map_local_;
77  planner_cspace_msgs::PlannerStatus::ConstPtr planner_status_;
78  costmap_cspace_msgs::CSpace3D::ConstPtr costmap_;
79  nav_msgs::Path::ConstPtr path_;
90  std::vector<tf2::Stamped<tf2::Transform>> traj_;
91  std::string test_scope_;
92 
94  : tfl_(tfbuf_)
95  , local_map_apply_cnt_(0)
96  {
97  sub_map_ = nh_.subscribe("map_global", 1, &Navigate::cbMap, this);
98  sub_map_local_ = nh_.subscribe("map_local", 1, &Navigate::cbMapLocal, this);
99  sub_costmap_ = nh_.subscribe("costmap", 1, &Navigate::cbCostmap, this);
100  sub_status_ = nh_.subscribe(
101  "/planner_3d/status", 10, &Navigate::cbStatus, this);
102  sub_path_ = nh_.subscribe("path", 1, &Navigate::cbPath, this);
103  srv_forget_ =
104  nh_.serviceClient<std_srvs::EmptyRequest, std_srvs::EmptyResponse>(
105  "forget_planning_cost");
106  pub_map_ = nh_.advertise<nav_msgs::OccupancyGrid>("map", 1, true);
107  pub_map_local_ = nh_.advertise<nav_msgs::OccupancyGrid>("overlay", 1, true);
108  pub_initial_pose_ =
109  nh_.advertise<geometry_msgs::PoseWithCovarianceStamped>("initialpose", 1, true);
110  }
111 
112  virtual void SetUp()
113  {
114  test_scope_ = "[" + std::to_string(getpid()) + "] ";
115 
116  srv_forget_.waitForExistence(ros::Duration(10.0));
117  ros::Rate rate(10.0);
118 
119  geometry_msgs::PoseWithCovarianceStamped pose;
120  pose.header.frame_id = "map";
121  pose.pose.pose.position.x = 2.5;
122  pose.pose.pose.position.y = 0.45;
123  pose.pose.pose.orientation.z = 1.0;
124  pub_initial_pose_.publish(pose);
125 
126  const ros::Time deadline = ros::Time::now() + ros::Duration(15);
127 
128  while (ros::ok())
129  {
130  ros::spinOnce();
131  rate.sleep();
132  const ros::Time now = ros::Time::now();
133  if (now > deadline)
134  {
135  FAIL() << test_scope_ << now << " SetUp: transform timeout" << std::endl;
136  }
137  if (tfbuf_.canTransform("map", "base_link", now, ros::Duration(0.5)))
138  {
139  break;
140  }
141  }
142 
143  while (ros::ok() && !map_)
144  {
145  ros::spinOnce();
146  rate.sleep();
147  const ros::Time now = ros::Time::now();
148  if (now > deadline)
149  {
150  FAIL() << test_scope_ << now << " SetUp: map timeout" << std::endl;
151  }
152  }
153  pub_map_.publish(map_);
154  std::cerr << test_scope_ << ros::Time::now() << " Map applied." << std::endl;
155 
156  while (ros::ok() && !costmap_)
157  {
158  ros::spinOnce();
159  rate.sleep();
160  const ros::Time now = ros::Time::now();
161  if (now > deadline)
162  {
163  FAIL() << test_scope_ << now << " SetUp: costmap timeout" << std::endl;
164  }
165  }
166 
167  std_srvs::EmptyRequest req;
168  std_srvs::EmptyResponse res;
169  srv_forget_.call(req, res);
170 
171  ros::Duration(1.0).sleep();
172  }
173  void cbCostmap(const costmap_cspace_msgs::CSpace3D::ConstPtr& msg)
174  {
175  costmap_ = msg;
176  std::cerr << test_scope_ << msg->header.stamp << " Costmap received." << std::endl;
177  }
178  void cbMap(const nav_msgs::OccupancyGrid::ConstPtr& msg)
179  {
180  map_ = msg;
181  std::cerr << test_scope_ << msg->header.stamp << " Map received." << std::endl;
182  }
183  void cbMapLocal(const nav_msgs::OccupancyGrid::ConstPtr& msg)
184  {
185  map_local_ = msg;
186  std::cerr << test_scope_ << msg->header.stamp << " Local map received." << std::endl;
187  }
188  void cbStatus(const planner_cspace_msgs::PlannerStatus::ConstPtr& msg)
189  {
190  if (!planner_status_ || planner_status_->status != msg->status || planner_status_->error != msg->error)
191  {
192  std::cerr << test_scope_ << msg->header.stamp << " Status updated." << msg << std::endl;
193  }
194  planner_status_ = msg;
195  }
196  void cbPath(const nav_msgs::Path::ConstPtr& msg)
197  {
198  if (!path_ || path_->poses.size() != msg->poses.size())
199  {
200  if (msg->poses.size() == 0)
201  {
202  std::cerr << test_scope_ << msg->header.stamp << " Path updated. (empty)" << std::endl;
203  }
204  else
205  {
206  std::cerr
207  << test_scope_ << msg->header.stamp << " Path updated." << std::endl
208  << msg->poses.front().pose.position.x << ", " << msg->poses.front().pose.position.y << std::endl
209  << msg->poses.back().pose.position.x << ", " << msg->poses.back().pose.position.y << std::endl;
210  }
211  path_ = msg;
212  }
213  }
214  void pubMapLocal()
215  {
216  if (map_local_)
217  {
218  pub_map_local_.publish(map_local_);
219  if ((local_map_apply_cnt_++) % 30 == 0)
220  std::cerr << test_scope_ << " Local map applied." << std::endl;
221  }
222  }
224  {
225  geometry_msgs::TransformStamped trans_tmp =
226  tfbuf_.lookupTransform("map", "base_link", now, ros::Duration(0.5));
228  tf2::fromMsg(trans_tmp, trans);
229  traj_.push_back(trans);
230  return trans;
231  }
233  {
234  double x_prev(0), y_prev(0);
235  tf2::Quaternion rot_prev(0, 0, 0, 1);
236 
237  std::cerr << test_scope_ << traj_.size() << " points recorded" << std::endl;
238 
239  for (const auto& t : traj_)
240  {
241  const double x = t.getOrigin().getX();
242  const double y = t.getOrigin().getY();
243  const tf2::Quaternion rot = t.getRotation();
244  const double yaw_diff = rot.angleShortestPath(rot_prev);
245  if (std::abs(x - x_prev) > 0.1 || std::abs(y - y_prev) > 0.1 || std::abs(yaw_diff) > 0.2)
246  {
247  x_prev = x;
248  y_prev = y;
249  rot_prev = rot;
250  std::cerr << t.stamp_ << " " << x << " " << y << " " << tf2::getYaw(rot) << std::endl;
251  }
252  }
253  }
254 };
255 
257 {
258  ros::Publisher pub_path = nh_.advertise<nav_msgs::Path>("patrol_nodes", 1, true);
259 
260  ros::spinOnce();
261  ASSERT_TRUE(static_cast<bool>(map_));
262 
263  nav_msgs::Path path;
264  path.poses.resize(2);
265  path.header.frame_id = "map";
266  path.poses[0].header.frame_id = path.header.frame_id;
267  path.poses[0].pose.position.x = 1.7;
268  path.poses[0].pose.position.y = 2.8;
269  path.poses[0].pose.orientation = tf2::toMsg(tf2::Quaternion(tf2::Vector3(0.0, 0.0, 1.0), -3.14));
270  path.poses[1].header.frame_id = path.header.frame_id;
271  path.poses[1].pose.position.x = 1.9;
272  path.poses[1].pose.position.y = 2.8;
273  path.poses[1].pose.orientation = tf2::toMsg(tf2::Quaternion(tf2::Vector3(0.0, 0.0, 1.0), -1.57));
274  pub_path.publish(path);
275 
276  tf2::Transform goal;
277  tf2::fromMsg(path.poses.back().pose, goal);
278 
279  ros::Rate wait(10);
280  const ros::Time deadline = ros::Time::now() + ros::Duration(60);
281  while (ros::ok())
282  {
283  ros::spinOnce();
284  wait.sleep();
285 
286  const ros::Time now = ros::Time::now();
287 
288  if (now > deadline)
289  {
290  dumpRobotTrajectory();
291  FAIL()
292  << test_scope_ << "Navigation timeout." << std::endl
293  << "now: " << now << std::endl
294  << "status: " << planner_status_;
295  break;
296  }
297 
299  try
300  {
301  trans = lookupRobotTrans(now);
302  }
303  catch (tf2::TransformException& e)
304  {
305  std::cerr << test_scope_ << e.what() << std::endl;
306  continue;
307  }
308 
309  auto goal_rel = trans.inverse() * goal;
310  if (goal_rel.getOrigin().length() < 0.2 &&
311  std::abs(tf2::getYaw(goal_rel.getRotation())) < 0.2)
312  {
313  std::cerr << test_scope_ << "Navigation success." << std::endl;
314  ros::Duration(2.0).sleep();
315  return;
316  }
317 
318  for (int x = -2; x <= 2; ++x)
319  {
320  for (int y = -1; y <= 1; ++y)
321  {
322  const tf2::Vector3 pos =
323  trans * tf2::Vector3(x * map_->info.resolution, y * map_->info.resolution, 0);
324  const int map_x = pos.x() / map_->info.resolution;
325  const int map_y = pos.y() / map_->info.resolution;
326  const size_t addr = map_x + map_y * map_->info.width;
327  ASSERT_LT(addr, map_->data.size());
328  ASSERT_LT(map_x, static_cast<int>(map_->info.width));
329  ASSERT_LT(map_y, static_cast<int>(map_->info.height));
330  ASSERT_GE(map_x, 0);
331  ASSERT_GE(map_y, 0);
332  ASSERT_NE(map_->data[addr], 100);
333  }
334  }
335  }
336  ASSERT_TRUE(false);
337 }
338 
339 TEST_F(Navigate, NavigateWithLocalMap)
340 {
341  ros::Publisher pub_path = nh_.advertise<nav_msgs::Path>("patrol_nodes", 1, true);
342 
343  ros::spinOnce();
344  ASSERT_TRUE(static_cast<bool>(map_));
345  ASSERT_TRUE(static_cast<bool>(map_local_));
346  pubMapLocal();
347  ros::Duration(0.2).sleep();
348 
349  nav_msgs::Path path;
350  path.poses.resize(1);
351  path.header.frame_id = "map";
352  path.poses[0].header.frame_id = path.header.frame_id;
353  path.poses[0].pose.position.x = 1.7;
354  path.poses[0].pose.position.y = 2.8;
355  path.poses[0].pose.orientation = tf2::toMsg(tf2::Quaternion(tf2::Vector3(0.0, 0.0, 1.0), -3.14));
356  pub_path.publish(path);
357 
358  tf2::Transform goal;
359  tf2::fromMsg(path.poses.back().pose, goal);
360 
361  ros::Rate wait(10);
362  const ros::Time deadline = ros::Time::now() + ros::Duration(60);
363  while (ros::ok())
364  {
365  pubMapLocal();
366  ros::spinOnce();
367  wait.sleep();
368 
369  const ros::Time now = ros::Time::now();
370 
371  if (now > deadline)
372  {
373  dumpRobotTrajectory();
374  FAIL()
375  << test_scope_ << "Navigation timeout." << std::endl
376  << "now: " << now << std::endl
377  << "status: " << planner_status_;
378  break;
379  }
380 
382  try
383  {
384  trans = lookupRobotTrans(now);
385  }
386  catch (tf2::TransformException& e)
387  {
388  std::cerr << test_scope_ << e.what() << std::endl;
389  continue;
390  }
391 
392  auto goal_rel = trans.inverse() * goal;
393  if (goal_rel.getOrigin().length() < 0.2 &&
394  std::abs(tf2::getYaw(goal_rel.getRotation())) < 0.2)
395  {
396  std::cerr << test_scope_ << "Navagation success." << std::endl;
397  ros::Duration(2.0).sleep();
398  return;
399  }
400 
401  for (int x = -2; x <= 2; ++x)
402  {
403  for (int y = -1; y <= 1; ++y)
404  {
405  const tf2::Vector3 pos =
406  trans * tf2::Vector3(x * map_->info.resolution, y * map_->info.resolution, 0);
407  const int map_x = pos.x() / map_->info.resolution;
408  const int map_y = pos.y() / map_->info.resolution;
409  const size_t addr = map_x + map_y * map_->info.width;
410  ASSERT_LT(addr, map_->data.size());
411  ASSERT_LT(map_x, static_cast<int>(map_->info.width));
412  ASSERT_LT(map_y, static_cast<int>(map_->info.height));
413  ASSERT_GE(map_x, 0);
414  ASSERT_GE(map_y, 0);
415  ASSERT_NE(map_->data[addr], 100);
416  ASSERT_NE(map_local_->data[addr], 100);
417  }
418  }
419  }
420  ASSERT_TRUE(false);
421 }
422 
423 TEST_F(Navigate, GlobalPlan)
424 {
425  ros::ServiceClient srv_plan =
426  nh_.serviceClient<nav_msgs::GetPlanRequest, nav_msgs::GetPlanResponse>(
427  "/planner_3d/make_plan");
428 
429  ros::spinOnce();
430  ASSERT_TRUE(static_cast<bool>(map_));
431 
432  nav_msgs::GetPlanRequest req;
433  nav_msgs::GetPlanResponse res;
434 
435  req.tolerance = 0.0;
436  req.start.header.frame_id = "map";
437  req.start.pose.position.x = 1.95;
438  req.start.pose.position.y = 0.45;
439  req.start.pose.orientation =
440  tf2::toMsg(tf2::Quaternion(tf2::Vector3(0.0, 0.0, 1.0), 3.14));
441 
442  req.goal.header.frame_id = "map";
443  req.goal.pose.position.x = 1.25;
444  req.goal.pose.position.y = 2.15;
445  req.goal.pose.orientation =
446  tf2::toMsg(tf2::Quaternion(tf2::Vector3(0.0, 0.0, 1.0), -3.14));
447  // Planning failes as (12, 21, 0) is in rock.
448  ASSERT_FALSE(srv_plan.call(req, res));
449 
450  // Goal grid is moved to (12, 22, 0).
451  req.tolerance = 0.1;
452  ASSERT_TRUE(srv_plan.call(req, res));
453  EXPECT_NEAR(1.25, res.plan.poses.back().pose.position.x, 1.0e-5);
454  EXPECT_NEAR(2.25, res.plan.poses.back().pose.position.y, 1.0e-5);
455 
456  // Goal grid is moved to (12, 23, 0). This is because cost of (12, 22, 0) is larger than 50.
457  req.tolerance = 0.2f;
458  ASSERT_TRUE(srv_plan.call(req, res));
459  EXPECT_NEAR(1.25, res.plan.poses.back().pose.position.x, 1.0e-5);
460  EXPECT_NEAR(2.35, res.plan.poses.back().pose.position.y, 1.0e-5);
461 
462  req.tolerance = 0.0;
463  req.goal.header.frame_id = "map";
464  req.goal.pose.position.x = 1.85;
465  req.goal.pose.position.y = 2.75;
466  req.goal.pose.orientation =
467  tf2::toMsg(tf2::Quaternion(tf2::Vector3(0.0, 0.0, 1.0), -1.57));
468  ASSERT_TRUE(srv_plan.call(req, res));
469 
470  EXPECT_NEAR(req.start.pose.position.x, res.plan.poses.front().pose.position.x, 1.0e-5);
471  EXPECT_NEAR(req.start.pose.position.y, res.plan.poses.front().pose.position.y, 1.0e-5);
472  EXPECT_NEAR(req.goal.pose.position.x, res.plan.poses.back().pose.position.x, 1.0e-5);
473  EXPECT_NEAR(req.goal.pose.position.y, res.plan.poses.back().pose.position.y, 1.0e-5);
474 
475  for (const geometry_msgs::PoseStamped& p : res.plan.poses)
476  {
477  const int map_x = p.pose.position.x / map_->info.resolution;
478  const int map_y = p.pose.position.y / map_->info.resolution;
479  const size_t addr = map_x + map_y * map_->info.width;
480  ASSERT_LT(addr, map_->data.size());
481  ASSERT_LT(map_x, static_cast<int>(map_->info.width));
482  ASSERT_LT(map_y, static_cast<int>(map_->info.height));
483  ASSERT_GE(map_x, 0);
484  ASSERT_GE(map_y, 0);
485  ASSERT_NE(map_->data[addr], 100);
486  }
487 }
488 
489 TEST_F(Navigate, RobotIsInRockOnSetGoal)
490 {
491  ros::Publisher pub_path = nh_.advertise<nav_msgs::Path>("patrol_nodes", 1, true);
492 
493  ros::spinOnce();
494  ASSERT_TRUE(static_cast<bool>(map_));
495  ASSERT_TRUE(static_cast<bool>(map_local_));
496  pubMapLocal();
497  ros::Duration(0.2).sleep();
498 
499  nav_msgs::Path path;
500  path.poses.resize(1);
501  path.header.frame_id = "map";
502  path.poses[0].header.frame_id = path.header.frame_id;
503  path.poses[0].pose.position.x = 1.19;
504  path.poses[0].pose.position.y = 1.90;
505  path.poses[0].pose.orientation.w = 1.0;
506  pub_path.publish(path);
507 
508  tf2::Transform goal;
509  tf2::fromMsg(path.poses.back().pose, goal);
510 
511  ros::Rate wait(10);
512  const ros::Time deadline = ros::Time::now() + ros::Duration(10);
513  while (ros::ok())
514  {
515  pubMapLocal();
516  ros::spinOnce();
517  wait.sleep();
518 
519  const ros::Time now = ros::Time::now();
520 
521  if (now > deadline)
522  {
523  dumpRobotTrajectory();
524  FAIL()
525  << test_scope_ << "Navigation timeout." << std::endl
526  << "now: " << now << std::endl
527  << "status: " << planner_status_;
528  break;
529  }
530 
531  if (planner_status_->error == planner_cspace_msgs::PlannerStatus::PATH_NOT_FOUND)
532  {
533  return;
534  }
535  }
536  ASSERT_TRUE(false);
537 }
538 
539 int main(int argc, char** argv)
540 {
541  testing::InitGoogleTest(&argc, argv);
542  ros::init(argc, argv, "test_navigate");
543 
544  return RUN_ALL_TESTS();
545 }
ServiceClient serviceClient(const std::string &service_name, bool persistent=false, const M_string &header_values=M_string())
void cbStatus(const planner_cspace_msgs::PlannerStatus::ConstPtr &msg)
void cbMap(const nav_msgs::OccupancyGrid::ConstPtr &msg)
nav_msgs::OccupancyGrid::ConstPtr map_
TEST_F(Navigate, Navigate)
Subscriber subscribe(const std::string &topic, uint32_t queue_size, void(T::*fp)(M), T *obj, const TransportHints &transport_hints=TransportHints())
std::vector< tf2::Stamped< tf2::Transform > > traj_
void cbCostmap(const costmap_cspace_msgs::CSpace3D::ConstPtr &msg)
void wait(int seconds)
ROSCPP_DECL void init(int &argc, char **argv, const std::string &name, uint32_t options=0)
bool call(MReq &req, MRes &res)
ros::Publisher pub_map_
nav_msgs::Path::ConstPtr path_
costmap_cspace_msgs::CSpace3D::ConstPtr costmap_
ros::Subscriber sub_map_local_
ros::Subscriber sub_map_
geometry_msgs::TransformStamped t
ros::Subscriber sub_path_
void pubMapLocal()
tf2::Stamped< tf2::Transform > lookupRobotTrans(const ros::Time &now)
planner_cspace_msgs::PlannerStatus::ConstPtr planner_status_
nav_msgs::OccupancyGrid::ConstPtr map_local_
tf2_ros::TransformListener tfl_
ros::NodeHandle nh_
virtual bool canTransform(const std::string &target_frame, const std::string &source_frame, const ros::Time &target_time, const ros::Duration timeout, std::string *errstr=NULL) const
std::ostream & operator<<(std::ostream &os, const PlannerStatus::ConstPtr &msg)
virtual geometry_msgs::TransformStamped lookupTransform(const std::string &target_frame, const std::string &source_frame, const ros::Time &time, const ros::Duration timeout) const
ros::Subscriber sub_costmap_
void publish(const boost::shared_ptr< M > &message) const
ros::Publisher pub_initial_pose_
bool waitForExistence(ros::Duration timeout=ros::Duration(-1))
void dumpRobotTrajectory()
void fromMsg(const A &, B &b)
ROSCPP_DECL bool ok()
ros::ServiceClient srv_forget_
tf2_ros::Buffer tfbuf_
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
bool sleep()
double getYaw(const A &a)
B toMsg(const A &a)
ros::Subscriber sub_status_
virtual void SetUp()
static Time now()
bool sleep() const
ros::Publisher pub_map_local_
tf2Scalar angleShortestPath(const Quaternion &q) const
ROSCPP_DECL void spinOnce()
void cbPath(const nav_msgs::Path::ConstPtr &msg)
std::string test_scope_
size_t local_map_apply_cnt_
void cbMapLocal(const nav_msgs::OccupancyGrid::ConstPtr &msg)
int main(int argc, char **argv)


planner_cspace
Author(s): Atsushi Watanabe
autogenerated on Mon Jul 3 2023 02:39:06