moveit2
The MoveIt Motion Planning Framework for ROS 2.
servo.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.cpp
35  * Project : moveit_servo
36  * Created : 05/17/2023
37  * Author : Brian O'Neil, Andy Zelenak, Blake Anderson, V Mohammed Ibrahim
38  */
39 
40 #include <moveit_servo/servo.hpp>
43 #include <rclcpp/rclcpp.hpp>
44 #include <moveit/utils/logger.hpp>
45 
46 // Disable -Wold-style-cast because all _THROTTLE macros trigger this
47 #pragma GCC diagnostic ignored "-Wold-style-cast"
48 
49 namespace
50 {
51 constexpr double ROBOT_STATE_WAIT_TIME = 5.0; // seconds
52 constexpr double STOPPED_VELOCITY_EPS = 1e-4;
53 } // namespace
54 
55 namespace moveit_servo
56 {
57 
58 Servo::Servo(const rclcpp::Node::SharedPtr& node, std::shared_ptr<const servo::ParamListener> servo_param_listener,
59  const planning_scene_monitor::PlanningSceneMonitorPtr& planning_scene_monitor)
60  : node_(node)
61  , logger_(moveit::getLogger("servo"))
62  , servo_param_listener_{ std::move(servo_param_listener) }
63  , planning_scene_monitor_{ planning_scene_monitor }
64 {
65  servo_params_ = servo_param_listener_->get_params();
66 
67  if (!validateParams(servo_params_))
68  {
69  RCLCPP_ERROR_STREAM(logger_, "Got invalid parameters, exiting.");
70  std::exit(EXIT_FAILURE);
71  }
72 
73  if (!planning_scene_monitor_->getStateMonitor()->waitForCompleteState(servo_params_.move_group_name,
74  ROBOT_STATE_WAIT_TIME))
75  {
76  RCLCPP_ERROR(logger_, "Timeout waiting for current state");
77  std::exit(EXIT_FAILURE);
78  }
79 
80  moveit::core::RobotStatePtr robot_state = planning_scene_monitor_->getStateMonitor()->getCurrentState();
81 
82  // Load the smoothing plugin
83  if (servo_params_.use_smoothing)
84  {
85  setSmoothingPlugin();
86  }
87  else
88  {
89  RCLCPP_WARN(logger_, "No smoothing plugin loaded");
90  }
91 
92  // Create the collision checker and start collision checking.
93  collision_monitor_ =
94  std::make_unique<CollisionMonitor>(planning_scene_monitor_, servo_params_, std::ref(collision_velocity_scale_));
95  collision_monitor_->start();
96 
97  servo_status_ = StatusCode::NO_WARNING;
98 
99  const auto& move_group_joint_names = planning_scene_monitor_->getRobotModel()
100  ->getJointModelGroup(servo_params_.move_group_name)
101  ->getActiveJointModelNames();
102  // Create subgroup map
103  for (const auto& sub_group_name : planning_scene_monitor_->getRobotModel()->getJointModelGroupNames())
104  {
105  // Skip move group
106  if (sub_group_name == servo_params_.move_group_name)
107  {
108  continue;
109  }
110  const auto& subgroup_joint_names =
111  planning_scene_monitor_->getRobotModel()->getJointModelGroup(sub_group_name)->getActiveJointModelNames();
112 
114  // For each joint name of the subgroup calculate the index in the move group joint vector
115  for (const auto& joint_name : subgroup_joint_names)
116  {
117  // Find subgroup joint name in move group joint names
118  const auto move_group_iterator =
119  std::find(move_group_joint_names.cbegin(), move_group_joint_names.cend(), joint_name);
120  // Calculate position and add a new mapping of joint name to move group joint vector position
121  new_map.insert(std::make_pair<std::string, std::size_t>(
122  std::string(joint_name), std::distance(move_group_joint_names.cbegin(), move_group_iterator)));
123  }
124  // Add new joint name to index map to existing maps
125  joint_name_to_index_maps_.insert(
126  std::make_pair<std::string, JointNameToMoveGroupIndexMap>(std::string(sub_group_name), std::move(new_map)));
127  }
128  RCLCPP_INFO_STREAM(logger_, "Servo initialized successfully");
129 }
130 
132 {
133  setCollisionChecking(false);
134 }
135 
136 void Servo::setSmoothingPlugin()
137 {
138  // Load the smoothing plugin
139  try
140  {
141  pluginlib::ClassLoader<online_signal_smoothing::SmoothingBaseClass> smoothing_loader(
142  "moveit_core", "online_signal_smoothing::SmoothingBaseClass");
143  smoother_ = smoothing_loader.createUniqueInstance(servo_params_.smoothing_filter_plugin_name);
144  }
145  catch (pluginlib::PluginlibException& ex)
146  {
147  RCLCPP_ERROR(logger_, "Exception while loading the smoothing plugin '%s': '%s'",
148  servo_params_.smoothing_filter_plugin_name.c_str(), ex.what());
149  std::exit(EXIT_FAILURE);
150  }
151 
152  // Initialize the smoothing plugin
153  moveit::core::RobotStatePtr robot_state = planning_scene_monitor_->getStateMonitor()->getCurrentState();
154  const int num_joints =
155  robot_state->getJointModelGroup(servo_params_.move_group_name)->getActiveJointModelNames().size();
156  if (!smoother_->initialize(node_, planning_scene_monitor_->getRobotModel(), num_joints))
157  {
158  RCLCPP_ERROR(logger_, "Smoothing plugin could not be initialized");
159  std::exit(EXIT_FAILURE);
160  }
162 }
163 
165 {
166  if (smoother_)
167  {
168  smoother_->doSmoothing(state.positions, state.velocities, state.accelerations);
169  }
170 }
171 
173 {
174  if (smoother_)
175  {
176  smoother_->reset(state.positions, state.velocities, state.accelerations);
177  }
178 }
179 
180 void Servo::setCollisionChecking(const bool check_collision)
181 {
182  check_collision ? collision_monitor_->start() : collision_monitor_->stop();
183 }
184 
185 bool Servo::validateParams(const servo::Params& servo_params) const
186 {
187  bool params_valid = true;
188  auto robot_state = planning_scene_monitor_->getStateMonitor()->getCurrentState();
189  auto joint_model_group = robot_state->getJointModelGroup(servo_params.move_group_name);
190  const std::string check_yaml_string = " Check the parameters YAML file used to launch this node.";
191  if (joint_model_group == nullptr)
192  {
193  RCLCPP_ERROR_STREAM(logger_, "The parameter 'move_group_name': `" << servo_params.move_group_name << '`'
194  << " is not valid." << check_yaml_string);
195  params_valid = false;
196  }
197 
198  if (servo_params.hard_stop_singularity_threshold <= servo_params.lower_singularity_threshold)
199  {
200  RCLCPP_ERROR_STREAM(logger_, "The parameter 'hard_stop_singularity_threshold' "
201  "should be greater than the parameter 'lower_singularity_threshold'. But the "
202  "'hard_stop_singularity_threshold' is: '"
203  << servo_params.hard_stop_singularity_threshold
204  << "' and the 'lower_singularity_threshold' is: '"
205  << servo_params.lower_singularity_threshold << "'" << check_yaml_string);
206  params_valid = false;
207  }
208 
209  if (!servo_params.publish_joint_positions && !servo_params.publish_joint_velocities &&
210  !servo_params.publish_joint_accelerations)
211  {
212  RCLCPP_ERROR_STREAM(logger_, "At least one of the parameters: 'publish_joint_positions' / "
213  "'publish_joint_velocities' / "
214  "'publish_joint_accelerations' must be true. But they are all false."
215  << check_yaml_string);
216  params_valid = false;
217  }
218 
219  if ((servo_params.command_out_type == "std_msgs/Float64MultiArray") && servo_params.publish_joint_positions &&
220  servo_params.publish_joint_velocities)
221  {
222  RCLCPP_ERROR_STREAM(
223  logger_, "When publishing a std_msgs/Float64MultiArray, "
224  "either the parameter 'publish_joint_positions' OR the parameter 'publish_joint_velocities' must "
225  "be set to true. But both are set to false."
226  << check_yaml_string);
227  params_valid = false;
228  }
229 
230  if (servo_params.scene_collision_proximity_threshold < servo_params.self_collision_proximity_threshold)
231  {
232  RCLCPP_ERROR_STREAM(logger_, "The parameter 'self_collision_proximity_threshold' should probably be less "
233  "than or equal to the parameter 'scene_collision_proximity_threshold'. But "
234  "'self_collision_proximity_threshold' is: '"
235  << servo_params.self_collision_proximity_threshold
236  << "' and 'scene_collision_proximity_threshold' is: '"
237  << servo_params.scene_collision_proximity_threshold << "'" << check_yaml_string);
238  params_valid = false;
239  }
240 
241  if (!servo_params.active_subgroup.empty() && servo_params.active_subgroup != servo_params.move_group_name &&
242  !joint_model_group->isSubgroup(servo_params.active_subgroup))
243  {
244  RCLCPP_ERROR_STREAM(logger_, "The parameter 'active_subgroup': '"
245  << servo_params.active_subgroup
246  << "' does not name a valid subgroup of 'joint group': '"
247  << servo_params.move_group_name << "'" << check_yaml_string);
248  params_valid = false;
249  }
250  if (servo_params.joint_limit_margins.size() !=
251  robot_state->getJointModelGroup(servo_params.move_group_name)->getActiveVariableCount())
252  {
253  RCLCPP_ERROR_STREAM(
254  logger_,
255  "The parameter 'joint_limit_margins' must have the same number of elements as the number of joints in the "
256  "move group. The size of 'joint_limit_margins' is '"
257  << servo_params.joint_limit_margins.size() << "' but the number of joints of the move group '"
258  << servo_params.move_group_name << "' is '"
259  << robot_state->getJointModelGroup(servo_params.move_group_name)->getActiveVariableCount() << "'"
260  << check_yaml_string);
261 
262  params_valid = false;
263  }
264  if (servo_params.joint_limit_margins.size() !=
265  robot_state->getJointModelGroup(servo_params.move_group_name)->getActiveVariableCount())
266  {
267  RCLCPP_ERROR(logger_,
268  "Parameter 'joint_limit_margins' must have the same number of elements as the number of joints in the "
269  "move_group. "
270  "Size of 'joint_limit_margins' is '%li', but number of joints in '%s' is '%i'. "
271  "Check the parameters YAML file used to launch this node.",
272  servo_params.joint_limit_margins.size(), servo_params.move_group_name.c_str(),
273  robot_state->getJointModelGroup(servo_params.move_group_name)->getActiveVariableCount());
274  params_valid = false;
275  }
276 
277  if (servo_params.max_expected_latency / MIN_POINTS_FOR_TRAJ_MSG < servo_params.publish_period)
278  {
279  RCLCPP_ERROR(
280  logger_,
281  "The publish period (%f sec) parameter must be less than 1/%d of the max expected latency parameter (%f sec).",
282  servo_params.publish_period, MIN_POINTS_FOR_TRAJ_MSG, servo_params.max_expected_latency);
283  params_valid = false;
284  }
285 
286  return params_valid;
287 }
288 
289 bool Servo::updateParams()
290 {
291  bool params_updated = false;
292  if (servo_param_listener_->is_old(servo_params_))
293  {
294  const auto params = servo_param_listener_->get_params();
295 
296  if (validateParams(params))
297  {
298  if (params.override_velocity_scaling_factor != servo_params_.override_velocity_scaling_factor)
299  {
300  RCLCPP_INFO_STREAM(logger_, "override_velocity_scaling_factor changed to : "
301  << std::to_string(params.override_velocity_scaling_factor));
302  }
303 
304  servo_params_ = params;
305  params_updated = true;
306  }
307  else
308  {
309  RCLCPP_WARN_STREAM(logger_, "Parameters will not be updated.");
310  }
311  }
312  return params_updated;
313 }
314 
315 servo::Params& Servo::getParams()
316 {
317  return servo_params_;
318 }
319 
321 {
322  return servo_status_;
323 }
324 
325 std::string Servo::getStatusMessage() const
326 {
327  return SERVO_STATUS_CODE_MAP.at(servo_status_);
328 }
329 
331 {
332  return expected_command_type_;
333 }
334 
335 void Servo::setCommandType(const CommandType& command_type)
336 {
337  expected_command_type_ = command_type;
338 }
339 
340 KinematicState Servo::haltJoints(const std::vector<int>& joints_to_halt, const KinematicState& current_state,
341  const KinematicState& target_state) const
342 {
343  KinematicState bounded_state(target_state.joint_names.size());
344  bounded_state.joint_names = target_state.joint_names;
345 
346  std::stringstream halting_joint_names;
347  for (const int idx : joints_to_halt)
348  {
349  halting_joint_names << bounded_state.joint_names[idx] + " ";
350  }
351  RCLCPP_WARN_STREAM(logger_, "Joint position limit reached on joints: " << halting_joint_names.str());
352 
353  const bool all_joint_halt =
354  (getCommandType() == CommandType::JOINT_JOG && servo_params_.halt_all_joints_in_joint_mode) ||
355  (getCommandType() == CommandType::TWIST && servo_params_.halt_all_joints_in_cartesian_mode);
356 
357  if (all_joint_halt)
358  {
359  // The velocities are initialized to zero by default, so we don't need to set it here.
360  bounded_state.positions = current_state.positions;
361  }
362  else
363  {
364  // Halt only the joints that are out of bounds
365  bounded_state.positions = target_state.positions;
366  bounded_state.velocities = target_state.velocities;
367  for (const int idx : joints_to_halt)
368  {
369  bounded_state.positions[idx] = current_state.positions[idx];
370  bounded_state.velocities[idx] = 0.0;
371  }
372  }
373 
374  return bounded_state;
375 }
376 
377 Eigen::VectorXd Servo::jointDeltaFromCommand(const ServoInput& command, const moveit::core::RobotStatePtr& robot_state)
378 {
379  // Determine joint_name_group_index_map, if no subgroup is active, the map is empty
380  const auto& active_subgroup_name =
381  servo_params_.active_subgroup.empty() ? servo_params_.move_group_name : servo_params_.active_subgroup;
382  const auto& joint_name_group_index_map = (active_subgroup_name != servo_params_.move_group_name) ?
383  joint_name_to_index_maps_.at(servo_params_.active_subgroup) :
385 
386  const int num_joints =
387  robot_state->getJointModelGroup(servo_params_.move_group_name)->getActiveJointModelNames().size();
388  Eigen::VectorXd joint_position_deltas(num_joints);
389  joint_position_deltas.setZero();
390 
391  JointDeltaResult delta_result;
392 
393  const CommandType expected_type = getCommandType();
394  if (command.index() == static_cast<size_t>(expected_type))
395  {
396  if (expected_type == CommandType::JOINT_JOG)
397  {
398  delta_result = jointDeltaFromJointJog(std::get<JointJogCommand>(command), robot_state, servo_params_,
399  joint_name_group_index_map);
400  servo_status_ = delta_result.first;
401  }
402  else if (expected_type == CommandType::TWIST)
403  {
404  // Transform the twist command to the planning frame, which is the base frame of the active subgroup's IK solver,
405  // before applying it. Additionally verify there is an IK solver, and that the transformation is successful.
406  const auto planning_frame_maybe = getIKSolverBaseFrame(robot_state, active_subgroup_name);
407  if (planning_frame_maybe.has_value())
408  {
409  const auto& planning_frame = *planning_frame_maybe;
410  const auto command_in_planning_frame_maybe = toPlanningFrame(std::get<TwistCommand>(command), planning_frame);
411  if (command_in_planning_frame_maybe.has_value())
412  {
413  delta_result = jointDeltaFromTwist(*command_in_planning_frame_maybe, robot_state, servo_params_,
414  planning_frame, joint_name_group_index_map);
415  servo_status_ = delta_result.first;
416  }
417  else
418  {
419  servo_status_ = StatusCode::INVALID;
420  RCLCPP_ERROR_STREAM(logger_, "Could not transform twist command to planning frame.");
421  }
422  }
423  else
424  {
425  servo_status_ = StatusCode::INVALID;
426  RCLCPP_ERROR(logger_, "No IK solver for planning group %s.", active_subgroup_name.c_str());
427  }
428  }
429  else if (expected_type == CommandType::POSE)
430  {
431  // Transform the pose command to the planning frame, which is the base frame of the active subgroup's IK solver,
432  // before applying it. The end effector frame is also extracted as the tip frame of the IK solver.
433  // Additionally verify there is an IK solver, and that the transformation is successful.
434  const auto planning_frame_maybe = getIKSolverBaseFrame(robot_state, active_subgroup_name);
435  const auto ee_frame_maybe = getIKSolverTipFrame(robot_state, active_subgroup_name);
436  if (planning_frame_maybe.has_value() && ee_frame_maybe.has_value())
437  {
438  const auto& planning_frame = *planning_frame_maybe;
439  const auto command_in_planning_frame_maybe = toPlanningFrame(std::get<PoseCommand>(command), planning_frame);
440  if (command_in_planning_frame_maybe.has_value())
441  {
442  delta_result = jointDeltaFromPose(*command_in_planning_frame_maybe, robot_state, servo_params_,
443  planning_frame, *ee_frame_maybe, joint_name_group_index_map);
444  servo_status_ = delta_result.first;
445  }
446  else
447  {
448  servo_status_ = StatusCode::INVALID;
449  RCLCPP_ERROR_STREAM(logger_, "Could not transform pose command to planning frame.");
450  }
451  }
452  else
453  {
454  servo_status_ = StatusCode::INVALID;
455  RCLCPP_ERROR(logger_, "No IK solver for planning group %s.", active_subgroup_name.c_str());
456  }
457  }
458 
459  if (servo_status_ != StatusCode::INVALID)
460  {
461  joint_position_deltas = delta_result.second;
462  }
463  }
464  else
465  {
466  servo_status_ = StatusCode::INVALID;
467  RCLCPP_WARN_STREAM(logger_, "Incoming servo command type does not match known command types.");
468  }
469 
470  return joint_position_deltas;
471 }
472 
473 KinematicState Servo::getNextJointState(const moveit::core::RobotStatePtr& robot_state, const ServoInput& command)
474 {
475  // Set status to clear
476  servo_status_ = StatusCode::NO_WARNING;
477 
478  // Update the parameters
479  updateParams();
480 
481  // Get the joint model group info.
482  const moveit::core::JointModelGroup* joint_model_group =
483  robot_state->getJointModelGroup(servo_params_.move_group_name);
484 
485  // Get necessary information about joints
486  const std::vector<std::string> joint_names = joint_model_group->getActiveJointModelNames();
487  const moveit::core::JointBoundsVector joint_bounds = joint_model_group->getActiveJointModelsBounds();
488  const int num_joints = joint_names.size();
489 
490  // Extract current state from robot state
491  KinematicState current_state = extractRobotState(robot_state, servo_params_.move_group_name);
492  KinematicState target_state(num_joints);
493  target_state.joint_names = joint_names;
494 
495  // Compute the change in joint position due to the incoming command
496  Eigen::VectorXd joint_position_delta = jointDeltaFromCommand(command, robot_state);
497 
498  if (collision_velocity_scale_ > 0 && collision_velocity_scale_ < 1)
499  {
500  servo_status_ = StatusCode::DECELERATE_FOR_COLLISION;
501  }
502  else if (collision_velocity_scale_ == 0)
503  {
504  servo_status_ = StatusCode::HALT_FOR_COLLISION;
505  }
506 
507  // Continue rest of the computations only if the command is valid
508  // The computations can be skipped also in case we are halting.
509  if (servo_status_ != StatusCode::INVALID && servo_status_ != StatusCode::HALT_FOR_COLLISION)
510  {
511  // Compute the next joint positions based on the joint position deltas
512  target_state.positions = current_state.positions + joint_position_delta;
513 
514  // Compute the joint velocities required to reach positions
515  target_state.velocities = joint_position_delta / servo_params_.publish_period;
516 
517  // Scale down the velocity based on joint velocity limit or user defined scaling if applicable.
518  const double joint_velocity_limit_scale = jointLimitVelocityScalingFactor(
519  target_state.velocities, joint_bounds, servo_params_.override_velocity_scaling_factor);
520  if (joint_velocity_limit_scale < 1.0) // 1.0 means no scaling.
521  {
522  RCLCPP_DEBUG_STREAM(logger_, "Joint velocity limit scaling applied by a factor of " << joint_velocity_limit_scale);
523  }
524  target_state.velocities *= joint_velocity_limit_scale;
525 
526  // Adjust joint position based on scaled down velocity
527  target_state.positions = current_state.positions + (target_state.velocities * servo_params_.publish_period);
528 
529  // Apply smoothing to the positions if a smoother was provided.
530  doSmoothing(target_state);
531 
532  // Apply collision scaling to the joint position delta
533  target_state.positions =
534  current_state.positions + collision_velocity_scale_ * (target_state.positions - current_state.positions);
535 
536  // Compute velocities based on smoothed joint positions
537  target_state.velocities = (target_state.positions - current_state.positions) / servo_params_.publish_period;
538 
539  // Check if any joints are going past joint position limits
540  const std::vector<int> joints_to_halt =
541  jointsToHalt(target_state.positions, target_state.velocities, joint_bounds, servo_params_.joint_limit_margins);
542 
543  // Apply halting if any joints need to be halted.
544  if (!joints_to_halt.empty())
545  {
546  servo_status_ = StatusCode::JOINT_BOUND;
547  target_state = haltJoints(joints_to_halt, current_state, target_state);
548  }
549  }
550 
551  // Update internal state of filter with final calculated command.
552  resetSmoothing(target_state);
553 
554  return target_state;
555 }
556 
557 std::optional<Eigen::Isometry3d> Servo::getPlanningToCommandFrameTransform(const std::string& command_frame,
558  const std::string& planning_frame) const
559 {
560  const moveit::core::RobotStatePtr robot_state = planning_scene_monitor_->getStateMonitor()->getCurrentState();
561  if (robot_state->knowsFrameTransform(command_frame) && (robot_state->knowsFrameTransform(planning_frame)))
562  {
563  return robot_state->getGlobalLinkTransform(planning_frame).inverse() *
564  robot_state->getGlobalLinkTransform(command_frame);
565  }
566  else
567  {
568  try
569  {
570  return tf2::transformToEigen(
571  planning_scene_monitor_->getTFClient()->lookupTransform(planning_frame, command_frame, rclcpp::Time(0)));
572  }
573  catch (tf2::TransformException& ex)
574  {
575  RCLCPP_ERROR(logger_, "Failed to get planning to command frame transform: %s", ex.what());
576  return std::nullopt;
577  }
578  }
579 }
580 
581 std::optional<TwistCommand> Servo::toPlanningFrame(const TwistCommand& command, const std::string& planning_frame) const
582 {
583  Eigen::VectorXd transformed_twist = command.velocities;
584 
585  if (command.frame_id != planning_frame)
586  {
587  // Look up the transform between the planning and command frames.
588  const auto planning_to_command_tf_maybe = getPlanningToCommandFrameTransform(command.frame_id, planning_frame);
589  if (!planning_to_command_tf_maybe.has_value())
590  {
591  return std::nullopt;
592  }
593  const auto& planning_to_command_tf = *planning_to_command_tf_maybe;
594 
595  if (servo_params_.apply_twist_commands_about_ee_frame)
596  {
597  // If the twist command is applied about the end effector frame, simply apply the rotation of the transform.
598  const auto planning_to_command_rotation = planning_to_command_tf.linear();
599  const Eigen::Vector3d translation_vector =
600  planning_to_command_rotation *
601  Eigen::Vector3d(command.velocities[0], command.velocities[1], command.velocities[2]);
602  const Eigen::Vector3d angular_vector =
603  planning_to_command_rotation *
604  Eigen::Vector3d(command.velocities[3], command.velocities[4], command.velocities[5]);
605 
606  // Update the values of the original command message to reflect the change in frame
607  transformed_twist.head<3>() = translation_vector;
608  transformed_twist.tail<3>() = angular_vector;
609  }
610  else
611  {
612  // If the twist command is applied about the planning frame, the spatial twist is calculated
613  // as shown in Equation 3.83 in http://hades.mech.northwestern.edu/images/7/7f/MR.pdf.
614  // The above equation defines twist as [angular; linear], but in our convention it is
615  // [linear; angular] so the adjoint matrix is also reordered accordingly.
616  Eigen::MatrixXd adjoint(6, 6);
617 
618  const Eigen::Matrix3d& rotation = planning_to_command_tf.rotation();
619  const Eigen::Vector3d& translation = planning_to_command_tf.translation();
620 
621  Eigen::Matrix3d skew_translation;
622  skew_translation.row(0) << 0, -translation(2), translation(1);
623  skew_translation.row(1) << translation(2), 0, -translation(0);
624  skew_translation.row(2) << -translation(1), translation(0), 0;
625 
626  adjoint.topLeftCorner(3, 3) = skew_translation * rotation;
627  adjoint.topRightCorner(3, 3) = rotation;
628  adjoint.bottomLeftCorner(3, 3) = rotation;
629  adjoint.bottomRightCorner(3, 3).setZero();
630 
631  transformed_twist = adjoint * transformed_twist;
632  }
633  }
634 
635  return TwistCommand{ planning_frame, transformed_twist };
636 }
637 
638 std::optional<PoseCommand> Servo::toPlanningFrame(const PoseCommand& command, const std::string& planning_frame) const
639 {
640  const auto planning_to_command_tf_maybe = getPlanningToCommandFrameTransform(command.frame_id, planning_frame);
641  if (!planning_to_command_tf_maybe)
642  {
643  return std::nullopt;
644  }
645 
646  const auto& planning_to_command_tf = *planning_to_command_tf_maybe;
647  return PoseCommand{ planning_frame, planning_to_command_tf * command.pose };
648 }
649 
651 {
652  moveit::core::RobotStatePtr robot_state = planning_scene_monitor_->getStateMonitor()->getCurrentState();
653  return extractRobotState(robot_state, servo_params_.move_group_name);
654 }
655 
656 std::pair<bool, KinematicState> Servo::smoothHalt(const KinematicState& halt_state)
657 {
658  auto target_state = halt_state;
659 
660  // If all velocities are near zero, robot has decelerated to a stop.
661  bool stopped = (target_state.velocities.cwiseAbs().array() < STOPPED_VELOCITY_EPS).all();
662 
663  if (!stopped)
664  {
665  // set target velocity
666  target_state.velocities *= 0.0;
667 
668  // apply smoothing: this will change target position/velocity to make slow down gradual
669  doSmoothing(target_state);
670 
671  // scale velocity in case of obstacle
672  target_state.velocities *= collision_velocity_scale_;
673 
674  for (long i = 0; i < halt_state.positions.size(); ++i)
675  {
676  target_state.positions[i] = halt_state.positions[i] + target_state.velocities[i] * servo_params_.publish_period;
677  const double vel = target_state.velocities[i];
678  target_state.velocities[i] = (std::abs(vel) > STOPPED_VELOCITY_EPS) ? vel : 0.0;
679  target_state.accelerations[i] =
680  (target_state.velocities[i] - halt_state.velocities[i]) / servo_params_.publish_period;
681  }
682  }
683 
684  resetSmoothing(target_state);
685  return std::make_pair(stopped, target_state);
686 }
687 
688 } // namespace moveit_servo
const JointBoundsVector & getActiveJointModelsBounds() const
Get the bounds for all the active joints.
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...
void setCommandType(const CommandType &command_type)
Set the type of incoming servo command.
Definition: servo.cpp:335
CommandType getCommandType() const
Get the type of command that servo is currently expecting.
Definition: servo.cpp:330
std::string getStatusMessage() const
Get the message associated with the current servo status.
Definition: servo.cpp:325
std::pair< bool, KinematicState > smoothHalt(const KinematicState &halt_state)
Smoothly halt at a commanded state when command goes stale.
Definition: servo.cpp:656
KinematicState getCurrentRobotState() const
Get the current state of the robot as given by planning scene monitor.
Definition: servo.cpp:650
StatusCode getStatus() const
Get the current status of servo.
Definition: servo.cpp:320
servo::Params & getParams()
Returns the most recent servo parameters.
Definition: servo.cpp:315
void resetSmoothing(const KinematicState &state)
Resets the smoothing plugin, if set, to a specified state.
Definition: servo.cpp:172
Servo(const rclcpp::Node::SharedPtr &node, std::shared_ptr< const servo::ParamListener > servo_param_listener, const planning_scene_monitor::PlanningSceneMonitorPtr &planning_scene_monitor)
Definition: servo.cpp:58
void setCollisionChecking(const bool check_collision)
Start/Stop collision checking thread.
Definition: servo.cpp:180
KinematicState getNextJointState(const moveit::core::RobotStatePtr &robot_state, const ServoInput &command)
Computes the joint state required to follow the given command.
Definition: servo.cpp:473
void doSmoothing(KinematicState &state)
Applies smoothing to an input state, if a smoothing plugin is set.
Definition: servo.cpp:164
Vec3fX< details::Vec3Data< double > > Vector3d
Definition: fcl_compat.h:89
std::vector< const JointModel::Bounds * > JointBoundsVector
JointDeltaResult jointDeltaFromTwist(const TwistCommand &command, const moveit::core::RobotStatePtr &robot_state, const servo::Params &servo_params, const std::string &planning_frame, const JointNameToMoveGroupIndexMap &joint_name_group_index_map)
Compute the change in joint position for the given twist command.
Definition: command.cpp:147
std::optional< std::string > getIKSolverTipFrame(const moveit::core::RobotStatePtr &robot_state, const std::string &group_name)
Get the tip (end-effector) frame of the current active joint group or subgroup's IK solver.
Definition: common.cpp:72
JointDeltaResult jointDeltaFromJointJog(const JointJogCommand &command, const moveit::core::RobotStatePtr &robot_state, const servo::Params &servo_params, const JointNameToMoveGroupIndexMap &joint_name_group_index_map)
Compute the change in joint position for the given joint jog command.
Definition: command.cpp:82
std::optional< std::string > getIKSolverBaseFrame(const moveit::core::RobotStatePtr &robot_state, const std::string &group_name)
Get the base frame of the current active joint group or subgroup's IK solver.
Definition: common.cpp:57
std::vector< int > jointsToHalt(const Eigen::VectorXd &positions, const Eigen::VectorXd &velocities, const moveit::core::JointBoundsVector &joint_bounds, const std::vector< double > &margins)
Finds the joints that are exceeding allowable position limits.
Definition: common.cpp:411
std::variant< JointJogCommand, TwistCommand, PoseCommand > ServoInput
Definition: datatypes.hpp:111
double jointLimitVelocityScalingFactor(const Eigen::VectorXd &velocities, const moveit::core::JointBoundsVector &joint_bounds, double scaling_override)
Apply velocity scaling based on joint limits. If the robot model does not have velocity limits define...
Definition: common.cpp:380
std::pair< StatusCode, Eigen::VectorXd > JointDeltaResult
Definition: datatypes.hpp:85
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_LEAVING_SINGULARITY, "Moving away from a singularity, decelerating" }, { 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" } })
constexpr int MIN_POINTS_FOR_TRAJ_MSG
Definition: common.hpp:58
JointDeltaResult jointDeltaFromPose(const PoseCommand &command, const moveit::core::RobotStatePtr &robot_state, const servo::Params &servo_params, const std::string &planning_frame, const std::string &ee_frame, const JointNameToMoveGroupIndexMap &joint_name_group_index_map)
Compute the change in joint position for the given pose command.
Definition: command.cpp:211
std::unordered_map< std::string, std::size_t > JointNameToMoveGroupIndexMap
Definition: datatypes.hpp:134
KinematicState extractRobotState(const moveit::core::RobotStatePtr &robot_state, const std::string &move_group_name)
Extract the state from a RobotStatePtr instance.
Definition: common.cpp:494
Main namespace for MoveIt.
Definition: exceptions.h:43
double distance(const urdf::Pose &transform)
Definition: pr2_arm_ik.h:55
std::vector< std::string > joint_names
Definition: datatypes.hpp:116
Eigen::VectorXd accelerations
Definition: datatypes.hpp:117
Eigen::VectorXd velocities
Definition: datatypes.hpp:117