teleop_joint_pd_example_controller.cpp
Go to the documentation of this file.
1 // Copyright (c) 2020 Franka Emika GmbH
2 // Use of this source code is governed by the Apache-2.0 license, see LICENSE
5 
9 #include <ros/ros.h>
10 #include <urdf/model.h>
11 
12 #include <Eigen/Dense>
13 #include <algorithm>
14 #include <cmath>
15 #include <functional>
16 #include <string>
17 #include <vector>
18 
19 using Vector7d = Eigen::Matrix<double, 7, 1>;
20 
21 const std::string kControllerName = "TeleopJointPDExampleController";
22 
24 
26  ros::NodeHandle& node_handle) {
27  std::string leader_arm_id;
28  std::string follower_arm_id;
29 
30  std::vector<std::string> leader_joint_names;
31  std::vector<std::string> follower_joint_names;
32 
33  try {
34  k_d_leader_lower_ = get7dParam("leader/d_gains_lower", node_handle);
35  k_d_leader_upper_ = get7dParam("leader/d_gains_upper", node_handle);
36  dq_max_leader_lower_ = get7dParam("leader/dq_max_lower", node_handle);
37  dq_max_leader_upper_ = get7dParam("leader/dq_max_upper", node_handle);
38 
39  k_p_follower_ = get7dParam("follower/p_gains", node_handle);
40  k_d_follower_ = get7dParam("follower/d_gains", node_handle);
41  k_dq_ = get7dParam("follower/drift_comp_gains", node_handle);
42  dq_max_lower_ = get7dParam("follower/dq_max_lower", node_handle);
43  dq_max_upper_ = get7dParam("follower/dq_max_upper", node_handle);
44  ddq_max_lower_ = get7dParam("follower/ddq_max_lower", node_handle);
45  ddq_max_upper_ = get7dParam("follower/ddq_max_upper", node_handle);
46 
48  get1dParam<double>("leader/contact_force_threshold", node_handle);
50  get1dParam<double>("follower/contact_force_threshold", node_handle);
51 
52  leader_arm_id = get1dParam<std::string>("leader/arm_id", node_handle);
53  follower_arm_id = get1dParam<std::string>("follower/arm_id", node_handle);
54 
55  leader_joint_names = getJointParams<std::string>("leader/joint_names", node_handle);
56  follower_joint_names = getJointParams<std::string>("follower/joint_names", node_handle);
57 
58  if (!node_handle.getParam("debug", debug_)) {
59  ROS_INFO_STREAM_NAMED(kControllerName, "Could not find parameter debug. Defaulting to "
60  << std::boolalpha << debug_);
61  }
62 
63  // Init for each arm
64  initArm(robot_hw, node_handle, leader_data_, leader_arm_id, leader_joint_names);
65  initArm(robot_hw, node_handle, follower_data_, follower_arm_id, follower_joint_names);
66  } catch (const std::invalid_argument& ex) {
67  ROS_ERROR_NAMED(kControllerName, "%s", ex.what());
68  return false;
69  }
70 
71  if (debug_) {
72  // Init for dynamic reconfigure
73  dynamic_reconfigure_teleop_param_node_ = ros::NodeHandle("dyn_reconf_teleop_param_node");
74  dynamic_server_teleop_param_ = std::make_unique<
75  dynamic_reconfigure::Server<franka_example_controllers::teleop_paramConfig>>(
77  dynamic_server_teleop_param_->setCallback(
78  boost::bind(&TeleopJointPDExampleController::teleopParamCallback, this, _1, _2));
79 
80  // Init for publishers
81  auto init_publisher = [&node_handle](auto& publisher, const auto& topic) {
82  publisher.init(node_handle, topic, 1);
83  publisher.lock();
84  publisher.msg_.name.resize(7);
85  publisher.msg_.position.resize(7);
86  publisher.msg_.velocity.resize(7);
87  publisher.msg_.effort.resize(7);
88  publisher.unlock();
89  };
90 
91  init_publisher(leader_target_pub_, "leader_target");
92  init_publisher(follower_target_pub_, "follower_target");
93  leader_contact_pub_.init(node_handle, "leader_contact", 1);
94  follower_contact_pub_.init(node_handle, "follower_contact", 1);
95  marker_pub_.init(node_handle, "marker_labels", 1, true);
96 
97  auto get_marker = [](const std::string& arm_id, int32_t id, const std::string& text) {
98  visualization_msgs::Marker marker;
99  marker.header.frame_id = arm_id + "_link0";
100  marker.header.stamp = ros::Time::now();
101  marker.ns = "basic_shapes";
102  marker.id = id;
103  marker.type = visualization_msgs::Marker::TEXT_VIEW_FACING;
104  marker.action = visualization_msgs::Marker::ADD;
105 
106  marker.pose.position.x = 0.0;
107  marker.pose.position.y = 0.0;
108  marker.pose.position.z = 1.0;
109  marker.pose.orientation.x = 0.0;
110  marker.pose.orientation.y = 0.0;
111  marker.pose.orientation.z = 0.0;
112  marker.pose.orientation.w = 1.0;
113 
114  marker.text = text;
115 
116  marker.scale.x = 0.3;
117  marker.scale.y = 0.3;
118  marker.scale.z = 0.1;
119 
120  marker.color.r = 0.0f;
121  marker.color.g = 1.0f;
122  marker.color.b = 0.0f;
123  marker.color.a = 1.0;
124  return marker;
125  };
126 
127  {
128  std::lock_guard<realtime_tools::RealtimePublisher<visualization_msgs::MarkerArray>> lock(
129  marker_pub_);
130  marker_pub_.msg_.markers.push_back(get_marker(leader_arm_id, 1, "leader"));
131  marker_pub_.msg_.markers.push_back(get_marker(follower_arm_id, 2, "follower"));
132  }
133  publishMarkers();
134  }
135 
136  return true;
137 }
138 
140  ros::NodeHandle& node_handle,
141  FrankaDataContainer& arm_data,
142  const std::string& arm_id,
143  const std::vector<std::string>& joint_names) {
144  auto* effort_joint_interface = robot_hw->get<hardware_interface::EffortJointInterface>();
145  if (not effort_joint_interface) {
146  throw std::invalid_argument(kControllerName +
147  ": Error getting effort joint interface from hardware of " +
148  arm_id + ".");
149  }
150 
151  arm_data.joint_handles.clear();
152  for (const auto& name : joint_names) {
153  try {
154  arm_data.joint_handles.push_back(effort_joint_interface->getHandle(name));
156  throw std::invalid_argument(kControllerName +
157  ": Exception getting joint handle: " + std::string(e.what()));
158  }
159  }
160 
161  // Get state interface.
162  auto* state_interface = robot_hw->get<franka_hw::FrankaStateInterface>();
163  if (not state_interface) {
164  throw std::invalid_argument(kControllerName + ": Error getting state interface from hardware.");
165  }
166 
167  try {
168  arm_data.state_handle = std::make_unique<franka_hw::FrankaStateHandle>(
169  state_interface->getHandle(arm_id + "_robot"));
171  throw std::invalid_argument(
173  ": Exception getting state handle from interface: " + std::string(ex.what()));
174  }
175 
176  // Setup joint walls
177  // Virtual joint position wall parameters
178  const std::array<double, 7> kPDZoneWidth = {{0.12, 0.09, 0.09, 0.09, 0.0349, 0.0349, 0.0349}};
179  const std::array<double, 7> kDZoneWidth = {{0.12, 0.09, 0.09, 0.09, 0.0349, 0.0349, 0.0349}};
180  const std::array<double, 7> kPDZoneStiffness = {
181  {2000.0, 2000.0, 1000.0, 1000.0, 500.0, 200.0, 200.0}};
182  const std::array<double, 7> kPDZoneDamping = {{30.0, 30.0, 30.0, 10.0, 5.0, 5.0, 5.0}};
183  const std::array<double, 7> kDZoneDamping = {{30.0, 30.0, 30.0, 10.0, 5.0, 5.0, 5.0}};
184 
185  std::array<double, 7> upper_joint_soft_limit;
186  std::array<double, 7> lower_joint_soft_limit;
187  getJointLimits(node_handle, joint_names, upper_joint_soft_limit, lower_joint_soft_limit);
188 
189  arm_data.virtual_joint_wall = std::make_unique<JointWallContainer<7>>(
190  upper_joint_soft_limit, lower_joint_soft_limit, kPDZoneWidth, kDZoneWidth, kPDZoneStiffness,
191  kPDZoneDamping, kDZoneDamping);
192 }
193 
195  // Reset joint walls to start from the current q, dq
198 
199  // Reset stored states to the current states.
200  franka::RobotState follower_robot_state = follower_data_.state_handle->getRobotState();
201  q_target_last_ = Eigen::Map<Vector7d>(follower_robot_state.q.data());
202  dq_target_last_.setZero();
203  leader_data_.tau_target_last.setZero();
205 
206  // Store alignment position from leader
207  franka::RobotState leader_robot_state = leader_data_.state_handle->getRobotState();
208  init_leader_q_ = Eigen::Map<Vector7d>(leader_robot_state.q.data());
210  if (debug_) {
211  publishMarkers();
212  }
213 }
214 
216  const ros::Duration& period) {
217  franka::RobotState leader_robot_state = leader_data_.state_handle->getRobotState();
218  franka::RobotState follower_robot_state = follower_data_.state_handle->getRobotState();
219  leader_data_.q = Eigen::Map<Vector7d>(leader_robot_state.q.data());
220  leader_data_.dq = Eigen::Map<Vector7d>(leader_robot_state.dq.data());
221  follower_data_.q = Eigen::Map<Vector7d>(follower_robot_state.q.data());
222  follower_data_.dq = Eigen::Map<Vector7d>(follower_robot_state.dq.data());
223 
225  // Check coefficient-wise if the two robots are aligned
226  const auto kNorm = (leader_data_.q - follower_data_.q).cwiseAbs().array();
227  if ((kNorm < kAlignmentTolerance_).all()) {
229  ROS_INFO_STREAM_NAMED(kControllerName, "Leader and follower are aligned");
230  }
231  }
232 
233  // Determine contact scaling factor depending on the external cartesian forces applied on the
234  // endeffector.
235  Vector6d leader_f_ext_hat = Eigen::Map<Vector6d>(leader_robot_state.K_F_ext_hat_K.data());
236  leader_data_.f_ext_norm = leader_f_ext_hat.head(3).norm();
240 
241  Vector6d follower_f_ext_hat = Eigen::Map<Vector6d>(follower_robot_state.K_F_ext_hat_K.data());
242  follower_data_.f_ext_norm = follower_f_ext_hat.head(3).norm();
246 
247  // Determine max velocities and accelerations of follower arm depending on tracking errors to
248  // avoid jumps and high velocities when starting example.
249  Vector7d q_deviation = (q_target_last_ - leader_data_.q).cwiseAbs();
250  Vector7d dq_max;
251  Vector7d ddq_max;
252 
254  dq_max = dq_max_align_;
255  ddq_max = ddq_max_align_;
258  Vector7d dalignment_error = (alignment_error_ - prev_alignment_error_) / period.toSec();
259 
261  k_d_follower_align_.asDiagonal() * dalignment_error;
262  } else {
263  for (size_t i = 0; i < 7; ++i) {
264  dq_max[i] = rampParameter(q_deviation[i], dq_max_lower_[i], dq_max_upper_[i],
266  ddq_max[i] = rampParameter(q_deviation[i], ddq_max_lower_[i], ddq_max_upper_[i],
268  }
270  }
271 
272  // Calculate target postions and velocities for follower arm
273  dq_target_ = saturateAndLimit(dq_unsaturated_, dq_target_last_, dq_max, ddq_max, period.toSec());
275  q_target_ = q_target_last_ + (dq_target_ * period.toSec());
277 
278  if (!leader_robot_state.current_errors && !follower_robot_state.current_errors) {
280  // Compute P control for the leader arm to stay in position.
282  } else {
283  // Compute force-feedback for the leader arm to render the haptic interaction of the follower
284  // robot. Add a slight damping to reduce vibrations.
285  // The force feedback is applied when the external forces on the follower arm exceed a
286  // threshold. While the leader arm is unguided (not in contact), the force-feedback is
287  // reduced. When the leader robot exceeds the soft limit velocities dq_max_leader_lower
288  // damping is increased gradually until it saturates when reaching dq_max_leader_upper to
289  // maximum damping.
290 
291  Vector7d follower_tau_ext_hat =
292  Eigen::Map<Vector7d>(follower_robot_state.tau_ext_hat_filtered.data());
293  Vector7d leader_damping_torque =
295  Vector7d leader_force_feedback =
299  (-follower_tau_ext_hat);
300 
301  leader_data_.tau_target = leader_force_feedback - leader_damping_torque;
302  }
303 
304  // Compute PD control for the follower arm to track the leader's motions.
309  } else {
310  // Control target torques to zero if any arm is in error state.
313  }
314 
315  // Add torques from joint walls to the torque commands.
316  auto to_eigen = [](const std::array<double, 7>& data) { return Vector7d(data.data()); };
317  auto from_eigen = [](const Vector7d& data) {
318  return std::array<double, 7>{data(0), data(1), data(2), data(3), data(4), data(5), data(6)};
319  };
320  std::array<double, 7> virtual_wall_tau_leader = leader_data_.virtual_joint_wall->computeTorque(
321  from_eigen(leader_data_.q), from_eigen(leader_data_.dq));
322 
323  std::array<double, 7> virtual_wall_tau_follower =
324  follower_data_.virtual_joint_wall->computeTorque(from_eigen(follower_data_.q),
325  from_eigen(follower_data_.dq));
326 
327  leader_data_.tau_target += to_eigen(virtual_wall_tau_leader);
328  follower_data_.tau_target += to_eigen(virtual_wall_tau_follower);
329 
330  // Store torques for next time step
333 
336 
337  if (debug_ && publish_rate_()) {
342  }
343 }
344 
346  for (size_t i = 0; i < 7; ++i) {
347  arm_data.joint_handles[i].setCommand(arm_data.tau_target[i]);
348  }
349 }
350 
351 Eigen::Matrix<double, 7, 1> TeleopJointPDExampleController::saturateAndLimit(const Vector7d& x_calc,
352  const Vector7d& x_last,
353  const Vector7d& x_max,
354  const Vector7d& dx_max,
355  const double delta_t) {
356  Vector7d x_limited;
357  for (size_t i = 0; i < 7; i++) {
358  double delta_x_max = dx_max[i] * delta_t;
359  double diff = x_calc[i] - x_last[i];
360  double x_saturated = x_last[i] + std::max(std::min(diff, delta_x_max), -delta_x_max);
361  x_limited[i] = std::max(std::min(x_saturated, x_max[i]), -x_max[i]);
362  }
363  return x_limited;
364 }
365 
367  const double neg_x_asymptote,
368  const double pos_x_asymptote,
369  const double shift_along_x,
370  const double increase_factor) {
371  double ramp =
372  0.5 * (pos_x_asymptote + neg_x_asymptote -
373  (pos_x_asymptote - neg_x_asymptote) * tanh(increase_factor * (x - shift_along_x)));
374  return ramp;
375 }
376 
378  franka_example_controllers::teleop_paramConfig& config,
379  uint32_t /*level*/) {
380  if (dynamic_reconfigure_mutex_.try_lock()) {
381  leader_damping_scaling_ = config.leader_damping_scaling;
382  follower_stiffness_scaling_ = config.follower_stiffness_scaling;
383  force_feedback_guiding_ = config.force_feedback_guiding;
384  force_feedback_idle_ = config.force_feedback_idle;
385  follower_data_.contact_force_threshold = config.follower_contact_force_threshold;
386  leader_data_.contact_force_threshold = config.leader_contact_force_threshold;
387 
388  dq_max_lower_[0] = config.dq_l_1;
389  dq_max_lower_[1] = config.dq_l_2;
390  dq_max_lower_[2] = config.dq_l_3;
391  dq_max_lower_[3] = config.dq_l_4;
392  dq_max_lower_[4] = config.dq_l_5;
393  dq_max_lower_[5] = config.dq_l_6;
394  dq_max_lower_[6] = config.dq_l_7;
395 
396  dq_max_upper_[0] = config.dq_u_1;
397  dq_max_upper_[1] = config.dq_u_2;
398  dq_max_upper_[2] = config.dq_u_3;
399  dq_max_upper_[3] = config.dq_u_4;
400  dq_max_upper_[4] = config.dq_u_5;
401  dq_max_upper_[5] = config.dq_u_6;
402  dq_max_upper_[6] = config.dq_u_7;
403 
404  ddq_max_lower_[0] = config.ddq_l_1;
405  ddq_max_lower_[1] = config.ddq_l_2;
406  ddq_max_lower_[2] = config.ddq_l_3;
407  ddq_max_lower_[3] = config.ddq_l_4;
408  ddq_max_lower_[4] = config.ddq_l_5;
409  ddq_max_lower_[5] = config.ddq_l_6;
410  ddq_max_lower_[6] = config.ddq_l_7;
411 
412  ddq_max_upper_[0] = config.ddq_u_1;
413  ddq_max_upper_[1] = config.ddq_u_2;
414  ddq_max_upper_[2] = config.ddq_u_3;
415  ddq_max_upper_[3] = config.ddq_u_4;
416  ddq_max_upper_[4] = config.ddq_u_5;
417  ddq_max_upper_[5] = config.ddq_u_6;
418  ddq_max_upper_[6] = config.ddq_u_7;
419 
420  ROS_INFO_NAMED(kControllerName, "Dynamic reconfigure: Controller params set.");
421  }
423 }
424 
426  const std::vector<std::string>& joint_names,
427  std::array<double, 7>& upper_joint_soft_limit,
428  std::array<double, 7>& lower_joint_soft_limit) {
429  const std::string& node_namespace = nh.getNamespace();
430  std::size_t found = node_namespace.find_last_of('/');
431  std::string parent_namespace = node_namespace.substr(0, found);
432 
433  if (!nh.hasParam(parent_namespace + "/robot_description")) {
434  throw std::invalid_argument(kControllerName + ": No parameter robot_description (namespace: " +
435  parent_namespace + ")found to set joint limits!");
436  }
437 
438  urdf::Model urdf_model;
439  if (!urdf_model.initParamWithNodeHandle(parent_namespace + "/robot_description", nh)) {
440  throw std::invalid_argument(kControllerName +
441  ": Could not initialize urdf model from robot_description "
442  "(namespace: " +
443  parent_namespace + ").");
444  }
446  for (size_t i = 0; i < joint_names.size(); ++i) {
447  const std::string& joint_name = joint_names.at(i);
448  auto urdf_joint = urdf_model.getJoint(joint_name);
449  if (!urdf_joint) {
451  ": Could not get joint " << joint_name << " from urdf");
452  }
453  if (!urdf_joint->safety) {
454  ROS_ERROR_STREAM_NAMED(kControllerName, ": Joint " << joint_name << " has no limits");
455  }
456  if (joint_limits_interface::getSoftJointLimits(urdf_joint, soft_limits)) {
457  upper_joint_soft_limit[i] = soft_limits.max_position;
458  lower_joint_soft_limit[i] = soft_limits.min_position;
459  } else {
460  ROS_ERROR_STREAM_NAMED(kControllerName, ": Could not parse joint limit for joint "
461  << joint_name << " for joint limit interfaces");
462  }
463  }
464 }
465 
467  if (leader_target_pub_.trylock()) {
468  for (size_t i = 0; i < 7; ++i) {
469  leader_target_pub_.msg_.name[i] = "panda_joint" + std::to_string(i + 1);
470  leader_target_pub_.msg_.position[i] = 0.0;
471  leader_target_pub_.msg_.velocity[i] = 0.0;
473  }
475  }
476 }
477 
480  for (size_t i = 0; i < 7; ++i) {
481  follower_target_pub_.msg_.name[i] = "panda_joint" + std::to_string(i + 1);
482  follower_target_pub_.msg_.position[i] = q_target_[i];
483  follower_target_pub_.msg_.velocity[i] = dq_target_[i];
485  }
487  }
488 }
489 
494  }
495 }
496 
501  }
502 }
503 
505  ros::NodeHandle& nh) {
506  auto buffer = getJointParams<double>(param_name, nh);
507  return Vector7d(Eigen::Map<Vector7d>(buffer.data()));
508 }
509 
511  auto simple_ramp = [](const double min, const double max, const double value) -> double {
512  if (value >= max) {
513  return 1.0;
514  }
515  if (value <= min) {
516  return 0.0;
517  }
518  return (value - min) / (max - min);
519  };
520  Vector7d damping;
521  for (size_t i = 0; i < 7; ++i) {
522  damping(i) = k_d_leader_lower_(i) +
523  simple_ramp(dq_max_leader_lower_(i), dq_max_leader_upper_(i), std::abs(dq(i))) *
525  }
526  return damping;
527 }
528 
530  marker_pub_.lock();
532 }
533 
534 } // namespace franka_example_controllers
535 
realtime_tools::RealtimePublisher< std_msgs::Float64 > follower_contact_pub_
std::array< double, 7 > tau_ext_hat_filtered
#define ROS_INFO_NAMED(name,...)
std::string arm_id
#define ROS_ERROR_STREAM_NAMED(name, args)
realtime_tools::RealtimePublisher< sensor_msgs::JointState > leader_target_pub_
IMETHOD Vector diff(const Vector &p_w_a, const Vector &p_w_b, double dt=1)
std::unique_ptr< dynamic_reconfigure::Server< franka_example_controllers::teleop_paramConfig > > dynamic_server_teleop_param_
URDF_EXPORT bool initParamWithNodeHandle(const std::string &param, const ros::NodeHandle &nh=ros::NodeHandle())
#define ROS_INFO_STREAM_NAMED(name, args)
std::array< double, 6 > K_F_ext_hat_K
Vector7d get7dParam(const std::string &param_name, ros::NodeHandle &nh)
realtime_tools::RealtimePublisher< std_msgs::Float64 > leader_contact_pub_
realtime_tools::RealtimePublisher< visualization_msgs::MarkerArray > marker_pub_
std::array< double, 7 > q
void initArm(hardware_interface::RobotHW *robot_hw, ros::NodeHandle &node_handle, FrankaDataContainer &arm_data, const std::string &arm_id, const std::vector< std::string > &joint_names)
INLINE Rall1d< T, V, S > tanh(const Rall1d< T, V, S > &arg)
Controller class for ros_control that allows force-feedback teleoperation of a follower arm from a le...
const char * what() const noexcept override
double rampParameter(double x, double neg_x_asymptote, double pos_x_asymptote, double shift_along_x, double increase_factor)
bool getSoftJointLimits(urdf::JointConstSharedPtr urdf_joint, SoftJointLimits &soft_limits)
void update(const ros::Time &, const ros::Duration &period) override
Computes the control-law and commands the resulting joint torques to the robots.
bool getParam(const std::string &key, std::string &s) const
std::array< double, 7 > dq
double min(double a, double b)
Contains functions for calculating torques generated by virtual walls.
INLINE Rall1d< T, V, S > sqrt(const Rall1d< T, V, S > &arg)
bool hasParam(const std::string &key) const
void starting(const ros::Time &) override
Prepares the controller for the real-time execution.
bool init(hardware_interface::RobotHW *robot_hw, ros::NodeHandle &node_handle) override
Initializes the controller class to be ready to run.
void init(const ros::NodeHandle &node, const std::string &topic, int queue_size, bool latched=false)
const std::string & getNamespace() const
Vector7d saturateAndLimit(const Vector7d &x_calc, const Vector7d &x_last, const Vector7d &x_max, const Vector7d &dx_max, double delta_t)
#define ROS_ERROR_NAMED(name,...)
static Time now()
Eigen::Matrix< double, 7, 1 > Vector7d
double max(double a, double b)
#define PLUGINLIB_EXPORT_CLASS(class_type, base_class_type)
realtime_tools::RealtimePublisher< sensor_msgs::JointState > follower_target_pub_
void teleopParamCallback(franka_example_controllers::teleop_paramConfig &config, uint32_t level)
static void getJointLimits(ros::NodeHandle &nh, const std::vector< std::string > &joint_names, std::array< double, 7 > &upper_joint_soft_limit, std::array< double, 7 > &lower_joint_soft_limit)
const std::string kControllerName


franka_example_controllers
Author(s): Franka Emika GmbH
autogenerated on Mon Sep 19 2022 03:06:01