ft_calib_node.cpp
Go to the documentation of this file.
1 /*
2  * ft_calib_node.cpp
3  *
4  *
5  * Created on: Sep 26, 2012
6  * Authors: Francisco Viña
7  * fevb <at> kth.se
8  */
9 
10 /* Copyright (c) 2012, Francisco Viña, CVAP, KTH
11  All rights reserved.
12 
13  Redistribution and use in source and binary forms, with or without
14  modification, are permitted provided that the following conditions are met:
15  * Redistributions of source code must retain the above copyright
16  notice, this list of conditions and the following disclaimer.
17  * Redistributions in binary form must reproduce the above copyright
18  notice, this list of conditions and the following disclaimer in the
19  documentation and/or other materials provided with the distribution.
20  * Neither the name of KTH nor the
21  names of its contributors may be used to endorse or promote products
22  derived from this software without specific prior written permission.
23 
24  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
25  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
26  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27  DISCLAIMED. IN NO EVENT SHALL KTH BE LIABLE FOR ANY
28  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
29  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
31  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 */
35 
36 #include <ros/ros.h>
37 #include <iostream>
38 #include <fstream>
39 #include <sstream>
40 #include <tf/tf.h>
41 #include <tf/transform_listener.h>
42 #include <tf/transform_datatypes.h>
45 #include <geometry_msgs/Pose.h>
46 #include <geometry_msgs/WrenchStamped.h>
47 #include <sensor_msgs/Imu.h>
50 #include <Eigen/Core>
51 
52 using namespace Calibration;
53 
54 
56 {
57 
58 
59 public:
64 
66  {
67  n_ = ros::NodeHandle("~");
68  spinner = new ros::AsyncSpinner(1);
69  spinner->start();
70 
71 
72 
73  topicSub_ft_raw_ = n_.subscribe("ft_raw", 1, &FTCalibNode::topicCallback_ft_raw, this);
74  topicSub_Accelerometer_ = n_.subscribe("imu", 1, &FTCalibNode::topicCallback_imu, this);
75 
76  m_pose_counter = 0;
77  m_ft_counter = 0;
78 
79  m_received_ft = false;
80  m_received_imu = false;
81 
82  m_finished = false;
83  m_tf_listener = new tf::TransformListener();
84 
85  m_ft_calib = new FTCalib();
86  }
87 
89  {
90  saveCalibData();
91  delete spinner;
92  delete m_group;
93  delete m_ft_calib;
94  delete m_tf_listener;
95  }
96 
97 
99  {
100 
101  // Get the moveit group name
102  if(n_.hasParam("moveit_group_name"))
103  {
104  n_.getParam("moveit_group_name", m_moveit_group_name);
105  }
106 
107  else
108  {
109  ROS_ERROR("No moveit_group_name parameter, shutting down node...");
110  n_.shutdown();
111  return false;
112  }
113 
114  // Get the name of output calibration file
115  if(n_.hasParam("calib_file_name"))
116  {
117  n_.getParam("calib_file_name", m_calib_file_name);
118  }
119 
120  else
121  {
122  ROS_WARN("No calib_file_name parameter, setting to default 'ft_calib.yaml'");
123  m_calib_file_name = std::string("ft_calib_data.yaml");
124  }
125 
126  // Get the name of calibration file directory
127  if(n_.hasParam("calib_file_dir"))
128  {
129  n_.getParam("calib_file_dir", m_calib_file_dir);
130  }
131 
132  else
133  {
134  ROS_WARN("No calib_file_dir parameter, setting to default '~/.ros/ft_calib' ");
135  m_calib_file_dir = std::string("~/.ros/ft_calib");
136  }
137 
138 
139  // Get the name of file to store the gravity and F/T measurements
140  if(n_.hasParam("meas_file_name"))
141  {
142  n_.getParam("meas_file_name", m_meas_file_name);
143  }
144 
145  else
146  {
147  ROS_WARN("No meas_file_name parameter, setting to default 'ft_calib_meas.txt'");
148  m_meas_file_name = std::string("ft_calib_meas.txt");
149  }
150 
151  // Get the name of directory to save gravity and force-torque measurements
152  if(n_.hasParam("meas_file_dir"))
153  {
154  n_.getParam("meas_file_dir", m_meas_file_dir);
155  }
156 
157  else
158  {
159  ROS_WARN("No meas_file_dir parameter, setting to default '~/.ros/ft_calib' ");
160  m_meas_file_dir = std::string("~/.ros/ft_calib");
161  }
162 
163  if(n_.hasParam("poses_frame_id"))
164  {
165  n_.getParam("poses_frame_id", m_poses_frame_id);
166  }
167 
168  else
169  {
170  ROS_ERROR("No poses_frame_id parameter, shutting down node ...");
171  n_.shutdown();
172  return false;
173  }
174 
175 
176  // whether the user wants to use random poses
177  n_.param("random_poses", m_random_poses, false);
178 
179  // number of random poses
180  n_.param("number_random_poses", m_number_random_poses, 30);
181 
182 
183  // initialize the file with gravity and F/T measurements
184 
185  // expand the path
186  if (!m_meas_file_dir.empty() && m_meas_file_dir[0] == '~') {
187  assert(m_meas_file_dir.size() == 1 or m_meas_file_dir[1] == '/'); // or other error handling
188  char const* home = getenv("HOME");
189  if (home or (home = getenv("USERPROFILE"))) {
190  m_meas_file_dir.replace(0, 1, home);
191  }
192  else {
193  char const *hdrive = getenv("HOMEDRIVE"),
194  *hm_meas_file_dir = getenv("HOMEPATH");
195  assert(hdrive); // or other error handling
196  assert(hm_meas_file_dir);
197  m_meas_file_dir.replace(0, 1, std::string(hdrive) + hm_meas_file_dir);
198  }
199  }
200 
201  std::ofstream meas_file;
202  meas_file.open((m_meas_file_dir + "/" + m_meas_file_name).c_str(), std::ios::out);
203 
204  std::stringstream meas_file_header;
205 
206  meas_file_header << "\% gravity , f/t measurements all expressed in F/T sensor frame\n";
207  meas_file_header << "\% [gx, gy, gz, fx, fy, fz, tx, ty, tz]\n";
208 
209  meas_file << meas_file_header.str();
210 
211  meas_file.close();
212 
213  return true;
214  }
215  // connects to the move arm servers
216  void init()
217  {
218  m_group = new moveit::planning_interface::MoveGroupInterface(m_moveit_group_name);
219  m_group->setPlannerId("RRTConnectkConfigDefault");
220  }
221 
222 
223  // Calibrates the FT sensor by putting the arm in several different positions
225  {
226 
227  std::stringstream ss;
228  ss << m_pose_counter;
229  Eigen::Matrix<double, 6, 1> pose;
230 
231  // either find poses from the parameter server
232  // poses should be in "pose%d" format (e.g. pose0, pose1, pose2 ...)
233  // and they should be float arrays of size 6
234  if(!m_random_poses)
235  {
236  if(!getPose("pose"+ss.str(), pose))
237  {
238  ROS_INFO("Finished group %s poses", m_group->getName().c_str());
239  m_finished = true;
240  return true;
241  }
242 
243  geometry_msgs::Pose pose_;
244  pose_.position.x = pose(0);
245  pose_.position.y = pose(1);
246  pose_.position.z = pose(2);
247 
249  q.setRPY((double)pose(3), (double)pose(4), (double)pose(5));
250 
251  tf::quaternionTFToMsg(q, pose_.orientation);
252 
253  geometry_msgs::PoseStamped pose_stamped;
254  pose_stamped.pose = pose_;
255  pose_stamped.header.frame_id = m_poses_frame_id;
256  pose_stamped.header.stamp = ros::Time::now();
257 
258  m_group->setPoseTarget(pose_stamped);
259 
260  }
261  else // or execute random poses
262  {
263  if(m_pose_counter<m_number_random_poses)
264  {
265  m_group->setRandomTarget();
266  ROS_INFO("Executing pose %d",m_pose_counter);
267  }
268 
269  else
270  {
271  ROS_INFO("Finished group %s random poses", m_group->getName().c_str());
272  m_finished = true;
273  return true;
274  }
275  }
276 
277 
278  m_pose_counter++;
279  m_group->move();
280  ROS_INFO("Finished executing pose %d", m_pose_counter-1);
281  return true;
282  }
283 
284  // gets the next pose from the parameter server
285  // pose in [x y z r p y] format ([m], [rad])
286  bool getPose(const std::string &pose_param_name, Eigen::Matrix<double, 6, 1> &pose)
287  {
288  XmlRpc::XmlRpcValue PoseXmlRpc;
289  if(n_.hasParam(pose_param_name))
290  {
291  n_.getParam(pose_param_name, PoseXmlRpc);
292  }
293 
294  else
295  {
296  ROS_WARN("Pose parameter %s not found", pose_param_name.c_str());
297  return false;
298  }
299 
300  if(PoseXmlRpc.size()!=6)
301  {
302  ROS_ERROR("Pose parameter %s wrong size (must be 6)", pose_param_name.c_str());
303  return false;
304  }
305 
306  for(unsigned int i=0; i<6; i++)
307  pose(i) = (double)PoseXmlRpc[i];
308 
309  return true;
310  }
311 
312  // prints out the pose (3-D positions) of the calibration frame at each of the positions
313  // of the left arm
315  {
316  double mass;
317  Eigen::Vector3d COM_pos;
318  Eigen::Vector3d f_bias;
319  Eigen::Vector3d t_bias;
320 
321  getCalib(mass, COM_pos, f_bias, t_bias);
322 
323  XmlRpc::XmlRpcValue bias;
324  bias.setSize(6);
325  for(unsigned int i=0; i<3; i++)
326  bias[i] = (double)f_bias(i);
327 
328  for(unsigned int i=0; i<3; i++)
329  bias[i+3] = (double)t_bias(i);
330 
331  XmlRpc::XmlRpcValue COM_pose;
332  COM_pose.setSize(6);
333  for(unsigned int i=0; i<3; i++)
334  COM_pose[i] = (double)COM_pos(i);
335 
336  for(unsigned int i=0; i<3; i++)
337  COM_pose[i+3] = 0.0;
338 
339  // set the parameters in the parameter server
340  n_.setParam("/ft_calib/bias", bias);
341  n_.setParam("/ft_calib/gripper_mass", mass);
342  n_.setParam("/ft_calib/gripper_com_frame_id", m_ft_raw.header.frame_id.c_str());
343  n_.setParam("/ft_calib/gripper_com_pose", COM_pose);
344 
345  // dump the parameters to YAML file
346  std::string file = m_calib_file_dir + std::string("/") + m_calib_file_name;
347 
348  // first create the directory
349  std::string command = std::string("mkdir -p ") + m_calib_file_dir;
350  std::system(command.c_str());
351 
352  // now dump the yaml file
353  command.clear();
354  command = std::string("rosparam dump ") + file + std::string(" /ft_calib");
355  std::system(command.c_str());
356  }
357 
358  // saves the gravity and force-torque measurements to a file for postprocessing
359  void saveMeasurements(geometry_msgs::Vector3Stamped gravity, geometry_msgs::WrenchStamped ft_meas)
360  {
361  std::ofstream meas_file;
362  meas_file.open((m_meas_file_dir + "/" + m_meas_file_name).c_str(), std::ios::out | std::ios::app);
363 
364  std::stringstream meas_file_text;
365 
366  meas_file_text << gravity.vector.x << " " << gravity.vector.y << " " << gravity.vector.z << " ";
367  meas_file_text << ft_meas.wrench.force.x << " " << ft_meas.wrench.force.y << " " << ft_meas.wrench.force.z << " ";
368  meas_file_text << ft_meas.wrench.torque.x << " " << ft_meas.wrench.torque.y << " " << ft_meas.wrench.torque.z << "\n";
369 
370  meas_file << meas_file_text.str();
371 
372  meas_file.close();
373  }
374 
375  // finished moving the arm through the poses set in the config file
376  bool finished()
377  {
378  return(m_finished);
379  }
380 
381  void topicCallback_ft_raw(const geometry_msgs::WrenchStamped::ConstPtr &msg)
382  {
383  ROS_DEBUG("In ft sensorcallback");
384  m_ft_raw = *msg;
385  m_received_ft = true;
386  }
387 
388 
389  // gets readings from accelerometer and transforms them to the FT sensor frame
390  void topicCallback_imu(const sensor_msgs::Imu::ConstPtr &msg)
391  {
392  ROS_DEBUG("In accelerometer read callback");
393 
394  m_imu= *msg;
395  m_received_imu = true;
396  }
397 
399  {
400 
401  m_ft_avg.wrench.force.x = -m_ft_avg.wrench.force.x/(double)m_ft_counter;
402  m_ft_avg.wrench.force.y = -m_ft_avg.wrench.force.y/(double)m_ft_counter;
403  m_ft_avg.wrench.force.z = -m_ft_avg.wrench.force.z/(double)m_ft_counter;
404 
405  m_ft_avg.wrench.torque.x = -m_ft_avg.wrench.torque.x/(double)m_ft_counter;
406  m_ft_avg.wrench.torque.y = -m_ft_avg.wrench.torque.y/(double)m_ft_counter;
407  m_ft_avg.wrench.torque.z = -m_ft_avg.wrench.torque.z/(double)m_ft_counter;
408 
409 
410  m_ft_counter = 0;
411 
412  if(!m_received_ft)
413  {
414  ROS_ERROR("Haven't received F/T sensor measurements");
415  return;
416  }
417 
418  if(!m_received_imu)
419  {
420  ROS_ERROR("Haven't received accelerometer readings");
421  return;
422  }
423 
424  // express gravity vector in F/T sensor frame
425  geometry_msgs::Vector3Stamped gravity;
426  gravity.header.stamp = ros::Time();
427  gravity.header.frame_id = m_imu.header.frame_id;
428  gravity.vector.x = -m_imu.linear_acceleration.x; // IMU will measure gravity in the opposite direction from F/T sensor, check https://github.com/kth-ros-pkg/force_torque_tools/pull/18
429  gravity.vector.y = -m_imu.linear_acceleration.y;
430  gravity.vector.z = -m_imu.linear_acceleration.z;
431 
432  geometry_msgs::Vector3Stamped gravity_ft_frame;
433 
434  try
435  {
436  m_tf_listener->transformVector(m_ft_raw.header.frame_id, gravity, gravity_ft_frame);
437  }
438 
439  catch(tf::TransformException &ex)
440  {
441  ROS_ERROR("Error transforming accelerometer reading to the F/T sensor frame");
442  ROS_ERROR("%s.", ex.what());
443  return;
444  }
445 
446  m_ft_calib->addMeasurement(gravity_ft_frame, m_ft_avg);
447  saveMeasurements(gravity_ft_frame, m_ft_avg);
448  }
449 
450  void getCalib(double &mass, Eigen::Vector3d &COM_pos, Eigen::Vector3d &f_bias, Eigen::Vector3d &t_bias)
451  {
452 
453  Eigen::VectorXd ft_calib = m_ft_calib->getCalib();
454 
455  mass = ft_calib(0);
456  if(mass<=0.0)
457  {
458  ROS_ERROR("Error in estimated mass (<= 0)");
459  // return;
460  }
461 
462  Eigen::Vector3d center_mass_position(ft_calib(1)/mass,
463  ft_calib(2)/mass,
464  ft_calib(3)/mass);
465 
466  COM_pos = center_mass_position;
467 
468  f_bias(0) = -ft_calib(4);
469  f_bias(1) = -ft_calib(5);
470  f_bias(2) = -ft_calib(6);
471  t_bias(0) = -ft_calib(7);
472  t_bias(1) = -ft_calib(8);
473  t_bias(2) = -ft_calib(9);
474 
475  }
476 
478  {
479 
480  if(m_ft_counter==0)
481  {
482 
483  m_ft_avg = m_ft_raw;
484 
485  }
486 
487  else
488  {
489 
490  m_ft_avg.wrench.force.x = m_ft_avg.wrench.force.x + m_ft_raw.wrench.force.x;
491  m_ft_avg.wrench.force.y = m_ft_avg.wrench.force.y + m_ft_raw.wrench.force.y;
492  m_ft_avg.wrench.force.z = m_ft_avg.wrench.force.z + m_ft_raw.wrench.force.z;
493 
494  m_ft_avg.wrench.torque.x = m_ft_avg.wrench.torque.x + m_ft_raw.wrench.torque.x;
495  m_ft_avg.wrench.torque.y = m_ft_avg.wrench.torque.y + m_ft_raw.wrench.torque.y;
496  m_ft_avg.wrench.torque.z = m_ft_avg.wrench.torque.z + m_ft_raw.wrench.torque.z;
497 
498  }
499  m_ft_counter++;
500  }
501 
502 
503 private:
504 
506 
507  unsigned int m_pose_counter;
508  unsigned int m_ft_counter;
509 
511 
514 
515  // ft calib stuff
517 
518  // expressed in FT sensor frame
519  geometry_msgs::WrenchStamped m_ft_raw;
520  geometry_msgs::WrenchStamped m_ft_avg; // average over 100 measurements
521 
522  // accelerometer readings
523  sensor_msgs::Imu m_imu;
524 
526 
527  // ***** ROS parameters ***** //
528  // name of the moveit group
529  std::string m_moveit_group_name;
530 
531  // name of output calibration file
532  std::string m_calib_file_name;
533 
534  // name of output directory
535  // default: ~/.ros/ft_calib
536  std::string m_calib_file_dir;
537 
538  // name of file with recorded gravity and F/T measurements
539  std::string m_meas_file_name;
540 
541  // name of directory for saving gravity and F/T measurements
542  // default: ~/.ros/ft_calib
543  std::string m_meas_file_dir;
544 
545  // frame id of the poses to be executed
546  std::string m_poses_frame_id;
547 
548  // if the user wants to execute just random poses
549  // default: false
551 
552  // number of random poses
553  // default: 30
555 
556 };
557 
558 int main(int argc, char **argv)
559 {
560  ros::init (argc, argv, "ft_calib_node");
561  ros::NodeHandle nh;
562 
563  FTCalibNode ft_calib_node;
564  if(!ft_calib_node.getROSParameters())
565  {
566  ft_calib_node.n_.shutdown();
567  ROS_ERROR("Error getting ROS parameters");
568 
569  }
570  ft_calib_node.init();
571 
573  double loop_rate_;
574  ft_calib_node.n_.param("loop_rate", loop_rate_, 650.0);
575  ros::Rate loop_rate(loop_rate_); // Hz
576 
577  // waiting time after end of each pose to take F/T measurements
578  double wait_time;
579  ft_calib_node.n_.param("wait_time", wait_time, 4.0);
580 
581  bool ret = false;
582  unsigned int n_measurements = 0;
583 
584  ros::Time t_end_move_arm = ros::Time::now();
585 
586  while (ft_calib_node.n_.ok() && !ft_calib_node.finished())
587  {
588 
589  // Move the arm, then calibrate sensor
590  if(!ret)
591  {
592  ret = ft_calib_node.moveNextPose();
593  t_end_move_arm = ros::Time::now();
594  }
595 
596  // average 100 measurements to calibrate the sensor in each position
597  else if ((ros::Time::now() - t_end_move_arm).toSec() > wait_time)
598  {
599  n_measurements++;
600  ft_calib_node.averageFTMeas(); // average over 100 measurements;
601 
602  if(n_measurements==100)
603  {
604  ret = false;
605  n_measurements = 0;
606 
607  ft_calib_node.addMeasurement(); // stacks up measurement matrices and FT measurementsa
608  double mass;
609  Eigen::Vector3d COM_pos;
610  Eigen::Vector3d f_bias;
611  Eigen::Vector3d t_bias;
612 
613  ft_calib_node.getCalib(mass, COM_pos, f_bias, t_bias);
614  std::cout << "-------------------------------------------------------------" << std::endl;
615  std::cout << "Current calibration estimate:" << std::endl;
616  std::cout << std::endl << std::endl;
617 
618  std::cout << "Mass: " << mass << std::endl << std::endl;
619 
620  std::cout << "Center of mass position (relative to FT sensor frame):" << std::endl;
621  std::cout << "[" << COM_pos(0) << ", " << COM_pos(1) << ", " << COM_pos(2) << "]";
622  std::cout << std::endl << std::endl;
623 
624 
625  std::cout << "FT bias: " << std::endl;
626  std::cout << "[" << f_bias(0) << ", " << f_bias(1) << ", " << f_bias(2) << ", ";
627  std::cout << t_bias(0) << ", " << t_bias(1) << ", " << t_bias(2) << "]";
628  std::cout << std::endl << std::endl;
629 
630 
631  std::cout << "-------------------------------------------------------------" << std::endl << std::endl << std::endl;
632  ft_calib_node.saveCalibData();
633  }
634 
635  }
636 
637 
638  ros::spinOnce();
639  loop_rate.sleep();
640  }
641 
642  ft_calib_node.saveCalibData();
643  ros::shutdown();
644  return 0;
645 }
ros::Subscriber topicSub_ft_raw_
void getCalib(double &mass, Eigen::Vector3d &COM_pos, Eigen::Vector3d &f_bias, Eigen::Vector3d &t_bias)
q
void topicCallback_imu(const sensor_msgs::Imu::ConstPtr &msg)
std::string m_meas_file_name
Subscriber subscribe(const std::string &topic, uint32_t queue_size, void(T::*fp)(M), T *obj, const TransportHints &transport_hints=TransportHints())
std::string m_calib_file_name
unsigned int m_ft_counter
int size() const
tf::TransformListener * m_tf_listener
bool moveNextPose()
ROSCPP_DECL void init(int &argc, char **argv, const std::string &name, uint32_t options=0)
std::string m_meas_file_dir
void topicCallback_ft_raw(const geometry_msgs::WrenchStamped::ConstPtr &msg)
#define ROS_WARN(...)
void saveCalibData()
sensor_msgs::Imu m_imu
ros::Subscriber topicSub_Accelerometer_
int main(int argc, char **argv)
ROSLIB_DECL std::string command(const std::string &cmd)
void setSize(int size)
FTCalib * m_ft_calib
void setRPY(const tfScalar &roll, const tfScalar &pitch, const tfScalar &yaw)
geometry_msgs::WrenchStamped m_ft_avg
#define ROS_INFO(...)
ros::AsyncSpinner * spinner
bool param(const std::string &param_name, T &param_val, const T &default_val) const
moveit::planning_interface::MoveGroupInterface * m_group
std::string m_moveit_group_name
static void quaternionTFToMsg(const Quaternion &bt, geometry_msgs::Quaternion &msg)
std::string m_calib_file_dir
bool getROSParameters()
geometry_msgs::WrenchStamped m_ft_raw
int m_number_random_poses
bool sleep()
std::string m_poses_frame_id
void saveMeasurements(geometry_msgs::Vector3Stamped gravity, geometry_msgs::WrenchStamped ft_meas)
const geometry_msgs::Pose * pose_
bool getPose(const std::string &pose_param_name, Eigen::Matrix< double, 6, 1 > &pose)
ros::NodeHandle n_
bool getParam(const std::string &key, std::string &s) const
void addMeasurement()
static Time now()
ROSCPP_DECL void shutdown()
bool ok() const
bool hasParam(const std::string &key) const
ROSCPP_DECL void spinOnce()
unsigned int m_pose_counter
#define ROS_ERROR(...)
void averageFTMeas()
void setParam(const std::string &key, const XmlRpc::XmlRpcValue &v) const
#define ROS_DEBUG(...)


force_torque_sensor_calib
Author(s): Francisco Vina
autogenerated on Mon May 10 2021 02:27:43