dual_arm_cartesian_impedance_example_controller.cpp
Go to the documentation of this file.
1 // Copyright (c) 2019 Franka Emika GmbH
2 // Use of this source code is governed by the Apache-2.0 license, see LICENSE
4 
5 #include <cmath>
6 #include <functional>
7 #include <memory>
8 
11 #include <franka/robot_state.h>
13 #include <franka_hw/trigger_rate.h>
14 #include <geometry_msgs/PoseStamped.h>
16 #include <ros/ros.h>
17 #include <ros/transport_hints.h>
18 #include <tf/transform_listener.h>
20 
22 
25  const std::string& arm_id,
26  const std::vector<std::string>& joint_names) {
27  FrankaDataContainer arm_data;
28  auto* model_interface = robot_hw->get<franka_hw::FrankaModelInterface>();
29  if (model_interface == nullptr) {
31  "DualArmCartesianImpedanceExampleController: Error getting model interface from hardware");
32  return false;
33  }
34  try {
35  arm_data.model_handle_ = std::make_unique<franka_hw::FrankaModelHandle>(
36  model_interface->getHandle(arm_id + "_model"));
39  "DualArmCartesianImpedanceExampleController: Exception getting model handle from "
40  "interface: "
41  << ex.what());
42  return false;
43  }
44 
45  auto* state_interface = robot_hw->get<franka_hw::FrankaStateInterface>();
46  if (state_interface == nullptr) {
48  "DualArmCartesianImpedanceExampleController: Error getting state interface from hardware");
49  return false;
50  }
51  try {
52  arm_data.state_handle_ = std::make_unique<franka_hw::FrankaStateHandle>(
53  state_interface->getHandle(arm_id + "_robot"));
56  "DualArmCartesianImpedanceExampleController: Exception getting state handle from "
57  "interface: "
58  << ex.what());
59  return false;
60  }
61 
62  auto* effort_joint_interface = robot_hw->get<hardware_interface::EffortJointInterface>();
63  if (effort_joint_interface == nullptr) {
65  "DualArmCartesianImpedanceExampleController: Error getting effort joint interface from "
66  "hardware");
67  return false;
68  }
69  for (size_t i = 0; i < 7; ++i) {
70  try {
71  arm_data.joint_handles_.push_back(effort_joint_interface->getHandle(joint_names[i]));
74  "DualArmCartesianImpedanceExampleController: Exception getting joint handles: "
75  << ex.what());
76  return false;
77  }
78  }
79 
80  arm_data.position_d_.setZero();
81  arm_data.orientation_d_.coeffs() << 0.0, 0.0, 0.0, 1.0;
82  arm_data.position_d_target_.setZero();
83  arm_data.orientation_d_target_.coeffs() << 0.0, 0.0, 0.0, 1.0;
84 
85  arm_data.cartesian_stiffness_.setZero();
86  arm_data.cartesian_damping_.setZero();
87 
88  arms_data_.emplace(std::make_pair(arm_id, std::move(arm_data)));
89 
90  return true;
91 }
92 
94  ros::NodeHandle& node_handle) {
95  std::vector<double> cartesian_stiffness_vector;
96  std::vector<double> cartesian_damping_vector;
97 
98  if (!node_handle.getParam("left/arm_id", left_arm_id_)) {
100  "DualArmCartesianImpedanceExampleController: Could not read parameter left_arm_id_");
101  return false;
102  }
103  std::vector<std::string> left_joint_names;
104  if (!node_handle.getParam("left/joint_names", left_joint_names) || left_joint_names.size() != 7) {
105  ROS_ERROR(
106  "DualArmCartesianImpedanceExampleController: Invalid or no left_joint_names parameters "
107  "provided, "
108  "aborting controller init!");
109  return false;
110  }
111 
112  if (!node_handle.getParam("right/arm_id", right_arm_id_)) {
114  "DualArmCartesianImpedanceExampleController: Could not read parameter right_arm_id_");
115  return false;
116  }
117 
118  boost::function<void(const geometry_msgs::PoseStamped::ConstPtr&)> callback =
120 
121  ros::SubscribeOptions subscribe_options;
122  subscribe_options.init("centering_frame_target_pose", 1, callback);
123  subscribe_options.transport_hints = ros::TransportHints().reliable().tcpNoDelay();
124  sub_target_pose_left_ = node_handle.subscribe(subscribe_options);
125 
126  std::vector<std::string> right_joint_names;
127  if (!node_handle.getParam("right/joint_names", right_joint_names) ||
128  right_joint_names.size() != 7) {
129  ROS_ERROR(
130  "DualArmCartesianImpedanceExampleController: Invalid or no right_joint_names parameters "
131  "provided, "
132  "aborting controller init!");
133  return false;
134  }
135 
136  bool left_success = initArm(robot_hw, left_arm_id_, left_joint_names);
137  bool right_success = initArm(robot_hw, right_arm_id_, right_joint_names);
138 
140  ros::NodeHandle("dynamic_reconfigure_compliance_param_node");
141 
142  dynamic_server_compliance_param_ = std::make_unique<dynamic_reconfigure::Server<
143  franka_combined_example_controllers::dual_arm_compliance_paramConfig>>(
145 
146  dynamic_server_compliance_param_->setCallback(boost::bind(
148 
149  // Get the transformation from right_O_frame to left_O_frame
150  tf::StampedTransform transform;
152  try {
153  if (listener.waitForTransform(left_arm_id_ + "_link0", right_arm_id_ + "_link0", ros::Time(0),
154  ros::Duration(4.0))) {
155  listener.lookupTransform(left_arm_id_ + "_link0", right_arm_id_ + "_link0", ros::Time(0),
156  transform);
157  } else {
158  ROS_ERROR(
159  "DualArmCartesianImpedanceExampleController: Failed to read transform from %s to %s. "
160  "Aborting init!",
161  (right_arm_id_ + "_link0").c_str(), (left_arm_id_ + "_link0").c_str());
162  return false;
163  }
164  } catch (tf::TransformException& ex) {
165  ROS_ERROR("DualArmCartesianImpedanceExampleController: %s", ex.what());
166  return false;
167  }
168  tf::transformTFToEigen(transform, Ol_T_Or_); // NOLINT (readability-identifier-naming)
169 
170  // Setup publisher for the centering frame.
172  center_frame_pub_.init(node_handle, "centering_frame", 1, true);
173 
174  return left_success && right_success;
175 }
176 
178  // compute initial velocity with jacobian and set x_attractor and q_d_nullspace
179  // to initial configuration
180  franka::RobotState initial_state = arm_data.state_handle_->getRobotState();
181  // get jacobian
182  std::array<double, 42> jacobian_array =
183  arm_data.model_handle_->getZeroJacobian(franka::Frame::kEndEffector);
184  // convert to eigen
185  Eigen::Map<Eigen::Matrix<double, 6, 7>> jacobian(jacobian_array.data());
186  Eigen::Map<Eigen::Matrix<double, 7, 1>> dq_initial(initial_state.dq.data());
187  Eigen::Map<Eigen::Matrix<double, 7, 1>> q_initial(initial_state.q.data());
188  Eigen::Affine3d initial_transform(Eigen::Matrix4d::Map(initial_state.O_T_EE.data()));
189 
190  // set target point to current state
191  arm_data.position_d_ = initial_transform.translation();
192  arm_data.orientation_d_ = Eigen::Quaterniond(initial_transform.linear());
193  arm_data.position_d_target_ = initial_transform.translation();
194  arm_data.orientation_d_target_ = Eigen::Quaterniond(initial_transform.linear());
195 
196  // set nullspace target configuration to initial q
197  arm_data.q_d_nullspace_ = q_initial;
198 }
199 
201  for (auto& arm_data : arms_data_) {
202  startingArm(arm_data.second);
203  }
204  franka::RobotState robot_state_right =
205  arms_data_.at(right_arm_id_).state_handle_->getRobotState();
206  franka::RobotState robot_state_left = arms_data_.at(left_arm_id_).state_handle_->getRobotState();
207  Eigen::Affine3d Ol_T_EEl(Eigen::Matrix4d::Map( // NOLINT (readability-identifier-naming)
208  robot_state_left.O_T_EE.data())); // NOLINT (readability-identifier-naming)
209  Eigen::Affine3d Or_T_EEr(Eigen::Matrix4d::Map( // NOLINT (readability-identifier-naming)
210  robot_state_right.O_T_EE.data())); // NOLINT (readability-identifier-naming)
211  EEr_T_EEl_ =
212  Or_T_EEr.inverse() * Ol_T_Or_.inverse() * Ol_T_EEl; // NOLINT (readability-identifier-naming)
213  EEl_T_C_.setIdentity();
214  Eigen::Vector3d EEr_r_EEr_EEl = // NOLINT (readability-identifier-naming)
215  EEr_T_EEl_.translation(); // NOLINT (readability-identifier-naming)
216  EEl_T_C_.translation() = -0.5 * EEr_T_EEl_.inverse().rotation() * EEr_r_EEr_EEl;
217 }
218 
220  const ros::Duration& /*period*/) {
221  for (auto& arm_data : arms_data_) {
222  updateArm(arm_data.second);
223  }
224  if (publish_rate_()) {
226  }
227 }
228 
230  // get state variables
231  franka::RobotState robot_state = arm_data.state_handle_->getRobotState();
232  std::array<double, 49> inertia_array = arm_data.model_handle_->getMass();
233  std::array<double, 7> coriolis_array = arm_data.model_handle_->getCoriolis();
234  std::array<double, 42> jacobian_array =
235  arm_data.model_handle_->getZeroJacobian(franka::Frame::kEndEffector);
236 
237  // convert to Eigen
238  Eigen::Map<Eigen::Matrix<double, 7, 1>> coriolis(coriolis_array.data());
239  Eigen::Map<Eigen::Matrix<double, 6, 7>> jacobian(jacobian_array.data());
240  Eigen::Map<Eigen::Matrix<double, 7, 1>> q(robot_state.q.data());
241  Eigen::Map<Eigen::Matrix<double, 7, 1>> dq(robot_state.dq.data());
242  Eigen::Map<Eigen::Matrix<double, 7, 1>> tau_J_d( // NOLINT (readability-identifier-naming)
243  robot_state.tau_J_d.data());
244  Eigen::Affine3d transform(Eigen::Matrix4d::Map(robot_state.O_T_EE.data()));
245  Eigen::Vector3d position(transform.translation());
246  Eigen::Quaterniond orientation(transform.linear());
247 
248  // compute error to desired pose
249  // position error
250  Eigen::Matrix<double, 6, 1> error;
251  error.head(3) << position - arm_data.position_d_;
252 
253  // orientation error
254  if (arm_data.orientation_d_.coeffs().dot(orientation.coeffs()) < 0.0) {
255  orientation.coeffs() << -orientation.coeffs();
256  }
257  // "difference" quaternion
258  Eigen::Quaterniond error_quaternion(orientation * arm_data.orientation_d_.inverse());
259  // convert to axis angle
260  Eigen::AngleAxisd error_quaternion_angle_axis(error_quaternion);
261  // compute "orientation error"
262  error.tail(3) << error_quaternion_angle_axis.axis() * error_quaternion_angle_axis.angle();
263 
264  // compute control
265  // allocate variables
266  Eigen::VectorXd tau_task(7), tau_nullspace(7), tau_d(7);
267 
268  // pseudoinverse for nullspace handling
269  // kinematic pseuoinverse
270  Eigen::MatrixXd jacobian_transpose_pinv;
271  franka_example_controllers::pseudoInverse(jacobian.transpose(), jacobian_transpose_pinv);
272 
273  // Cartesian PD control with damping ratio = 1
274  tau_task << jacobian.transpose() * (-arm_data.cartesian_stiffness_ * error -
275  arm_data.cartesian_damping_ * (jacobian * dq));
276  // nullspace PD control with damping ratio = 1
277  tau_nullspace << (Eigen::MatrixXd::Identity(7, 7) -
278  jacobian.transpose() * jacobian_transpose_pinv) *
279  (arm_data.nullspace_stiffness_ * (arm_data.q_d_nullspace_ - q) -
280  (2.0 * sqrt(arm_data.nullspace_stiffness_)) * dq);
281  // Desired torque
282  tau_d << tau_task + tau_nullspace + coriolis;
283  // Saturate torque rate to avoid discontinuities
284  tau_d << saturateTorqueRate(arm_data, tau_d, tau_J_d);
285  for (size_t i = 0; i < 7; ++i) {
286  arm_data.joint_handles_[i].setCommand(tau_d(i));
287  }
288 
289  // update parameters changed online either through dynamic reconfigure or through the interactive
290  // target by filtering
291  arm_data.cartesian_stiffness_ = arm_data.filter_params_ * arm_data.cartesian_stiffness_target_ +
292  (1.0 - arm_data.filter_params_) * arm_data.cartesian_stiffness_;
293  arm_data.cartesian_damping_ = arm_data.filter_params_ * arm_data.cartesian_damping_target_ +
294  (1.0 - arm_data.filter_params_) * arm_data.cartesian_damping_;
295  arm_data.nullspace_stiffness_ = arm_data.filter_params_ * arm_data.nullspace_stiffness_target_ +
296  (1.0 - arm_data.filter_params_) * arm_data.nullspace_stiffness_;
297  arm_data.position_d_ = arm_data.filter_params_ * arm_data.position_d_target_ +
298  (1.0 - arm_data.filter_params_) * arm_data.position_d_;
299  arm_data.orientation_d_ =
300  arm_data.orientation_d_.slerp(arm_data.filter_params_, arm_data.orientation_d_target_);
301 }
302 
304  const FrankaDataContainer& arm_data,
305  const Eigen::Matrix<double, 7, 1>& tau_d_calculated,
306  const Eigen::Matrix<double, 7, 1>& tau_J_d) { // NOLINT (readability-identifier-naming)
307  Eigen::Matrix<double, 7, 1> tau_d_saturated{};
308  for (size_t i = 0; i < 7; i++) {
309  double difference = tau_d_calculated[i] - tau_J_d[i];
310  tau_d_saturated[i] = tau_J_d[i] + std::max(std::min(difference, arm_data.delta_tau_max_),
311  -arm_data.delta_tau_max_);
312  }
313  return tau_d_saturated;
314 }
315 
317  franka_combined_example_controllers::dual_arm_compliance_paramConfig& config,
318  uint32_t /*level*/) {
319  auto& left_arm_data = arms_data_.at(left_arm_id_);
320  left_arm_data.cartesian_stiffness_target_.setIdentity();
321  left_arm_data.cartesian_stiffness_target_.topLeftCorner(3, 3)
322  << config.left_translational_stiffness * Eigen::Matrix3d::Identity();
323  left_arm_data.cartesian_stiffness_target_.bottomRightCorner(3, 3)
324  << config.left_rotational_stiffness * Eigen::Matrix3d::Identity();
325  left_arm_data.cartesian_damping_target_.setIdentity();
326 
327  left_arm_data.cartesian_damping_target_.topLeftCorner(3, 3)
328  << 2 * sqrt(config.left_translational_stiffness) * Eigen::Matrix3d::Identity();
329  left_arm_data.cartesian_damping_target_.bottomRightCorner(3, 3)
330  << 2 * sqrt(config.left_rotational_stiffness) * Eigen::Matrix3d::Identity();
331  left_arm_data.nullspace_stiffness_target_ = config.left_nullspace_stiffness;
332 
333  auto& right_arm_data = arms_data_.at(right_arm_id_);
334  right_arm_data.cartesian_stiffness_target_.setIdentity();
335  right_arm_data.cartesian_stiffness_target_.topLeftCorner(3, 3)
336  << config.right_translational_stiffness * Eigen::Matrix3d::Identity();
337  right_arm_data.cartesian_stiffness_target_.bottomRightCorner(3, 3)
338  << config.right_rotational_stiffness * Eigen::Matrix3d::Identity();
339  right_arm_data.cartesian_damping_target_.setIdentity();
340 
341  right_arm_data.cartesian_damping_target_.topLeftCorner(3, 3)
342  << 2 * sqrt(config.right_translational_stiffness) * Eigen::Matrix3d::Identity();
343  right_arm_data.cartesian_damping_target_.bottomRightCorner(3, 3)
344  << 2 * sqrt(config.right_rotational_stiffness) * Eigen::Matrix3d::Identity();
345  right_arm_data.nullspace_stiffness_target_ = config.right_nullspace_stiffness;
346 }
347 
349  const geometry_msgs::PoseStamped::ConstPtr& msg) {
350  try {
351  if (msg->header.frame_id != left_arm_id_ + "_link0") {
353  "DualArmCartesianImpedanceExampleController: Got pose target with invalid"
354  " frame_id "
355  << msg->header.frame_id << ". Expected " << left_arm_id_ + "_link0");
356  return;
357  }
358 
359  // Set target for the left robot.
360  auto& left_arm_data = arms_data_.at(left_arm_id_);
361  Eigen::Affine3d Ol_T_C; // NOLINT (readability-identifier-naming)
362  tf::poseMsgToEigen(msg->pose, Ol_T_C);
363  Eigen::Affine3d Ol_T_EEl_d = // NOLINT (readability-identifier-naming)
364  Ol_T_C * EEl_T_C_.inverse(); // NOLINT (readability-identifier-naming)
365  left_arm_data.position_d_target_ = Ol_T_EEl_d.translation();
366  Eigen::Quaterniond last_orientation_d_target(left_arm_data.orientation_d_target_);
367  Eigen::Quaterniond new_orientation_target(Ol_T_EEl_d.linear());
368  if (last_orientation_d_target.coeffs().dot(new_orientation_target.coeffs()) < 0.0) {
369  new_orientation_target.coeffs() << -new_orientation_target.coeffs();
370  }
371  Ol_T_EEl_d.linear() = new_orientation_target.matrix();
372  left_arm_data.orientation_d_target_ = Ol_T_EEl_d.linear();
373 
374  // Compute target for the right endeffector given the static desired transform from left to
375  // right endeffector.
376  Eigen::Affine3d Or_T_EEr_d = Ol_T_Or_.inverse() // NOLINT (readability-identifier-naming)
377  * Ol_T_EEl_d * // NOLINT (readability-identifier-naming)
378  EEr_T_EEl_.inverse(); // NOLINT (readability-identifier-naming)
379  auto& right_arm_data = arms_data_.at(right_arm_id_);
380  right_arm_data.position_d_target_ =
381  Or_T_EEr_d.translation(); // NOLINT (readability-identifier-naming)
382  last_orientation_d_target = Eigen::Quaterniond(right_arm_data.orientation_d_target_);
383  right_arm_data.orientation_d_target_ =
384  Or_T_EEr_d.linear(); // NOLINT (readability-identifier-naming)
385  if (last_orientation_d_target.coeffs().dot(right_arm_data.orientation_d_target_.coeffs()) <
386  0.0) {
387  right_arm_data.orientation_d_target_.coeffs()
388  << -right_arm_data.orientation_d_target_.coeffs();
389  }
390 
391  } catch (std::out_of_range& ex) {
392  ROS_ERROR_STREAM("DualArmCartesianImpedanceExampleController: Exception setting target poses.");
393  }
394 }
395 
397  if (center_frame_pub_.trylock()) {
398  franka::RobotState robot_state_left =
399  arms_data_.at(left_arm_id_).state_handle_->getRobotState();
400  Eigen::Affine3d Ol_T_EEl(Eigen::Matrix4d::Map( // NOLINT (readability-identifier-naming)
401  robot_state_left.O_T_EE.data())); // NOLINT (readability-identifier-naming)
402  Eigen::Affine3d Ol_T_C = Ol_T_EEl * EEl_T_C_; // NOLINT (readability-identifier-naming)
404  center_frame_pub_.msg_.header.frame_id = left_arm_id_ + "_link0";
406  }
407 }
408 
409 } // namespace franka_example_controllers
410 
Eigen::Matrix< double, 6, 6 > cartesian_damping_target_
Unfiltered raw value.
void starting(const ros::Time &) override
Prepares the controller for the real-time execution.
std::unique_ptr< dynamic_reconfigure::Server< franka_combined_example_controllers::dual_arm_compliance_paramConfig > > dynamic_server_compliance_param_
Nodehandle for the dynamic reconfigure namespace.
void complianceParamCallback(franka_combined_example_controllers::dual_arm_compliance_paramConfig &config, uint32_t)
Callback for updates on the parameterization of the controller in terms of stiffnesses.
Eigen::Affine3d EEl_T_C_
< Transformation from the centering frame to the left end effector.
void pseudoInverse(const Eigen::MatrixXd &M_, Eigen::MatrixXd &M_pinv_, bool damped=true)
q
Header header
TransportHints & reliable()
Eigen::Matrix< double, 6, 6 > cartesian_stiffness_
To track the target pose.
void publishCenteringPose()
Publishes a Pose Stamped for visualization of the current centering pose.
Eigen::Matrix< double, 6, 6 > cartesian_damping_
To damp cartesian motions.
void poseEigenToMsg(const Eigen::Affine3d &e, geometry_msgs::Pose &m)
Subscriber subscribe(const std::string &topic, uint32_t queue_size, void(T::*fp)(M), T *obj, const TransportHints &transport_hints=TransportHints())
void poseMsgToEigen(const geometry_msgs::Pose &m, Eigen::Affine3d &e)
Eigen::Affine3d EEr_T_EEl_
< Target transformation between the two endeffectors.
void update(const ros::Time &, const ros::Duration &period) override
Computes the control-law and commands the resulting joint torques to the robot.
Eigen::Matrix< double, 7, 1 > q_d_nullspace_
Target joint pose for nullspace motion.
void targetPoseCallback(const geometry_msgs::PoseStamped::ConstPtr &msg)
Callback method that handles updates of the target poses.
std::unique_ptr< franka_hw::FrankaStateHandle > state_handle_
To read to complete robot state.
std::vector< hardware_interface::JointHandle > joint_handles_
To command joint torques.
std::unique_ptr< franka_hw::FrankaModelHandle > model_handle_
To have access to e.g. jacobians.
realtime_tools::RealtimePublisher< geometry_msgs::PoseStamped > center_frame_pub_
Rate to trigger publishing the current pose of the centering frame.
Eigen::Matrix< double, 7, 1 > saturateTorqueRate(const FrankaDataContainer &arm_data, const Eigen::Matrix< double, 7, 1 > &tau_d_calculated, const Eigen::Matrix< double, 7, 1 > &tau_J_d)
Saturates torque commands to ensure feasibility.
std::string left_arm_id_
Name of the left arm, retrieved from the parameter server.
std::map< std::string, FrankaDataContainer > arms_data_
Holds all relevant data for both arms.
bool waitForTransform(const std::string &target_frame, const std::string &source_frame, const ros::Time &time, const ros::Duration &timeout, const ros::Duration &polling_sleep_duration=ros::Duration(0.01), std::string *error_msg=NULL) const
TransportHints & tcpNoDelay(bool nodelay=true)
std::array< double, 7 > q
double nullspace_stiffness_
[Nm/rad] To track the initial joint configuration in the nullspace of the Cartesian motion...
Eigen::Quaterniond orientation_d_
Target orientation of the end effector.
Controller class for ros_control that renders two decoupled Cartesian impedances for the tracking of ...
def error(args, kwargs)
bool init(hardware_interface::RobotHW *robot_hw, ros::NodeHandle &node_handle) override
Initializes the controller class to be ready to run.
double filter_params_
[-] PT1-Filter constant to smooth target values set by dynamic reconfigure servers (stiffness/damping...
This container holds all data and parameters used to control one panda arm with a Cartesian impedance...
std::array< double, 7 > dq
void updateArm(FrankaDataContainer &arm_data)
Computes the decoupled controller update for a single arm.
TransportHints transport_hints
std::array< double, 16 > O_T_EE
Eigen::Matrix< double, 6, 6 > cartesian_stiffness_target_
Unfiltered raw value.
void lookupTransform(const std::string &target_frame, const std::string &source_frame, const ros::Time &time, StampedTransform &transform) const
Eigen::Vector3d position_d_
Target position of the end effector.
void init(const ros::NodeHandle &node, const std::string &topic, int queue_size, bool latched=false)
void startingArm(FrankaDataContainer &arm_data)
Prepares all internal states to be ready to run the real-time control for one arm.
std::array< double, 7 > tau_J_d
bool getParam(const std::string &key, std::string &s) const
PLUGINLIB_EXPORT_CLASS(moveit_fake_controller_manager::MoveItFakeControllerManager, moveit_controller_manager::MoveItControllerManager)
bool initArm(hardware_interface::RobotHW *robot_hw, const std::string &arm_id, const std::vector< std::string > &joint_names)
Initializes a single Panda robot arm.
const double delta_tau_max_
[Nm/ms] Maximum difference in joint-torque per timestep.
void transformTFToEigen(const tf::Transform &t, Eigen::Isometry3d &e)
void init(const std::string &_topic, uint32_t _queue_size, const boost::function< void(const boost::shared_ptr< M const > &)> &_callback, const boost::function< boost::shared_ptr< M >(void)> &factory_fn=DefaultMessageCreator< M >())
#define ROS_ERROR_STREAM(args)
#define ROS_ERROR(...)
std::string right_arm_id_
Name of the right arm, retrieved from the parameter server.


franka_example_controllers
Author(s): Franka Emika GmbH
autogenerated on Fri Oct 23 2020 03:47:17