moveit2
The MoveIt Motion Planning Framework for ROS 2.
servo_calcs.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2  * BSD 3-Clause License
3  *
4  * Copyright (c) 2019, Los Alamos National Security, LLC
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 are met:
9  *
10  * * Redistributions of source code must retain the above copyright notice, this
11  * list of conditions and the following disclaimer.
12  *
13  * * Redistributions in binary form must reproduce the above copyright notice,
14  * this list of conditions and the following disclaimer in the documentation
15  * and/or other materials provided with the distribution.
16  *
17  * * Neither the name of the copyright holder nor the names of its
18  * contributors may be used to endorse or promote products derived from
19  * this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE
25  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32  *******************************************************************************/
33 
34 /* Title : servo_calcs.cpp
35  * Project : moveit_servo
36  * Created : 1/11/2019
37  * Author : Brian O'Neil, Andy Zelenak, Blake Anderson
38  */
39 
40 #include <cassert>
41 #include <thread>
42 #include <chrono>
43 #include <mutex>
44 
45 #include <std_msgs/msg/bool.hpp>
46 #include <tf2_geometry_msgs/tf2_geometry_msgs.hpp>
47 
48 // #include <moveit_servo/make_shared_from_pool.h> // TODO(adamp): create an issue about this
51 #include <moveit_servo/utilities.h>
52 
53 using namespace std::chrono_literals; // for s, ms, etc.
54 
55 namespace moveit_servo
56 {
57 namespace
58 {
59 static const rclcpp::Logger LOGGER = rclcpp::get_logger("moveit_servo.servo_calcs");
60 constexpr auto ROS_LOG_THROTTLE_PERIOD = std::chrono::milliseconds(3000).count();
61 static constexpr double STOPPED_VELOCITY_EPS = 1e-4; // rad/s
62 } // namespace
63 
64 // Constructor for the class that handles servoing calculations
65 ServoCalcs::ServoCalcs(const rclcpp::Node::SharedPtr& node,
66  const std::shared_ptr<const moveit_servo::ServoParameters>& parameters,
67  const planning_scene_monitor::PlanningSceneMonitorPtr& planning_scene_monitor)
68  : node_(node)
69  , parameters_(parameters)
70  , planning_scene_monitor_(planning_scene_monitor)
71  , stop_requested_(true)
72  , done_stopping_(false)
73  , paused_(false)
74  , robot_link_command_frame_(parameters->robot_link_command_frame)
75  , smoothing_loader_("moveit_core", "online_signal_smoothing::SmoothingBaseClass")
76 {
77  // Register callback for changes in robot_link_command_frame
78  bool callback_success = parameters_->registerSetParameterCallback(parameters->ns + ".robot_link_command_frame",
79  [this](const rclcpp::Parameter& parameter) {
80  return robotLinkCommandFrameCallback(parameter);
81  });
82  if (!callback_success)
83  {
84  throw std::runtime_error("Failed to register setParameterCallback");
85  }
86 
87  // MoveIt Setup
88  current_state_ = planning_scene_monitor_->getStateMonitor()->getCurrentState();
89  joint_model_group_ = current_state_->getJointModelGroup(parameters_->move_group_name);
90  if (joint_model_group_ == nullptr)
91  {
92  RCLCPP_ERROR_STREAM(LOGGER, "Invalid move group name: `" << parameters_->move_group_name << "`");
93  throw std::runtime_error("Invalid move group name");
94  }
95 
96  // Subscribe to command topics
97  twist_stamped_sub_ = node_->create_subscription<geometry_msgs::msg::TwistStamped>(
98  parameters_->cartesian_command_in_topic, rclcpp::SystemDefaultsQoS(),
99  [this](const geometry_msgs::msg::TwistStamped::ConstSharedPtr& msg) { return twistStampedCB(msg); });
100 
101  joint_cmd_sub_ = node_->create_subscription<control_msgs::msg::JointJog>(
102  parameters_->joint_command_in_topic, rclcpp::SystemDefaultsQoS(),
103  [this](const control_msgs::msg::JointJog::ConstSharedPtr& msg) { return jointCmdCB(msg); });
104 
105  // ROS Server for allowing drift in some dimensions
106  drift_dimensions_server_ = node_->create_service<moveit_msgs::srv::ChangeDriftDimensions>(
107  "~/change_drift_dimensions",
108  [this](const std::shared_ptr<moveit_msgs::srv::ChangeDriftDimensions::Request>& req,
109  const std::shared_ptr<moveit_msgs::srv::ChangeDriftDimensions::Response>& res) {
110  return changeDriftDimensions(req, res);
111  });
112 
113  // ROS Server for changing the control dimensions
114  control_dimensions_server_ = node_->create_service<moveit_msgs::srv::ChangeControlDimensions>(
115  "~/change_control_dimensions",
116  [this](const std::shared_ptr<moveit_msgs::srv::ChangeControlDimensions::Request>& req,
117  const std::shared_ptr<moveit_msgs::srv::ChangeControlDimensions::Response>& res) {
118  return changeControlDimensions(req, res);
119  });
120 
121  // ROS Server to reset the status, e.g. so the arm can move again after a collision
122  reset_servo_status_ = node_->create_service<std_srvs::srv::Empty>(
123  "~/reset_servo_status",
124  [this](const std::shared_ptr<std_srvs::srv::Empty::Request>& req,
125  const std::shared_ptr<std_srvs::srv::Empty::Response>& res) { return resetServoStatus(req, res); });
126 
127  // Subscribe to the collision_check topic
128  collision_velocity_scale_sub_ = node_->create_subscription<std_msgs::msg::Float64>(
129  "~/collision_velocity_scale", rclcpp::SystemDefaultsQoS(),
130  [this](const std_msgs::msg::Float64::ConstSharedPtr& msg) { return collisionVelocityScaleCB(msg); });
131 
132  // Publish freshly-calculated joints to the robot.
133  // Put the outgoing msg in the right format (trajectory_msgs/JointTrajectory or std_msgs/Float64MultiArray).
134  if (parameters_->command_out_type == "trajectory_msgs/JointTrajectory")
135  {
136  trajectory_outgoing_cmd_pub_ = node_->create_publisher<trajectory_msgs::msg::JointTrajectory>(
137  parameters_->command_out_topic, rclcpp::SystemDefaultsQoS());
138  }
139  else if (parameters_->command_out_type == "std_msgs/Float64MultiArray")
140  {
141  multiarray_outgoing_cmd_pub_ = node_->create_publisher<std_msgs::msg::Float64MultiArray>(
142  parameters_->command_out_topic, rclcpp::SystemDefaultsQoS());
143  }
144 
145  // Publish status
146  status_pub_ = node_->create_publisher<std_msgs::msg::Int8>(parameters_->status_topic, rclcpp::SystemDefaultsQoS());
147 
149  num_joints_ = internal_joint_state_.name.size();
150  internal_joint_state_.position.resize(num_joints_);
151  internal_joint_state_.velocity.resize(num_joints_);
152  delta_theta_.setZero(num_joints_);
153 
154  for (std::size_t i = 0; i < num_joints_; ++i)
155  {
156  // A map for the indices of incoming joint commands
158  }
159 
160  // Load the smoothing plugin
161  try
162  {
163  smoother_ = smoothing_loader_.createSharedInstance(parameters_->smoothing_filter_plugin_name);
164  }
165  catch (pluginlib::PluginlibException& ex)
166  {
167  RCLCPP_ERROR(LOGGER, "Exception while loading the smoothing plugin '%s': '%s'",
168  parameters_->smoothing_filter_plugin_name.c_str(), ex.what());
169  std::exit(EXIT_FAILURE);
170  }
171 
172  // Initialize the smoothing plugin
173  if (!smoother_->initialize(node_, planning_scene_monitor_->getRobotModel(), num_joints_))
174  {
175  RCLCPP_ERROR(LOGGER, "Smoothing plugin could not be initialized");
176  std::exit(EXIT_FAILURE);
177  }
178 
179  // A matrix of all zeros is used to check whether matrices have been initialized
180  Eigen::Matrix3d empty_matrix;
181  empty_matrix.setZero();
182  tf_moveit_to_ee_frame_ = empty_matrix;
183  tf_moveit_to_robot_cmd_frame_ = empty_matrix;
184 
185  // Get the IK solver for the group
187  if (!ik_solver_)
188  {
189  use_inv_jacobian_ = true;
190  RCLCPP_WARN(
191  LOGGER,
192  "No kinematics solver instantiated for group '%s'. Will use inverse Jacobian for servo calculations instead.",
193  joint_model_group_->getName().c_str());
194  }
195  else if (!ik_solver_->supportsGroup(joint_model_group_))
196  {
197  use_inv_jacobian_ = true;
198  RCLCPP_WARN(LOGGER,
199  "The loaded kinematics plugin does not support group '%s'. Will use inverse Jacobian for servo "
200  "calculations instead.",
201  joint_model_group_->getName().c_str());
202  }
203 }
204 
206 {
207  stop();
208 }
209 
211 {
212  // Stop the thread if we are currently running
213  stop();
214 
215  // Set up the "last" published message, in case we need to send it first
216  auto initial_joint_trajectory = std::make_unique<trajectory_msgs::msg::JointTrajectory>();
217  initial_joint_trajectory->header.stamp = node_->now();
218  initial_joint_trajectory->header.frame_id = parameters_->planning_frame;
219  initial_joint_trajectory->joint_names = internal_joint_state_.name;
220  trajectory_msgs::msg::JointTrajectoryPoint point;
221  point.time_from_start = rclcpp::Duration::from_seconds(parameters_->publish_period);
222  if (parameters_->publish_joint_positions)
223  planning_scene_monitor_->getStateMonitor()->getCurrentState()->copyJointGroupPositions(joint_model_group_,
224  point.positions);
225  if (parameters_->publish_joint_velocities)
226  {
227  std::vector<double> velocity(num_joints_);
228  point.velocities = velocity;
229  }
230  if (parameters_->publish_joint_accelerations)
231  {
232  // I do not know of a robot that takes acceleration commands.
233  // However, some controllers check that this data is non-empty.
234  // Send all zeros, for now.
235  point.accelerations.resize(num_joints_);
236  }
237  initial_joint_trajectory->points.push_back(point);
238  last_sent_command_ = std::move(initial_joint_trajectory);
239 
240  current_state_ = planning_scene_monitor_->getStateMonitor()->getCurrentState();
241  tf_moveit_to_ee_frame_ = current_state_->getGlobalLinkTransform(parameters_->planning_frame).inverse() *
242  current_state_->getGlobalLinkTransform(parameters_->ee_frame_name);
243  tf_moveit_to_robot_cmd_frame_ = current_state_->getGlobalLinkTransform(parameters_->planning_frame).inverse() *
244  current_state_->getGlobalLinkTransform(robot_link_command_frame_);
245  if (!use_inv_jacobian_)
246  {
247  ik_base_to_tip_frame_ = current_state_->getGlobalLinkTransform(ik_solver_->getBaseFrame()).inverse() *
248  current_state_->getGlobalLinkTransform(ik_solver_->getTipFrame());
249  }
250 
251  stop_requested_ = false;
252  thread_ = std::thread([this] { mainCalcLoop(); });
253  new_input_cmd_ = false;
254 }
255 
257 {
258  // Request stop
259  stop_requested_ = true;
260 
261  // Notify condition variable in case the thread is blocked on it
262  {
263  // scope so the mutex is unlocked after so the thread can continue
264  // and therefore be joinable
265  const std::lock_guard<std::mutex> lock(main_loop_mutex_);
266  new_input_cmd_ = false;
267  input_cv_.notify_all();
268  }
269 
270  // Join the thread
271  if (thread_.joinable())
272  {
273  thread_.join();
274  }
275 }
276 
278 {
279  rclcpp::WallRate rate(1.0 / parameters_->publish_period);
280 
281  while (rclcpp::ok() && !stop_requested_)
282  {
283  // lock the input state mutex
284  std::unique_lock<std::mutex> main_loop_lock(main_loop_mutex_);
285 
286  // low latency mode -- begin calculations as soon as a new command is received.
287  if (parameters_->low_latency_mode)
288  {
289  input_cv_.wait(main_loop_lock, [this] { return (new_input_cmd_ || stop_requested_); });
290  }
291 
292  // reset new_input_cmd_ flag
293  new_input_cmd_ = false;
294 
295  // run servo calcs
296  const auto start_time = node_->now();
298  const auto run_duration = node_->now() - start_time;
299 
300  // Log warning when the run duration was longer than the period
301  if (run_duration.seconds() > parameters_->publish_period)
302  {
303  rclcpp::Clock& clock = *node_->get_clock();
304  RCLCPP_WARN_STREAM_THROTTLE(LOGGER, clock, ROS_LOG_THROTTLE_PERIOD,
305  "run_duration: " << run_duration.seconds() << " (" << parameters_->publish_period
306  << ")");
307  }
308 
309  // normal mode, unlock input mutex and wait for the period of the loop
310  if (!parameters_->low_latency_mode)
311  {
312  main_loop_lock.unlock();
313  rate.sleep();
314  }
315  }
316 }
317 
319 {
320  // Publish status each loop iteration
321  auto status_msg = std::make_unique<std_msgs::msg::Int8>();
322  status_msg->data = static_cast<int8_t>(status_);
323  status_pub_->publish(std::move(status_msg));
324 
325  // After we publish, status, reset it back to no warnings
327 
328  // Always update the joints and end-effector transform for 2 reasons:
329  // 1) in case the getCommandFrameTransform() method is being used
330  // 2) so the low-pass filters are up to date and don't cause a jump
331  updateJoints();
332 
333  // Update from latest state
334  current_state_ = planning_scene_monitor_->getStateMonitor()->getCurrentState();
335 
338  if (latest_joint_cmd_)
340 
341  // Check for stale cmds
343  rclcpp::Duration::from_seconds(parameters_->incoming_command_timeout));
345  rclcpp::Duration::from_seconds(parameters_->incoming_command_timeout));
346 
349 
350  // Get the transform from MoveIt planning frame to servoing command frame
351  // Calculate this transform to ensure it is available via C++ API
352  // We solve (planning_frame -> base -> robot_link_command_frame)
353  // by computing (base->planning_frame)^-1 * (base->robot_link_command_frame)
354  tf_moveit_to_robot_cmd_frame_ = current_state_->getGlobalLinkTransform(parameters_->planning_frame).inverse() *
355  current_state_->getGlobalLinkTransform(robot_link_command_frame_);
356 
357  // Calculate the transform from MoveIt planning frame to End Effector frame
358  // Calculate this transform to ensure it is available via C++ API
359  tf_moveit_to_ee_frame_ = current_state_->getGlobalLinkTransform(parameters_->planning_frame).inverse() *
360  current_state_->getGlobalLinkTransform(parameters_->ee_frame_name);
361 
362  if (!use_inv_jacobian_)
363  {
364  ik_base_to_tip_frame_ = current_state_->getGlobalLinkTransform(ik_solver_->getBaseFrame()).inverse() *
365  current_state_->getGlobalLinkTransform(ik_solver_->getTipFrame());
366  }
367 
369 
370  // Don't end this function without updating the filters
371  updated_filters_ = false;
372 
373  // If paused or while waiting for initial servo commands, just keep the low-pass filters up to date with current
374  // joints so a jump doesn't occur when restarting
376  {
378 
379  // Check if there are any new commands with valid timestamp
381  twist_stamped_cmd_.header.stamp == rclcpp::Time(0.) && joint_servo_cmd_.header.stamp == rclcpp::Time(0.);
382 
383  // Early exit
384  return;
385  }
386 
387  // If not waiting for initial command, and not paused.
388  // Do servoing calculations only if the robot should move, for efficiency
389  // Create new outgoing joint trajectory command message
390  auto joint_trajectory = std::make_unique<trajectory_msgs::msg::JointTrajectory>();
391 
392  // Prioritize cartesian servoing above joint servoing
393  // Only run commands if not stale and nonzero
395  {
396  if (!cartesianServoCalcs(twist_stamped_cmd_, *joint_trajectory))
397  {
399  return;
400  }
401  }
403  {
404  if (!jointServoCalcs(joint_servo_cmd_, *joint_trajectory))
405  {
407  return;
408  }
409  }
410  else
411  {
412  // Joint trajectory is not populated with anything, so set it to the last positions and 0 velocity
413  *joint_trajectory = *last_sent_command_;
414  for (auto& point : joint_trajectory->points)
415  {
416  point.velocities.assign(point.velocities.size(), 0);
417  }
418  }
419 
420  // Print a warning to the user if both are stale
422  {
423  filteredHalt(*joint_trajectory);
424  }
425  else
426  {
427  done_stopping_ = false;
428  }
429 
430  // Skip the servoing publication if all inputs have been zero for several cycles in a row.
431  // num_outgoing_halt_msgs_to_publish == 0 signifies that we should keep republishing forever.
432  if (!have_nonzero_command_ && done_stopping_ && (parameters_->num_outgoing_halt_msgs_to_publish != 0) &&
433  (zero_velocity_count_ > parameters_->num_outgoing_halt_msgs_to_publish))
434  {
435  ok_to_publish_ = false;
436  rclcpp::Clock& clock = *node_->get_clock();
437  RCLCPP_DEBUG_STREAM_THROTTLE(LOGGER, clock, ROS_LOG_THROTTLE_PERIOD, "All-zero command. Doing nothing.");
438  }
439  // Skip servoing publication if both types of commands are stale.
441  {
442  ok_to_publish_ = false;
443  rclcpp::Clock& clock = *node_->get_clock();
444  RCLCPP_DEBUG_STREAM_THROTTLE(LOGGER, clock, ROS_LOG_THROTTLE_PERIOD,
445  "Skipping publishing because incoming commands are stale.");
446  }
447  else
448  {
449  ok_to_publish_ = true;
450  }
451 
452  // Store last zero-velocity message flag to prevent superfluous warnings.
453  // Cartesian and joint commands must both be zero.
455  {
456  // Avoid overflow
457  if (zero_velocity_count_ < std::numeric_limits<int>::max())
459  }
460  else
461  {
463  }
464 
465  if (ok_to_publish_ && !paused_)
466  {
467  // Clear out position commands if user did not request them (can cause interpolation issues)
468  if (!parameters_->publish_joint_positions)
469  {
470  joint_trajectory->points[0].positions.clear();
471  }
472  // Likewise for velocity and acceleration
473  if (!parameters_->publish_joint_velocities)
474  {
475  joint_trajectory->points[0].velocities.clear();
476  }
477  if (!parameters_->publish_joint_accelerations)
478  {
479  joint_trajectory->points[0].accelerations.clear();
480  }
481 
482  // Put the outgoing msg in the right format
483  // (trajectory_msgs/JointTrajectory or std_msgs/Float64MultiArray).
484  if (parameters_->command_out_type == "trajectory_msgs/JointTrajectory")
485  {
486  // When a joint_trajectory_controller receives a new command, a stamp of 0 indicates "begin immediately"
487  // See http://wiki.ros.org/joint_trajectory_controller#Trajectory_replacement
488  joint_trajectory->header.stamp = rclcpp::Time(0);
489  *last_sent_command_ = *joint_trajectory;
490  trajectory_outgoing_cmd_pub_->publish(std::move(joint_trajectory));
491  }
492  else if (parameters_->command_out_type == "std_msgs/Float64MultiArray")
493  {
494  auto joints = std::make_unique<std_msgs::msg::Float64MultiArray>();
495  if (parameters_->publish_joint_positions && !joint_trajectory->points.empty())
496  joints->data = joint_trajectory->points[0].positions;
497  else if (parameters_->publish_joint_velocities && !joint_trajectory->points.empty())
498  joints->data = joint_trajectory->points[0].velocities;
499  *last_sent_command_ = *joint_trajectory;
500  multiarray_outgoing_cmd_pub_->publish(std::move(joints));
501  }
502  }
503 
504  // Update the filters if we haven't yet
505  if (!updated_filters_)
507 }
508 
509 rcl_interfaces::msg::SetParametersResult ServoCalcs::robotLinkCommandFrameCallback(const rclcpp::Parameter& parameter)
510 {
511  const std::lock_guard<std::mutex> lock(main_loop_mutex_);
512  rcl_interfaces::msg::SetParametersResult result;
513  result.successful = true;
514  robot_link_command_frame_ = parameter.as_string();
515  RCLCPP_INFO_STREAM(LOGGER, "robot_link_command_frame changed to: " + robot_link_command_frame_);
516  return result;
517 };
518 
519 // Perform the servoing calculations
520 bool ServoCalcs::cartesianServoCalcs(geometry_msgs::msg::TwistStamped& cmd,
521  trajectory_msgs::msg::JointTrajectory& joint_trajectory)
522 {
523  // Check for nan's in the incoming command
524  if (!checkValidCommand(cmd))
525  return false;
526 
527  // Set uncontrolled dimensions to 0 in command frame
529 
530  // Transform the command to the MoveGroup planning frame
531  if (cmd.header.frame_id != parameters_->planning_frame)
532  {
533  Eigen::Vector3d translation_vector(cmd.twist.linear.x, cmd.twist.linear.y, cmd.twist.linear.z);
534  Eigen::Vector3d angular_vector(cmd.twist.angular.x, cmd.twist.angular.y, cmd.twist.angular.z);
535 
536  // If the incoming frame is empty or is the command frame, we use the previously calculated tf
537  if (cmd.header.frame_id.empty() || cmd.header.frame_id == robot_link_command_frame_)
538  {
539  translation_vector = tf_moveit_to_robot_cmd_frame_.linear() * translation_vector;
540  angular_vector = tf_moveit_to_robot_cmd_frame_.linear() * angular_vector;
541  }
542  else if (cmd.header.frame_id == parameters_->ee_frame_name)
543  {
544  // If the frame is the EE frame, we already have that transform as well
545  translation_vector = tf_moveit_to_ee_frame_.linear() * translation_vector;
546  angular_vector = tf_moveit_to_ee_frame_.linear() * angular_vector;
547  }
548  else
549  {
550  // We solve (planning_frame -> base -> cmd.header.frame_id)
551  // by computing (base->planning_frame)^-1 * (base->cmd.header.frame_id)
552  const auto tf_moveit_to_incoming_cmd_frame =
553  current_state_->getGlobalLinkTransform(parameters_->planning_frame).inverse() *
554  current_state_->getGlobalLinkTransform(cmd.header.frame_id);
555  translation_vector = tf_moveit_to_incoming_cmd_frame.linear() * translation_vector;
556  angular_vector = tf_moveit_to_incoming_cmd_frame.linear() * angular_vector;
557  }
558 
559  // Put these components back into a TwistStamped
560  cmd.header.frame_id = parameters_->planning_frame;
561  cmd.twist.linear.x = translation_vector(0);
562  cmd.twist.linear.y = translation_vector(1);
563  cmd.twist.linear.z = translation_vector(2);
564  cmd.twist.angular.x = angular_vector(0);
565  cmd.twist.angular.y = angular_vector(1);
566  cmd.twist.angular.z = angular_vector(2);
567  }
568 
569  Eigen::VectorXd delta_x = scaleCartesianCommand(cmd);
570 
571  Eigen::MatrixXd jacobian = current_state_->getJacobian(joint_model_group_);
572 
573  removeDriftDimensions(jacobian, delta_x);
574 
575  Eigen::JacobiSVD<Eigen::MatrixXd> svd =
576  Eigen::JacobiSVD<Eigen::MatrixXd>(jacobian, Eigen::ComputeThinU | Eigen::ComputeThinV);
577  Eigen::MatrixXd matrix_s = svd.singularValues().asDiagonal();
578  Eigen::MatrixXd pseudo_inverse = svd.matrixV() * matrix_s.inverse() * svd.matrixU().transpose();
579 
580  // Convert from cartesian commands to joint commands
581  // Use an IK solver plugin if we have one, otherwise use inverse Jacobian.
582  if (!use_inv_jacobian_)
583  {
584  // get a transformation matrix with the desired position change &
585  // get a transformation matrix with desired orientation change
586  Eigen::Isometry3d tf_pos_delta(Eigen::Isometry3d::Identity());
587  tf_pos_delta.translate(Eigen::Vector3d(delta_x[0], delta_x[1], delta_x[2]));
588 
589  Eigen::Isometry3d tf_rot_delta(Eigen::Isometry3d::Identity());
590  Eigen::Quaterniond q = Eigen::AngleAxisd(delta_x[3], Eigen::Vector3d::UnitX()) *
591  Eigen::AngleAxisd(delta_x[4], Eigen::Vector3d::UnitY()) *
592  Eigen::AngleAxisd(delta_x[5], Eigen::Vector3d::UnitZ());
593  tf_rot_delta.rotate(q);
594 
595  // Poses passed to IK solvers are assumed to be in some tip link (usually EE) reference frame
596  // First, find the new tip link position without newly applied rotation
597 
598  auto tf_no_new_rot = tf_pos_delta * ik_base_to_tip_frame_;
599  // we want the rotation to be applied in the requested reference frame,
600  // but we want the rotation to be about the EE point in space, not the origin.
601  // So, we need to translate to origin, rotate, then translate back
602  // Given T = transformation matrix from origin -> EE point in space (translation component of tf_no_new_rot)
603  // and T' as the opposite transformation, EE point in space -> origin (translation only)
604  // apply final transformation as T * R * T' * tf_no_new_rot
605  auto tf_translation = tf_no_new_rot.translation();
606  auto tf_neg_translation = Eigen::Isometry3d::Identity(); // T'
607  tf_neg_translation(0, 3) = -tf_translation(0, 0);
608  tf_neg_translation(1, 3) = -tf_translation(1, 0);
609  tf_neg_translation(2, 3) = -tf_translation(2, 0);
610  auto tf_pos_translation = Eigen::Isometry3d::Identity(); // T
611  tf_pos_translation(0, 3) = tf_translation(0, 0);
612  tf_pos_translation(1, 3) = tf_translation(1, 0);
613  tf_pos_translation(2, 3) = tf_translation(2, 0);
614 
615  // T * R * T' * tf_no_new_rot
616  auto tf = tf_pos_translation * tf_rot_delta * tf_neg_translation * tf_no_new_rot;
617  geometry_msgs::msg::Pose next_pose = tf2::toMsg(tf);
618 
619  // setup for IK call
620  std::vector<double> solution(num_joints_);
621  moveit_msgs::msg::MoveItErrorCodes err;
623  opts.return_approximate_solution = true;
624  if (ik_solver_->searchPositionIK(next_pose, internal_joint_state_.position, parameters_->publish_period / 2.0,
625  solution, err, opts))
626  {
627  // find the difference in joint positions that will get us to the desired pose
628  for (size_t i = 0; i < num_joints_; ++i)
629  {
630  delta_theta_.coeffRef(i) = solution.at(i) - internal_joint_state_.position.at(i);
631  }
632  }
633  else
634  {
635  RCLCPP_WARN(LOGGER, "Could not find IK solution for requested motion, got error code %d", err.val);
636  return false;
637  }
638  }
639  else
640  {
641  // no supported IK plugin, use inverse Jacobian
642  delta_theta_ = pseudo_inverse * delta_x;
643  }
644 
646  parameters_->hard_stop_singularity_threshold,
647  parameters_->lower_singularity_threshold,
648  parameters_->leaving_singularity_threshold_multiplier,
649  *node_->get_clock(), current_state_, status_);
650 
652 }
653 
654 bool ServoCalcs::jointServoCalcs(const control_msgs::msg::JointJog& cmd,
655  trajectory_msgs::msg::JointTrajectory& joint_trajectory)
656 {
657  // Check for nan's
658  if (!checkValidCommand(cmd))
659  return false;
660 
661  // Apply user-defined scaling
663 
664  // Perform internal servo with the command
665  return internalServoUpdate(delta_theta_, joint_trajectory, ServoType::JOINT_SPACE);
666 }
667 
668 bool ServoCalcs::internalServoUpdate(Eigen::ArrayXd& delta_theta,
669  trajectory_msgs::msg::JointTrajectory& joint_trajectory,
670  const ServoType servo_type)
671 {
672  // The order of operations here is:
673  // 1. apply velocity scaling for collisions (in the position domain)
674  // 2. low-pass filter the position command in applyJointUpdate()
675  // 3. calculate velocities in applyJointUpdate()
676  // 4. apply velocity limits
677  // 5. apply position limits. This is a higher priority than velocity limits, so check it last.
678 
679  // Set internal joint state from original
681 
682  // Apply collision scaling
683  double collision_scale = collision_velocity_scale_;
684  if (collision_scale > 0 && collision_scale < 1)
685  {
687  rclcpp::Clock& clock = *node_->get_clock();
688  RCLCPP_WARN_STREAM_THROTTLE(LOGGER, clock, ROS_LOG_THROTTLE_PERIOD, SERVO_STATUS_CODE_MAP.at(status_));
689  }
690  else if (collision_scale == 0)
691  {
693  rclcpp::Clock& clock = *node_->get_clock();
694  RCLCPP_ERROR_STREAM_THROTTLE(LOGGER, clock, ROS_LOG_THROTTLE_PERIOD, "Halting for collision!");
695  }
696  delta_theta *= collision_scale;
697 
698  // Loop thru joints and update them, calculate velocities, and filter
699  if (!applyJointUpdate(delta_theta, internal_joint_state_))
700  return false;
701 
702  // Mark the lowpass filters as updated for this cycle
703  updated_filters_ = true;
704 
705  // Enforce SRDF velocity limits
707  parameters_->override_velocity_scaling_factor);
708 
709  // Enforce SRDF position limits, might halt if needed, set prev_vel to 0
710  const auto joints_to_halt = enforcePositionLimits(internal_joint_state_);
711  if (!joints_to_halt.empty())
712  {
714  if ((servo_type == ServoType::JOINT_SPACE && !parameters_->halt_all_joints_in_joint_mode) ||
715  (servo_type == ServoType::CARTESIAN_SPACE && !parameters_->halt_all_joints_in_cartesian_mode))
716  {
717  suddenHalt(internal_joint_state_, joints_to_halt);
718  }
719  else
720  {
722  }
723  }
724 
725  // compose outgoing message
727 
728  // Modify the output message if we are using gazebo
729  if (parameters_->use_gazebo)
730  {
732  }
733 
734  return true;
735 }
736 
737 bool ServoCalcs::applyJointUpdate(const Eigen::ArrayXd& delta_theta, sensor_msgs::msg::JointState& joint_state)
738 {
739  // All the sizes must match
740  if (joint_state.position.size() != static_cast<std::size_t>(delta_theta.size()) ||
741  joint_state.velocity.size() != joint_state.position.size())
742  {
743  rclcpp::Clock& clock = *node_->get_clock();
744  RCLCPP_ERROR_STREAM_THROTTLE(LOGGER, clock, ROS_LOG_THROTTLE_PERIOD,
745  "Lengths of output and increments do not match.");
746  return false;
747  }
748 
749  for (std::size_t i = 0; i < joint_state.position.size(); ++i)
750  {
751  // Increment joint
752  joint_state.position[i] += delta_theta[i];
753  }
754 
755  smoother_->doSmoothing(joint_state.position);
756 
757  for (std::size_t i = 0; i < joint_state.position.size(); ++i)
758  {
759  // Calculate joint velocity
760  joint_state.velocity[i] =
761  (joint_state.position.at(i) - original_joint_state_.position.at(i)) / parameters_->publish_period;
762  }
763 
764  return true;
765 }
766 
767 // Spam several redundant points into the trajectory. The first few may be skipped if the
768 // time stamp is in the past when it reaches the client. Needed for gazebo simulation.
769 // Start from 1 because the first point's timestamp is already 1*parameters_->publish_period
770 void ServoCalcs::insertRedundantPointsIntoTrajectory(trajectory_msgs::msg::JointTrajectory& joint_trajectory,
771  int count) const
772 {
773  if (count < 2)
774  return;
775  joint_trajectory.points.resize(count);
776  auto point = joint_trajectory.points[0];
777  // Start from 1 because we already have the first point. End at count+1 so (total #) == count
778  for (int i = 1; i < count; ++i)
779  {
780  point.time_from_start = rclcpp::Duration::from_seconds((i + 1) * parameters_->publish_period);
781  joint_trajectory.points[i] = point;
782  }
783 }
784 
785 void ServoCalcs::resetLowPassFilters(const sensor_msgs::msg::JointState& joint_state)
786 {
787  smoother_->reset(joint_state.position);
788  updated_filters_ = true;
789 }
790 
791 void ServoCalcs::composeJointTrajMessage(const sensor_msgs::msg::JointState& joint_state,
792  trajectory_msgs::msg::JointTrajectory& joint_trajectory)
793 {
794  // When a joint_trajectory_controller receives a new command, a stamp of 0 indicates "begin immediately"
795  // See http://wiki.ros.org/joint_trajectory_controller#Trajectory_replacement
796  joint_trajectory.header.stamp = rclcpp::Time(0);
797  joint_trajectory.header.frame_id = parameters_->planning_frame;
798  joint_trajectory.joint_names = joint_state.name;
799 
800  trajectory_msgs::msg::JointTrajectoryPoint point;
801  point.time_from_start = rclcpp::Duration::from_seconds(parameters_->publish_period);
802  if (parameters_->publish_joint_positions)
803  point.positions = joint_state.position;
804  if (parameters_->publish_joint_velocities)
805  point.velocities = joint_state.velocity;
806  if (parameters_->publish_joint_accelerations)
807  {
808  // I do not know of a robot that takes acceleration commands.
809  // However, some controllers check that this data is non-empty.
810  // Send all zeros, for now.
811  std::vector<double> acceleration(num_joints_);
812  point.accelerations = acceleration;
813  }
814  joint_trajectory.points.push_back(point);
815 }
816 
817 std::vector<const moveit::core::JointModel*>
818 ServoCalcs::enforcePositionLimits(sensor_msgs::msg::JointState& joint_state) const
819 {
820  // Halt if we're past a joint margin and joint velocity is moving even farther past
821  double joint_angle = 0;
822  std::vector<const moveit::core::JointModel*> joints_to_halt;
823  for (auto joint : joint_model_group_->getActiveJointModels())
824  {
825  for (std::size_t c = 0; c < joint_state.name.size(); ++c)
826  {
827  // Use the most recent robot joint state
828  if (joint_state.name[c] == joint->getName())
829  {
830  joint_angle = joint_state.position.at(c);
831  break;
832  }
833  }
834 
835  if (!joint->satisfiesPositionBounds(&joint_angle, -parameters_->joint_limit_margin))
836  {
837  const std::vector<moveit_msgs::msg::JointLimits>& limits = joint->getVariableBoundsMsg();
838 
839  // Joint limits are not defined for some joints. Skip them.
840  if (!limits.empty())
841  {
842  // Check if pending velocity command is moving in the right direction
843  auto joint_itr = std::find(joint_state.name.begin(), joint_state.name.end(), joint->getName());
844  auto joint_idx = std::distance(joint_state.name.begin(), joint_itr);
845 
846  if ((joint_state.velocity.at(joint_idx) < 0 &&
847  (joint_angle < (limits[0].min_position + parameters_->joint_limit_margin))) ||
848  (joint_state.velocity.at(joint_idx) > 0 &&
849  (joint_angle > (limits[0].max_position - parameters_->joint_limit_margin))))
850  {
851  joints_to_halt.push_back(joint);
852  }
853  }
854  }
855  }
856  if (!joints_to_halt.empty())
857  {
858  std::ostringstream joints_names;
859  std::transform(joints_to_halt.cbegin(), std::prev(joints_to_halt.cend()),
860  std::ostream_iterator<std::string>(joints_names, ", "),
861  [](const auto& joint) { return joint->getName(); });
862  joints_names << joints_to_halt.back()->getName();
863  RCLCPP_WARN_STREAM_THROTTLE(LOGGER, *node_->get_clock(), ROS_LOG_THROTTLE_PERIOD,
864  node_->get_name()
865  << " " << joints_names.str() << " close to a position limit. Halting.");
866  }
867  return joints_to_halt;
868 }
869 
870 void ServoCalcs::filteredHalt(trajectory_msgs::msg::JointTrajectory& joint_trajectory)
871 {
872  // Prepare the joint trajectory message to stop the robot
873  joint_trajectory.points.clear();
874  joint_trajectory.points.emplace_back();
875 
876  // Deceleration algorithm:
877  // Set positions to original_joint_state_
878  // Filter
879  // Calculate velocities
880  // Check if velocities are close to zero. Round to zero, if so.
881  // Set done_stopping_ flag
882  assert(original_joint_state_.position.size() >= num_joints_);
883  joint_trajectory.points[0].positions = original_joint_state_.position;
884  smoother_->doSmoothing(joint_trajectory.points[0].positions);
885  done_stopping_ = true;
886  if (parameters_->publish_joint_velocities)
887  {
888  joint_trajectory.points[0].velocities = std::vector<double>(num_joints_, 0);
889  for (std::size_t i = 0; i < num_joints_; ++i)
890  {
891  joint_trajectory.points[0].velocities.at(i) =
892  (joint_trajectory.points[0].positions.at(i) - original_joint_state_.position.at(i)) /
893  parameters_->publish_period;
894  // If velocity is very close to zero, round to zero
895  if (joint_trajectory.points[0].velocities.at(i) > STOPPED_VELOCITY_EPS)
896  {
897  done_stopping_ = false;
898  }
899  }
900  // If every joint is very close to stopped, round velocity to zero
901  if (done_stopping_)
902  {
903  std::fill(joint_trajectory.points[0].velocities.begin(), joint_trajectory.points[0].velocities.end(), 0);
904  }
905  }
906 
907  if (parameters_->publish_joint_accelerations)
908  {
909  joint_trajectory.points[0].accelerations = std::vector<double>(num_joints_, 0);
910  for (std::size_t i = 0; i < num_joints_; ++i)
911  {
912  joint_trajectory.points[0].accelerations.at(i) =
913  (joint_trajectory.points[0].velocities.at(i) - original_joint_state_.velocity.at(i)) /
914  parameters_->publish_period;
915  }
916  }
917 
918  joint_trajectory.points[0].time_from_start = rclcpp::Duration::from_seconds(parameters_->publish_period);
919 }
920 
921 void ServoCalcs::suddenHalt(sensor_msgs::msg::JointState& joint_state,
922  const std::vector<const moveit::core::JointModel*>& joints_to_halt) const
923 {
924  // Set the position to the original position, and velocity to 0 for input joints
925  for (const auto& joint_to_halt : joints_to_halt)
926  {
927  const auto joint_it = std::find(joint_state.name.cbegin(), joint_state.name.cend(), joint_to_halt->getName());
928  if (joint_it != joint_state.name.cend())
929  {
930  const auto joint_index = std::distance(joint_state.name.cbegin(), joint_it);
931  joint_state.position.at(joint_index) = original_joint_state_.position.at(joint_index);
932  joint_state.velocity.at(joint_index) = 0.0;
933  }
934  }
935 }
936 
938 {
939  // Get the latest joint group positions
940  current_state_ = planning_scene_monitor_->getStateMonitor()->getCurrentState();
941  current_state_->copyJointGroupPositions(joint_model_group_, internal_joint_state_.position);
942  current_state_->copyJointGroupVelocities(joint_model_group_, internal_joint_state_.velocity);
943 
944  // Cache the original joints in case they need to be reset
946 }
947 
948 bool ServoCalcs::checkValidCommand(const control_msgs::msg::JointJog& cmd)
949 {
950  for (double velocity : cmd.velocities)
951  {
952  if (std::isnan(velocity))
953  {
954  rclcpp::Clock& clock = *node_->get_clock();
955  RCLCPP_WARN_STREAM_THROTTLE(LOGGER, clock, ROS_LOG_THROTTLE_PERIOD,
956  "nan in incoming command. Skipping this datapoint.");
957  return false;
958  }
959  }
960  return true;
961 }
962 
963 bool ServoCalcs::checkValidCommand(const geometry_msgs::msg::TwistStamped& cmd)
964 {
965  if (std::isnan(cmd.twist.linear.x) || std::isnan(cmd.twist.linear.y) || std::isnan(cmd.twist.linear.z) ||
966  std::isnan(cmd.twist.angular.x) || std::isnan(cmd.twist.angular.y) || std::isnan(cmd.twist.angular.z))
967  {
968  rclcpp::Clock& clock = *node_->get_clock();
969  RCLCPP_WARN_STREAM_THROTTLE(LOGGER, clock, ROS_LOG_THROTTLE_PERIOD,
970  "nan in incoming command. Skipping this datapoint.");
971  return false;
972  }
973 
974  // If incoming commands should be in the range [-1:1], check for |delta|>1
975  if (parameters_->command_in_type == "unitless")
976  {
977  if ((fabs(cmd.twist.linear.x) > 1) || (fabs(cmd.twist.linear.y) > 1) || (fabs(cmd.twist.linear.z) > 1) ||
978  (fabs(cmd.twist.angular.x) > 1) || (fabs(cmd.twist.angular.y) > 1) || (fabs(cmd.twist.angular.z) > 1))
979  {
980  rclcpp::Clock& clock = *node_->get_clock();
981  RCLCPP_WARN_STREAM_THROTTLE(LOGGER, clock, ROS_LOG_THROTTLE_PERIOD,
982  "Component of incoming command is >1. Skipping this datapoint.");
983  return false;
984  }
985  }
986 
987  return true;
988 }
989 
990 // Scale the incoming jog command. Returns a vector of position deltas
991 Eigen::VectorXd ServoCalcs::scaleCartesianCommand(const geometry_msgs::msg::TwistStamped& command)
992 {
993  Eigen::VectorXd result(6);
994  result.setZero(); // Or the else case below leads to misery
995 
996  // Apply user-defined scaling if inputs are unitless [-1:1]
997  if (parameters_->command_in_type == "unitless")
998  {
999  result[0] = parameters_->linear_scale * parameters_->publish_period * command.twist.linear.x;
1000  result[1] = parameters_->linear_scale * parameters_->publish_period * command.twist.linear.y;
1001  result[2] = parameters_->linear_scale * parameters_->publish_period * command.twist.linear.z;
1002  result[3] = parameters_->rotational_scale * parameters_->publish_period * command.twist.angular.x;
1003  result[4] = parameters_->rotational_scale * parameters_->publish_period * command.twist.angular.y;
1004  result[5] = parameters_->rotational_scale * parameters_->publish_period * command.twist.angular.z;
1005  }
1006  // Otherwise, commands are in m/s and rad/s
1007  else if (parameters_->command_in_type == "speed_units")
1008  {
1009  result[0] = command.twist.linear.x * parameters_->publish_period;
1010  result[1] = command.twist.linear.y * parameters_->publish_period;
1011  result[2] = command.twist.linear.z * parameters_->publish_period;
1012  result[3] = command.twist.angular.x * parameters_->publish_period;
1013  result[4] = command.twist.angular.y * parameters_->publish_period;
1014  result[5] = command.twist.angular.z * parameters_->publish_period;
1015  }
1016  else
1017  {
1018  rclcpp::Clock& clock = *node_->get_clock();
1019  RCLCPP_ERROR_STREAM_THROTTLE(LOGGER, clock, ROS_LOG_THROTTLE_PERIOD, "Unexpected command_in_type");
1020  }
1021 
1022  return result;
1023 }
1024 
1025 Eigen::VectorXd ServoCalcs::scaleJointCommand(const control_msgs::msg::JointJog& command)
1026 {
1027  Eigen::VectorXd result(num_joints_);
1028  result.setZero();
1029 
1030  std::size_t c;
1031  for (std::size_t m = 0; m < command.joint_names.size(); ++m)
1032  {
1033  try
1034  {
1035  c = joint_state_name_map_.at(command.joint_names[m]);
1036  }
1037  catch (const std::out_of_range& e)
1038  {
1039  rclcpp::Clock& clock = *node_->get_clock();
1040  RCLCPP_WARN_STREAM_THROTTLE(LOGGER, clock, ROS_LOG_THROTTLE_PERIOD, "Ignoring joint " << command.joint_names[m]);
1041  continue;
1042  }
1043  // Apply user-defined scaling if inputs are unitless [-1:1]
1044  if (parameters_->command_in_type == "unitless")
1045  result[c] = command.velocities[m] * parameters_->joint_scale * parameters_->publish_period;
1046  // Otherwise, commands are in m/s and rad/s
1047  else if (parameters_->command_in_type == "speed_units")
1048  result[c] = command.velocities[m] * parameters_->publish_period;
1049  else
1050  {
1051  rclcpp::Clock& clock = *node_->get_clock();
1052  RCLCPP_ERROR_STREAM_THROTTLE(LOGGER, clock, ROS_LOG_THROTTLE_PERIOD,
1053  "Unexpected command_in_type, check yaml file.");
1054  }
1055  }
1056 
1057  return result;
1058 }
1059 
1060 void ServoCalcs::removeDimension(Eigen::MatrixXd& jacobian, Eigen::VectorXd& delta_x, unsigned int row_to_remove) const
1061 {
1062  unsigned int num_rows = jacobian.rows() - 1;
1063  unsigned int num_cols = jacobian.cols();
1064 
1065  if (row_to_remove < num_rows)
1066  {
1067  jacobian.block(row_to_remove, 0, num_rows - row_to_remove, num_cols) =
1068  jacobian.block(row_to_remove + 1, 0, num_rows - row_to_remove, num_cols);
1069  delta_x.segment(row_to_remove, num_rows - row_to_remove) =
1070  delta_x.segment(row_to_remove + 1, num_rows - row_to_remove);
1071  }
1072  jacobian.conservativeResize(num_rows, num_cols);
1073  delta_x.conservativeResize(num_rows);
1074 }
1075 
1076 void ServoCalcs::removeDriftDimensions(Eigen::MatrixXd& matrix, Eigen::VectorXd& delta_x)
1077 {
1078  // May allow some dimensions to drift, based on drift_dimensions
1079  // i.e. take advantage of task redundancy.
1080  // Remove the Jacobian rows corresponding to True in the vector drift_dimensions
1081  // Work backwards through the 6-vector so indices don't get out of order
1082  for (auto dimension = matrix.rows() - 1; dimension >= 0; --dimension)
1083  {
1084  if (drift_dimensions_[dimension] && matrix.rows() > 1)
1085  {
1086  removeDimension(matrix, delta_x, dimension);
1087  }
1088  }
1089 }
1090 
1091 void ServoCalcs::enforceControlDimensions(geometry_msgs::msg::TwistStamped& command)
1092 {
1093  // Can't loop through the message, so check them all
1094  if (!control_dimensions_[0])
1095  command.twist.linear.x = 0;
1096  if (!control_dimensions_[1])
1097  command.twist.linear.y = 0;
1098  if (!control_dimensions_[2])
1099  command.twist.linear.z = 0;
1100  if (!control_dimensions_[3])
1101  command.twist.angular.x = 0;
1102  if (!control_dimensions_[4])
1103  command.twist.angular.y = 0;
1104  if (!control_dimensions_[5])
1105  command.twist.angular.z = 0;
1106 }
1107 
1108 bool ServoCalcs::getCommandFrameTransform(Eigen::Isometry3d& transform)
1109 {
1110  const std::lock_guard<std::mutex> lock(main_loop_mutex_);
1111  transform = tf_moveit_to_robot_cmd_frame_;
1112 
1113  // All zeros means the transform wasn't initialized, so return false
1114  return !transform.matrix().isZero(0);
1115 }
1116 
1117 bool ServoCalcs::getCommandFrameTransform(geometry_msgs::msg::TransformStamped& transform)
1118 {
1119  const std::lock_guard<std::mutex> lock(main_loop_mutex_);
1120  // All zeros means the transform wasn't initialized, so return false
1121  if (tf_moveit_to_robot_cmd_frame_.matrix().isZero(0))
1122  {
1123  return false;
1124  }
1125 
1126  transform =
1128  return true;
1129 }
1130 
1131 bool ServoCalcs::getEEFrameTransform(Eigen::Isometry3d& transform)
1132 {
1133  const std::lock_guard<std::mutex> lock(main_loop_mutex_);
1134  transform = tf_moveit_to_ee_frame_;
1135 
1136  // All zeros means the transform wasn't initialized, so return false
1137  return !transform.matrix().isZero(0);
1138 }
1139 
1140 bool ServoCalcs::getEEFrameTransform(geometry_msgs::msg::TransformStamped& transform)
1141 {
1142  const std::lock_guard<std::mutex> lock(main_loop_mutex_);
1143  // All zeros means the transform wasn't initialized, so return false
1144  if (tf_moveit_to_ee_frame_.matrix().isZero(0))
1145  {
1146  return false;
1147  }
1148 
1149  transform =
1151  return true;
1152 }
1153 
1154 void ServoCalcs::twistStampedCB(const geometry_msgs::msg::TwistStamped::ConstSharedPtr& msg)
1155 {
1156  const std::lock_guard<std::mutex> lock(main_loop_mutex_);
1157  latest_twist_stamped_ = msg;
1159 
1160  if (msg->header.stamp != rclcpp::Time(0.))
1161  latest_twist_command_stamp_ = msg->header.stamp;
1162 
1163  // notify that we have a new input
1164  new_input_cmd_ = true;
1165  input_cv_.notify_all();
1166 }
1167 
1168 void ServoCalcs::jointCmdCB(const control_msgs::msg::JointJog::ConstSharedPtr& msg)
1169 {
1170  const std::lock_guard<std::mutex> lock(main_loop_mutex_);
1171  latest_joint_cmd_ = msg;
1173 
1174  if (msg->header.stamp != rclcpp::Time(0.))
1175  latest_joint_command_stamp_ = msg->header.stamp;
1176 
1177  // notify that we have a new input
1178  new_input_cmd_ = true;
1179  input_cv_.notify_all();
1180 }
1181 
1182 void ServoCalcs::collisionVelocityScaleCB(const std_msgs::msg::Float64::ConstSharedPtr& msg)
1183 {
1184  collision_velocity_scale_ = msg->data;
1185 }
1186 
1187 void ServoCalcs::changeDriftDimensions(const std::shared_ptr<moveit_msgs::srv::ChangeDriftDimensions::Request>& req,
1188  const std::shared_ptr<moveit_msgs::srv::ChangeDriftDimensions::Response>& res)
1189 {
1190  drift_dimensions_[0] = req->drift_x_translation;
1191  drift_dimensions_[1] = req->drift_y_translation;
1192  drift_dimensions_[2] = req->drift_z_translation;
1193  drift_dimensions_[3] = req->drift_x_rotation;
1194  drift_dimensions_[4] = req->drift_y_rotation;
1195  drift_dimensions_[5] = req->drift_z_rotation;
1196 
1197  res->success = true;
1198 }
1199 
1200 void ServoCalcs::changeControlDimensions(const std::shared_ptr<moveit_msgs::srv::ChangeControlDimensions::Request>& req,
1201  const std::shared_ptr<moveit_msgs::srv::ChangeControlDimensions::Response>& res)
1202 {
1203  control_dimensions_[0] = req->control_x_translation;
1204  control_dimensions_[1] = req->control_y_translation;
1205  control_dimensions_[2] = req->control_z_translation;
1206  control_dimensions_[3] = req->control_x_rotation;
1207  control_dimensions_[4] = req->control_y_rotation;
1208  control_dimensions_[5] = req->control_z_rotation;
1209 
1210  res->success = true;
1211 }
1212 
1213 bool ServoCalcs::resetServoStatus(const std::shared_ptr<std_srvs::srv::Empty::Request>& /*req*/,
1214  const std::shared_ptr<std_srvs::srv::Empty::Response>& /*res*/)
1215 {
1217  return true;
1218 }
1219 
1220 void ServoCalcs::setPaused(bool paused)
1221 {
1222  paused_ = paused;
1223 }
1224 
1225 } // namespace moveit_servo
const std::vector< const JointModel * > & getActiveJointModels() const
Get the active joints in this group (that have controllable DOF). This does not include mimic joints.
const std::string & getName() const
Get the name of the joint group.
const kinematics::KinematicsBaseConstPtr getSolverInstance() const
const std::vector< std::string > & getActiveJointModelNames() const
Get the names of the active joints in this group. These are the names of the joints returned by getJo...
const std::shared_ptr< const moveit_servo::ServoParameters > parameters_
Definition: servo_calcs.h:261
std::atomic< bool > paused_
Definition: servo_calcs.h:320
unsigned int num_joints_
Definition: servo_calcs.h:331
void removeDimension(Eigen::MatrixXd &matrix, Eigen::VectorXd &delta_x, unsigned int row_to_remove) const
rclcpp::Publisher< std_msgs::msg::Int8 >::SharedPtr status_pub_
Definition: servo_calcs.h:306
Eigen::Isometry3d tf_moveit_to_ee_frame_
Definition: servo_calcs.h:342
std::array< bool, 6 > control_dimensions_
Definition: servo_calcs.h:337
std::array< bool, 6 > drift_dimensions_
Definition: servo_calcs.h:334
void changeDriftDimensions(const std::shared_ptr< moveit_msgs::srv::ChangeDriftDimensions::Request > &req, const std::shared_ptr< moveit_msgs::srv::ChangeDriftDimensions::Response > &res)
moveit::core::RobotStatePtr current_state_
Definition: servo_calcs.h:287
void resetLowPassFilters(const sensor_msgs::msg::JointState &joint_state)
Set the filters to the specified values.
sensor_msgs::msg::JointState original_joint_state_
Definition: servo_calcs.h:293
void updateJoints()
Parse the incoming joint msg for the joints of our MoveGroup.
control_msgs::msg::JointJog joint_servo_cmd_
Definition: servo_calcs.h:283
void insertRedundantPointsIntoTrajectory(trajectory_msgs::msg::JointTrajectory &joint_trajectory, int count) const
Gazebo simulations have very strict message timestamp requirements. Satisfy Gazebo by stuffing multip...
void setPaused(bool paused)
Pause or unpause processing servo commands while keeping the timers alive.
rclcpp::Service< moveit_msgs::srv::ChangeControlDimensions >::SharedPtr control_dimensions_server_
Definition: servo_calcs.h:309
Eigen::Isometry3d tf_moveit_to_robot_cmd_frame_
Definition: servo_calcs.h:341
rclcpp::Service< moveit_msgs::srv::ChangeDriftDimensions >::SharedPtr drift_dimensions_server_
Definition: servo_calcs.h:310
std::string robot_link_command_frame_
Definition: servo_calcs.h:355
const int gazebo_redundant_message_count_
Definition: servo_calcs.h:329
rclcpp::Publisher< std_msgs::msg::Float64MultiArray >::SharedPtr multiarray_outgoing_cmd_pub_
Definition: servo_calcs.h:308
std::map< std::string, std::size_t > joint_state_name_map_
Definition: servo_calcs.h:294
bool jointServoCalcs(const control_msgs::msg::JointJog &cmd, trajectory_msgs::msg::JointTrajectory &joint_trajectory)
Do servoing calculations for direct commands to a joint.
void stop()
Stop the currently running thread.
bool cartesianServoCalcs(geometry_msgs::msg::TwistStamped &cmd, trajectory_msgs::msg::JointTrajectory &joint_trajectory)
Do servoing calculations for Cartesian twist commands.
pluginlib::ClassLoader< online_signal_smoothing::SmoothingBaseClass > smoothing_loader_
Definition: servo_calcs.h:359
rclcpp::Service< std_srvs::srv::Empty >::SharedPtr reset_servo_status_
Definition: servo_calcs.h:311
rclcpp::Subscription< geometry_msgs::msg::TwistStamped >::SharedPtr twist_stamped_sub_
Definition: servo_calcs.h:303
control_msgs::msg::JointJog::ConstSharedPtr latest_joint_cmd_
Definition: servo_calcs.h:344
geometry_msgs::msg::TwistStamped::ConstSharedPtr latest_twist_stamped_
Definition: servo_calcs.h:343
geometry_msgs::msg::TwistStamped twist_stamped_cmd_
Definition: servo_calcs.h:282
rclcpp::Subscription< control_msgs::msg::JointJog >::SharedPtr joint_cmd_sub_
Definition: servo_calcs.h:304
std::condition_variable input_cv_
Definition: servo_calcs.h:351
Eigen::ArrayXd delta_theta_
Definition: servo_calcs.h:327
sensor_msgs::msg::JointState internal_joint_state_
Definition: servo_calcs.h:293
rclcpp::Subscription< std_msgs::msg::Float64 >::SharedPtr collision_velocity_scale_sub_
Definition: servo_calcs.h:305
void changeControlDimensions(const std::shared_ptr< moveit_msgs::srv::ChangeControlDimensions::Request > &req, const std::shared_ptr< moveit_msgs::srv::ChangeControlDimensions::Response > &res)
Start the main calculation timer.
bool getEEFrameTransform(Eigen::Isometry3d &transform)
void collisionVelocityScaleCB(const std_msgs::msg::Float64::ConstSharedPtr &msg)
void twistStampedCB(const geometry_msgs::msg::TwistStamped::ConstSharedPtr &msg)
Eigen::VectorXd scaleJointCommand(const control_msgs::msg::JointJog &command)
If incoming velocity commands are from a unitless joystick, scale them to physical units....
bool internalServoUpdate(Eigen::ArrayXd &delta_theta, trajectory_msgs::msg::JointTrajectory &joint_trajectory, const ServoType servo_type)
Handles all aspects of the servoing after the desired joint commands are established Joint and Cartes...
rclcpp::Time latest_joint_command_stamp_
Definition: servo_calcs.h:346
rcl_interfaces::msg::SetParametersResult robotLinkCommandFrameCallback(const rclcpp::Parameter &parameter)
void mainCalcLoop()
Run the main calculation loop.
void calculateSingleIteration()
Do calculations for a single iteration. Publish one outgoing command.
void jointCmdCB(const control_msgs::msg::JointJog::ConstSharedPtr &msg)
rclcpp::Time latest_twist_command_stamp_
Definition: servo_calcs.h:345
const moveit::core::JointModelGroup * joint_model_group_
Definition: servo_calcs.h:285
rclcpp::Publisher< trajectory_msgs::msg::JointTrajectory >::SharedPtr trajectory_outgoing_cmd_pub_
Definition: servo_calcs.h:307
trajectory_msgs::msg::JointTrajectory::SharedPtr last_sent_command_
Definition: servo_calcs.h:299
planning_scene_monitor::PlanningSceneMonitorPtr planning_scene_monitor_
Definition: servo_calcs.h:264
bool checkValidCommand(const control_msgs::msg::JointJog &cmd)
void composeJointTrajMessage(const sensor_msgs::msg::JointState &joint_state, trajectory_msgs::msg::JointTrajectory &joint_trajectory)
Compose the outgoing JointTrajectory message.
bool getCommandFrameTransform(Eigen::Isometry3d &transform)
void suddenHalt(sensor_msgs::msg::JointState &joint_state, const std::vector< const moveit::core::JointModel * > &joints_to_halt) const
Suddenly halt for a joint limit or other critical issue. Is handled differently for position vs....
std::shared_ptr< online_signal_smoothing::SmoothingBaseClass > smoother_
Definition: servo_calcs.h:297
void filteredHalt(trajectory_msgs::msg::JointTrajectory &joint_trajectory)
Come to a halt in a smooth way. Apply a smoothing plugin, if one is configured.
void start()
Start the timer where we do work and publish outputs.
Eigen::VectorXd scaleCartesianCommand(const geometry_msgs::msg::TwistStamped &command)
If incoming velocity commands are from a unitless joystick, scale them to physical units....
Eigen::Isometry3d ik_base_to_tip_frame_
Definition: servo_calcs.h:362
void enforceControlDimensions(geometry_msgs::msg::TwistStamped &command)
void removeDriftDimensions(Eigen::MatrixXd &matrix, Eigen::VectorXd &delta_x)
bool resetServoStatus(const std::shared_ptr< std_srvs::srv::Empty::Request > &req, const std::shared_ptr< std_srvs::srv::Empty::Response > &res)
Service callback to reset Servo status, e.g. so the arm can move again after a collision.
std::shared_ptr< rclcpp::Node > node_
Definition: servo_calcs.h:258
kinematics::KinematicsBaseConstPtr ik_solver_
Definition: servo_calcs.h:361
bool applyJointUpdate(const Eigen::ArrayXd &delta_theta, sensor_msgs::msg::JointState &joint_state)
Joint-wise update of a sensor_msgs::msg::JointState with given delta's Also filters and calculates th...
std::atomic< bool > done_stopping_
Definition: servo_calcs.h:316
std::vector< const moveit::core::JointModel * > enforcePositionLimits(sensor_msgs::msg::JointState &joint_state) const
Avoid overshooting joint limits.
constexpr size_t ROS_LOG_THROTTLE_PERIOD
Vec3fX< details::Vec3Data< double > > Vector3d
Definition: fcl_compat.h:89
double velocityScalingFactorForSingularity(const moveit::core::JointModelGroup *joint_model_group, const Eigen::VectorXd &commanded_twist, const Eigen::JacobiSVD< Eigen::MatrixXd > &svd, const Eigen::MatrixXd &pseudo_inverse, const double hard_stop_singularity_threshold, const double lower_singularity_threshold, const double leaving_singularity_threshold_multiplier, rclcpp::Clock &clock, moveit::core::RobotStatePtr &current_state, StatusCode &status)
Possibly calculate a velocity scaling factor, due to proximity of singularity and direction of motion...
Definition: utilities.cpp:88
const std::unordered_map< StatusCode, std::string > SERVO_STATUS_CODE_MAP({ { StatusCode::INVALID, "Invalid" }, { StatusCode::NO_WARNING, "No warnings" }, { StatusCode::DECELERATE_FOR_APPROACHING_SINGULARITY, "Moving closer to a singularity, decelerating" }, { StatusCode::HALT_FOR_SINGULARITY, "Very close to a singularity, emergency stop" }, { StatusCode::DECELERATE_FOR_COLLISION, "Close to a collision, decelerating" }, { StatusCode::HALT_FOR_COLLISION, "Collision detected, emergency stop" }, { StatusCode::JOINT_BOUND, "Close to a joint bound (position or velocity), halting" }, { StatusCode::DECELERATE_FOR_LEAVING_SINGULARITY, "Moving away from a singularity, decelerating" } })
geometry_msgs::msg::TransformStamped convertIsometryToTransform(const Eigen::Isometry3d &eigen_tf, const std::string &parent_frame, const std::string &child_frame)
Helper function for converting Eigen::Isometry3d to geometry_msgs/TransformStamped.
Definition: utilities.cpp:64
bool isNonZero(const geometry_msgs::msg::TwistStamped &msg)
Helper function for detecting zeroed message.
Definition: utilities.cpp:46
void enforceVelocityLimits(const moveit::core::JointModelGroup *joint_model_group, const double publish_period, sensor_msgs::msg::JointState &joint_state, const double override_velocity_scaling_factor=0.0)
Decrease robot position change and velocity, if needed, to satisfy joint velocity limits.
double distance(const urdf::Pose &transform)
Definition: pr2_arm_ik.h:55
const rclcpp::Logger LOGGER
Definition: async_test.h:31
A set of options for the kinematics solver.