calibrate_base.cpp
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2015 Fetch Robotics Inc.
00003  * Copyright (C) 2014 Unbounded Robotics Inc.
00004  *
00005  * Licensed under the Apache License, Version 2.0 (the "License");
00006  * you may not use this file except in compliance with the License.
00007  * You may obtain a copy of the License at
00008  *
00009  *     http://www.apache.org/licenses/LICENSE-2.0
00010  *
00011  * Unless required by applicable law or agreed to in writing, software
00012  * distributed under the License is distributed on an "AS IS" BASIS,
00013  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00014  * See the License for the specific language governing permissions and
00015  * limitations under the License.
00016  */
00017 
00018 // Author: Michael Ferguson
00019 
00020 #include <cmath>
00021 #include <fstream>
00022 #include <boost/thread/recursive_mutex.hpp>
00023 
00024 #include <ros/ros.h>
00025 #include <geometry_msgs/Twist.h>
00026 #include <sensor_msgs/Imu.h>
00027 #include <sensor_msgs/LaserScan.h>
00028 #include <nav_msgs/Odometry.h>
00029 
00030 #define PI          3.14159265359
00031 
00032 class BaseCalibration
00033 {
00034 public:
00035   BaseCalibration(ros::NodeHandle& n) : ready_(false)
00036   {
00037     // Setup times
00038     last_odom_stamp_ = last_imu_stamp_ = last_scan_stamp_ = ros::Time::now();
00039 
00040     // Get params
00041     ros::NodeHandle nh("~");
00042 
00043     // Min/Max acceptable error when aligning with wall
00044     nh.param<double>("min_angle", min_angle_, -0.5);
00045     nh.param<double>("max_angle", max_angle_, 0.5);
00046 
00047     // How fast to accelerate
00048     nh.param<double>("accel_limit", accel_limit_, 2.0);
00049 
00050     // cmd vel publisher
00051     cmd_pub_ = n.advertise<geometry_msgs::Twist>("cmd_vel", 10);
00052 
00053     // Subscribe
00054     odom_subscriber_ = n.subscribe("odom", 5, &BaseCalibration::odometryCallback, this);
00055     imu_subscriber_ =  n.subscribe("imu", 5, &BaseCalibration::imuCallback, this);
00056     scan_subscriber_ = n.subscribe("base_scan", 1, &BaseCalibration::laserCallback, this);
00057 
00058     resetInternal();
00059   }
00060 
00061   void reset()
00062   {
00063     scan_.clear();
00064     odom_.clear();
00065     imu_.clear();
00066   }
00067 
00068   std::string print()
00069   {
00070     std::stringstream ss;
00071     ss << scan_r2_ << " " << imu_angle_ << " " << odom_angle_ << " " << scan_angle_;
00072     return ss.str();
00073   }
00074 
00075   std::string printCalibrationData()
00076   {
00077     double odom, imu;
00078     ros::NodeHandle nh;
00079     nh.param<double>("base_controller/track_width", odom, 0.37476);
00080     nh.param<double>("imu/gyro/scale", imu, 0.001221729);
00081 
00082     // scaling to be computed
00083     double odom_scale = 0.0;
00084     double imu_scale = 0.0;
00085 
00086     // get sum
00087     for (size_t i = 0; i < scan_.size(); ++i)
00088     {
00089       odom_scale += (scan_[i] - odom_[i]) / odom_[i];
00090       imu_scale += (scan_[i] - imu_[i]) / imu_[i];
00091     }
00092     // divide sum by size
00093     odom_scale /= scan_.size();
00094     imu_scale /= scan_.size();
00095     // output odom/imu values
00096     std::stringstream ss;
00097     ss << "odom: " << odom * (1.0 + odom_scale) << std::endl;
00098     ss << "imu: " << imu * (1.0 + imu_scale) << std::endl;
00099     return ss.str();
00100   }
00101 
00103   bool align(bool verbose = false)
00104   {
00105     while (!ready_)
00106       ros::Duration(0.1).sleep();
00107 
00108     std::cout << "aligning..." << std::endl;
00109 
00110     double velocity = 0.2;
00111     if (scan_angle_ < 0)
00112       velocity = -0.2;
00113 
00114     while (fabs(scan_angle_) > 0.2 || (scan_r2_ < 0.1))
00115     {
00116       if (verbose)
00117         std::cout << scan_r2_ << " " << scan_angle_ << std::endl;
00118       sendVelocityCommand(velocity);
00119       ros::Duration(0.02).sleep();
00120     }
00121     sendVelocityCommand(0.0);
00122     std::cout << "...done" << std::endl;
00123     ros::Duration(0.25).sleep();
00124 
00125     return true;
00126   }
00127 
00129   bool spin(double velocity, int rotations, bool verbose = false)
00130   {
00131     double scan_start = scan_angle_;
00132 
00133     align();
00134     resetInternal();
00135     std::cout << "spin..." << std::endl;
00136 
00137     // need to account for de-acceleration time (v^2/2a)
00138     double angle = rotations * 2 * PI - (0.5 * velocity * velocity / accel_limit_);
00139 
00140     while (fabs(odom_angle_) < angle)
00141     {
00142       if (verbose)
00143         std::cout << scan_angle_ << " " << odom_angle_ << " " << imu_angle_ << std::endl;
00144       sendVelocityCommand(velocity);
00145       ros::Duration(0.02).sleep();
00146     }
00147     sendVelocityCommand(0.0);
00148     std::cout << "...done" << std::endl;
00149 
00150     // wait to stop
00151     ros::Duration(0.5 + fabs(velocity) / accel_limit_).sleep();
00152 
00153     // save measurements
00154     imu_.push_back(imu_angle_);
00155     odom_.push_back(odom_angle_);
00156     if (velocity > 0)
00157       scan_.push_back(scan_start + 2 * rotations * PI - scan_angle_);
00158     else
00159       scan_.push_back(scan_start - 2 * rotations * PI - scan_angle_);
00160 
00161     return true;
00162   }
00163 
00164 private:
00165   void odometryCallback(const nav_msgs::Odometry::Ptr& odom)
00166   {
00167     boost::recursive_mutex::scoped_lock lock(data_mutex_);
00168 
00169     double dt = (odom->header.stamp - last_odom_stamp_).toSec();
00170     odom_angle_ += odom->twist.twist.angular.z * dt;
00171 
00172     last_odom_stamp_ = odom->header.stamp;
00173   }
00174 
00175   void imuCallback(const sensor_msgs::Imu::Ptr& imu)
00176   {
00177     boost::recursive_mutex::scoped_lock lock(data_mutex_);
00178 
00179     double dt = (imu->header.stamp - last_imu_stamp_).toSec();
00180     imu_angle_ += imu->angular_velocity.z * dt;
00181 
00182     last_imu_stamp_ = imu->header.stamp;
00183   }
00184 
00185   void laserCallback(const sensor_msgs::LaserScan::Ptr& scan)
00186   {
00187     boost::recursive_mutex::scoped_lock lock(data_mutex_);
00188 
00189     double angle = scan->angle_min;
00190     double mean_x, mean_y, n;
00191     mean_x = mean_y = n = 0;
00192     int start = -1;
00193     for (size_t i = 0; i < scan->ranges.size(); ++i, angle += scan->angle_increment)
00194     {
00195       if (angle < min_angle_ || angle > max_angle_)
00196         continue;
00197       if (start < 0)
00198         start = i;
00199 
00200       // Compute point
00201       double px = sin(angle) * scan->ranges[i];
00202       double py = cos(angle) * scan->ranges[i];
00203 
00204       mean_x += px;
00205       mean_y += py;
00206       ++n;
00207     }
00208 
00209     if (n == 0)
00210       return;
00211 
00212     mean_x /= n;
00213     mean_y /= n;
00214 
00215     angle = scan->angle_min + start * scan->angle_increment;  // reset angle
00216     double x, y, xx, xy, yy;
00217     x = y = xx = xy = yy = n = 0;
00218     for (size_t i = start; i < scan->ranges.size(); ++i, angle += scan->angle_increment)
00219     {
00220       if (angle > max_angle_)
00221         break;
00222 
00223       // Compute point
00224       double px = sin(angle) * scan->ranges[i] - mean_x;
00225       double py = cos(angle) * scan->ranges[i] - mean_y;
00226 
00227       // Sums for simple linear regression
00228       xx += px * px;
00229       xy += px * py;
00230       x += px;
00231       y += py;
00232       yy += py * py;
00233       ++n;
00234     }
00235 
00236     scan_dist_ = mean_y;
00237     scan_angle_ = atan2((n*xy-x*y)/(n*xx-x*x), 1.0);
00238     scan_r2_ = fabs(xy)/(xx * yy);
00239     last_scan_stamp_ = scan->header.stamp;
00240     ready_ = true;
00241   }
00242 
00243   // Send a rotational velocity command
00244   void sendVelocityCommand(double vel)
00245   {
00246     geometry_msgs::Twist twist;
00247     twist.angular.z = vel;
00248     cmd_pub_.publish(twist);
00249   }
00250 
00251   // reset the odom/imu counters
00252   void resetInternal()
00253   {
00254     boost::recursive_mutex::scoped_lock lock(data_mutex_);
00255     odom_angle_ = imu_angle_ = scan_angle_ = scan_r2_ = 0.0;
00256   }
00257 
00258   ros::Publisher cmd_pub_;
00259 
00260   ros::Subscriber odom_subscriber_;
00261   ros::Subscriber imu_subscriber_;
00262   ros::Subscriber scan_subscriber_;
00263 
00264   ros::Time last_odom_stamp_;
00265   double odom_angle_;
00266 
00267   ros::Time last_imu_stamp_;
00268   double imu_angle_;
00269 
00270   ros::Time last_scan_stamp_;
00271   double scan_angle_, scan_r2_, scan_dist_;
00272 
00273   double min_angle_, max_angle_;
00274   double accel_limit_;
00275 
00276   std::vector<double> scan_;
00277   std::vector<double> imu_;
00278   std::vector<double> odom_;
00279 
00280   boost::recursive_mutex data_mutex_;
00281   bool ready_;
00282 };
00283 
00284 int main(int argc, char** argv)
00285 {
00286   ros::init(argc, argv,"base_calibration_node");
00287   ros::NodeHandle nh;
00288 
00289   // for callbacks
00290   ros::AsyncSpinner spinner(1);
00291   spinner.start();
00292 
00293   BaseCalibration b(nh);
00294   b.reset();
00295 
00296   // rotate at several different speeds
00297   b.spin(0.5, 1);
00298   b.spin(1.5, 1);
00299   b.spin(3.0, 2);
00300   b.spin(-0.5, 1);
00301   b.spin(-1.5, 1);
00302   b.spin(-3.0, 2);
00303 
00304   // TODO: drive towards wall, to calibrate rollout
00305 
00306   // output yaml file
00307   {
00308     // Generate datecode
00309     char datecode[80];
00310     {
00311       std::time_t t = std::time(NULL);
00312       std::strftime(datecode, 80, "%Y_%m_%d_%H_%M_%S", std::localtime(&t));
00313     }
00314     std::stringstream yaml_name;
00315     yaml_name << "/tmp/base_calibration_" << datecode << ".yaml";
00316     std::ofstream file;
00317     file.open(yaml_name.str().c_str());
00318     std::string cal = b.printCalibrationData();
00319     file << cal;
00320     file.close();
00321     std::cout << cal;
00322   }
00323 
00324   spinner.stop();
00325   return 0;
00326 }


robot_calibration
Author(s): Michael Ferguson
autogenerated on Sat May 20 2017 02:35:31