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
00037
00038 from __future__ import with_statement
00039
00040 import roslib; roslib.load_manifest('turtlebot_calibration')
00041 import yaml
00042 import PyKDL
00043 import rospy
00044 from sensor_msgs.msg import Imu
00045 from nav_msgs.msg import Odometry
00046 from geometry_msgs.msg import Twist
00047 from turtlebot_calibration.msg import ScanAngle
00048 from math import *
00049 import threading
00050 import dynamic_reconfigure.client
00051 import os
00052 import subprocess
00053 import yaml
00054
00055 def quat_to_angle(quat):
00056 rot = PyKDL.Rotation.Quaternion(quat.x, quat.y, quat.z, quat.w)
00057 return rot.GetRPY()[2]
00058
00059 def normalize_angle(angle):
00060 res = angle
00061 while res > pi:
00062 res -= 2.0*pi
00063 while res < -pi:
00064 res += 2.0*pi
00065 return res
00066
00067
00068 class CalibrateRobot:
00069 def __init__(self):
00070 self.lock = threading.Lock()
00071
00072 self.has_gyro = rospy.get_param("turtlebot_node/has_gyro")
00073 rospy.loginfo('has_gyro %s'%self.has_gyro)
00074 if self.has_gyro:
00075 self.sub_imu = rospy.Subscriber('imu', Imu, self.imu_cb)
00076
00077 self.sub_odom = rospy.Subscriber('odom', Odometry, self.odom_cb)
00078 self.sub_scan = rospy.Subscriber('scan_angle', ScanAngle, self.scan_cb)
00079 self.cmd_pub = rospy.Publisher('cmd_vel', Twist)
00080 self.imu_time = rospy.Time()
00081 self.odom_time = rospy.Time()
00082 self.scan_time = rospy.Time()
00083
00084
00085 self.inital_wall_angle = rospy.get_param("inital_wall_angle", 0.1)
00086 self.imu_calibrate_time = rospy.get_param("imu_calibrate_time", 10.0)
00087 self.imu_angle = 0
00088 self.imu_time = rospy.Time.now()
00089 self.scan_angle = 0
00090 self.scan_time = rospy.Time.now()
00091 self.odom_angle = 0
00092 self.odom_time = rospy.Time.now()
00093
00094 def calibrate(self, speed, imu_drift=0):
00095
00096 (imu_start_angle, odom_start_angle, scan_start_angle,
00097 imu_start_time, odom_start_time, scan_start_time) = self.sync_timestamps()
00098 last_angle = odom_start_angle
00099 turn_angle = 0
00100 while turn_angle < 2*pi:
00101 if rospy.is_shutdown():
00102 return
00103 cmd = Twist()
00104 cmd.angular.z = speed
00105 self.cmd_pub.publish(cmd)
00106 rospy.sleep(0.1)
00107 with self.lock:
00108 delta_angle = normalize_angle(self.odom_angle - last_angle)
00109 turn_angle += delta_angle
00110 last_angle = self.odom_angle
00111 self.cmd_pub.publish(Twist())
00112
00113 (imu_end_angle, odom_end_angle, scan_end_angle,
00114 imu_end_time, odom_end_time, scan_end_time) = self.sync_timestamps()
00115
00116 scan_delta = 2*pi + normalize_angle(scan_end_angle - scan_start_angle)
00117
00118 odom_delta = 2*pi + normalize_angle(odom_end_angle - odom_start_angle)
00119 rospy.loginfo('Odom error: %f percent'%(100.0*((odom_delta/scan_delta)-1.0)))
00120
00121 if self.has_gyro:
00122 imu_delta = 2*pi + normalize_angle(imu_end_angle - imu_start_angle) - imu_drift*(imu_end_time - imu_start_time).to_sec()
00123 rospy.loginfo('Imu error: %f percent'%(100.0*((imu_delta/scan_delta)-1.0)))
00124 imu_result = imu_delta/scan_delta
00125 else:
00126 imu_result = None
00127
00128 return (imu_result, odom_delta/scan_delta)
00129
00130
00131 def imu_drift(self):
00132 if not self.has_gyro:
00133 return 0
00134
00135 rospy.loginfo('Estimating imu drift')
00136 (imu_start_angle, odom_start_angle, scan_start_angle,
00137 imu_start_time, odom_start_time, scan_start_time) = self.sync_timestamps()
00138 rospy.sleep(self.imu_calibrate_time)
00139 (imu_end_angle, odom_end_angle, scan_end_angle,
00140 imu_end_time, odom_end_time, scan_end_time) = self.sync_timestamps()
00141
00142 imu_drift = normalize_angle(imu_end_angle - imu_start_angle) / ((imu_end_time - imu_start_time).to_sec())
00143 rospy.loginfo(' ... imu drift is %f degrees per second'%(imu_drift*180.0/pi))
00144 return imu_drift
00145
00146
00147 def align(self):
00148 self.sync_timestamps()
00149 rospy.loginfo("Aligning base with wall")
00150 with self.lock:
00151 angle = self.scan_angle
00152 cmd = Twist()
00153
00154 while angle < -self.inital_wall_angle or angle > self.inital_wall_angle:
00155 if rospy.is_shutdown():
00156 exit(0)
00157 if angle > 0:
00158 cmd.angular.z = -0.3
00159 else:
00160 cmd.angular.z = 0.3
00161 self.cmd_pub.publish(cmd)
00162 rospy.sleep(0.05)
00163 with self.lock:
00164 angle = self.scan_angle
00165
00166
00167
00168 def sync_timestamps(self, start_time=None):
00169 if not start_time:
00170 start_time = rospy.Time.now() + rospy.Duration(0.5)
00171 while not rospy.is_shutdown():
00172 rospy.sleep(0.3)
00173 with self.lock:
00174 if self.imu_time < start_time and self.has_gyro:
00175 rospy.loginfo("Still waiting for imu")
00176 elif self.odom_time < start_time:
00177 rospy.loginfo("Still waiting for odom")
00178 elif self.scan_time < start_time:
00179 rospy.loginfo("Still waiting for scan")
00180 else:
00181 return (self.imu_angle, self.odom_angle, self.scan_angle,
00182 self.imu_time, self.odom_time, self.scan_time)
00183 exit(0)
00184
00185
00186 def imu_cb(self, msg):
00187 with self.lock:
00188 angle = quat_to_angle(msg.orientation)
00189 self.imu_angle = angle
00190 self.imu_time = msg.header.stamp
00191
00192 def odom_cb(self, msg):
00193 with self.lock:
00194 angle = quat_to_angle(msg.pose.pose.orientation)
00195 self.odom_angle = angle
00196 self.odom_time = msg.header.stamp
00197
00198 def scan_cb(self, msg):
00199 with self.lock:
00200 angle = msg.scan_angle
00201 self.scan_angle = angle
00202 self.scan_time = msg.header.stamp
00203
00204 def get_usb_to_serial_id():
00205 usbpath = subprocess.check_output("readlink -f /sys/class/tty/ttyUSB0", shell=True)
00206 usbpath = usbpath.strip()
00207 if len(usbpath) == 0:
00208 return None
00209 serialid = ""
00210 try:
00211 f = open(usbpath + "/../../../../serial", "r")
00212 serialid = f.read().strip()
00213 f.close()
00214 except:
00215 pass
00216 try:
00217 f = open(usbpath + "/../../../../idVendor", "r")
00218 serialid += f.read().strip()
00219 f.close()
00220 f = open(usbpath + "/../../../../idProduct", "r")
00221 serialid += f.read().strip()
00222 f.close()
00223 except:
00224 pass
00225 if len(serialid.strip()) == 0:
00226 return None
00227 return serialid
00228
00229 def get_kinect_serial():
00230 ret = subprocess.check_output("lsusb -v -d 045e:02ae | grep Serial | awk '{print $3}'", shell=True)
00231 if len(ret) > 0:
00232 return ret.strip()
00233 return None
00234
00235 def getCurrentParams(drclient):
00236 allparams = drclient.get_configuration()
00237 return (allparams['gyro_scale_correction'], allparams['odom_angular_scale_correction'], allparams['gyro_measurement_range'])
00238
00239 def writeParams(drclient, newparams):
00240 r = drclient.update_configuration(newparams)
00241 rospy.loginfo("Automatically updated the params in the current running instance of ROS, no need to restart.")
00242
00243 def writeParamsToCalibrationFile(newparams):
00244 kinect_serial = get_kinect_serial()
00245 if kinect_serial is None:
00246 kinect_serial = get_usb_to_serial_id()
00247 if kinect_serial is None:
00248 return
00249 ros_home = os.environ.get('ROS_HOME')
00250 if ros_home is None:
00251 ros_home = "~/.ros"
00252 calib_dir = os.path.expanduser(ros_home +"/turtlebot_create/")
00253 calib_file = calib_dir +str(kinect_serial) + ".yaml"
00254
00255 if os.path.isfile(calib_file):
00256 f = open(calib_file, 'r')
00257 docs = yaml.load_all(f)
00258 d = docs.next()
00259 for k,v in newparams.iteritems():
00260 d[k] = v
00261 newparams = d
00262 f.close()
00263 try:
00264 os.makedirs(calib_dir)
00265 except:
00266 pass
00267 with open(calib_file, 'w') as outfile:
00268 outfile.write( yaml.dump(newparams, default_flow_style=False) )
00269 rospy.loginfo("Saved the params to the calibration file: %s" % calib_file)
00270
00271 def writeParamsToLaunchFile(gyro, odom, gyro_range):
00272 try:
00273 f = open("/etc/ros/distro/turtlebot.launch", "r")
00274
00275 foo = []
00276 for lines in f:
00277 if "turtlebot_node/gyro_scale_correction" in lines:
00278 foo.append(" <param name=\"turtlebot_node/gyro_scale_correction\" value=\"%f\"/>\n" % gyro)
00279 elif "turtlebot_node/odom_angular_scale_correction" in lines:
00280 foo.append(" <param name=\"turtlebot_node/odom_angular_scale_correction\" value=\"%f\"/>\n" % odom)
00281 elif "turtlebot_node/gyro_measurement_range" in lines:
00282 foo.append(" <param name=\"turtlebot_node/gyro_measurement_range\" value=\"%f\"/>\n" % gyro_range)
00283 else:
00284 foo.append(lines)
00285 f.close()
00286
00287
00288 f = open("/etc/ros/distro/turtlebot.launch", "w")
00289 for i in foo:
00290 f.write(i)
00291 f.close()
00292 rospy.loginfo("Automatically updated turtlebot.launch, please restart the turtlebot service.")
00293 except:
00294 rospy.loginfo("Could not automatically update turtlebot.launch, please manually update it.")
00295
00296 def warnAboutGyroRange(drclient):
00297 params = getCurrentParams(drclient)
00298 rospy.logwarn("***** If you have not manually set the gyro range parameter you must do so before running calibration. Cancel this run and see http://wiki.ros.org/turtlebot_calibration/Tutorials/Calibrate%20Odometry%20and%20Gyro")
00299 rospy.logwarn("******* turtlebot_node/gyro_measurement_range is currently set to: %d ******" % params[2])
00300
00301
00302 def main():
00303 rospy.init_node('scan_to_angle')
00304 robot = CalibrateRobot()
00305 imu_res = 1.0
00306
00307 drclient = dynamic_reconfigure.client.Client("turtlebot_node")
00308 warnAboutGyroRange(drclient)
00309
00310 imu_drift = robot.imu_drift()
00311 imu_corr = []
00312 odom_corr = []
00313 for speed in (0.3, 0.7, 1.0, 1.5):
00314 robot.align()
00315 (imu, odom) = robot.calibrate(speed, imu_drift)
00316 if imu:
00317 imu_corr.append(imu)
00318 odom_corr.append(odom)
00319
00320 (prev_gyro, prev_odom, gyro_range) = getCurrentParams(drclient)
00321 if len(imu_corr)>0:
00322 imu_res = prev_gyro * (1.0/(sum(imu_corr)/len(imu_corr)))
00323 rospy.loginfo("Set the 'turtlebot_node/gyro_scale_correction' parameter to %f"%imu_res)
00324
00325 odom_res = prev_odom * (1.0/(sum(odom_corr)/len(odom_corr)))
00326 rospy.loginfo("Set the 'turtlebot_node/odom_angular_scale_correction' parameter to %f"%odom_res)
00327 writeParamsToLaunchFile(imu_res, odom_res, gyro_range)
00328
00329 newparams = {'gyro_scale_correction' : imu_res, 'odom_angular_scale_correction' : odom_res, 'gyro_measurement_range' : gyro_range}
00330 writeParamsToCalibrationFile(newparams)
00331 writeParams(drclient, newparams)
00332
00333 if __name__ == '__main__':
00334 main()