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 This script is used to profile the robot base. It commands a particular
00036 velocity to the base_controller and then plots the actual outputs.
00037 """
00038
00039 import argparse
00040 import time
00041 import numpy
00042 from math import sin, cos, atan2, hypot, fabs, sqrt, pi
00043
00044 import rospy
00045 from geometry_msgs.msg import Twist
00046 from nav_msgs.msg import Odometry
00047 from std_msgs.msg import Float64
00048 from tf.transformations import quaternion_multiply, quaternion_about_axis, quaternion_matrix, translation_matrix
00049 from sensor_msgs.msg import JointState
00050
00051
00052 class BaseController:
00053 '''
00054 Units:
00055 - x is m/s, accel_x is m/s^2
00056 - r is rad/s, accel_r is rad/s^2
00057
00058 '''
00059 def __init__(self):
00060 '''
00061 @brief Instantiate each joint's publisher that publishes Float64 as
00062 position/velocity (velocity for wheels that rotate about pitch,
00063 position for yaw).
00064 '''
00065 self.sub_cmd_vel = rospy.Subscriber("cmd_vel", Twist, self.cmdCb)
00066 self.sub_state = rospy.Subscriber("joint_states", JointState, self.stateCb)
00067 self.pub_bl_r = rospy.Publisher("bl_rotation_joint_position_controller/command", Float64, queue_size=1)
00068 self.pub_br_r = rospy.Publisher("br_rotation_joint_position_controller/command", Float64, queue_size=1)
00069 self.pub_fl_r = rospy.Publisher("fl_rotation_joint_position_controller/command", Float64, queue_size=1)
00070 self.pub_fr_r = rospy.Publisher("fr_rotation_joint_position_controller/command", Float64, queue_size=1)
00071 self.pub_bl_w = rospy.Publisher("bl_wheel_joint_velocity_controller/command", Float64, queue_size=1)
00072 self.pub_br_w = rospy.Publisher("br_wheel_joint_velocity_controller/command", Float64, queue_size=1)
00073 self.pub_fl_w = rospy.Publisher("fl_wheel_joint_velocity_controller/command", Float64, queue_size=1)
00074 self.pub_fr_w = rospy.Publisher("fr_wheel_joint_velocity_controller/command", Float64, queue_size=1)
00075
00076 self.publish_odom = rospy.get_param('~publish_odom', True)
00077 if self.publish_odom:
00078 self.pub_odom = rospy.Publisher("odom", Odometry, queue_size=1)
00079
00080 self.state = JointState()
00081 self.cmd = Twist()
00082 self.curr_cmd = self.cmd
00083 self.last_cmd = self.cmd
00084 self.cmd_time = rospy.Time()
00085 self.odom = Odometry()
00086 self.odom.header.frame_id = "odom"
00087 self.odom.pose.pose.orientation.w = 1
00088 self.odom.pose.covariance[0] = self.odom.pose.covariance[7] = self.odom.pose.covariance[14] = self.odom.pose.covariance[21] = self.odom.pose.covariance[28] = self.odom.pose.covariance[35] = 1
00089 self.odom.twist.covariance[0] = self.odom.twist.covariance[7] = self.odom.twist.covariance[14] = self.odom.twist.covariance[21] = self.odom.twist.covariance[28] = self.odom.twist.covariance[35] = 1
00090 self.last_control_time = rospy.Time.now()
00091
00092 self.x = 0
00093 self.y = 0
00094 self.th = 0
00095
00096 rospy.on_shutdown(self.cleanup)
00097
00098 rospy.sleep(1)
00099 rospy.loginfo("Start base controller (publish_odom: %s)"%(self.publish_odom))
00100
00101 def cleanup(self):
00102 rospy.loginfo("Stop base controller")
00103 self.cmd_time = rospy.Time.now()
00104 self.last_cmd = Twist()
00105 self.control()
00106 time.sleep(1)
00107
00108 def control(self, sec_idle=1.0):
00109 '''
00110 @brief Intended to be called periodically.
00111 Reads the last velocity command, interpretes for SPUR mechanism,
00112 then publish as Float64 that is the data type
00113 dynamixel_controllers receive.
00114 @type sec_idle: float
00115 @param sec_idle: Ideling seconds before robot stops moving
00116 '''
00117 control_interval = (rospy.Time.now() - self.last_control_time).to_sec()
00118 self.last_control_time = rospy.Time.now()
00119
00120 if (rospy.Time.now() - self.cmd_time).to_sec() > sec_idle:
00121 self.cmd = Twist()
00122
00123
00124 accel_trans_limit = 100
00125 accel_rotate_limit = 100
00126 raw_linear_x = self.cmd.linear.x - self.last_cmd.linear.x
00127 raw_linear_y = self.cmd.linear.y - self.last_cmd.linear.y
00128 raw_rotate = self.cmd.angular.z - self.last_cmd.angular.z
00129 if control_interval > 0 and \
00130 (abs(raw_linear_x)/control_interval > accel_trans_limit or \
00131 abs(raw_linear_y)/control_interval > accel_trans_limit or \
00132 abs(raw_rotate)/control_interval > accel_rotate_limit) :
00133 rospy.logwarn("Too Large accel: cmd_vel %7.3f %7.3f %7.3f, cmd_vel_dot %7.3f %7.3f %7.3f" %
00134 (self.cmd.linear.x, self.cmd.linear.y, self.cmd.angular.z,
00135 abs(raw_linear_x)/control_interval,
00136 abs(raw_linear_y)/control_interval,
00137 abs(raw_rotate)/control_interval))
00138
00139 self.curr_cmd.linear.x = self.last_cmd.linear.x + max(min(raw_linear_x, accel_trans_limit*control_interval), -accel_trans_limit*control_interval)
00140 self.curr_cmd.linear.y = self.last_cmd.linear.y + max(min(raw_linear_y, accel_trans_limit*control_interval), -accel_trans_limit*control_interval)
00141 self.curr_cmd.angular.z = self.last_cmd.angular.z + max(min(raw_rotate, accel_rotate_limit*control_interval), -accel_rotate_limit*control_interval)
00142 rospy.logdebug("cmd_vel %f %f %f" % (self.curr_cmd.linear.x, self.curr_cmd.linear.y, self.curr_cmd.angular.z))
00143
00144 diameter = 0.1
00145 offset_x = 0.15
00146 offset_y = 0.15
00147
00148
00149 fr_v_x = self.curr_cmd.linear.x - self.curr_cmd.angular.z * (-offset_y)
00150 fr_v_y = self.curr_cmd.linear.y + self.curr_cmd.angular.z * (offset_x)
00151 fl_v_x = self.curr_cmd.linear.x - self.curr_cmd.angular.z * (offset_y)
00152 fl_v_y = self.curr_cmd.linear.y + self.curr_cmd.angular.z * (offset_x)
00153 br_v_x = self.curr_cmd.linear.x - self.curr_cmd.angular.z * (-offset_y)
00154 br_v_y = self.curr_cmd.linear.y + self.curr_cmd.angular.z * (-offset_x)
00155 bl_v_x = self.curr_cmd.linear.x - self.curr_cmd.angular.z * (offset_y)
00156 bl_v_y = self.curr_cmd.linear.y + self.curr_cmd.angular.z * (-offset_x)
00157
00158 fr_v = hypot(fr_v_x, fr_v_y)
00159 fl_v = hypot(fl_v_x, fl_v_y)
00160 br_v = hypot(br_v_x, br_v_y)
00161 bl_v = hypot(bl_v_x, bl_v_y)
00162 fr_a = atan2(fr_v_y, fr_v_x)
00163 fl_a = atan2(fl_v_y, fl_v_x)
00164 br_a = atan2(br_v_y, br_v_x)
00165 bl_a = atan2(bl_v_y, bl_v_x)
00166
00167
00168 fr_v = -fr_v if fabs(fr_a) > pi / 2 else fr_v
00169 fr_a = fr_a - pi if fr_a > pi / 2 else fr_a
00170 fr_a = fr_a + pi if fr_a < -pi / 2 else fr_a
00171 fl_v = -fl_v if fabs(fl_a) > pi / 2 else fl_v
00172 fl_a = fl_a - pi if fl_a > pi / 2 else fl_a
00173 fl_a = fl_a + pi if fl_a < -pi / 2 else fl_a
00174 br_v = -br_v if fabs(br_a) > pi / 2 else br_v
00175 br_a = br_a - pi if br_a > pi / 2 else br_a
00176 br_a = br_a + pi if br_a < -pi / 2 else br_a
00177 bl_v = -bl_v if fabs(bl_a) > pi / 2 else bl_v
00178 bl_a = bl_a - pi if bl_a > pi / 2 else bl_a
00179 bl_a = bl_a + pi if bl_a < -pi / 2 else bl_a
00180
00181 rospy.logdebug("wheel %f %f %f %f" % (fr_v, fl_v, br_v, bl_v))
00182 rospy.logdebug("rotate %f %f %f %f" % (fr_a, fl_a, br_a, bl_a))
00183
00184 self.pub_fr_w.publish(Float64(-1*fr_v/(diameter / 2.0)))
00185 self.pub_fl_w.publish(Float64( fl_v/(diameter / 2.0)))
00186 self.pub_br_w.publish(Float64(-1*br_v/(diameter / 2.0)))
00187 self.pub_bl_w.publish(Float64( bl_v/(diameter / 2.0)))
00188 self.pub_fr_r.publish(Float64(-1*fr_a))
00189 self.pub_fl_r.publish(Float64(-1*fl_a))
00190 self.pub_br_r.publish(Float64(-1*br_a))
00191 self.pub_bl_r.publish(Float64(-1*bl_a))
00192
00193
00194 self.odom.header.stamp = rospy.Time.now()
00195 self.odom.header.frame_id = "odom"
00196 c_fr_v = c_fl_v = c_br_v = c_bl_v = c_fr_a = c_fl_a = c_br_a = c_bl_a = 0
00197 for i in range(len(self.state.name)):
00198 if self.state.name[i] == 'bl_rotation_joint':
00199 c_bl_a = self.state.position[i]*-1
00200 if self.state.name[i] == 'br_rotation_joint':
00201 c_br_a = self.state.position[i]*-1
00202 if self.state.name[i] == 'fl_rotation_joint':
00203 c_fl_a = self.state.position[i]*-1
00204 if self.state.name[i] == 'fr_rotation_joint':
00205 c_fr_a = self.state.position[i]*-1
00206 if self.state.name[i] == 'bl_wheel_joint':
00207 c_bl_v = self.state.velocity[i]*(diameter / 2.0)
00208 if abs(c_bl_v) < 0.002 :
00209 c_bl_v = 0
00210 if self.state.name[i] == 'br_wheel_joint':
00211 c_br_v = self.state.velocity[i]*(diameter / 2.0)*-1
00212 if abs(c_br_v) < 0.002 :
00213 c_br_v = 0
00214 if self.state.name[i] == 'fl_wheel_joint':
00215 c_fl_v = self.state.velocity[i]*(diameter / 2.0)
00216 if abs(c_fl_v) < 0.002 :
00217 c_fl_v = 0
00218 if self.state.name[i] == 'fr_wheel_joint':
00219 c_fr_v = self.state.velocity[i]*(diameter / 2.0)*-1
00220 if abs(c_fr_v) < 0.002 :
00221 c_fr_v = 0
00222 offset = sqrt(offset_x*offset_x + offset_y*offset_y)
00223 curr_linear_x = (cos(c_fl_a)*c_fl_v + cos(c_fr_a)*c_fr_v + cos(c_br_a)*c_br_v + cos(c_bl_a)*c_bl_v)/4
00224 curr_linear_y = (sin(c_fl_a)*c_fl_v + sin(c_fr_a)*c_fr_v + sin(c_br_a)*c_br_v + sin(c_bl_a)*c_bl_v)/4
00225
00226
00227
00228
00229 curr_angular_z = (c_fr_v*cos( pi/4-c_fr_a) - c_fl_v*cos(-pi/4-c_fl_a) +
00230 c_br_v*cos(-pi/4-c_br_a) - c_bl_v*cos( pi/4-c_bl_a)) / (4*offset)
00231
00232
00233
00234
00235 delta_x = (curr_linear_x * cos(self.th) - curr_linear_y * sin(self.th)) * control_interval
00236 delta_y = (curr_linear_x * sin(self.th) + curr_linear_y * cos(self.th)) * control_interval
00237 delta_th = curr_angular_z * control_interval
00238 self.x += delta_x
00239 self.y += delta_y
00240 self.th += delta_th
00241
00242 q = quaternion_about_axis(self.th, (0, 0, 1))
00243 self.odom.pose.pose.position.x = self.x
00244 self.odom.pose.pose.position.y = self.y
00245 self.odom.pose.pose.position.z = 0
00246 self.odom.pose.pose.orientation.x = q[0]
00247 self.odom.pose.pose.orientation.y = q[1]
00248 self.odom.pose.pose.orientation.z = q[2]
00249 self.odom.pose.pose.orientation.w = q[3]
00250
00251 self.odom.twist.twist.linear.x = curr_linear_x
00252 self.odom.twist.twist.linear.y = curr_linear_y
00253 self.odom.twist.twist.angular.z = curr_angular_z
00254
00255 if self.publish_odom:
00256 self.pub_odom.publish(self.odom)
00257
00258 self.last_cmd = self.curr_cmd;
00259
00260 def cmdCb(self, msg):
00261 self.cmd = msg
00262 self.cmd_time = rospy.Time.now()
00263 rospy.logdebug("cmd_vel %f %f %f" % (msg.linear.x, msg.linear.y, msg.angular.z))
00264
00265 def stateCb(self, msg):
00266 self.state = msg
00267
00268 if __name__ == "__main__":
00269 parser = argparse.ArgumentParser(description = __doc__)
00270
00271 parser.add_argument("--rate", help="Controller rate", type=float, default=100.0)
00272 args, unknown = parser.parse_known_args()
00273
00274 try:
00275 rospy.init_node("spur_base_controller")
00276
00277 b = BaseController()
00278 rate = rospy.Rate(args.rate)
00279 sec_idle = rospy.get_param('/spur/spur_base_controller/sec_idle', 1.0)
00280
00281
00282 while not rospy.is_shutdown():
00283 b.control(sec_idle)
00284 rate.sleep()
00285
00286 except rospy.ROSInterruptException:
00287 pass