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 from kobuki_msgs.msg import ScanAngle
00022
00023 def wrap_to_pi(x):
00024 a = mod(mod(x,2*pi)+2*pi, 2*pi)
00025 if (a > pi):
00026 a -= 2*pi
00027 return a
00028
00029 class Tester(object):
00030 def __init__(self):
00031 rospy.init_node("gyro_perf")
00032 self.state='INIT'
00033 self.done = False
00034 self.reset_angle = True
00035 self.triggered = False
00036 self.scan_angle = None
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 if self.scan_angle is None:
00100 return
00101 scan_angle = self.scan_angle
00102 if abs(scan_angle) > radians(1.0):
00103 cmd_vel = Twist()
00104 cmd_vel.angular.z = -0.33*scan_angle/abs(scan_angle)
00105 self.pub_velocity.publish(cmd_vel)
00106 self.init_time = rospy.Time.now().to_time()
00107 if self.debug: print 'state:', self.state, ':', scan_angle, '-->', cmd_vel.angular.z
00108 return
00109 else:
00110 cmd_vel = Twist()
00111 cmd_vel.linear.x = 0.0
00112 cmd_vel.angular.z = 0.0
00113 self.pub_velocity.publish(cmd_vel)
00114
00115 if self.elapsed_time > 5.0:
00116 if self.debug: print 'alining ready.'
00117
00118 self.pub_sound.publish(Sound(value=Sound.RECHARGE))
00119 self.state = 'WAIT_TRIGGER'
00120 if self.debug: print 'state:', self.state
00121 return
00122 else:
00123 if self.debug: print 'state:', self.state
00124 return
00125
00126 if self.state == 'WAIT_TRIGGER':
00127 if self.triggered or not self.use_button:
00128 self.pub_sound.publish(Sound(value=Sound.CLEANINGEND))
00129 self.state = 'CALC_ANGLE_PRERUN'
00130 self.reset_angle = True
00131 self.angle_count = 0
00132 if self.debug: print 'state:', self.state
00133 return
00134 else:
00135 return
00136
00137 if self.state == 'CALC_ANGLE_PRERUN':
00138 if self.angle_count >= self.max_sample:
00139 self.angle_prerun = self.angle_avg
00140 if self.debug: print 'angle prerun: ', self.angle_prerun
00141
00142 self.pub_sound.publish(Sound(value=Sound.RECHARGE))
00143
00144 self.state = 'RUNNING'
00145 self.init_time = rospy.Time.now().to_time()
00146 if self.debug: print 'state:', self.state
00147
00148 self.init_imu = True
00149 self.init_angle = 0.0
00150 self.angle = 0.0
00151 self.angle_rate = 0.0
00152 self.diff_angle = 0.0
00153 self.accumulated_angle = 0.0
00154
00155 return
00156 else:
00157 if self.debug: print 'state:', self.state, ':', self.angle_count, self.scan_angle, self.angle_sum, self.angle_avg
00158 return
00159
00160 if self.state == 'RUNNING':
00161 if self.ending_condition():
00162 self.state = 'STOP'
00163 cmd_vel = Twist()
00164 cmd_vel.linear.x = 0.0
00165 cmd_vel.angular.z = 0.0
00166 self.pub_velocity.publish(cmd_vel)
00167 self.running_time = self.elapsed_time
00168 self.init_time = rospy.Time.now().to_time()
00169 if self.debug: print 'state:', self.state
00170 return
00171 else:
00172 cmd_vel = Twist()
00173 cmd_vel.linear.x = self.command_vx
00174 cmd_vel.angular.z = self.command_wz
00175 self.pub_velocity.publish(cmd_vel)
00176 if self.debug: print 'state:', self.state, ':', degrees(self.accumulated_angle), self.test_angle
00177 return
00178
00179 if self.state == 'STOP':
00180 if self.elapsed_time > 5.0:
00181 self.reset_angle = True
00182 self.angle_count = 0
00183 self.state = 'CALC_ANGLE_POSTRUN'
00184 if self.debug: print 'state:', self.state
00185 return
00186 else:
00187 if self.debug: print 'state:', self.state
00188 return
00189
00190 if self.state == 'CALC_ANGLE_POSTRUN':
00191 if self.angle_count >= self.max_sample:
00192 self.angle_postrun = self.angle_avg
00193
00194 angle_gyro = self.accumulated_angle
00195 sign = angle_gyro/abs(angle_gyro)
00196 angle_laser = sign*radians(self.test_angle)+wrap_to_pi(self.angle_postrun - self.angle_prerun)
00197
00198 rospy.loginfo('test_angle: {0} deg'.format(self.test_angle))
00199 rospy.loginfo('test_command: {0} m/s {1} deg/s'.format(self.command_vx, degrees(self.command_wz)))
00200 rospy.loginfo('running_time: {0} s'.format(self.running_time))
00201 rospy.loginfo('')
00202 rospy.loginfo('angle_prerun: {0} deg'.format(degrees(self.angle_prerun)))
00203 rospy.loginfo('angle_postrun: {0} deg'.format(degrees(self.angle_postrun)))
00204 rospy.loginfo('angle_gyro: {0} deg'.format(degrees(angle_gyro)))
00205 rospy.loginfo('angle_laser: {0} deg'.format(degrees(angle_laser)))
00206 rospy.loginfo('')
00207 rospy.loginfo('error: {0} deg/rev'.format(angle_gyro/angle_laser*360.0-360.0))
00208 rospy.loginfo('Done')
00209 self.pub_sound.publish(Sound(value=Sound.CLEANINGSTART))
00210
00211 self.state = 'DONE'
00212 self.done = True
00213 if self.debug: print 'state:', self.state
00214 rospy.signal_shutdown('jobs_done')
00215 time.sleep(5.0)
00216 return
00217 else:
00218 if self.debug: print 'state:', self.state, ':', self.angle_count, self.scan_angle, self.angle_sum, self.angle_avg
00219 return
00220
00221 def imuCallback(self,data):
00222 if rospy.is_shutdown(): return
00223 quat = data.orientation
00224 q = [quat.x, quat.y, quat.z, quat.w]
00225 roll, pitch, yaw = euler_from_quaternion(q)
00226
00227 if self.init_imu:
00228 self.init_angle = yaw
00229 self.init_imu = False
00230
00231 self.diff_angle = wrap_to_pi(yaw - self.init_angle - self.angle)
00232 self.accumulated_angle += self.diff_angle
00233 self.angle = wrap_to_pi(yaw - self.init_angle)
00234 self.angle_rate = data.angular_velocity.z
00235
00236 def angleCallback(self,data):
00237 if rospy.is_shutdown(): return
00238
00239 self.scan_angle = data.scan_angle
00240
00241 if self.reset_angle:
00242 if self.debug: print 'Resetted'
00243 self.angle_count = 0
00244 self.angle_sum = 0
00245 self.scan_angle = 0
00246 self.reset_angle = False
00247
00248 if self.angle_count >= self.max_sample:
00249 return
00250
00251 self.angle_count += 1
00252 self.angle_sum += data.scan_angle
00253 self.angle_avg = self.angle_sum / self.angle_count
00254
00255 def buttonCallback(self,data):
00256 if rospy.is_shutdown(): return
00257 if data.button == ButtonEvent.Button0 and data.state == ButtonEvent.RELEASED:
00258
00259 self.triggered=True
00260 return
00261
00262
00263
00264
00265
00266 if __name__ == '__main__':
00267 print
00268 print "It test gyro on kobuki and print gyro drift errors."
00269 print
00270 try:
00271 instance = Tester()
00272 rospy.spin()
00273 except rospy.ROSInterruptException: pass