00001
00002
00003
00004 import roslib; roslib.load_manifest('kobuki_testsuite')
00005 import rospy
00006
00007 import time
00008 from math import pi
00009 from kobuki_msgs.msg import ButtonEvent
00010 from kobuki_msgs.msg import Sound
00011 from geometry_msgs.msg import Twist
00012
00013 from sensor_msgs.msg import Imu
00014 from geometry_msgs.msg import Quaternion
00015 from geometry_msgs.msg import Pose2D
00016
00017 from tf.transformations import euler_from_quaternion
00018 from math import degrees, radians
00019 from numpy import mod
00020
00021
00022 from kobuki_testsuite.msg import ScanAngle
00023
00024 def wrap_to_pi(x):
00025 a = mod(mod(x,2*pi)+2*pi, 2*pi)
00026 if (a > pi):
00027 a -= 2*pi
00028 return a
00029
00030 class Tester(object):
00031 def __init__(self):
00032 rospy.init_node("gyro_perf")
00033 self.state='INIT'
00034 self.done = False
00035 self.reset_angle = True
00036 self.triggered = False
00037
00038 self.debug = rospy.get_param('~debug', False)
00039
00040 self.command_vx = rospy.get_param('~command_vx', 0.1)
00041 self.command_wz = radians(rospy.get_param('~command_wz', 5.0))
00042
00043 if self.debug: print self.command_vx, self.command_wz
00044
00045 self.init_imu = True
00046 self.init_angle = 0.0
00047 self.angle = 0.0
00048 self.angle_rate = 0.0
00049 self.diff_angle = 0.0
00050 self.accumulated_angle = 0.0
00051
00052 self.init_time = rospy.Time.now().to_time()
00053 if self.debug: print 'init time: {0:2.4f}'.format(self.init_time)
00054
00055 self.test_angle = rospy.get_param('~test_angle', 360.0)
00056 self.max_sample = rospy.get_param('~max_sample', 500)
00057
00058 self.use_button = rospy.get_param('~use_button', False)
00059
00060 self.sub_angle = rospy.Subscriber("angle_abs", ScanAngle, self.angleCallback)
00061 self.sub_gyro = rospy.Subscriber("imu_data", Imu, self.imuCallback)
00062 self.sub_button = rospy.Subscriber("button", ButtonEvent, self.buttonCallback)
00063 self.pub_velocity = rospy.Publisher("cmd_vel", Twist)
00064 self.pub_sound = rospy.Publisher("sound", Sound)
00065
00066 if self.debug: print 'state:', self.state
00067 self.state='WAIT_CONNECTION'
00068 if self.debug: print 'state:', self.state
00069 rospy.Timer(rospy.Duration.from_sec(0.02), self.timerCallback)
00070
00071 def ending_condition(self):
00072 cond = abs(self.accumulated_angle) >= abs(radians(self.test_angle))
00073 return cond
00074
00075 def timerCallback(self, event):
00076 if rospy.is_shutdown(): return
00077 if self.done: return
00078
00079 self.elapsed_time = event.current_real.to_time()-self.init_time
00080 if event.last_real == None:
00081 event.last_real = rospy.Time.from_sec(0.0)
00082 event.last_expected = rospy.Time.from_sec(0.0)
00083 event.last_duration = 0.0
00084
00085 if self.state == 'WAIT_CONNECTION':
00086 if self.sub_angle.get_num_connections() > 0 and\
00087 self.sub_gyro.get_num_connections() > 0 and\
00088 self.pub_velocity.get_num_connections() > 0:
00089 if self.debug: print 'connection ready.'
00090
00091 self.state = 'ALIGNING'
00092 if self.debug: print 'state:', self.state
00093 return
00094 else:
00095 if self.debug: print 'not ready yet.'
00096 return
00097
00098 if self.state == "ALIGNING":
00099 scan_angle = self.scan_angle
00100 if abs(scan_angle) > radians(1.0):
00101 cmd_vel = Twist()
00102 cmd_vel.angular.z = -0.33*scan_angle/abs(scan_angle)
00103 self.pub_velocity.publish(cmd_vel)
00104 self.init_time = rospy.Time.now().to_time()
00105 if self.debug: print 'state:', self.state, ':', scan_angle, '-->', cmd_vel.angular.z
00106 return
00107 else:
00108 cmd_vel = Twist()
00109 cmd_vel.linear.x = 0.0
00110 cmd_vel.angular.z = 0.0
00111 self.pub_velocity.publish(cmd_vel)
00112
00113 if self.elapsed_time > 5.0:
00114 if self.debug: print 'alining ready.'
00115
00116 self.pub_sound.publish(Sound(value=Sound.RECHARGE))
00117 self.state = 'WAIT_TRIGGER'
00118 if self.debug: print 'state:', self.state
00119 return
00120 else:
00121 if self.debug: print 'state:', self.state
00122 return
00123
00124 if self.state == 'WAIT_TRIGGER':
00125 if self.triggered or not self.use_button:
00126 self.pub_sound.publish(Sound(value=Sound.CLEANINGEND))
00127 self.state = 'CALC_ANGLE_PRERUN'
00128 self.reset_angle = True
00129 self.angle_count = 0
00130 if self.debug: print 'state:', self.state
00131 return
00132 else:
00133 return
00134
00135 if self.state == 'CALC_ANGLE_PRERUN':
00136 if self.angle_count >= self.max_sample:
00137 self.angle_prerun = self.angle_avg
00138 if self.debug: print 'angle prerun: ', self.angle_prerun
00139
00140 self.pub_sound.publish(Sound(value=Sound.RECHARGE))
00141
00142 self.state = 'RUNNING'
00143 self.init_time = rospy.Time.now().to_time()
00144 if self.debug: print 'state:', self.state
00145
00146 self.init_imu = True
00147 self.init_angle = 0.0
00148 self.angle = 0.0
00149 self.angle_rate = 0.0
00150 self.diff_angle = 0.0
00151 self.accumulated_angle = 0.0
00152
00153 return
00154 else:
00155 if self.debug: print 'state:', self.state, ':', self.angle_count, self.scan_angle, self.angle_sum, self.angle_avg
00156 return
00157
00158 if self.state == 'RUNNING':
00159 if self.ending_condition():
00160 self.state = 'STOP'
00161 cmd_vel = Twist()
00162 cmd_vel.linear.x = 0.0
00163 cmd_vel.angular.z = 0.0
00164 self.pub_velocity.publish(cmd_vel)
00165 self.running_time = self.elapsed_time
00166 self.init_time = rospy.Time.now().to_time()
00167 if self.debug: print 'state:', self.state
00168 return
00169 else:
00170 cmd_vel = Twist()
00171 cmd_vel.linear.x = self.command_vx
00172 cmd_vel.angular.z = self.command_wz
00173 self.pub_velocity.publish(cmd_vel)
00174 if self.debug: print 'state:', self.state, ':', degrees(self.accumulated_angle), self.test_angle
00175 return
00176
00177 if self.state == 'STOP':
00178 if self.elapsed_time > 5.0:
00179 self.reset_angle = True
00180 self.angle_count = 0
00181 self.state = 'CALC_ANGLE_POSTRUN'
00182 if self.debug: print 'state:', self.state
00183 return
00184 else:
00185 if self.debug: print 'state:', self.state
00186 return
00187
00188 if self.state == 'CALC_ANGLE_POSTRUN':
00189 if self.angle_count >= self.max_sample:
00190 self.angle_postrun = self.angle_avg
00191
00192 angle_gyro = self.accumulated_angle
00193 sign = angle_gyro/abs(angle_gyro)
00194 angle_laser = sign*radians(self.test_angle)+wrap_to_pi(self.angle_postrun - self.angle_prerun)
00195
00196 rospy.loginfo('test_angle: {0} deg'.format(self.test_angle))
00197 rospy.loginfo('test_command: {0} m/s {1} deg/s'.format(self.command_vx, degrees(self.command_wz)))
00198 rospy.loginfo('running_time: {0} s'.format(self.running_time))
00199 rospy.loginfo('')
00200 rospy.loginfo('angle_prerun: {0} deg'.format(degrees(self.angle_prerun)))
00201 rospy.loginfo('angle_postrun: {0} deg'.format(degrees(self.angle_postrun)))
00202 rospy.loginfo('angle_gyro: {0} deg'.format(degrees(angle_gyro)))
00203 rospy.loginfo('angle_laser: {0} deg'.format(degrees(angle_laser)))
00204 rospy.loginfo('')
00205 rospy.loginfo('error: {0} deg/rev'.format(angle_gyro/angle_laser*360.0-360.0))
00206 rospy.loginfo('Done')
00207 self.pub_sound.publish(Sound(value=Sound.CLEANINGSTART))
00208
00209 self.state = 'DONE'
00210 self.done = True
00211 if self.debug: print 'state:', self.state
00212 rospy.signal_shutdown('jobs_done')
00213 time.sleep(5.0)
00214 return
00215 else:
00216 if self.debug: print 'state:', self.state, ':', self.angle_count, self.scan_angle, self.angle_sum, self.angle_avg
00217 return
00218
00219 def imuCallback(self,data):
00220 if rospy.is_shutdown(): return
00221 quat = data.orientation
00222 q = [quat.x, quat.y, quat.z, quat.w]
00223 roll, pitch, yaw = euler_from_quaternion(q)
00224
00225 if self.init_imu:
00226 self.init_angle = yaw
00227 self.init_imu = False
00228
00229 self.diff_angle = wrap_to_pi(yaw - self.init_angle - self.angle)
00230 self.accumulated_angle += self.diff_angle
00231 self.angle = wrap_to_pi(yaw - self.init_angle)
00232 self.angle_rate = data.angular_velocity.z
00233
00234 def angleCallback(self,data):
00235 if rospy.is_shutdown(): return
00236
00237 self.scan_angle = data.scan_angle
00238
00239 if self.reset_angle:
00240 if self.debug: print 'Resetted'
00241 self.angle_count = 0
00242 self.angle_sum = 0
00243 self.scan_angle = 0
00244 self.reset_angle = False
00245
00246 if self.angle_count >= self.max_sample:
00247 return
00248
00249 self.angle_count += 1
00250 self.angle_sum += data.scan_angle
00251 self.angle_avg = self.angle_sum / self.angle_count
00252
00253 def buttonCallback(self,data):
00254 if rospy.is_shutdown(): return
00255 if data.button == ButtonEvent.Button0 and data.state == ButtonEvent.RELEASED:
00256
00257 self.triggered=True
00258 return
00259
00260
00261
00262
00263
00264 if __name__ == '__main__':
00265 print
00266 print "It test gyro on kobuki and print gyro drift errors."
00267 print
00268 try:
00269 instance = Tester()
00270 rospy.spin()
00271 except rospy.ROSInterruptException: pass