moveit2
The MoveIt Motion Planning Framework for ROS 2.
servo_launch_test_common.hpp
Go to the documentation of this file.
1 /*********************************************************************
2  * Software License Agreement (BSD License)
3  *
4  * Copyright (c) 2020, PickNik Inc.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  * notice, this list of conditions and the following disclaimer.
13  * * Redistributions in binary form must reproduce the above
14  * copyright notice, this list of conditions and the following
15  * disclaimer in the documentation and/or other materials provided
16  * with the distribution.
17  * * Neither the name of PickNik Inc. nor the names of its
18  * contributors may be used to endorse or promote products derived
19  * from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  *********************************************************************/
34 
35 /* Title : servo_launch_test_common.hpp
36  * Project : moveit_servo
37  * Created : 08/03/2020
38  * Author : Adam Pettinger
39  */
40 
41 // C++
42 #include <string>
43 
44 // ROS
45 #include <rclcpp/rclcpp.hpp>
46 #include <std_srvs/srv/trigger.hpp>
47 #include <std_msgs/msg/int8.hpp>
48 #include <std_msgs/msg/float64.hpp>
49 #include <std_msgs/msg/float64_multi_array.hpp>
50 #include <control_msgs/msg/joint_jog.hpp>
51 #include <geometry_msgs/msg/twist_stamped.hpp>
52 #include <trajectory_msgs/msg/joint_trajectory.hpp>
53 #include <sensor_msgs/msg/joint_state.hpp>
54 #include <moveit_msgs/srv/change_drift_dimensions.hpp>
55 #include <moveit_msgs/srv/change_control_dimensions.hpp>
56 
57 // Testing
58 #include <gtest/gtest.h>
59 
60 // Servo
63 
64 #pragma once
65 
66 static const rclcpp::Logger LOGGER = rclcpp::get_logger("moveit_servo.servo_launch_test_common.hpp");
67 
68 namespace moveit_servo
69 {
70 class ServoFixture : public ::testing::Test
71 {
72 public:
73  void SetUp() override
74  {
75  ASSERT_TRUE(servo_parameters_.get() != nullptr);
76  executor_->add_node(node_);
77  executor_thread_ = std::thread([this]() { this->executor_->spin(); });
78  }
79 
81  : node_(std::make_shared<rclcpp::Node>("servo_testing"))
82  , executor_(std::make_shared<rclcpp::executors::SingleThreadedExecutor>())
83  {
84  // read parameters and store them in shared pointer to constant
86  if (servo_parameters_ == nullptr)
87  {
88  RCLCPP_FATAL(LOGGER, "Failed to load the servo parameters");
89  return;
90  }
91 
92  // store test constants as shared pointer to constant struct
93  {
94  auto test_parameters = std::make_shared<struct TestParameters>();
95  test_parameters->publish_hz = 2.0 / servo_parameters_->incoming_command_timeout;
96  test_parameters->publish_period = 1.0 / test_parameters->publish_hz;
97  test_parameters->timeout_iterations = 50 * test_parameters->publish_hz;
98  test_parameters->servo_node_name = "/servo_node";
99  test_parameters_ = test_parameters;
100  }
101 
102  // Init ROS interfaces
103  // Publishers
104  pub_twist_cmd_ = node_->create_publisher<geometry_msgs::msg::TwistStamped>(
105  resolveServoTopicName(servo_parameters_->cartesian_command_in_topic), rclcpp::SystemDefaultsQoS());
106  pub_joint_cmd_ = node_->create_publisher<control_msgs::msg::JointJog>(
107  resolveServoTopicName(servo_parameters_->joint_command_in_topic), rclcpp::SystemDefaultsQoS());
108  }
109 
110  void TearDown() override
111  {
112  // If the stop client isn't null, we set it up and likely started the Servo. Stop it.
113  // Otherwise the Servo is still running when another test starts...
114  if (!client_servo_stop_)
115  {
116  stop();
117  }
118  executor_->cancel();
119  if (executor_thread_.joinable())
120  executor_thread_.join();
121  }
122 
123  std::string resolveServoTopicName(std::string topic_name)
124  {
125  if (topic_name.at(0) == '~')
126  return topic_name.replace(0, 1, test_parameters_->servo_node_name);
127  else
128  return topic_name;
129  }
130 
131  // Set up for callbacks (so they aren't run for EVERY test)
133  {
134  // Start client
135  client_servo_start_ = node_->create_client<std_srvs::srv::Trigger>(resolveServoTopicName("~/start_servo"));
136  while (!client_servo_start_->service_is_ready())
137  {
138  if (!rclcpp::ok())
139  {
140  RCLCPP_ERROR(LOGGER, "Interrupted while waiting for the service. Exiting.");
141  return false;
142  }
143  RCLCPP_INFO(LOGGER, "client_servo_start_ service not available, waiting again...");
144  rclcpp::sleep_for(std::chrono::milliseconds(500));
145  }
146 
147  // If we setup the start client, also setup the stop client...
148  client_servo_stop_ = node_->create_client<std_srvs::srv::Trigger>(resolveServoTopicName("~/stop_servo"));
149  while (!client_servo_stop_->service_is_ready())
150  {
151  if (!rclcpp::ok())
152  {
153  RCLCPP_ERROR(LOGGER, "Interrupted while waiting for the service. Exiting.");
154  return false;
155  }
156  RCLCPP_INFO(LOGGER, "client_servo_stop_ service not available, waiting again...");
157  rclcpp::sleep_for(std::chrono::milliseconds(500));
158  }
159 
160  // Status sub (we need this to check that we've started / stopped)
161  sub_servo_status_ = node_->create_subscription<std_msgs::msg::Int8>(
162  resolveServoTopicName(servo_parameters_->status_topic), rclcpp::SystemDefaultsQoS(),
163  [this](const std_msgs::msg::Int8::ConstSharedPtr& msg) { return statusCB(msg); });
164  return true;
165  }
166 
168  {
169  client_servo_pause_ = node_->create_client<std_srvs::srv::Trigger>(resolveServoTopicName("~/pause_servo"));
170  while (!client_servo_pause_->service_is_ready())
171  {
172  if (!rclcpp::ok())
173  {
174  RCLCPP_ERROR(LOGGER, "Interrupted while waiting for the service. Exiting.");
175  return false;
176  }
177  RCLCPP_INFO(LOGGER, "client_servo_pause_ service not available, waiting again...");
178  rclcpp::sleep_for(std::chrono::milliseconds(500));
179  }
180  return true;
181  }
182 
184  {
185  client_servo_unpause_ = node_->create_client<std_srvs::srv::Trigger>(resolveServoTopicName("~/unpause_servo"));
186  while (!client_servo_unpause_->service_is_ready())
187  {
188  if (!rclcpp::ok())
189  {
190  RCLCPP_ERROR(LOGGER, "Interrupted while waiting for the service. Exiting.");
191  return false;
192  }
193  RCLCPP_INFO(LOGGER, "client_servo_unpause_ service not available, waiting again...");
194  rclcpp::sleep_for(std::chrono::milliseconds(500));
195  }
196  return true;
197  }
198 
200  {
201  client_change_control_dims_ = node_->create_client<moveit_msgs::srv::ChangeControlDimensions>(
202  resolveServoTopicName("~/change_control_dimensions"));
203  while (!client_change_control_dims_->service_is_ready())
204  {
205  if (!rclcpp::ok())
206  {
207  RCLCPP_ERROR(LOGGER, "Interrupted while waiting for the service. Exiting.");
208  return false;
209  }
210  RCLCPP_INFO(LOGGER, "client_change_control_dims_ service not available, waiting again...");
211  rclcpp::sleep_for(std::chrono::milliseconds(500));
212  }
213  return true;
214  }
215 
217  {
218  client_change_drift_dims_ = node_->create_client<moveit_msgs::srv::ChangeDriftDimensions>(
219  resolveServoTopicName("~/change_drift_dimensions"));
220  while (!client_change_drift_dims_->service_is_ready())
221  {
222  if (!rclcpp::ok())
223  {
224  RCLCPP_ERROR(LOGGER, "Interrupted while waiting for the service. Exiting.");
225  return false;
226  }
227  RCLCPP_INFO(LOGGER, "client_change_drift_dims_ service not available, waiting again...");
228  rclcpp::sleep_for(std::chrono::milliseconds(500));
229  }
230  return true;
231  }
232 
234  {
235  sub_collision_scale_ = node_->create_subscription<std_msgs::msg::Float64>(
236  resolveServoTopicName("~/collision_velocity_scale"), rclcpp::SystemDefaultsQoS(),
237  [this](const std_msgs::msg::Float64::ConstSharedPtr& msg) { return collisionScaleCB(msg); });
238  return true;
239  }
240 
241  bool setupCommandSub(const std::string& command_type)
242  {
243  if (command_type == "trajectory_msgs/JointTrajectory")
244  {
245  sub_trajectory_cmd_output_ = node_->create_subscription<trajectory_msgs::msg::JointTrajectory>(
246  resolveServoTopicName(servo_parameters_->command_out_topic), rclcpp::SystemDefaultsQoS(),
247  [this](const trajectory_msgs::msg::JointTrajectory::ConstSharedPtr& msg) { return trajectoryCommandCB(msg); });
248  return true;
249  }
250  else if (command_type == "std_msgs/Float64MultiArray")
251  {
252  sub_array_cmd_output_ = node_->create_subscription<std_msgs::msg::Float64MultiArray>(
253  resolveServoTopicName(servo_parameters_->command_out_topic), rclcpp::SystemDefaultsQoS(),
254  [this](const std_msgs::msg::Float64MultiArray::ConstSharedPtr& msg) { return arrayCommandCB(msg); });
255  return true;
256  }
257  else
258  {
259  RCLCPP_ERROR(LOGGER, "Invalid command type for Servo output command");
260  return false;
261  }
262  }
263 
265  {
266  sub_joint_state_ = node_->create_subscription<sensor_msgs::msg::JointState>(
267  resolveServoTopicName(servo_parameters_->joint_topic), rclcpp::SystemDefaultsQoS(),
268  [this](const sensor_msgs::msg::JointState::ConstSharedPtr& msg) { return jointStateCB(msg); });
269  return true;
270  }
271 
272  void statusCB(const std_msgs::msg::Int8::ConstSharedPtr& msg)
273  {
274  const std::lock_guard<std::mutex> lock(latest_state_mutex_);
275  ++num_status_;
276  latest_status_ = static_cast<StatusCode>(msg->data);
278  status_seen_ = true;
279  }
280 
281  void collisionScaleCB(const std_msgs::msg::Float64::ConstSharedPtr& msg)
282  {
283  const std::lock_guard<std::mutex> lock(latest_state_mutex_);
285  latest_collision_scale_ = msg->data;
286  }
287 
288  void jointStateCB(const sensor_msgs::msg::JointState::ConstSharedPtr& msg)
289  {
290  const std::lock_guard<std::mutex> lock(latest_state_mutex_);
292  latest_joint_state_ = msg;
293  }
294 
295  void trajectoryCommandCB(const trajectory_msgs::msg::JointTrajectory::ConstSharedPtr& msg)
296  {
297  const std::lock_guard<std::mutex> lock(latest_state_mutex_);
298  ++num_commands_;
299  latest_traj_cmd_ = msg;
300  }
301 
302  void arrayCommandCB(const std_msgs::msg::Float64MultiArray::ConstSharedPtr& msg)
303  {
304  const std::lock_guard<std::mutex> lock(latest_state_mutex_);
305  ++num_commands_;
306  latest_array_cmd_ = msg;
307  }
308 
310  {
311  const std::lock_guard<std::mutex> lock(latest_state_mutex_);
312  return latest_status_;
313  }
314 
315  size_t getNumStatus()
316  {
317  const std::lock_guard<std::mutex> lock(latest_state_mutex_);
318  return num_status_;
319  }
320 
322  {
323  const std::lock_guard<std::mutex> lock(latest_state_mutex_);
324  num_status_ = 0;
325  }
326 
328  {
329  const std::lock_guard<std::mutex> lock(latest_state_mutex_);
331  }
332 
334  {
335  const std::lock_guard<std::mutex> lock(latest_state_mutex_);
336  return num_collision_scale_;
337  }
338 
340  {
341  const std::lock_guard<std::mutex> lock(latest_state_mutex_);
343  }
344 
345  sensor_msgs::msg::JointState getLatestJointState()
346  {
347  const std::lock_guard<std::mutex> lock(latest_state_mutex_);
348  return *latest_joint_state_;
349  }
350 
352  {
353  const std::lock_guard<std::mutex> lock(latest_state_mutex_);
354  return num_joint_state_;
355  }
356 
358  {
359  const std::lock_guard<std::mutex> lock(latest_state_mutex_);
360  num_joint_state_ = 0;
361  }
362 
363  trajectory_msgs::msg::JointTrajectory getLatestTrajCommand()
364  {
365  const std::lock_guard<std::mutex> lock(latest_state_mutex_);
366  return *latest_traj_cmd_;
367  }
368 
369  std_msgs::msg::Float64MultiArray getLatestArrayCommand()
370  {
371  const std::lock_guard<std::mutex> lock(latest_state_mutex_);
372  return *latest_array_cmd_;
373  }
374 
375  size_t getNumCommands()
376  {
377  const std::lock_guard<std::mutex> lock(latest_state_mutex_);
378  return num_commands_;
379  }
380 
382  {
383  const std::lock_guard<std::mutex> lock(latest_state_mutex_);
384  num_commands_ = 0;
385  }
386 
387  void watchForStatus(StatusCode code_to_watch_for)
388  {
389  const std::lock_guard<std::mutex> lock(latest_state_mutex_);
390  status_seen_ = false;
391  status_tracking_code_ = code_to_watch_for;
392  }
393 
395  {
396  const std::lock_guard<std::mutex> lock(latest_state_mutex_);
397  return status_seen_;
398  }
399 
400  bool start()
401  {
402  auto time_start = node_->now();
403  auto start_result = client_servo_start_->async_send_request(std::make_shared<std_srvs::srv::Trigger::Request>());
404  if (!start_result.get()->success)
405  {
406  RCLCPP_ERROR(LOGGER, "Error returned form service call to start servo");
407  return false;
408  }
409  RCLCPP_INFO_STREAM(LOGGER, "Wait for start servo: " << (node_->now() - time_start).seconds());
410 
411  // Test that status messages start
412  rclcpp::WallRate publish_loop_rate(test_parameters_->publish_hz);
413  time_start = node_->now();
414  auto num_statuses_start = getNumStatus();
415  size_t iterations = 0;
416  while (getNumStatus() == num_statuses_start && iterations++ < test_parameters_->timeout_iterations)
417  {
418  publish_loop_rate.sleep();
419  }
420 
421  RCLCPP_INFO_STREAM(LOGGER, "Wait for status num increasing: " << (node_->now() - time_start).seconds());
422 
423  if (iterations >= test_parameters_->timeout_iterations)
424  {
425  RCLCPP_ERROR(LOGGER, "Timeout waiting for status num increasing");
426  return false;
427  }
428 
429  return getNumStatus() > num_statuses_start;
430  }
431 
432  bool stop()
433  {
434  auto time_start = node_->now();
435  auto stop_result = client_servo_stop_->async_send_request(std::make_shared<std_srvs::srv::Trigger::Request>());
436  if (!stop_result.get()->success)
437  {
438  RCLCPP_ERROR(LOGGER, "Error returned form service call to stop servo");
439  return false;
440  }
441  return true;
442  }
443 
444 protected:
445  rclcpp::Node::SharedPtr node_;
446  rclcpp::Executor::SharedPtr executor_;
447  std::thread executor_thread_;
448  std::shared_ptr<const moveit_servo::ServoParameters> servo_parameters_;
449 
451  {
452  double publish_hz;
455  std::string servo_node_name;
456  };
457  std::shared_ptr<const struct TestParameters> test_parameters_;
458 
459  // ROS interfaces
460 
461  // Service Clients
462  rclcpp::Client<std_srvs::srv::Trigger>::SharedPtr client_servo_start_;
463  rclcpp::Client<std_srvs::srv::Trigger>::SharedPtr client_servo_stop_;
464  rclcpp::Client<std_srvs::srv::Trigger>::SharedPtr client_servo_pause_;
465  rclcpp::Client<std_srvs::srv::Trigger>::SharedPtr client_servo_unpause_;
466  rclcpp::Client<moveit_msgs::srv::ChangeControlDimensions>::SharedPtr client_change_control_dims_;
467  rclcpp::Client<moveit_msgs::srv::ChangeDriftDimensions>::SharedPtr client_change_drift_dims_;
468 
469  // Publishers
470  rclcpp::Publisher<geometry_msgs::msg::TwistStamped>::SharedPtr pub_twist_cmd_;
471  rclcpp::Publisher<control_msgs::msg::JointJog>::SharedPtr pub_joint_cmd_;
472 
473  // Subscribers
474  rclcpp::Subscription<std_msgs::msg::Int8>::SharedPtr sub_servo_status_;
475  rclcpp::Subscription<std_msgs::msg::Float64>::SharedPtr sub_collision_scale_;
476  rclcpp::Subscription<sensor_msgs::msg::JointState>::SharedPtr sub_joint_state_;
477  rclcpp::Subscription<trajectory_msgs::msg::JointTrajectory>::SharedPtr sub_trajectory_cmd_output_;
478  rclcpp::Subscription<std_msgs::msg::Float64MultiArray>::SharedPtr sub_array_cmd_output_;
479 
480  // Subscription counting and data
481  size_t num_status_;
483 
486 
488  sensor_msgs::msg::JointState::ConstSharedPtr latest_joint_state_;
489 
491  trajectory_msgs::msg::JointTrajectory::ConstSharedPtr latest_traj_cmd_;
492  std_msgs::msg::Float64MultiArray::ConstSharedPtr latest_array_cmd_;
493 
496 
497  mutable std::mutex latest_state_mutex_;
498 }; // class ServoFixture
499 
500 } // namespace moveit_servo
rclcpp::Client< std_srvs::srv::Trigger >::SharedPtr client_servo_stop_
void arrayCommandCB(const std_msgs::msg::Float64MultiArray::ConstSharedPtr &msg)
std_msgs::msg::Float64MultiArray::ConstSharedPtr latest_array_cmd_
rclcpp::Executor::SharedPtr executor_
rclcpp::Client< std_srvs::srv::Trigger >::SharedPtr client_servo_pause_
std::string resolveServoTopicName(std::string topic_name)
void trajectoryCommandCB(const trajectory_msgs::msg::JointTrajectory::ConstSharedPtr &msg)
rclcpp::Client< moveit_msgs::srv::ChangeDriftDimensions >::SharedPtr client_change_drift_dims_
rclcpp::Publisher< control_msgs::msg::JointJog >::SharedPtr pub_joint_cmd_
void jointStateCB(const sensor_msgs::msg::JointState::ConstSharedPtr &msg)
sensor_msgs::msg::JointState::ConstSharedPtr latest_joint_state_
std_msgs::msg::Float64MultiArray getLatestArrayCommand()
rclcpp::Subscription< std_msgs::msg::Float64 >::SharedPtr sub_collision_scale_
rclcpp::Client< std_srvs::srv::Trigger >::SharedPtr client_servo_start_
void watchForStatus(StatusCode code_to_watch_for)
sensor_msgs::msg::JointState getLatestJointState()
trajectory_msgs::msg::JointTrajectory getLatestTrajCommand()
rclcpp::Subscription< sensor_msgs::msg::JointState >::SharedPtr sub_joint_state_
rclcpp::Subscription< std_msgs::msg::Float64MultiArray >::SharedPtr sub_array_cmd_output_
rclcpp::Subscription< std_msgs::msg::Int8 >::SharedPtr sub_servo_status_
void collisionScaleCB(const std_msgs::msg::Float64::ConstSharedPtr &msg)
void statusCB(const std_msgs::msg::Int8::ConstSharedPtr &msg)
rclcpp::Publisher< geometry_msgs::msg::TwistStamped >::SharedPtr pub_twist_cmd_
bool setupCommandSub(const std::string &command_type)
rclcpp::Client< moveit_msgs::srv::ChangeControlDimensions >::SharedPtr client_change_control_dims_
std::shared_ptr< const struct TestParameters > test_parameters_
trajectory_msgs::msg::JointTrajectory::ConstSharedPtr latest_traj_cmd_
rclcpp::Client< std_srvs::srv::Trigger >::SharedPtr client_servo_unpause_
std::shared_ptr< const moveit_servo::ServoParameters > servo_parameters_
rclcpp::Subscription< trajectory_msgs::msg::JointTrajectory >::SharedPtr sub_trajectory_cmd_output_
const rclcpp::Logger LOGGER
Definition: async_test.h:31
static SharedConstPtr makeServoParameters(const rclcpp::Node::SharedPtr &node, const std::string &ns="moveit_servo", bool dynamic_parameters=true)