moveit2
The MoveIt Motion Planning Framework for ROS 2.
pose_tracking.cpp
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 
37 
38 #include <chrono>
39 using namespace std::literals;
40 
41 namespace
42 {
43 static const rclcpp::Logger LOGGER = rclcpp::get_logger("moveit_servo.pose_tracking");
44 constexpr size_t LOG_THROTTLE_PERIOD = 10; // sec
45 
46 // Helper template for declaring and getting ros param
47 template <typename T>
48 void declareOrGetParam(T& output_value, const std::string& param_name, const rclcpp::Node::SharedPtr& node,
49  const rclcpp::Logger& logger, const T default_value = T{})
50 {
51  try
52  {
53  if (node->has_parameter(param_name))
54  {
55  node->get_parameter<T>(param_name, output_value);
56  }
57  else
58  {
59  output_value = node->declare_parameter<T>(param_name, default_value);
60  }
61  }
62  catch (const rclcpp::exceptions::InvalidParameterTypeException& e)
63  {
64  RCLCPP_WARN_STREAM(logger, "InvalidParameterTypeException(" << param_name << "): " << e.what());
65  RCLCPP_ERROR_STREAM(logger, "Error getting parameter \'" << param_name << "\', check parameter type in YAML file");
66  throw e;
67  }
68 
69  RCLCPP_INFO_STREAM(logger, "Found parameter - " << param_name << ": " << output_value);
70 }
71 } // namespace
72 
73 namespace moveit_servo
74 {
75 PoseTracking::PoseTracking(const rclcpp::Node::SharedPtr& node, const ServoParameters::SharedConstPtr& servo_parameters,
76  const planning_scene_monitor::PlanningSceneMonitorPtr& planning_scene_monitor)
77  : node_(node)
78  , servo_parameters_(servo_parameters)
79  , planning_scene_monitor_(planning_scene_monitor)
80  , loop_rate_(1.0 / servo_parameters->publish_period)
81  , transform_buffer_(node_->get_clock())
82  , transform_listener_(transform_buffer_)
83  , stop_requested_(false)
84 {
85  readROSParams();
86 
87  robot_model_ = planning_scene_monitor_->getRobotModel();
88 
89  // Initialize PID controllers
90  initializePID(x_pid_config_, cartesian_position_pids_);
91  initializePID(y_pid_config_, cartesian_position_pids_);
92  initializePID(z_pid_config_, cartesian_position_pids_);
93  initializePID(angular_pid_config_, cartesian_orientation_pids_);
94 
95  // Use the C++ interface that Servo provides
96  servo_ = std::make_unique<moveit_servo::Servo>(node_, servo_parameters_, planning_scene_monitor_);
97  servo_->start();
98 
99  // Connect to Servo ROS interfaces
100  target_pose_sub_ = node_->create_subscription<geometry_msgs::msg::PoseStamped>(
101  "target_pose", rclcpp::SystemDefaultsQoS(),
102  [this](const geometry_msgs::msg::PoseStamped::ConstSharedPtr& msg) { return targetPoseCallback(msg); });
103 
104  // Publish outgoing twist commands to the Servo object
105  twist_stamped_pub_ = node_->create_publisher<geometry_msgs::msg::TwistStamped>(
106  servo_->getParameters()->cartesian_command_in_topic, rclcpp::SystemDefaultsQoS());
107 }
108 
110  const double angular_tolerance, const double target_pose_timeout)
111 {
112  // Reset stop requested flag before starting motions
113  stop_requested_ = false;
114  // Wait a bit for a target pose message to arrive.
115  // The target pose may get updated by new messages as the robot moves (in a callback function).
116  const rclcpp::Time start_time = node_->now();
117 
118  while ((!haveRecentTargetPose(target_pose_timeout) || !haveRecentEndEffectorPose(target_pose_timeout)) &&
119  ((node_->now() - start_time).seconds() < target_pose_timeout))
120  {
121  if (servo_->getCommandFrameTransform(command_frame_transform_))
122  {
123  command_frame_transform_stamp_ = node_->now();
124  }
125  std::this_thread::sleep_for(1ms);
126  }
127 
128  if (!haveRecentTargetPose(target_pose_timeout))
129  {
130  RCLCPP_ERROR_STREAM(LOGGER, "The target pose was not updated recently. Aborting.");
132  }
133 
134  // Continue sending PID controller output to Servo until one of the following conditions is met:
135  // - Goal tolerance is satisfied
136  // - Target pose becomes outdated
137  // - Command frame transform becomes outdated
138  // - Another thread requested a stop
139  while (rclcpp::ok())
140  {
141  if (satisfiesPoseTolerance(positional_tolerance, angular_tolerance))
142  {
143  RCLCPP_INFO_STREAM(LOGGER, "The target pose is achieved!");
144  break;
145  }
146  // Attempt to update robot pose
147  if (servo_->getCommandFrameTransform(command_frame_transform_))
148  {
149  command_frame_transform_stamp_ = node_->now();
150  }
151 
152  // Check that end-effector pose (command frame transform) is recent enough.
153  if (!haveRecentEndEffectorPose(target_pose_timeout))
154  {
155  RCLCPP_ERROR_STREAM(LOGGER, "The end effector pose was not updated in time. Aborting.");
156  doPostMotionReset();
158  }
159 
160  if (stop_requested_)
161  {
162  RCLCPP_INFO_STREAM(LOGGER, "Halting servo motion, a stop was requested.");
163  doPostMotionReset();
165  }
166 
167  // Compute servo command from PID controller output and send it to the Servo object, for execution
168  twist_stamped_pub_->publish(*calculateTwistCommand());
169 
170  if (!loop_rate_.sleep())
171  {
172  RCLCPP_WARN_STREAM_THROTTLE(LOGGER, *node_->get_clock(), LOG_THROTTLE_PERIOD, "Target control rate was missed");
173  }
174  }
175 
176  doPostMotionReset();
178 }
179 
180 void PoseTracking::readROSParams()
181 {
182  const std::string ns = "moveit_servo";
183 
184  declareOrGetParam(planning_frame_, ns + ".planning_frame", node_, LOGGER);
185  declareOrGetParam(move_group_name_, ns + ".move_group_name", node_, LOGGER);
186 
187  if (!planning_scene_monitor_->getRobotModel()->hasJointModelGroup(move_group_name_))
188  {
189  RCLCPP_ERROR_STREAM(LOGGER, "Unable to find the specified joint model group: " << move_group_name_);
190  }
191 
192  double publish_period;
193  declareOrGetParam(publish_period, ns + ".publish_period", node_, LOGGER);
194 
195  x_pid_config_.dt = publish_period;
196  y_pid_config_.dt = publish_period;
197  z_pid_config_.dt = publish_period;
198  angular_pid_config_.dt = publish_period;
199 
200  double windup_limit;
201  declareOrGetParam(windup_limit, ns + ".windup_limit", node_, LOGGER);
202  x_pid_config_.windup_limit = windup_limit;
203  y_pid_config_.windup_limit = windup_limit;
204  z_pid_config_.windup_limit = windup_limit;
205  angular_pid_config_.windup_limit = windup_limit;
206 
207  declareOrGetParam(x_pid_config_.k_p, ns + ".x_proportional_gain", node_, LOGGER);
208  declareOrGetParam(x_pid_config_.k_p, ns + ".x_proportional_gain", node_, LOGGER);
209  declareOrGetParam(y_pid_config_.k_p, ns + ".y_proportional_gain", node_, LOGGER);
210  declareOrGetParam(z_pid_config_.k_p, ns + ".z_proportional_gain", node_, LOGGER);
211  declareOrGetParam(x_pid_config_.k_i, ns + ".x_integral_gain", node_, LOGGER);
212  declareOrGetParam(y_pid_config_.k_i, ns + ".y_integral_gain", node_, LOGGER);
213  declareOrGetParam(z_pid_config_.k_i, ns + ".z_integral_gain", node_, LOGGER);
214  declareOrGetParam(x_pid_config_.k_d, ns + ".x_derivative_gain", node_, LOGGER);
215  declareOrGetParam(y_pid_config_.k_d, ns + ".y_derivative_gain", node_, LOGGER);
216  declareOrGetParam(z_pid_config_.k_d, ns + ".z_derivative_gain", node_, LOGGER);
217 
218  declareOrGetParam(angular_pid_config_.k_p, ns + ".angular_proportional_gain", node_, LOGGER);
219  declareOrGetParam(angular_pid_config_.k_i, ns + ".angular_integral_gain", node_, LOGGER);
220  declareOrGetParam(angular_pid_config_.k_d, ns + ".angular_derivative_gain", node_, LOGGER);
221 }
222 
223 void PoseTracking::initializePID(const PIDConfig& pid_config, std::vector<control_toolbox::Pid>& pid_vector)
224 {
225  bool use_anti_windup = true;
226  pid_vector.push_back(control_toolbox::Pid(pid_config.k_p, pid_config.k_i, pid_config.k_d, pid_config.windup_limit,
227  -pid_config.windup_limit, use_anti_windup));
228 }
229 
230 bool PoseTracking::haveRecentTargetPose(const double timespan)
231 {
232  std::lock_guard<std::mutex> lock(target_pose_mtx_);
233  return ((node_->now() - target_pose_.header.stamp).seconds() < timespan);
234 }
235 
236 bool PoseTracking::haveRecentEndEffectorPose(const double timespan)
237 {
238  return ((node_->now() - command_frame_transform_stamp_).seconds() < timespan);
239 }
240 
241 bool PoseTracking::satisfiesPoseTolerance(const Eigen::Vector3d& positional_tolerance, const double angular_tolerance)
242 {
243  std::lock_guard<std::mutex> lock(target_pose_mtx_);
244  double x_error = target_pose_.pose.position.x - command_frame_transform_.translation()(0);
245  double y_error = target_pose_.pose.position.y - command_frame_transform_.translation()(1);
246  double z_error = target_pose_.pose.position.z - command_frame_transform_.translation()(2);
247 
248  // If uninitialized, likely haven't received the target pose yet.
249  if (!angular_error_)
250  return false;
251 
252  return ((std::abs(x_error) < positional_tolerance(0)) && (std::abs(y_error) < positional_tolerance(1)) &&
253  (std::abs(z_error) < positional_tolerance(2)) && (std::abs(*angular_error_) < angular_tolerance));
254 }
255 
256 void PoseTracking::targetPoseCallback(const geometry_msgs::msg::PoseStamped::ConstSharedPtr& msg)
257 {
258  std::lock_guard<std::mutex> lock(target_pose_mtx_);
259  target_pose_ = *msg;
260  // If the target pose is not defined in planning frame, transform the target pose.
261  if (target_pose_.header.frame_id != planning_frame_)
262  {
263  try
264  {
265  geometry_msgs::msg::TransformStamped target_to_planning_frame = transform_buffer_.lookupTransform(
266  planning_frame_, target_pose_.header.frame_id, rclcpp::Time(0), rclcpp::Duration(100ms));
267  tf2::doTransform(target_pose_, target_pose_, target_to_planning_frame);
268 
269  // Prevent doTransform from copying a stamp of 0, which will cause the haveRecentTargetPose check to fail servo motions
270  target_pose_.header.stamp = node_->now();
271  }
272  catch (const tf2::TransformException& ex)
273  {
274  RCLCPP_WARN_STREAM(LOGGER, ex.what());
275  return;
276  }
277  }
278 }
279 
280 geometry_msgs::msg::TwistStamped::ConstSharedPtr PoseTracking::calculateTwistCommand()
281 {
282  // use the shared pool to create a message more efficiently
283  auto msg = moveit::util::make_shared_from_pool<geometry_msgs::msg::TwistStamped>();
284 
285  // Get twist components from PID controllers
286  geometry_msgs::msg::Twist& twist = msg->twist;
287  Eigen::Quaterniond q_desired;
288 
289  // Scope mutex locking only to operations which require access to target pose.
290  {
291  std::lock_guard<std::mutex> lock(target_pose_mtx_);
292  msg->header.frame_id = target_pose_.header.frame_id;
293 
294  // Position
295  twist.linear.x = cartesian_position_pids_[0].computeCommand(
296  target_pose_.pose.position.x - command_frame_transform_.translation()(0), loop_rate_.period().count());
297  twist.linear.y = cartesian_position_pids_[1].computeCommand(
298  target_pose_.pose.position.y - command_frame_transform_.translation()(1), loop_rate_.period().count());
299  twist.linear.z = cartesian_position_pids_[2].computeCommand(
300  target_pose_.pose.position.z - command_frame_transform_.translation()(2), loop_rate_.period().count());
301 
302  // Orientation algorithm:
303  // - Find the orientation error as a quaternion: q_error = q_desired * q_current ^ -1
304  // - Use the angle-axis PID controller to calculate an angular rate
305  // - Convert to angular velocity for the TwistStamped message
306  q_desired = Eigen::Quaterniond(target_pose_.pose.orientation.w, target_pose_.pose.orientation.x,
307  target_pose_.pose.orientation.y, target_pose_.pose.orientation.z);
308  }
309 
310  Eigen::Quaterniond q_current(command_frame_transform_.rotation());
311  Eigen::Quaterniond q_error = q_desired * q_current.inverse();
312 
313  // Convert axis-angle to angular velocity
314  Eigen::AngleAxisd axis_angle(q_error);
315  // Cache the angular error, for rotation tolerance checking
316  angular_error_ = axis_angle.angle();
317 
318  double ang_vel_magnitude =
319  cartesian_orientation_pids_[0].computeCommand(*angular_error_, loop_rate_.period().count());
320  twist.angular.x = ang_vel_magnitude * axis_angle.axis()[0];
321  twist.angular.y = ang_vel_magnitude * axis_angle.axis()[1];
322  twist.angular.z = ang_vel_magnitude * axis_angle.axis()[2];
323 
324  msg->header.stamp = node_->now();
325 
326  return msg;
327 }
328 
330 {
331  stop_requested_ = true;
332 
333  // Send a 0 command to Servo to halt arm motion
334  auto msg = moveit::util::make_shared_from_pool<geometry_msgs::msg::TwistStamped>();
335  {
336  std::lock_guard<std::mutex> lock(target_pose_mtx_);
337  msg->header.frame_id = target_pose_.header.frame_id;
338  }
339  msg->header.stamp = node_->now();
340  twist_stamped_pub_->publish(*msg);
341 }
342 
343 void PoseTracking::doPostMotionReset()
344 {
345  stopMotion();
346  stop_requested_ = false;
347  angular_error_ = {};
348 
349  // Reset error integrals and previous errors of PID controllers
350  cartesian_position_pids_[0].reset();
351  cartesian_position_pids_[1].reset();
352  cartesian_position_pids_[2].reset();
353  cartesian_orientation_pids_[0].reset();
354 }
355 
356 void PoseTracking::updatePIDConfig(const double x_proportional_gain, const double x_integral_gain,
357  const double x_derivative_gain, const double y_proportional_gain,
358  const double y_integral_gain, const double y_derivative_gain,
359  const double z_proportional_gain, const double z_integral_gain,
360  const double z_derivative_gain, const double angular_proportional_gain,
361  const double angular_integral_gain, const double angular_derivative_gain)
362 {
363  stopMotion();
364 
365  x_pid_config_.k_p = x_proportional_gain;
366  x_pid_config_.k_i = x_integral_gain;
367  x_pid_config_.k_d = x_derivative_gain;
368  y_pid_config_.k_p = y_proportional_gain;
369  y_pid_config_.k_i = y_integral_gain;
370  y_pid_config_.k_d = y_derivative_gain;
371  z_pid_config_.k_p = z_proportional_gain;
372  z_pid_config_.k_i = z_integral_gain;
373  z_pid_config_.k_d = z_derivative_gain;
374 
375  angular_pid_config_.k_p = angular_proportional_gain;
376  angular_pid_config_.k_i = angular_integral_gain;
377  angular_pid_config_.k_d = angular_derivative_gain;
378 
379  cartesian_position_pids_.clear();
380  cartesian_orientation_pids_.clear();
381  initializePID(x_pid_config_, cartesian_position_pids_);
382  initializePID(y_pid_config_, cartesian_position_pids_);
383  initializePID(z_pid_config_, cartesian_position_pids_);
384  initializePID(angular_pid_config_, cartesian_orientation_pids_);
385 
386  doPostMotionReset();
387 }
388 
389 void PoseTracking::getPIDErrors(double& x_error, double& y_error, double& z_error, double& orientation_error)
390 {
391  double dummy1, dummy2;
392  cartesian_position_pids_.at(0).getCurrentPIDErrors(x_error, dummy1, dummy2);
393  cartesian_position_pids_.at(1).getCurrentPIDErrors(y_error, dummy1, dummy2);
394  cartesian_position_pids_.at(2).getCurrentPIDErrors(z_error, dummy1, dummy2);
395  cartesian_orientation_pids_.at(0).getCurrentPIDErrors(orientation_error, dummy1, dummy2);
396 }
397 
399 {
400  std::lock_guard<std::mutex> lock(target_pose_mtx_);
401  target_pose_ = geometry_msgs::msg::PoseStamped();
402  target_pose_.header.stamp = rclcpp::Time(RCL_ROS_TIME);
403 }
404 
405 bool PoseTracking::getCommandFrameTransform(geometry_msgs::msg::TransformStamped& transform)
406 {
407  return servo_->getCommandFrameTransform(transform);
408 }
409 } // namespace moveit_servo
void getPIDErrors(double &x_error, double &y_error, double &z_error, double &orientation_error)
PoseTrackingStatusCode moveToPose(const Eigen::Vector3d &positional_tolerance, const double angular_tolerance, const double target_pose_timeout)
std::unique_ptr< moveit_servo::Servo > servo_
bool getCommandFrameTransform(geometry_msgs::msg::TransformStamped &transform)
void stopMotion()
A method for a different thread to stop motion and return early from control loop.
void resetTargetPose()
Re-initialize the target pose to an empty message. Can be used to reset motion between waypoints.
void updatePIDConfig(const double x_proportional_gain, const double x_integral_gain, const double x_derivative_gain, const double y_proportional_gain, const double y_integral_gain, const double y_derivative_gain, const double z_proportional_gain, const double z_integral_gain, const double z_derivative_gain, const double angular_proportional_gain, const double angular_integral_gain, const double angular_derivative_gain)
Change PID parameters. Motion is stopped before the update.
Vec3fX< details::Vec3Data< double > > Vector3d
Definition: fcl_compat.h:89
void declareOrGetParam(const std::string &param_name, T &output_value, const T &default_value, const rclcpp::Node::SharedPtr &node)
const rclcpp::Logger LOGGER
Definition: async_test.h:31
constexpr size_t LOG_THROTTLE_PERIOD
std::shared_ptr< const ServoParameters > SharedConstPtr