45constexpr double SCALING_OVERRIDE_THRESHOLD = 0.01;
48constexpr double MIN_ANGLE_THRESHOLD = 1E-16;
51constexpr double PLANNING_SCENE_PUBLISHING_FREQUENCY = 25.0;
58 const std::string& group_name)
60 const auto ik_solver = robot_state->getJointModelGroup(group_name)->getSolverInstance();
64 return ik_solver->getBaseFrame();
73 const std::string& group_name)
75 const auto ik_solver = robot_state->getJointModelGroup(group_name)->getSolverInstance();
79 return ik_solver->getTipFrame();
90 return command.allFinite();
95 Eigen::Matrix3d identity, rotation;
96 identity.setIdentity();
97 rotation = command.linear();
99 const bool is_valid_rotation = rotation.allFinite() && identity.isApprox(rotation.inverse() * rotation);
101 const bool is_valid_translation =
isValidCommand(command.translation());
102 return is_valid_rotation && is_valid_translation;
116 const Eigen::Isometry3d& base_to_tip_frame_transform)
119 Eigen::Isometry3d tf_pos_delta(Eigen::Isometry3d::Identity());
120 tf_pos_delta.translate(Eigen::Vector3d(delta_x[0], delta_x[1], delta_x[2]));
123 Eigen::Isometry3d tf_rot_delta(Eigen::Isometry3d::Identity());
124 const Eigen::Vector3d rot_vec = delta_x.block<3, 1>(3, 0, 3, 1);
125 double angle = rot_vec.norm();
126 if (angle > MIN_ANGLE_THRESHOLD)
128 const Eigen::Quaterniond q(Eigen::AngleAxisd(angle, rot_vec / angle).toRotationMatrix());
129 tf_rot_delta.rotate(q);
133 const Eigen::Isometry3d tf_no_new_rot = tf_pos_delta * base_to_tip_frame_transform;
141 Eigen::Isometry3d tf_neg_translation = Eigen::Isometry3d::Identity();
142 tf_neg_translation.translation() = -1 * tf_no_new_rot.translation();
143 Eigen::Isometry3d tf_pos_translation = Eigen::Isometry3d::Identity();
144 tf_pos_translation.translation() = tf_no_new_rot.translation();
147 return tf2::toMsg(tf_pos_translation * tf_rot_delta * tf_neg_translation * tf_no_new_rot);
150std::optional<trajectory_msgs::msg::JointTrajectory>
158 trajectory_msgs::msg::JointTrajectory joint_trajectory;
160 joint_trajectory.joint_names = joint_cmd_rolling_window.front().joint_names;
161 joint_trajectory.points.reserve(joint_cmd_rolling_window.size() + 1);
162 joint_trajectory.header.stamp = joint_cmd_rolling_window.front().time_stamp;
164 auto add_point = [servo_params](trajectory_msgs::msg::JointTrajectory& joint_trajectory,
const KinematicState& state) {
165 trajectory_msgs::msg::JointTrajectoryPoint point;
166 size_t num_joints = state.positions.size();
167 point.positions.reserve(num_joints);
168 point.velocities.reserve(num_joints);
169 point.accelerations.reserve(num_joints);
170 if (servo_params.publish_joint_positions)
172 for (
const auto& pos : state.positions)
174 point.positions.emplace_back(pos);
177 if (servo_params.publish_joint_velocities)
179 for (
const auto& vel : state.velocities)
181 point.velocities.emplace_back(vel);
184 if (servo_params.publish_joint_accelerations)
186 for (
const auto& acc : state.accelerations)
188 point.accelerations.emplace_back(acc);
191 point.time_from_start = state.time_stamp - joint_trajectory.header.stamp;
192 joint_trajectory.points.emplace_back(point);
195 for (
size_t i = 0; i < joint_cmd_rolling_window.size() - 1; ++i)
197 add_point(joint_trajectory, joint_cmd_rolling_window[i]);
200 return joint_trajectory;
204 double max_expected_latency,
const rclcpp::Time& cur_time)
207 next_joint_state.
time_stamp = cur_time + rclcpp::Duration::from_seconds(max_expected_latency);
208 const auto active_time_window = rclcpp::Duration::from_seconds(max_expected_latency);
209 while (!joint_cmd_rolling_window.empty() &&
210 joint_cmd_rolling_window.front().time_stamp < (cur_time - active_time_window))
212 joint_cmd_rolling_window.pop_front();
216 while (!joint_cmd_rolling_window.empty() && next_joint_state.
time_stamp == joint_cmd_rolling_window.back().time_stamp)
218 joint_cmd_rolling_window.pop_back();
224 if (joint_cmd_rolling_window.size() >= 2)
226 size_t num_points = joint_cmd_rolling_window.size();
227 auto& last_state = joint_cmd_rolling_window[num_points - 1];
228 auto& second_last_state = joint_cmd_rolling_window[num_points - 2];
230 Eigen::VectorXd direction_1 = second_last_state.positions - last_state.positions;
231 Eigen::VectorXd direction_2 = next_joint_state.
positions - last_state.positions;
232 Eigen::VectorXd signs = (direction_1.array().sign() - direction_2.array().sign()).round();
233 for (
long i = 0; i < last_state.velocities.size(); ++i)
240 last_state.velocities[i] = 0;
244 const double delta_time_1 = (next_joint_state.
time_stamp - last_state.time_stamp).seconds();
245 const double delta_time_2 = (last_state.time_stamp - second_last_state.time_stamp).seconds();
246 const auto velocity_1 = (next_joint_state.
positions[i] - last_state.positions[i]) / delta_time_1;
247 const auto velocity_2 = (last_state.positions[i] - second_last_state.positions[i]) / delta_time_2;
248 last_state.velocities[i] = (std::abs(velocity_1) < std::abs(velocity_2)) ? velocity_1 : velocity_2;
250 next_joint_state.
velocities[i] = last_state.velocities[i];
255 joint_cmd_rolling_window.push_back(next_joint_state);
261 std_msgs::msg::Float64MultiArray multi_array;
262 const size_t num_joints = joint_state.
joint_names.size();
263 multi_array.data.resize(num_joints);
264 if (servo_params.publish_joint_positions)
266 for (
size_t i = 0; i < num_joints; ++i)
268 multi_array.data[i] = joint_state.
positions[i];
271 else if (servo_params.publish_joint_velocities)
273 for (
size_t i = 0; i < num_joints; ++i)
275 multi_array.data[i] = joint_state.
velocities[i];
283 const Eigen::VectorXd& target_delta_x,
284 const servo::Params& servo_params)
290 robot_state->getJointModelGroup(servo_params.move_group_name);
293 const double lower_singularity_threshold = servo_params.lower_singularity_threshold;
294 const double hard_stop_singularity_threshold = servo_params.hard_stop_singularity_threshold;
295 const double leaving_singularity_threshold_multiplier = servo_params.leaving_singularity_threshold_multiplier;
298 const size_t dims = target_delta_x.size();
301 const Eigen::JacobiSVD<Eigen::MatrixXd> current_svd = Eigen::JacobiSVD<Eigen::MatrixXd>(
302 robot_state->getJacobian(joint_model_group), Eigen::ComputeThinU | Eigen::ComputeThinV);
303 const Eigen::MatrixXd matrix_s = current_svd.singularValues().asDiagonal();
306 const Eigen::MatrixXd pseudo_inverse = current_svd.matrixV() * matrix_s.inverse() * current_svd.matrixU().transpose();
312 Eigen::VectorXd vector_towards_singularity = current_svd.matrixU().col(dims - 1);
316 const double current_condition_number = current_svd.singularValues()(0) / current_svd.singularValues()(dims - 1);
319 const Eigen::VectorXd delta_x = vector_towards_singularity * servo_params.singularity_step_scale;
322 Eigen::VectorXd next_joint_angles;
323 robot_state->copyJointGroupPositions(joint_model_group, next_joint_angles);
324 next_joint_angles += pseudo_inverse * delta_x;
327 robot_state->setJointGroupPositions(joint_model_group, next_joint_angles);
328 const Eigen::JacobiSVD<Eigen::MatrixXd> next_svd = Eigen::JacobiSVD<Eigen::MatrixXd>(
329 robot_state->getJacobian(joint_model_group), Eigen::ComputeThinU | Eigen::ComputeThinV);
332 const double next_condition_number = next_svd.singularValues()(0) / next_svd.singularValues()(dims - 1);
337 if (next_condition_number <= current_condition_number)
339 vector_towards_singularity *= -1;
343 const bool moving_towards_singularity = vector_towards_singularity.dot(target_delta_x) > 0;
347 double upper_threshold;
348 if (moving_towards_singularity)
350 upper_threshold = hard_stop_singularity_threshold;
354 const double threshold_size = (hard_stop_singularity_threshold - lower_singularity_threshold);
355 upper_threshold = lower_singularity_threshold + (threshold_size * leaving_singularity_threshold_multiplier);
359 double velocity_scale = 1.0;
360 const bool is_above_lower_limit = current_condition_number > lower_singularity_threshold;
361 const bool is_below_hard_stop_limit = current_condition_number < hard_stop_singularity_threshold;
362 if (is_above_lower_limit && is_below_hard_stop_limit)
365 (current_condition_number - lower_singularity_threshold) / (upper_threshold - lower_singularity_threshold);
371 else if (!is_below_hard_stop_limit)
374 velocity_scale = 0.0;
377 return std::make_pair(velocity_scale, servo_status);
384 double min_scaling_factor = scaling_override;
385 if (scaling_override < SCALING_OVERRIDE_THRESHOLD)
387 min_scaling_factor = 1.0;
392 for (
const auto& joint_bound : joint_bounds)
394 for (
const auto& variable_bound : *joint_bound)
396 const auto& target_vel = velocities(idx);
397 if (variable_bound.velocity_bounded_ && target_vel != 0.0)
400 const auto bounded_vel = std::clamp(target_vel, variable_bound.min_velocity_, variable_bound.max_velocity_);
401 const auto joint_scaling_factor = bounded_vel / target_vel;
402 min_scaling_factor = std::min(min_scaling_factor, joint_scaling_factor);
408 return min_scaling_factor;
413 const std::vector<double>& margins)
415 std::vector<size_t> variable_indices_to_halt;
418 size_t variable_idx = 0;
419 for (
const auto& joint_bound : joint_bounds)
421 bool halt_joint =
false;
422 for (
const auto& variable_bound : *joint_bound)
425 if (variable_bound.position_bounded_)
427 const bool approaching_negative_bound =
428 velocities[variable_idx] < 0 &&
429 positions[variable_idx] < (variable_bound.min_position_ + margins[variable_idx]);
430 const bool approaching_positive_bound =
431 velocities[variable_idx] > 0 &&
432 positions[variable_idx] > (variable_bound.max_position_ - margins[variable_idx]);
433 if (approaching_negative_bound || approaching_positive_bound)
443 for (
size_t k = variable_idx - joint_bound->size(); k < variable_idx; ++k)
445 variable_indices_to_halt.push_back(k);
450 return variable_indices_to_halt;
455 const std::string& parent_frame,
456 const std::string& child_frame)
458 geometry_msgs::msg::TransformStamped output = tf2::eigenToTransform(eigen_tf);
459 output.header.frame_id = parent_frame;
460 output.child_frame_id = child_frame;
467 command.
frame_id = msg.header.frame_id;
469 const Eigen::Vector3d translation(msg.pose.position.x, msg.pose.position.y, msg.pose.position.z);
470 const Eigen::Quaterniond rotation(msg.pose.orientation.w, msg.pose.orientation.x, msg.pose.orientation.y,
471 msg.pose.orientation.z);
473 command.
pose = Eigen::Isometry3d::Identity();
474 command.
pose.translate(translation);
475 command.
pose.linear() = rotation.normalized().toRotationMatrix();
481 const servo::Params& servo_params)
484 std::string robot_description_name =
"robot_description";
485 node->get_parameter_or(
"robot_description_name", robot_description_name, robot_description_name);
489 node, robot_description_name,
"planning_scene_monitor");
496 servo_params.check_octomap_collisions);
498 if (servo_params.is_primary_planning_scene_monitor)
510 std::string(node->get_fully_qualified_name()) +
511 "/publish_planning_scene");
522 robot_state->copyJointGroupPositions(joint_model_group, current_state.
positions);
523 robot_state->copyJointGroupVelocities(joint_model_group, current_state.
velocities);
525 robot_state->copyJointGroupAccelerations(joint_model_group, current_state.
accelerations);
529 [](
double v) { return isnan(v); }))
531 robot_state->zeroAccelerations();
532 robot_state->copyJointGroupAccelerations(joint_model_group, current_state.
accelerations);
535 return current_state;
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...
static const std::string DEFAULT_COLLISION_OBJECT_TOPIC
The name of the topic used by default for receiving collision objects in the world.
static const std::string DEFAULT_PLANNING_SCENE_WORLD_TOPIC
@ UPDATE_SCENE
The entire scene was updated.
std::vector< const JointModel::Bounds * > JointBoundsVector
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.
std::optional< trajectory_msgs::msg::JointTrajectory > composeTrajectoryMessage(const servo::Params &servo_params, const std::deque< KinematicState > &joint_cmd_rolling_window)
Create a trajectory message from a rolling window queue of joint state commands. Method optionally re...
geometry_msgs::msg::Pose poseFromCartesianDelta(const Eigen::VectorXd &delta_x, const Eigen::Isometry3d &base_to_tip_frame_transform)
Create a pose message for the provided change in Cartesian position.
std::pair< double, StatusCode > velocityScalingFactorForSingularity(const moveit::core::RobotStatePtr &robot_state, const Eigen::VectorXd &target_delta_x, const servo::Params &servo_params)
Computes scaling factor for velocity when the robot is near a singularity.
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.
@ DECELERATE_FOR_APPROACHING_SINGULARITY
@ DECELERATE_FOR_LEAVING_SINGULARITY
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...
std_msgs::msg::Float64MultiArray composeMultiArrayMessage(const servo::Params &servo_params, const KinematicState &joint_state)
Create a Float64MultiArray message from given joint state.
bool isValidCommand(const Eigen::VectorXd &command)
Checks if a given VectorXd is a valid command.
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.
void updateSlidingWindow(KinematicState &next_joint_state, std::deque< KinematicState > &joint_cmd_rolling_window, double max_expected_latency, const rclcpp::Time &cur_time)
Adds a new joint state command to a queue containing commands over a time window. Also modifies the v...
constexpr int MIN_POINTS_FOR_TRAJ_MSG
std::vector< size_t > jointVariablesToHalt(const Eigen::VectorXd &positions, const Eigen::VectorXd &velocities, const moveit::core::JointBoundsVector &joint_bounds, const std::vector< double > &margins)
Finds the joint variable indices corresponding to joints exceeding allowable position limits.
KinematicState extractRobotState(const moveit::core::RobotStatePtr &robot_state, const std::string &move_group_name)
Extract the state from a RobotStatePtr instance.
PoseCommand poseFromPoseStamped(const geometry_msgs::msg::PoseStamped &msg)
Convert a PoseStamped message to a Servo Pose.
planning_scene_monitor::PlanningSceneMonitorPtr createPlanningSceneMonitor(const rclcpp::Node::SharedPtr &node, const servo::Params &servo_params)
Creates the planning scene monitor used by servo.
Eigen::VectorXd positions
std::vector< std::string > joint_names
Eigen::VectorXd accelerations
Eigen::VectorXd velocities
Eigen::Vector< double, 6 > velocities