base_controller.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 
00003 # Software License Agreement (BSD License)
00004 #
00005 # Copyright (c) 2015, Tamagawa University. All rights reserved.
00006 #
00007 # Redistribution and use in source and binary forms, with or without
00008 # modification, are permitted provided that the following conditions
00009 # are met:
00010 #
00011 #  * Redistributions of source code must retain the above copyright
00012 #    notice, this list of conditions and the following disclaimer.
00013 #  * Redistributions in binary form must reproduce the above
00014 #    copyright notice, this list of conditions and the following
00015 #    disclaimer in the documentation and/or other materials provided
00016 #    with the distribution.
00017 #  * Neither the name of the association nor the names of its
00018 #    contributors may be used to endorse or promote products derived
00019 #    from this software without specific prior written permission.
00020 #
00021 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00022 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00023 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00024 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00025 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00026 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00027 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00028 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00029 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00030 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00031 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00032 # POSSIBILITY OF SUCH DAMAGE.
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()        # subscribed command
00081         self.cmd = Twist()        # subscribed command
00082         self.curr_cmd = self.cmd  # current target velocity
00083         self.last_cmd = self.cmd  # previous velocity
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: ## if new cmd_vel did not comes for 5 sec
00121             self.cmd = Twist()
00122 
00123         ### velocity control (raw commnd velocity, we need to filter this)
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  # caster diameter
00145         offset_x = 0.15  # caster offset
00146         offset_y = 0.15
00147         #
00148         # http://www.chiefdelphi.com/media/papers/download/2614
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         # v[m/s] = r[rad/s] * 0.1[m]  ## 0.1 = diameter
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         # fix for -pi/2 - pi/2
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)))  # right wheel has -1 axis orientation
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))  # All steerage wheels' rotational direction needs
00189         self.pub_fl_r.publish(Float64(-1*fl_a))  # to be negated since they are upside down.
00190         self.pub_br_r.publish(Float64(-1*br_a))
00191         self.pub_bl_r.publish(Float64(-1*bl_a))
00192 
00193         ## Odometry
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         # rospy.loginfo("%f %f %f %f" % (c_fr_v*cos(pi/4-c_fr_a) , - c_fl_v*cos(-pi/4-c_fl_a),
00226         #                                c_br_v*cos(-pi/4-c_br_a), - c_bl_v*cos(pi/4-c_bl_a) ))
00227         # rospy.loginfo("%f %f %f %f" % (cos(pi/4-c_fr_a) , - cos(-pi/4-c_fl_a),
00228         #                                cos(-pi/4-c_br_a), - cos(pi/4-c_bl_a)))
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         # rospy.loginfo("wheel   %f %f %f %f" % (c_fr_v, c_fl_v, c_br_v, c_bl_v))
00233         # rospy.loginfo("rotate  %f %f %f %f" % (c_fr_a, c_fl_a, c_br_a, c_bl_a))
00234         # rospy.loginfo("curent cmd %f %f %f" % (curr_linear_x, curr_linear_y, curr_angular_z))
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         # rospy.loginfo("                     -> %f %f %f\n", self.x, self.y, self.th)
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     # Accept control rate. Default is 100 hz.
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         # Keep looping unless the system receives shutdown signal.
00282         while not rospy.is_shutdown():
00283             b.control(sec_idle)
00284             rate.sleep()
00285 
00286     except rospy.ROSInterruptException:
00287         pass


spur_controller
Author(s): Tokyo Opensource Robotics Programmer 534o <534o@opensource-robotics.tokyo.jp>, Isaac I. Y. Saito
autogenerated on Sat Jun 8 2019 19:44:12