calibrate.py
Go to the documentation of this file.
1 #! /usr/bin/python
2 #***********************************************************
3 # Software License Agreement (BSD License)
4 #
5 # Copyright (c) 2008, Willow Garage, Inc.
6 # All rights reserved.
7 #
8 # Redistribution and use in source and binary forms, with or without
9 # modification, are permitted provided that the following conditions
10 # are met:
11 #
12 # * Redistributions of source code must retain the above copyright
13 # notice, this list of conditions and the following disclaimer.
14 # * Redistributions in binary form must reproduce the above
15 # copyright notice, this list of conditions and the following
16 # disclaimer in the documentation and/or other materials provided
17 # with the distribution.
18 # * Neither the name of the Willow Garage nor the names of its
19 # contributors may be used to endorse or promote products derived
20 # from this software without specific prior written permission.
21 #
22 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
32 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33 # POSSIBILITY OF SUCH DAMAGE.
34 #
35 
36 # Author: Wim Meeussen
37 
38 from __future__ import with_statement
39 
40 import roslib; roslib.load_manifest('turtlebot_calibration')
41 import yaml
42 import PyKDL
43 import rospy
44 from sensor_msgs.msg import Imu
45 from nav_msgs.msg import Odometry
46 from geometry_msgs.msg import Twist
47 from turtlebot_calibration.msg import ScanAngle
48 from math import *
49 import threading
50 import dynamic_reconfigure.client
51 import os
52 import subprocess
53 import yaml
54 
55 def quat_to_angle(quat):
56  rot = PyKDL.Rotation.Quaternion(quat.x, quat.y, quat.z, quat.w)
57  return rot.GetRPY()[2]
58 
59 def normalize_angle(angle):
60  res = angle
61  while res > pi:
62  res -= 2.0*pi
63  while res < -pi:
64  res += 2.0*pi
65  return res
66 
67 
69  def __init__(self):
70  self.lock = threading.Lock()
71 
72  self.has_gyro = rospy.get_param("turtlebot_node/has_gyro")
73  rospy.loginfo('has_gyro %s'%self.has_gyro)
74  if self.has_gyro:
75  self.sub_imu = rospy.Subscriber('imu', Imu, self.imu_cb)
76 
77  self.sub_odom = rospy.Subscriber('odom', Odometry, self.odom_cb)
78  self.sub_scan = rospy.Subscriber('scan_angle', ScanAngle, self.scan_cb)
79  self.cmd_pub = rospy.Publisher('cmd_vel', Twist)
80  self.imu_time = rospy.Time()
81  self.odom_time = rospy.Time()
82  self.scan_time = rospy.Time()
83 
84  # params
85  self.inital_wall_angle = rospy.get_param("inital_wall_angle", 0.1)
86  self.imu_calibrate_time = rospy.get_param("imu_calibrate_time", 10.0)
87  self.imu_angle = 0
88  self.imu_time = rospy.Time.now()
89  self.scan_angle = 0
90  self.scan_time = rospy.Time.now()
91  self.odom_angle = 0
92  self.odom_time = rospy.Time.now()
93 
94  def calibrate(self, speed, imu_drift=0):
95  # rotate 360 degrees
96  (imu_start_angle, odom_start_angle, scan_start_angle,
97  imu_start_time, odom_start_time, scan_start_time) = self.sync_timestamps()
98  last_angle = odom_start_angle
99  turn_angle = 0
100  while turn_angle < 2*pi:
101  if rospy.is_shutdown():
102  return
103  cmd = Twist()
104  cmd.angular.z = speed
105  self.cmd_pub.publish(cmd)
106  rospy.sleep(0.1)
107  with self.lock:
108  delta_angle = normalize_angle(self.odom_angle - last_angle)
109  turn_angle += delta_angle
110  last_angle = self.odom_angle
111  self.cmd_pub.publish(Twist())
112 
113  (imu_end_angle, odom_end_angle, scan_end_angle,
114  imu_end_time, odom_end_time, scan_end_time) = self.sync_timestamps()
115 
116  scan_delta = 2*pi + normalize_angle(scan_end_angle - scan_start_angle)
117 
118  odom_delta = 2*pi + normalize_angle(odom_end_angle - odom_start_angle)
119  rospy.loginfo('Odom error: %f percent'%(100.0*((odom_delta/scan_delta)-1.0)))
120 
121  if self.has_gyro:
122  imu_delta = 2*pi + normalize_angle(imu_end_angle - imu_start_angle) - imu_drift*(imu_end_time - imu_start_time).to_sec()
123  rospy.loginfo('Imu error: %f percent'%(100.0*((imu_delta/scan_delta)-1.0)))
124  imu_result = imu_delta/scan_delta
125  else:
126  imu_result = None
127 
128  return (imu_result, odom_delta/scan_delta)
129 
130 
131  def imu_drift(self):
132  if not self.has_gyro:
133  return 0
134  # estimate imu drift
135  rospy.loginfo('Estimating imu drift')
136  (imu_start_angle, odom_start_angle, scan_start_angle,
137  imu_start_time, odom_start_time, scan_start_time) = self.sync_timestamps()
138  rospy.sleep(self.imu_calibrate_time)
139  (imu_end_angle, odom_end_angle, scan_end_angle,
140  imu_end_time, odom_end_time, scan_end_time) = self.sync_timestamps()
141 
142  imu_drift = normalize_angle(imu_end_angle - imu_start_angle) / ((imu_end_time - imu_start_time).to_sec())
143  rospy.loginfo(' ... imu drift is %f degrees per second'%(imu_drift*180.0/pi))
144  return imu_drift
145 
146 
147  def align(self):
148  self.sync_timestamps()
149  rospy.loginfo("Aligning base with wall")
150  with self.lock:
151  angle = self.scan_angle
152  cmd = Twist()
153 
154  while angle < -self.inital_wall_angle or angle > self.inital_wall_angle:
155  if rospy.is_shutdown():
156  exit(0)
157  if angle > 0:
158  cmd.angular.z = -0.3
159  else:
160  cmd.angular.z = 0.3
161  self.cmd_pub.publish(cmd)
162  rospy.sleep(0.05)
163  with self.lock:
164  angle = self.scan_angle
165 
166 
167 
168  def sync_timestamps(self, start_time=None):
169  if not start_time:
170  start_time = rospy.Time.now() + rospy.Duration(0.5)
171  while not rospy.is_shutdown():
172  rospy.sleep(0.3)
173  with self.lock:
174  if self.imu_time < start_time and self.has_gyro:
175  rospy.loginfo("Still waiting for imu")
176  elif self.odom_time < start_time:
177  rospy.loginfo("Still waiting for odom")
178  elif self.scan_time < start_time:
179  rospy.loginfo("Still waiting for scan")
180  else:
181  return (self.imu_angle, self.odom_angle, self.scan_angle,
182  self.imu_time, self.odom_time, self.scan_time)
183  exit(0)
184 
185 
186  def imu_cb(self, msg):
187  with self.lock:
188  angle = quat_to_angle(msg.orientation)
189  self.imu_angle = angle
190  self.imu_time = msg.header.stamp
191 
192  def odom_cb(self, msg):
193  with self.lock:
194  angle = quat_to_angle(msg.pose.pose.orientation)
195  self.odom_angle = angle
196  self.odom_time = msg.header.stamp
197 
198  def scan_cb(self, msg):
199  with self.lock:
200  angle = msg.scan_angle
201  self.scan_angle = angle
202  self.scan_time = msg.header.stamp
203 
205  usbpath = subprocess.check_output("readlink -f /sys/class/tty/ttyUSB0", shell=True)
206  usbpath = usbpath.strip()
207  if len(usbpath) == 0:
208  return None
209  serialid = ""
210  try:
211  f = open(usbpath + "/../../../../serial", "r")
212  serialid = f.read().strip()
213  f.close()
214  except:
215  pass
216  try:
217  f = open(usbpath + "/../../../../idVendor", "r")
218  serialid += f.read().strip()
219  f.close()
220  f = open(usbpath + "/../../../../idProduct", "r")
221  serialid += f.read().strip()
222  f.close()
223  except:
224  pass
225  if len(serialid.strip()) == 0:
226  return None
227  return serialid
228 
230  ret = subprocess.check_output("lsusb -v -d 045e:02ae | grep Serial | awk '{print $3}'", shell=True)
231  if len(ret) > 0:
232  return ret.strip()
233  return None
234 
235 def getCurrentParams(drclient):
236  allparams = drclient.get_configuration()
237  return (allparams['gyro_scale_correction'], allparams['odom_angular_scale_correction'], allparams['gyro_measurement_range'])
238 
239 def writeParams(drclient, newparams):
240  r = drclient.update_configuration(newparams)
241  rospy.loginfo("Automatically updated the params in the current running instance of ROS, no need to restart.")
242 
244  kinect_serial = get_kinect_serial()
245  if kinect_serial is None:
246  kinect_serial = get_usb_to_serial_id() # can't find a kinect, attempt to use the usb to serial convert's id as a backup
247  if kinect_serial is None:
248  return
249  ros_home = os.environ.get('ROS_HOME')
250  if ros_home is None:
251  ros_home = "~/.ros"
252  calib_dir = os.path.expanduser(ros_home +"/turtlebot_create/")
253  calib_file = calib_dir +str(kinect_serial) + ".yaml"
254  # if the file exists, load into a dict, update the new params, and then save
255  if os.path.isfile(calib_file):
256  f = open(calib_file, 'r')
257  docs = yaml.load_all(f)
258  d = docs.next()
259  for k,v in newparams.iteritems():
260  d[k] = v
261  newparams = d
262  f.close()
263  try:
264  os.makedirs(calib_dir)
265  except:
266  pass
267  with open(calib_file, 'w') as outfile:
268  outfile.write( yaml.dump(newparams, default_flow_style=False) )
269  rospy.loginfo("Saved the params to the calibration file: %s" % calib_file)
270 
271 def writeParamsToLaunchFile(gyro, odom, gyro_range):
272  try:
273  f = open("/etc/ros/distro/turtlebot.launch", "r")
274  # this is totally NOT the best way to solve this problem.
275  foo = []
276  for lines in f:
277  if "turtlebot_node/gyro_scale_correction" in lines:
278  foo.append(" <param name=\"turtlebot_node/gyro_scale_correction\" value=\"%f\"/>\n" % gyro)
279  elif "turtlebot_node/odom_angular_scale_correction" in lines:
280  foo.append(" <param name=\"turtlebot_node/odom_angular_scale_correction\" value=\"%f\"/>\n" % odom)
281  elif "turtlebot_node/gyro_measurement_range" in lines:
282  foo.append(" <param name=\"turtlebot_node/gyro_measurement_range\" value=\"%f\"/>\n" % gyro_range)
283  else:
284  foo.append(lines)
285  f.close()
286 
287  # and... write!
288  f = open("/etc/ros/distro/turtlebot.launch", "w")
289  for i in foo:
290  f.write(i)
291  f.close()
292  rospy.loginfo("Automatically updated turtlebot.launch, please restart the turtlebot service.")
293  except:
294  rospy.loginfo("Could not automatically update turtlebot.launch, please manually update it.")
295 
296 def warnAboutGyroRange(drclient):
297  params = getCurrentParams(drclient)
298  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")
299  rospy.logwarn("******* turtlebot_node/gyro_measurement_range is currently set to: %d ******" % params[2])
300 
301 
302 def main():
303  rospy.init_node('scan_to_angle')
304  robot = CalibrateRobot()
305  imu_res = 1.0
306 
307  drclient = dynamic_reconfigure.client.Client("turtlebot_node")
308  warnAboutGyroRange(drclient)
309 
310  imu_drift = robot.imu_drift()
311  imu_corr = []
312  odom_corr = []
313  for speed in (0.3, 0.7, 1.0, 1.5):
314  robot.align()
315  (imu, odom) = robot.calibrate(speed, imu_drift)
316  if imu:
317  imu_corr.append(imu)
318  odom_corr.append(odom)
319 
320  (prev_gyro, prev_odom, gyro_range) = getCurrentParams(drclient)
321  if len(imu_corr)>0:
322  imu_res = prev_gyro * (1.0/(sum(imu_corr)/len(imu_corr)))
323  rospy.loginfo("Set the 'turtlebot_node/gyro_scale_correction' parameter to %f"%imu_res)
324 
325  odom_res = prev_odom * (1.0/(sum(odom_corr)/len(odom_corr)))
326  rospy.loginfo("Set the 'turtlebot_node/odom_angular_scale_correction' parameter to %f"%odom_res)
327  writeParamsToLaunchFile(imu_res, odom_res, gyro_range)
328 
329  newparams = {'gyro_scale_correction' : imu_res, 'odom_angular_scale_correction' : odom_res, 'gyro_measurement_range' : gyro_range}
331  writeParams(drclient, newparams)
332 
333 if __name__ == '__main__':
334  main()
def quat_to_angle(quat)
Definition: calibrate.py:55
def get_usb_to_serial_id()
Definition: calibrate.py:204
def odom_cb(self, msg)
Definition: calibrate.py:192
def get_kinect_serial()
Definition: calibrate.py:229
def main()
Definition: calibrate.py:302
def warnAboutGyroRange(drclient)
Definition: calibrate.py:296
def imu_cb(self, msg)
Definition: calibrate.py:186
def writeParams(drclient, newparams)
Definition: calibrate.py:239
def scan_cb(self, msg)
Definition: calibrate.py:198
def writeParamsToCalibrationFile(newparams)
Definition: calibrate.py:243
def writeParamsToLaunchFile(gyro, odom, gyro_range)
Definition: calibrate.py:271
def sync_timestamps(self, start_time=None)
Definition: calibrate.py:168
def getCurrentParams(drclient)
Definition: calibrate.py:235
def calibrate(self, speed, imu_drift=0)
Definition: calibrate.py:94
def normalize_angle(angle)
Definition: calibrate.py:59


turtlebot_calibration
Author(s): Wim Meeussen
autogenerated on Mon Jun 10 2019 15:44:05