00001 
00002 
00003 
00004 
00005 
00006 
00007 
00008 
00009 
00010 
00011 
00012 
00013 
00014 
00015 
00016 
00017 
00018 
00019 
00020 
00021 
00022 
00023 
00024 
00025 
00026 
00027 
00028 
00029 
00030 
00031 
00032 
00033 
00034 
00035 
00036 #include <assert.h>
00037 #include <math.h>
00038 #include <iostream>
00039 
00040 #include <boost/format.hpp>
00041 
00042 #include "microstrain_3dmgx2_imu/3dmgx2.h"
00043 
00044 #include "ros/time.h"
00045 #include "self_test/self_test.h"
00046 #include "diagnostic_msgs/DiagnosticStatus.h"
00047 #include "diagnostic_updater/diagnostic_updater.h"
00048 #include "diagnostic_updater/update_functions.h"
00049 #include "diagnostic_updater/DiagnosticStatusWrapper.h"
00050 
00051 #include "sensor_msgs/Imu.h"
00052 #include "std_srvs/Empty.h"
00053 
00054 #include "tf/transform_datatypes.h"
00055 #include "microstrain_3dmgx2_imu/AddOffset.h"
00056 
00057 #include "std_msgs/Bool.h"
00058 
00059 using namespace std;
00060 
00061 class ImuNode 
00062 {
00063 public:
00064   microstrain_3dmgx2_imu::IMU imu;
00065   sensor_msgs::Imu reading;
00066 
00067   string port;
00068 
00069   microstrain_3dmgx2_imu::IMU::cmd cmd;
00070 
00071   self_test::TestRunner self_test_;
00072   diagnostic_updater::Updater diagnostic_;
00073 
00074   ros::NodeHandle node_handle_;
00075   ros::NodeHandle private_node_handle_;
00076   ros::Publisher imu_data_pub_;
00077   ros::ServiceServer add_offset_serv_;
00078   ros::ServiceServer calibrate_serv_;
00079   ros::Publisher is_calibrated_pub_;
00080 
00081   bool running;
00082 
00083   bool autocalibrate_;
00084   bool calibrate_requested_;
00085   bool calibrated_;
00086   
00087   int error_count_;
00088   int slow_count_;
00089   std::string was_slow_;
00090   std::string error_status_;
00091 
00092   string frameid_;
00093   
00094   double offset_;
00095     
00096   double bias_x_;
00097   double bias_y_;
00098   double bias_z_;
00099 
00100   double angular_velocity_stdev_, angular_velocity_covariance_;
00101   double linear_acceleration_covariance_, linear_acceleration_stdev_;
00102   double orientation_covariance_, orientation_stdev_;
00103 
00104   double max_drift_rate_;
00105 
00106   double desired_freq_;
00107   diagnostic_updater::FrequencyStatus freq_diag_;
00108   
00109   ImuNode(ros::NodeHandle h) : self_test_(), diagnostic_(), 
00110   node_handle_(h), private_node_handle_("~"), calibrate_requested_(false),
00111   error_count_(0), 
00112   slow_count_(0), 
00113   desired_freq_(100), 
00114   freq_diag_(diagnostic_updater::FrequencyStatusParam(&desired_freq_, &desired_freq_, 0.05))
00115   {
00116     ros::NodeHandle imu_node_handle(node_handle_, "imu");
00117     
00118     private_node_handle_.param("autocalibrate", autocalibrate_, true);
00119     private_node_handle_.param("assume_calibrated", calibrated_, false);
00120     private_node_handle_.param("port", port, string("/dev/ttyUSB0"));
00121     private_node_handle_.param("max_drift_rate", max_drift_rate_, 0.0002);
00122 
00123 
00124     imu_data_pub_ = imu_node_handle.advertise<sensor_msgs::Imu>("data", 100);
00125     add_offset_serv_ = private_node_handle_.advertiseService("add_offset", &ImuNode::addOffset, this);
00126     calibrate_serv_ = imu_node_handle.advertiseService("calibrate", &ImuNode::calibrate, this);
00127     is_calibrated_pub_ = imu_node_handle.advertise<std_msgs::Bool>("is_calibrated", 1, true);
00128 
00129     publish_is_calibrated();
00130 
00131     cmd = microstrain_3dmgx2_imu::IMU::CMD_ACCEL_ANGRATE_ORIENT;
00132     
00133     running = false;
00134 
00135     bias_x_ = bias_y_ = bias_z_ = 0;
00136 
00137     private_node_handle_.param("frame_id", frameid_, string("imu"));
00138     reading.header.frame_id = frameid_;
00139 
00140     private_node_handle_.param("time_offset", offset_, 0.0);
00141 
00142     private_node_handle_.param("linear_acceleration_stdev", linear_acceleration_stdev_, 0.098); 
00143     private_node_handle_.param("orientation_stdev", orientation_stdev_, 0.035); 
00144     private_node_handle_.param("angular_velocity_stdev", angular_velocity_stdev_, 0.012); 
00145 
00146     double angular_velocity_covariance = angular_velocity_stdev_ * angular_velocity_stdev_;
00147     double orientation_covariance = orientation_stdev_ * orientation_stdev_;
00148     double linear_acceleration_covariance = linear_acceleration_stdev_ * linear_acceleration_stdev_;
00149     
00150     reading.linear_acceleration_covariance[0] = linear_acceleration_covariance;
00151     reading.linear_acceleration_covariance[4] = linear_acceleration_covariance;
00152     reading.linear_acceleration_covariance[8] = linear_acceleration_covariance;
00153 
00154     reading.angular_velocity_covariance[0] = angular_velocity_covariance;
00155     reading.angular_velocity_covariance[4] = angular_velocity_covariance;
00156     reading.angular_velocity_covariance[8] = angular_velocity_covariance;
00157     
00158     reading.orientation_covariance[0] = orientation_covariance;
00159     reading.orientation_covariance[4] = orientation_covariance;
00160     reading.orientation_covariance[8] = orientation_covariance;
00161     
00162     self_test_.add("Close Test", this, &ImuNode::pretest);
00163     self_test_.add("Interruption Test", this, &ImuNode::InterruptionTest);
00164     self_test_.add("Connect Test", this, &ImuNode::ConnectTest);
00165     self_test_.add("Read ID Test", this, &ImuNode::ReadIDTest);
00166     self_test_.add("Gyro Bias Test", this, &ImuNode::GyroBiasTest);
00167     self_test_.add("Streamed Data Test", this, &ImuNode::StreamedDataTest);
00168     self_test_.add("Gravity Test", this, &ImuNode::GravityTest);
00169     self_test_.add("Disconnect Test", this, &ImuNode::DisconnectTest);
00170     self_test_.add("Resume Test", this, &ImuNode::ResumeTest);
00171 
00172     diagnostic_.add( freq_diag_ );
00173     diagnostic_.add( "Calibration Status", this, &ImuNode::calibrationStatus );
00174     diagnostic_.add( "IMU Status", this, &ImuNode::deviceStatus );
00175   }
00176 
00177   ~ImuNode()
00178   {
00179     stop();
00180   }
00181 
00182   void setErrorStatusf(const char *format, ...)
00183   {
00184     va_list va;
00185     char buff[1000]; 
00186     va_start(va, format);
00187     if (vsnprintf(buff, 1000, format, va) >= 1000)
00188       ROS_DEBUG("Really long string in setErrorStatus, it was truncated.");
00189     std::string value = std::string(buff);
00190     setErrorStatus(buff);
00191   }
00192 
00193   
00194   void setErrorStatus(const std::string msg)
00195   {
00196     if (error_status_ != msg)
00197     {
00198       error_status_ = msg;
00199       ROS_ERROR("%s You may find further details at http://www.ros.org/wiki/microstrain_3dmgx2_imu/Troubleshooting", msg.c_str());
00200     }
00201     else
00202     {
00203       ROS_DEBUG("%s You may find further details at http://www.ros.org/wiki/microstrain_3dmgx2_imu/Troubleshooting", msg.c_str());
00204     }
00205   } 
00206 
00207   void clearErrorStatus()
00208   {
00209     error_status_.clear();
00210   }
00211 
00212   int start()
00213   {
00214     stop();
00215 
00216     try
00217     {
00218       try
00219       {
00220         imu.openPort(port.c_str());
00221       } catch (microstrain_3dmgx2_imu::Exception& e) {
00222         error_count_++;
00223         setErrorStatus(e.what());
00224         diagnostic_.broadcast(2, e.what());
00225         return -1;
00226       }
00227 
00228       diagnostic_.setHardwareID(getID(true));
00229 
00230       ROS_INFO("Initializing IMU time with offset %f.", offset_);
00231 
00232       imu.initTime(offset_);
00233 
00234       if (autocalibrate_ || calibrate_requested_)
00235       {
00236         doCalibrate();
00237         calibrate_requested_ = false;
00238         autocalibrate_ = false; 
00239       }
00240       else
00241       {
00242         ROS_INFO("Not calibrating the IMU sensor. Use the calibrate service to calibrate it before use.");
00243       }
00244 
00245       ROS_INFO("IMU sensor initialized.");
00246 
00247       imu.setContinuous(cmd);
00248 
00249       freq_diag_.clear();
00250 
00251       running = true;
00252 
00253     } catch (microstrain_3dmgx2_imu::Exception& e) {
00254       error_count_++;
00255       setErrorStatusf("Exception thrown while starting IMU. This sometimes happens if you are not connected to an IMU or if another process is trying to access the IMU port. You may try 'lsof|grep %s' to see if other processes have the port open. %s", port.c_str(), e.what());
00256       diagnostic_.broadcast(2, "Error opening IMU.");
00257       return -1;
00258     }
00259 
00260     return(0);
00261   }
00262       
00263   std::string getID(bool output_info = false)
00264   {
00265       char dev_name[17];
00266       char dev_model_num[17];
00267       char dev_serial_num[17];
00268       char dev_opt[17];
00269       imu.getDeviceIdentifierString(microstrain_3dmgx2_imu::IMU::ID_DEVICE_NAME, dev_name);
00270       imu.getDeviceIdentifierString(microstrain_3dmgx2_imu::IMU::ID_MODEL_NUMBER, dev_model_num);
00271       imu.getDeviceIdentifierString(microstrain_3dmgx2_imu::IMU::ID_SERIAL_NUMBER, dev_serial_num);
00272       imu.getDeviceIdentifierString(microstrain_3dmgx2_imu::IMU::ID_DEVICE_OPTIONS, dev_opt);
00273       
00274       if (output_info)
00275         ROS_INFO("Connected to IMU [%s] model [%s] s/n [%s] options [%s]",
00276           dev_name, dev_model_num, dev_serial_num, dev_opt);
00277       
00278       char *dev_name_ptr = dev_name;
00279       char *dev_model_num_ptr = dev_model_num;
00280       char *dev_serial_num_ptr = dev_serial_num;
00281       
00282       while (*dev_name_ptr == ' ')
00283         dev_name_ptr++;
00284       while (*dev_model_num_ptr == ' ')
00285         dev_model_num_ptr++;
00286       while (*dev_serial_num_ptr == ' ')
00287         dev_serial_num_ptr++;
00288 
00289       return (boost::format("%s_%s-%s")%dev_name_ptr%dev_model_num_ptr%dev_serial_num_ptr).str();
00290   }
00291   
00292   int stop()
00293   {
00294     if(running)
00295     {
00296       try
00297       {
00298         imu.closePort();
00299       } catch (microstrain_3dmgx2_imu::Exception& e) {
00300         error_count_++;
00301         ROS_INFO("Exception thrown while stopping IMU. %s", e.what());
00302       }
00303       running = false;
00304     }
00305 
00306     return(0);
00307   }
00308 
00309   int publish_datum()
00310   {
00311     try
00312     {
00313       static double prevtime = 0;
00314       double starttime = ros::Time::now().toSec();
00315       if (prevtime && prevtime - starttime > 0.05)
00316       {
00317         ROS_WARN("Full IMU loop took %f ms. Nominal is 10ms.", 1000 * (prevtime - starttime));
00318         was_slow_ = "Full IMU loop was slow.";
00319         slow_count_++;
00320       }
00321       getData(reading);
00322       double endtime = ros::Time::now().toSec();
00323       if (endtime - starttime > 0.05)
00324       {
00325         ROS_WARN("Gathering data took %f ms. Nominal is 10ms.", 1000 * (endtime - starttime));
00326         was_slow_ = "Full IMU loop was slow.";
00327         slow_count_++;
00328       }
00329       prevtime = starttime;
00330       starttime = ros::Time::now().toSec();
00331       imu_data_pub_.publish(reading);
00332       endtime = ros::Time::now().toSec();
00333       if (endtime - starttime > 0.05)
00334       {
00335         ROS_WARN("Publishing took %f ms. Nominal is 10 ms.", 1000 * (endtime - starttime));
00336         was_slow_ = "Full IMU loop was slow.";
00337         slow_count_++;
00338       }
00339         
00340       freq_diag_.tick();
00341       clearErrorStatus(); 
00342     } catch (microstrain_3dmgx2_imu::Exception& e) {
00343       error_count_++;
00344       usleep(20000); 
00345       if (!ros::isShuttingDown()) 
00346         ROS_WARN("Exception thrown while trying to get the IMU reading. This sometimes happens due to a communication glitch, or if another process is trying to access the IMU port. You may try 'lsof|grep %s' to see if other processes have the port open. %s", port.c_str(), e.what());
00347       return -1;
00348     }
00349 
00350     return(0);
00351   }
00352 
00353   bool spin()
00354   {
00355     while (!ros::isShuttingDown()) 
00356     {
00357       if (start() == 0)
00358       {
00359         while(node_handle_.ok()) {
00360           if(publish_datum() < 0)
00361             break;
00362           self_test_.checkTest();
00363           diagnostic_.update();
00364           ros::spinOnce();
00365         }
00366       } else {
00367         
00368         
00369         usleep(1000000);
00370         self_test_.checkTest();
00371         ros::spinOnce();
00372       }
00373     }
00374 
00375     stop();
00376 
00377     return true;
00378   }
00379 
00380   void publish_is_calibrated()
00381   {
00382     std_msgs::Bool msg;
00383     msg.data = calibrated_;
00384     is_calibrated_pub_.publish(msg);
00385   }
00386 
00387   void pretest(diagnostic_updater::DiagnosticStatusWrapper& status)
00388   {
00389     try
00390     {
00391       imu.closePort();
00392 
00393       status.summary(0, "Device closed successfully.");
00394     } catch (microstrain_3dmgx2_imu::Exception& e) {
00395       status.summary(1, "Failed to close device.");
00396     }
00397   }
00398 
00399   void InterruptionTest(diagnostic_updater::DiagnosticStatusWrapper& status)
00400   {
00401     if (imu_data_pub_.getNumSubscribers() == 0 )
00402       status.summary(0, "No operation interrupted.");
00403     else
00404       status.summary(1, "There were active subscribers.  Running of self test interrupted operations.");
00405   }
00406 
00407   void ConnectTest(diagnostic_updater::DiagnosticStatusWrapper& status)
00408   {
00409     imu.openPort(port.c_str());
00410 
00411     status.summary(0, "Connected successfully.");
00412   }
00413 
00414   void ReadIDTest(diagnostic_updater::DiagnosticStatusWrapper& status)
00415   {
00416     self_test_.setID(getID());
00417     
00418     status.summary(0, "Read Successfully");
00419   }
00420 
00421   void GyroBiasTest(diagnostic_updater::DiagnosticStatusWrapper& status)
00422   {
00423     imu.initGyros(&bias_x_, &bias_y_, &bias_z_);
00424 
00425     status.summary(0, "Successfully calculated gyro biases.");
00426 
00427     status.add("Bias_X", bias_x_);
00428     status.add("Bias_Y", bias_y_);
00429     status.add("Bias_Z", bias_z_);
00430   }
00431 
00432 
00433   void getData(sensor_msgs::Imu& data)
00434   {
00435     uint64_t time;
00436     double accel[3];
00437     double angrate[3];
00438     double orientation[9];
00439 
00440     imu.receiveAccelAngrateOrientation(&time, accel, angrate, orientation);
00441     data.linear_acceleration.x = accel[0];
00442     data.linear_acceleration.y = accel[1];
00443     data.linear_acceleration.z = accel[2];
00444  
00445     data.angular_velocity.x = angrate[0];
00446     data.angular_velocity.y = angrate[1];
00447     data.angular_velocity.z = angrate[2];
00448       
00449     tf::Quaternion quat;
00450     (tf::Matrix3x3(-1,0,0,
00451                  0,1,0,
00452                  0,0,-1)*
00453     tf::Matrix3x3(orientation[0], orientation[3], orientation[6],
00454                  orientation[1], orientation[4], orientation[7],
00455                  orientation[2], orientation[5], orientation[8])).getRotation(quat);
00456     
00457     tf::quaternionTFToMsg(quat, data.orientation);
00458       
00459     data.header.stamp = ros::Time::now().fromNSec(time);
00460   }
00461 
00462 
00463   void StreamedDataTest(diagnostic_updater::DiagnosticStatusWrapper& status)
00464   {
00465     uint64_t time;
00466     double accel[3];
00467     double angrate[3];
00468 
00469     if (!imu.setContinuous(microstrain_3dmgx2_imu::IMU::CMD_ACCEL_ANGRATE))
00470     {
00471       status.summary(2, "Could not start streaming data.");
00472     } else {
00473 
00474       for (int i = 0; i < 100; i++)
00475       {
00476         imu.receiveAccelAngrate(&time, accel, angrate);
00477       }
00478       
00479       imu.stopContinuous();
00480 
00481       status.summary(0, "Data streamed successfully.");
00482     }
00483   }
00484 
00485   void GravityTest(diagnostic_updater::DiagnosticStatusWrapper& status)
00486   {
00487     uint64_t time;
00488     double accel[3];
00489     double angrate[3];
00490 
00491     double grav = 0.0;
00492 
00493     double grav_x = 0.0;
00494     double grav_y = 0.0;
00495     double grav_z = 0.0;
00496 
00497     if (!imu.setContinuous(microstrain_3dmgx2_imu::IMU::CMD_ACCEL_ANGRATE))
00498     {
00499       status.summary(2, "Could not start streaming data.");
00500     } else {
00501 
00502       int num = 200;
00503 
00504       for (int i = 0; i < num; i++)
00505       {
00506         imu.receiveAccelAngrate(&time, accel, angrate);
00507         
00508         grav_x += accel[0];
00509         grav_y += accel[1];
00510         grav_z += accel[2];
00511 
00512       }
00513       
00514       imu.stopContinuous();
00515 
00516       grav += sqrt( pow(grav_x / (double)(num), 2.0) + 
00517                     pow(grav_y / (double)(num), 2.0) + 
00518                     pow(grav_z / (double)(num), 2.0));
00519       
00520       
00521       double err = (grav - 9.796);
00522       
00523       if (fabs(err) < .05)
00524       {
00525         status.summary(0, "Gravity detected correctly.");
00526       } else {
00527         status.summaryf(2, "Measured gravity deviates by %f", err);
00528       }
00529 
00530       status.add("Measured gravity", grav);
00531       status.add("Gravity error", err);
00532     }
00533   }
00534 
00535   void DisconnectTest(diagnostic_updater::DiagnosticStatusWrapper& status)
00536   {
00537     imu.closePort();
00538 
00539     status.summary(0, "Disconnected successfully.");
00540   }
00541 
00542   void ResumeTest(diagnostic_updater::DiagnosticStatusWrapper& status)
00543   {
00544     if (running)
00545     {
00546 
00547       imu.openPort(port.c_str());
00548       freq_diag_.clear();
00549 
00550       if (imu.setContinuous(cmd) != true)
00551       {
00552         status.summary(2, "Failed to resume previous mode of operation.");
00553         return;
00554       }
00555     }
00556 
00557     status.summary(0, "Previous operation resumed successfully.");    
00558   }
00559 
00560   void deviceStatus(diagnostic_updater::DiagnosticStatusWrapper &status)
00561   {
00562     if (!running)
00563       status.summary(2, "IMU is stopped");
00564     else if (!was_slow_.empty())
00565     {
00566       status.summary(1, "Excessive delay");
00567       was_slow_.clear();
00568     }
00569     else
00570       status.summary(0, "IMU is running");
00571 
00572     status.add("Device", port);
00573     status.add("TF frame", frameid_);
00574     status.add("Error count", error_count_);
00575     status.add("Excessive delay", slow_count_);
00576   }
00577 
00578   void calibrationStatus(diagnostic_updater::DiagnosticStatusWrapper& status)
00579   {
00580     if (calibrated_)
00581     {
00582       status.summary(0, "Gyro is calibrated");
00583       status.add("X bias", bias_x_);
00584       status.add("Y bias", bias_y_);
00585       status.add("Z bias", bias_z_);
00586     }
00587     else
00588       status.summary(2, "Gyro not calibrated");
00589       
00590       
00591 
00592   }
00593 
00594   bool addOffset(microstrain_3dmgx2_imu::AddOffset::Request &req, microstrain_3dmgx2_imu::AddOffset::Response &resp)
00595   {
00596     double offset = req.add_offset;
00597     offset_ += offset;
00598 
00599     ROS_INFO("Adding %f to existing IMU time offset.", offset);
00600     ROS_INFO("Total IMU time offset is now %f.", offset_);
00601 
00602     
00603     imu.setFixedOffset(offset_);
00604 
00605     
00606     private_node_handle_.setParam("time_offset", offset_);
00607 
00608     
00609     resp.total_offset = offset_;
00610 
00611     return true;
00612   }
00613 
00614   bool calibrate(std_srvs::Empty::Request &req, std_srvs::Empty::Response &resp)
00615   {
00616     bool old_running = running;
00617 
00618     try
00619     {
00620       calibrate_requested_ = true;
00621       if (old_running)
00622       {
00623         stop();
00624         start(); 
00625       }
00626       else
00627       {
00628         imu.openPort(port.c_str());
00629         doCalibrate();
00630         imu.closePort();
00631       } 
00632     } catch (microstrain_3dmgx2_imu::Exception& e) {
00633       error_count_++;
00634       calibrated_ = false;
00635       publish_is_calibrated();
00636       ROS_ERROR("Exception thrown while calibrating IMU %s", e.what());
00637       stop();
00638       if (old_running)
00639         start(); 
00640       return false;
00641     }
00642     
00643     return true;
00644   }
00645 
00646   void doCalibrate()
00647   { 
00648     ROS_INFO("Calibrating IMU gyros.");
00649     imu.initGyros(&bias_x_, &bias_y_, &bias_z_);
00650     
00651     
00652     if (!imu.setContinuous(microstrain_3dmgx2_imu::IMU::CMD_ACCEL_ANGRATE_ORIENT)){
00653       ROS_ERROR("Could not start streaming data to verify calibration");
00654     } 
00655     else {
00656       double x_rate = 0.0;
00657       double y_rate = 0.0;
00658       double z_rate = 0.0;
00659       size_t count = 0;
00660       getData(reading);
00661       ros::Time start_time = reading.header.stamp;
00662 
00663       while(reading.header.stamp - start_time < ros::Duration(2.0)){
00664         getData(reading);
00665         x_rate += reading.angular_velocity.x;
00666         y_rate += reading.angular_velocity.y;
00667         z_rate += reading.angular_velocity.z;
00668         ++count;
00669       }
00670 
00671       double average_rate = sqrt(x_rate*x_rate + y_rate*y_rate + z_rate*z_rate) / count;
00672 
00673       if (count < 200){
00674         ROS_WARN("Imu: calibration check acquired fewer than 200 samples.");
00675       }
00676       
00677       
00678       if (average_rate < max_drift_rate_) {
00679         ROS_INFO("Imu: calibration check succeeded: average angular drift %f mdeg/sec < %f mdeg/sec", average_rate*180*1000/M_PI, max_drift_rate_*180*1000/M_PI);
00680         calibrated_ = true;
00681         publish_is_calibrated();
00682         ROS_INFO("IMU gyro calibration completed.");
00683         freq_diag_.clear();
00684       }
00685       
00686       else{
00687         calibrated_ = false;
00688         publish_is_calibrated();
00689         ROS_ERROR("Imu: calibration check failed: average angular drift = %f mdeg/sec > %f mdeg/sec", average_rate*180*1000/M_PI, max_drift_rate_*180*1000/M_PI);
00690       }
00691       imu.stopContinuous();
00692     }
00693   }
00694 };
00695 
00696 int
00697 main(int argc, char** argv)
00698 {
00699   ros::init(argc, argv, "microstrain_3dmgx2_node");
00700 
00701   ros::NodeHandle nh;
00702 
00703   ImuNode in(nh);
00704   in.spin();
00705 
00706   return(0);
00707 }