test_dynamic_parameter_change.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2023, 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 <memory>
32 
33 #include <gtest/gtest.h>
34 
35 #include <dynamic_reconfigure/client.h>
36 #include <move_base_msgs/MoveBaseAction.h>
37 #include <nav_msgs/OccupancyGrid.h>
38 #include <nav_msgs/Odometry.h>
39 #include <planner_cspace/Planner3DConfig.h>
40 #include <ros/ros.h>
43 
45 
47  : public ActionTestBase<move_base_msgs::MoveBaseAction, ACTION_TOPIC_MOVE_BASE>
48 {
49 public:
50  void SetUp() final
51  {
52  path_ = nullptr;
53  planner_3d_client_.reset(
54  new dynamic_reconfigure::Client<planner_cspace::Planner3DConfig>("/planner_3d/"));
56  pub_map_overlay_ = node_.advertise<nav_msgs::OccupancyGrid>("map_overlay", 1, true);
57  pub_odom_ = node_.advertise<nav_msgs::Odometry>("odom", 1, true); // not actually used
58 
59  const ros::Time deadline = ros::Time::now() + ros::Duration(2);
61  {
62  ros::Duration(0.1).sleep();
63  ASSERT_TRUE(ros::ok());
64  ASSERT_LT(ros::Time::now(), deadline);
65  }
66  ros::Duration(0.5).sleep(); // wait some more time to ensure tf topic connection
67 
68  map_overlay_.header.frame_id = "map";
69  map_overlay_.info.resolution = 0.1;
70  map_overlay_.info.width = 32;
71  map_overlay_.info.height = 32;
72  map_overlay_.info.origin.position.x = 0.0;
73  map_overlay_.info.origin.position.y = 0.0;
74  map_overlay_.info.origin.position.z = 0.0;
75  map_overlay_.info.origin.orientation.x = 0.0;
76  map_overlay_.info.origin.orientation.y = 0.0;
77  map_overlay_.info.origin.orientation.z = 0.0;
78  map_overlay_.info.origin.orientation.w = 1.0;
79  map_overlay_.data.resize(map_overlay_.info.width * map_overlay_.info.height, 0);
80  publishMapAndRobot(0, 0, 0);
82 
83  default_config_ = planner_cspace::Planner3DConfig::__getDefault__();
84  default_config_.max_retry_num = 5;
85  default_config_.tolerance_range = 0.0;
86  default_config_.temporary_escape = false;
87  default_config_.goal_tolerance_lin = 0.05;
88  default_config_.cost_in_place_turn = 3.0;
89  default_config_.min_curve_radius = 0.4;
90  default_config_.max_vel = 0.3;
91  default_config_.max_ang_vel = 1.0;
92  ASSERT_TRUE(planner_3d_client_->setConfiguration(default_config_));
93  }
94  void TearDown() final
95  {
96  move_base_->cancelAllGoals();
97  }
98 
99 protected:
100  void cbPath(const nav_msgs::Path::ConstPtr& msg)
101  {
102  path_ = msg;
105  }
106 
107  move_base_msgs::MoveBaseGoal CreateGoalInFree()
108  {
109  move_base_msgs::MoveBaseGoal goal;
110  goal.target_pose.header.stamp = ros::Time::now();
111  goal.target_pose.header.frame_id = "map";
112  goal.target_pose.pose.position.x = 1.25;
113  goal.target_pose.pose.position.y = 1.05;
114  goal.target_pose.pose.position.z = 0.0;
115  goal.target_pose.pose.orientation.x = 0.0;
116  goal.target_pose.pose.orientation.y = 0.0;
117  goal.target_pose.pose.orientation.z = std::sin(M_PI / 4);
118  goal.target_pose.pose.orientation.w = std::cos(M_PI / 4);
119  return goal;
120  }
121 
123  {
124  for (size_t i = 0; i < path_->poses.size() - 1; ++i)
125  {
126  const auto& pose1 = path_->poses[i];
127  const auto& pose2 = path_->poses[i + 1];
128  const double distance = std::hypot(pose1.pose.position.x - pose2.pose.position.x,
129  pose1.pose.position.y - pose2.pose.position.y);
130  const double yaw_diff = std::abs(tf2::getYaw(pose1.pose.orientation) - tf2::getYaw(pose2.pose.orientation));
131  if (distance > 1.0e-3 && yaw_diff > 1.0e-3)
132  {
133  return true;
134  }
135  }
136  return false;
137  }
138 
139  ::testing::AssertionResult comparePath(const nav_msgs::Path& path1, const nav_msgs::Path& path2)
140  {
141  if (path1.poses.size() != path2.poses.size())
142  {
143  return ::testing::AssertionFailure()
144  << "Path size different: " << path1.poses.size() << " != " << path2.poses.size();
145  }
146  for (size_t i = 0; i < path1.poses.size(); ++i)
147  {
148  const geometry_msgs::Point& pos1 = path1.poses[i].pose.position;
149  const geometry_msgs::Point& pos2 = path2.poses[i].pose.position;
150  if (std::abs(pos1.x - pos2.x) > 1.0e-6)
151  {
152  return ::testing::AssertionFailure() << "X different at #" << i << ": " << pos1.x << " != " << pos2.x;
153  }
154  if (std::abs(pos1.y - pos2.y) > 1.0e-6)
155  {
156  return ::testing::AssertionFailure() << "Y different at #" << i << ": " << pos1.y << " != " << pos2.y;
157  }
158  const double yaw1 = tf2::getYaw(path1.poses[i].pose.orientation);
159  const double yaw2 = tf2::getYaw(path2.poses[i].pose.orientation);
160  if (std::abs(yaw1 - yaw2) > 1.0e-6)
161  {
162  return ::testing::AssertionFailure() << "Yaw different at #" << i << ": " << yaw1 << " != " << yaw2;
163  }
164  }
165  return ::testing::AssertionSuccess();
166  }
167 
169  {
170  move_base_->sendGoal(CreateGoalInFree());
171 
172  ros::spinOnce(); // Flush message buffer
173  path_ = nullptr;
174 
175  const ros::Time start_time = ros::Time::now();
176  ros::Time deadline = start_time + ros::Duration(1.0);
177  while (ros::ok())
178  {
179  ros::Duration(0.1).sleep();
180  ros::spinOnce();
181  if (path_ && (path_->header.stamp > start_time) && (path_->poses.size() > 0))
182  {
183  break;
184  }
185  ASSERT_LT(ros::Time::now(), deadline)
186  << "Failed to plan:" << move_base_->getState().toString() << statusString();
187  }
188  }
189 
190  void publishMapAndRobot(const double x, const double y, const double yaw)
191  {
192  const ros::Time current_time = ros::Time::now();
193  geometry_msgs::TransformStamped trans;
194  trans.header.stamp = current_time;
195  trans.header.frame_id = "odom";
196  trans.child_frame_id = "base_link";
197  trans.transform.translation = tf2::toMsg(tf2::Vector3(x, y, 0.0));
198  trans.transform.rotation = tf2::toMsg(tf2::Quaternion(tf2::Vector3(0.0, 0.0, 1.0), yaw));
199  tfb_.sendTransform(trans);
200 
201  nav_msgs::Odometry odom;
202  odom.header.frame_id = "odom";
203  odom.header.stamp = current_time;
204  odom.child_frame_id = "base_link";
205  odom.pose.pose.position.x = x;
206  odom.pose.pose.position.y = y;
207  odom.pose.pose.orientation = tf2::toMsg(tf2::Quaternion(tf2::Vector3(0.0, 0.0, 1.0), yaw));
208  pub_odom_.publish(odom);
209 
211  }
212 
213  double getAveragePathInterval(const ros::Duration& costmap_publishing_interval)
214  {
215  publishMapAndRobot(2.55, 0.45, M_PI);
216  ros::Duration(0.3).sleep();
217  move_base_->sendGoal(CreateGoalInFree());
218  while (ros::ok() && (move_base_->getState() != actionlib::SimpleClientGoalState::ACTIVE))
219  {
220  ros::spinOnce();
221  }
222 
224  publishMapAndRobot(2.55, 0.45, M_PI);
225  ros::Time last_costmap_publishing_time = ros::Time::now();
226  ros::Rate r(100);
227  while (ros::ok() && (last_path_received_time_ == ros::Time()))
228  {
229  if ((ros::Time::now() - last_costmap_publishing_time) > costmap_publishing_interval)
230  {
231  publishMapAndRobot(2.55, 0.45, M_PI);
232  last_costmap_publishing_time = ros::Time::now();
233  };
234  ros::spinOnce();
235  r.sleep();
236  }
237  const ros::Time initial_path_received_time_ = last_path_received_time_;
238  const int prev_path_received_count = path_received_count_;
239  while (ros::ok() && (path_received_count_ < prev_path_received_count + 10))
240  {
241  if ((ros::Time::now() - last_costmap_publishing_time) > costmap_publishing_interval)
242  {
243  publishMapAndRobot(2.55, 0.45, M_PI);
244  last_costmap_publishing_time = ros::Time::now();
245  }
246  ros::spinOnce();
247  r.sleep();
248  }
249  return (last_path_received_time_ - initial_path_received_time_).toSec() /
250  (path_received_count_ - prev_path_received_count);
251  }
252 
255  nav_msgs::Path::ConstPtr path_;
256  std::unique_ptr<dynamic_reconfigure::Client<planner_cspace::Planner3DConfig>> planner_3d_client_;
259  nav_msgs::OccupancyGrid map_overlay_;
260  planner_cspace::Planner3DConfig default_config_;
263 };
264 
266 {
267  publishMapAndRobot(2.55, 0.45, M_PI);
268  ros::Duration(0.5).sleep();
269 
270  sendGoalAndWaitForPath();
271  // The default path is including curves.
272  EXPECT_TRUE(isPathIncludingCurves());
273 
274  planner_cspace::Planner3DConfig config = default_config_;
275  // Large min_curve_radius disables curves.
276  config.min_curve_radius = 10.0;
277  ASSERT_TRUE(planner_3d_client_->setConfiguration(config));
278 
279  sendGoalAndWaitForPath();
280  // The default path is including curves.
281  EXPECT_FALSE(isPathIncludingCurves());
282 }
283 
284 TEST_F(DynamicParameterChangeTest, StartPosePrediction)
285 {
286  publishMapAndRobot(1.65, 0.65, M_PI);
287  ros::Duration(0.5).sleep();
288  sendGoalAndWaitForPath();
289  const nav_msgs::Path initial_path = *path_;
290 
291  // The path is changed to keep distance from the obstacle.
292  map_overlay_.data[13 + 5 * map_overlay_.info.width] = 100;
293  publishMapAndRobot(1.65, 0.65, M_PI);
294  ros::Duration(0.5).sleep();
295  sendGoalAndWaitForPath();
296  EXPECT_FALSE(comparePath(initial_path, *path_));
297 
298  // Enable start pose prediction.
299  move_base_->cancelAllGoals();
300  planner_cspace::Planner3DConfig config = default_config_;
301  config.keep_a_part_of_previous_path = true;
302  config.dist_stop_to_previous_path = 0.1;
303  ASSERT_TRUE(planner_3d_client_->setConfiguration(config));
304 
305  // No obstacle and the path is same as the first one.
306  map_overlay_.data[13 + 5 * map_overlay_.info.width] = 0;
307  publishMapAndRobot(1.65, 0.65, M_PI);
308  ros::Duration(0.5).sleep();
309  sendGoalAndWaitForPath();
310  EXPECT_TRUE(comparePath(initial_path, *path_));
311 
312  // The path does not change as a part of the previous path is kept and it is not possible to keep distance from
313  // the obstacle.
314  map_overlay_.data[13 + 5 * map_overlay_.info.width] = 100;
315  publishMapAndRobot(1.65, 0.65, M_PI);
316  ros::Duration(0.5).sleep();
317  sendGoalAndWaitForPath();
318  EXPECT_TRUE(comparePath(initial_path, *path_));
319 
320  // It is expected that the robot reaches the goal during the path planning.
321  move_base_->cancelAllGoals();
322  map_overlay_.data[13 + 5 * map_overlay_.info.width] = 0;
323  publishMapAndRobot(1.25, 0.95, M_PI / 2);
324  ros::Duration(0.5).sleep();
325  sendGoalAndWaitForPath();
326  const nav_msgs::Path short_path = *path_;
327  // In the second path planning after cancel, the exptected start pose is same as the goal.
328  sendGoalAndWaitForPath();
329  EXPECT_TRUE(comparePath(short_path, *path_));
330 }
331 
332 TEST_F(DynamicParameterChangeTest, TriggerPlanByCostmapUpdate)
333 {
334  publishMapAndRobot(2.55, 0.45, M_PI);
335  ros::Duration(0.5).sleep();
336  sendGoalAndWaitForPath();
337 
338  const ros::Duration costmap_publishing_interval(0.1);
339  // The path planning frequency is 4.0 Hz (Designated by the "freq" paramteer)
340  const double default_interval = getAveragePathInterval(costmap_publishing_interval);
341  EXPECT_NEAR(default_interval, 1.0 / default_config_.freq, (1.0 / default_config_.freq) * 0.1);
342 
343  planner_cspace::Planner3DConfig config = default_config_;
344  config.trigger_plan_by_costmap_update = true;
345  config.costmap_watchdog = 0.5;
346  ASSERT_TRUE(planner_3d_client_->setConfiguration(config));
347 
348  // The path planning is trigger by the callback of CSpace3DUpdate, so its frequency is same as the frequency of
349  // CSpace3DUpdate (10 Hz).
350  const double interval_triggered_by_costmap = getAveragePathInterval(costmap_publishing_interval);
351  EXPECT_NEAR(interval_triggered_by_costmap, costmap_publishing_interval.toSec(),
352  costmap_publishing_interval.toSec() * 0.1);
353 
354  // The path planning is trigger by costmap_watchdog_(0.5 seconds) when CSpace3DUpdate is not published.
355  const double interval_triggered_by_watchdog = getAveragePathInterval(ros::Duration(100));
356  EXPECT_NEAR(interval_triggered_by_watchdog, config.costmap_watchdog, config.costmap_watchdog * 0.1);
357 }
358 
359 int main(int argc, char** argv)
360 {
361  testing::InitGoogleTest(&argc, argv);
362  ros::init(argc, argv, "test_dynamic_parameter_change");
363  return RUN_ALL_TESTS();
364 }
DynamicParameterChangeTest::sendGoalAndWaitForPath
void sendGoalAndWaitForPath()
Definition: test_dynamic_parameter_change.cpp:168
msg
msg
ros::Publisher
ros::init
ROSCPP_DECL void init(const M_string &remappings, const std::string &name, uint32_t options=0)
tf2::getYaw
double getYaw(const A &a)
ros.h
ros::spinOnce
ROSCPP_DECL void spinOnce()
DynamicParameterChangeTest::getAveragePathInterval
double getAveragePathInterval(const ros::Duration &costmap_publishing_interval)
Definition: test_dynamic_parameter_change.cpp:213
ros::Publisher::publish
void publish(const boost::shared_ptr< M > &message) const
ros::NodeHandle::advertise
Publisher advertise(AdvertiseOptions &ops)
ros::ok
ROSCPP_DECL bool ok()
DynamicParameterChangeTest::TearDown
void TearDown() final
Definition: test_dynamic_parameter_change.cpp:94
transform_broadcaster.h
TEST_F
TEST_F(DynamicParameterChangeTest, DisableCurves)
Definition: test_dynamic_parameter_change.cpp:265
ros::Subscriber::getNumPublishers
uint32_t getNumPublishers() const
ActionTestBase< move_base_msgs::MoveBaseAction, ACTION_TOPIC_MOVE_BASE >::statusString
std::string statusString() const
Definition: action_test_base.h:113
DynamicParameterChangeTest::comparePath
::testing::AssertionResult comparePath(const nav_msgs::Path &path1, const nav_msgs::Path &path2)
Definition: test_dynamic_parameter_change.cpp:139
DynamicParameterChangeTest::pub_odom_
ros::Publisher pub_odom_
Definition: test_dynamic_parameter_change.cpp:258
tf2_ros::TransformBroadcaster::sendTransform
void sendTransform(const geometry_msgs::TransformStamped &transform)
DynamicParameterChangeTest::isPathIncludingCurves
bool isPathIncludingCurves() const
Definition: test_dynamic_parameter_change.cpp:122
DynamicParameterChangeTest
Definition: test_dynamic_parameter_change.cpp:46
DynamicParameterChangeTest::CreateGoalInFree
move_base_msgs::MoveBaseGoal CreateGoalInFree()
Definition: test_dynamic_parameter_change.cpp:107
ActionTestBase< move_base_msgs::MoveBaseAction, ACTION_TOPIC_MOVE_BASE >::move_base_
ActionClientPtr move_base_
Definition: action_test_base.h:125
DynamicParameterChangeTest::path_received_count_
int path_received_count_
Definition: test_dynamic_parameter_change.cpp:261
ros::NodeHandle::subscribe
Subscriber subscribe(const std::string &topic, uint32_t queue_size, const boost::function< void(C)> &callback, const VoidConstPtr &tracked_object=VoidConstPtr(), const TransportHints &transport_hints=TransportHints())
ros::Rate::sleep
bool sleep()
DynamicParameterChangeTest::map_overlay_
nav_msgs::OccupancyGrid map_overlay_
Definition: test_dynamic_parameter_change.cpp:259
action_test_base.h
DynamicParameterChangeTest::publishMapAndRobot
void publishMapAndRobot(const double x, const double y, const double yaw)
Definition: test_dynamic_parameter_change.cpp:190
transform_listener.h
DynamicParameterChangeTest::pub_map_overlay_
ros::Publisher pub_map_overlay_
Definition: test_dynamic_parameter_change.cpp:257
ros::Time
ActionTestBase::SetUp
void SetUp()
Definition: action_test_base.h:62
DynamicParameterChangeTest::last_path_received_time_
ros::Time last_path_received_time_
Definition: test_dynamic_parameter_change.cpp:262
actionlib::SimpleClientGoalState::ACTIVE
ACTIVE
tf2_ros::TransformBroadcaster
ActionTestBase
Definition: action_test_base.h:51
tf2::Quaternion
DynamicParameterChangeTest::path_
nav_msgs::Path::ConstPtr path_
Definition: test_dynamic_parameter_change.cpp:255
DynamicParameterChangeTest::sub_path_
ros::Subscriber sub_path_
Definition: test_dynamic_parameter_change.cpp:254
tf2::toMsg
B toMsg(const A &a)
DynamicParameterChangeTest::tfb_
tf2_ros::TransformBroadcaster tfb_
Definition: test_dynamic_parameter_change.cpp:253
ros::Rate
main
int main(int argc, char **argv)
Definition: test_dynamic_parameter_change.cpp:359
ros::Duration::sleep
bool sleep() const
ros::Publisher::getNumSubscribers
uint32_t getNumSubscribers() const
DurationBase< Duration >::toSec
double toSec() const
ActionTestBase< move_base_msgs::MoveBaseAction, ACTION_TOPIC_MOVE_BASE >::node_
ros::NodeHandle node_
Definition: action_test_base.h:123
DynamicParameterChangeTest::cbPath
void cbPath(const nav_msgs::Path::ConstPtr &msg)
Definition: test_dynamic_parameter_change.cpp:100
DynamicParameterChangeTest::default_config_
planner_cspace::Planner3DConfig default_config_
Definition: test_dynamic_parameter_change.cpp:260
DynamicParameterChangeTest::SetUp
void SetUp() final
Definition: test_dynamic_parameter_change.cpp:50
DynamicParameterChangeTest::planner_3d_client_
std::unique_ptr< dynamic_reconfigure::Client< planner_cspace::Planner3DConfig > > planner_3d_client_
Definition: test_dynamic_parameter_change.cpp:256
ros::Duration
ros::Subscriber
ros::Time::now
static Time now()


planner_cspace
Author(s): Atsushi Watanabe
autogenerated on Fri May 16 2025 02:15:23