Example #1
0
class ODriveNode(object):

    last_speed = 0.0
    driver = None
    last_cmd_vel_time = None
    
    # Robot wheel_track params for velocity -> motor speed conversion
    wheel_track = .483
    tyre_circumference = .5282438
    encoder_counts_per_rev = 24
    m_s_to_value = 45.433 #change this back to 0. Changed for testing purposes  
    axis_for_right = 0

    # Startup parameters
    connect_on_startup = True
    calibrate_on_startup = True
    engage_on_startup = True
    
    def __init__(self): 

        self.axis_for_right = float(rospy.get_param('~axis_for_right', 0)) # if right calibrates first, this should be 0, else 1
        self.wheel_track = float(rospy.get_param('~wheel_track', .483)) # m, distance between wheel centres
        self.tyre_circumference = float(rospy.get_param('~tyre_circumference', 0.5282438)) # used to translate velocity commands in m/s into motor rpm
        
        
        self.connect_on_startup   = rospy.get_param('~connect_on_startup', True)
        self.calibrate_on_startup = rospy.get_param('~calibrate_on_startup', True)
        self.engage_on_startup    = rospy.get_param('~engage_on_startup', True)

        self.has_preroll = rospy.get_param('~use_preroll', False)        
        self.max_speed   = rospy.get_param('~max_speed', 176) #was set to .3144 but changed to 50 for testing
        self.max_angular = rospy.get_param('~max_angular', 270) 
        rospy.on_shutdown(self.terminate)

        rospy.Service('connect_driver',    std_srvs.srv.Trigger, self.connect_driver)
        rospy.Service('disconnect_driver', std_srvs.srv.Trigger, self.disconnect_driver)
    
        rospy.Service('calibrate_motors',  std_srvs.srv.Trigger, self.calibrate_motor)
        rospy.Service('engage_motors',     std_srvs.srv.Trigger, self.engage_motor)
        rospy.Service('release_motors',    std_srvs.srv.Trigger, self.release_motor)

        self.vel_subscribe = rospy.Subscriber("cmd_vel", Twist, self.cmd_vel_callback, queue_size=2) #subscirber node is good but do need to check the cmd_vel_callback method 
        
        self.timer = rospy.Timer(rospy.Duration(0.1), self.timer_check) # stop motors if no cmd_vel received > 1second

                
	if not self.connect_on_startup:
            rospy.loginfo("ODrive node started, but not connected.")
            return
        
        if not self.connect_driver(None)[0]:
            return # Failed to connect
        
        if not self.calibrate_on_startup:
            rospy.loginfo("ODrive node started and connected. Not calibrated.")
            return
        
        if not self.calibrate_motor(None)[0]:
            return
            
        if not self.engage_on_startup:
            rospy.loginfo("ODrive connected and configured. Engage to drive.")
            return
        
        if not self.engage_motor(None)[0]:
            return
        
        rospy.loginfo("ODrive connected and configured. Ready to drive.")
        
    def terminate(self):
        if self.driver:
            self.driver.release()
        self.timer.shutdown()
    
    # ROS services
    def connect_driver(self, request):
        if self.driver:
            rospy.logerr("Already connected.")
            return (False, "Already connected.")
        
        self.driver = ODriveInterfaceAPI(logger=ROSLogger())
        rospy.loginfo("Connecting to ODrive...")
        if not self.driver.connect(right_axis=self.axis_for_right):
            self.driver = False
            rospy.logerr("Failed to connect.")
            return (False, "Failed to connect.")
            
        rospy.loginfo("ODrive connected.")
        
        self.m_s_to_value = self.driver.encoder_cpr/self.tyre_circumference
                
        
        return (True, "ODrive connected successfully")
    
    def disconnect_driver(self, request):
        if not self.driver:
            rospy.logerr("Not connected.")
            return (False, "Not connected.")
        if not self.driver.disconnect():
            return (False, "Failed disconnection, but try reconnecting.")
        return (True, "Disconnection success.")
    
    def calibrate_motor(self, request):
        if not self.driver:
            rospy.logerr("Not connected.")
            return (False, "Not connected.")
            
        if self.has_preroll:
            if not self.driver.preroll():
                return (False, "Failed preroll.")        
        else:
            if not self.driver.calibrate():
                return (False, "Failed calibration.")
                
        return (True, "Calibration success.")
                    
    def engage_motor(self, request):
        if not self.driver:
            rospy.logerr("Not connected.")
            return (False, "Not connected.")
        if not self.driver.engage():
            return (False, "Failed to engage motor.")
        return (True, "Engage motor success.")
    
    def release_motor(self, request):
        if not self.driver:
            rospy.logerr("Not connected.")
            return (False, "Not connected.")
        if not self.driver.release():
            return (False, "Failed to release motor.")
        return (True, "Release motor success.")
    
    def reset_odometry(self, request):
        self.x = 0.0
        self.y = 0.0
        self.theta = 0.0
        
        return(True, "Odometry reset.")
    
    # Helpers and callbacks
    def constrain(self, val):
	return min(self.max_speed, max(-self.max_speed, val))

    def cmd_vel_callback(self, twist):
	
	motor_linear = self.constrain(twist.linear.x) / 0.8
	
	motor_angular = self.constrain(twist.angular.z)

	angular_to_linear = motor_angular * (self.wheel_track/1.0)

	left_motor_val = int((motor_linear - angular_to_linear) * self.m_s_to_value)
	right_motor_val = int((motor_linear + angular_to_linear) * self.m_s_to_value)

        if self.last_speed < 0+0.001 and abs(left_motor_val) < 0+0.001 and abs(right_motor_val) < 0+0.001:
            return
                
        self.driver.drive(left_motor_val, right_motor_val)

        self.last_speed = max(abs(left_motor_val), abs(right_motor_val))
        self.last_cmd_vel_time = rospy.Time.now()
                
    def timer_current(self, event):
        if not self.driver or not hasattr(self.driver, 'driver') or not hasattr(self.driver.driver, 'axis0'):
            return
        
        self.left_current_accumulator += self.driver.left_axis.motor.current_control.Ibus
        self.right_current_accumulator += self.driver.right_axis.motor.current_control.Ibus
        
        self.current_loop_count += 1
        if self.current_loop_count >= 9:
            # publish appropriate axis
            self.current_publisher_left.publish(self.left_current_accumulator)
            self.current_publisher_right.publish(self.right_current_accumulator)
            
            self.current_loop_count = 0
            self.left_current_accumulator = 0.0
            self.right_current_accumulator = 0.0
        #self.current_timer = rospy.Timer(rospy.Duration(0.02), self.timer_current) # publish motor currents at 10Hz, read at 50Hz
        #self.current_publisher_left  = rospy.Publisher('left_current', Float64, queue_size=2)
        #self.current_publisher_right = rospy.Publisher('right_current', Float64, queue_size=2)
    
    def timer_check(self, event):
        """Check for cmd_vel 1 sec timeout. """
        if not self.driver:
            return
        
        if self.last_cmd_vel_time is None:
            return
        
        # if moving, and no cmd_vel received, stop
        if (event.current_real-self.last_cmd_vel_time).to_sec() > 1.0 and (self.last_speed > 0):
            rospy.logdebug("No /cmd_vel received in > 1s, stopping.")
            self.driver.drive(0,0)
            self.last_speed = 0
            self.last_cmd_vel_time = event.current_real
Example #2
0
class ODriveNode(object):
    last_speed = 0.0
    driver = None
    last_cmd_vel_time = None

    # Robot wheel_track params for velocity -> motor speed conversion
    wheel_track = None
    tyre_circumference = None
    encoder_counts_per_rev = None
    m_s_to_value = 0
    axis_for_right = 0

    # Startup parameters
    connect_on_startup = False
    calibrate_on_startup = False
    engage_on_startup = False

    def __init__(self):
        self.axis_for_right = float(rospy.get_param(
            '~axis_for_right',
            0))  # if right calibrates first, this should be 0, else 1
        self.wheel_track = float(rospy.get_param(
            '~wheel_track', 0.285))  # m, distance between wheel centres
        self.tyre_circumference = float(
            rospy.get_param('~tyre_circumference', 0.341)
        )  # used to translate velocity commands in m/s into motor rpm

        self.connect_on_startup = rospy.get_param('~connect_on_startup', False)
        self.calibrate_on_startup = rospy.get_param('~calibrate_on_startup',
                                                    False)
        self.engage_on_startup = rospy.get_param('~engage_on_startup', False)

        self.max_speed = rospy.get_param('~max_speed', 0.5)
        self.max_angular = rospy.get_param('~max_angular', 1.0)

        self.publish_current = rospy.get_param('~publish_current', True)

        self.has_preroll = rospy.get_param('~use_preroll', False)

        self.publish_current = rospy.get_param('~publish_current', True)

        self.publish_odom = rospy.get_param('~publish_odom', True)
        self.publish_tf = rospy.get_param('~publish_odom_tf', True)
        self.odom_topic = rospy.get_param('~odom_topic', "odom")
        self.odom_frame = rospy.get_param('~odom_frame', "odom")
        self.base_frame = rospy.get_param('~base_frame', "base_link")
        self.odom_calc_hz = rospy.get_param('~odom_calc_hz', 20)

        self.publish_joint_state = rospy.get_param('~publish_joint_state',
                                                   True)
        self.joint_state_topic = rospy.get_param('~joint_state_topic',
                                                 "joint_state")
        self.Calibrate_Axis = rospy.get_param('~Calibrate_Axis', 1)
        self.motor_id = rospy.get_param('~motor_id', "0")

        rospy.on_shutdown(self.terminate)

        rospy.Service('connect_driver', std_srvs.srv.Trigger,
                      self.connect_driver)
        rospy.Service('disconnect_driver', std_srvs.srv.Trigger,
                      self.disconnect_driver)

        rospy.Service('calibrate_motors', std_srvs.srv.Trigger,
                      self.calibrate_motor)
        rospy.Service('engage_motors', std_srvs.srv.Trigger, self.engage_motor)
        rospy.Service('release_motors', std_srvs.srv.Trigger,
                      self.release_motor)

        self.vel_subscribe = rospy.Subscriber("/cmd_vel",
                                              Twist,
                                              self.cmd_vel_callback,
                                              queue_size=2)

        self.timer = rospy.Timer(
            rospy.Duration(0.1),
            self.timer_check)  # stop motors if no cmd_vel received > 1second

        if self.publish_current:
            self.current_loop_count = 0
            self.left_current_accumulator = 0.0
            self.right_current_accumulator = 0.0
            self.current_timer = rospy.Timer(
                rospy.Duration(0.05), self.timer_current
            )  # publish motor currents at 10Hz, read at 50Hz
            self.current_publisher_left = rospy.Publisher(
                'odrive/left_current', Float64, queue_size=2)
            self.current_publisher_right = rospy.Publisher(
                'odrive/right_current', Float64, queue_size=2)
            rospy.loginfo("ODrive will publish motor currents.")

        if self.publish_odom:
            rospy.Service('reset_odometry', std_srvs.srv.Trigger,
                          self.reset_odometry)

            self.odom_publisher = rospy.Publisher(self.odom_topic,
                                                  Odometry,
                                                  queue_size=2)
            # setup message
            self.odom_msg = Odometry()
            #print(dir(self.odom_msg))
            self.odom_msg.header.frame_id = self.odom_frame
            self.odom_msg.child_frame_id = self.base_frame
            self.odom_msg.pose.pose.position.z = 0.0  # always on the ground, we hope
            self.odom_msg.pose.pose.orientation.x = 0.0  # always vertical
            self.odom_msg.pose.pose.orientation.y = 0.0  # always vertical
            self.odom_msg.twist.twist.linear.y = 0.0  # no sideways
            self.odom_msg.twist.twist.linear.z = 0.0  # or upwards... only forward
            self.odom_msg.twist.twist.angular.x = 0.0  # or roll
            self.odom_msg.twist.twist.angular.y = 0.0  # or pitch... only yaw

            # store current location to be updated.
            self.x = 0.0
            self.y = 0.0
            self.theta = 0.0

            # setup transform
            self.tf_publisher = tf2_ros.TransformBroadcaster()
            self.tf_msg = TransformStamped()
            self.tf_msg.header.frame_id = self.odom_frame
            self.tf_msg.child_frame_id = self.base_frame
            self.tf_msg.transform.translation.z = 0.0
            self.tf_msg.transform.rotation.x = 0.0
            self.tf_msg.transform.rotation.y = 0.0

            self.odom_timer = rospy.Timer(
                rospy.Duration(1 / float(self.odom_calc_hz)),
                self.timer_odometry)

        if self.publish_joint_state:
            self.joint_state_publisher = rospy.Publisher(
                self.joint_state_topic, JointState, queue_size=2)
            # setup message
            self.joint_state_msg = JointState()
            self.joint_state_msg.name = self.motor_id

            self.state_subscriber = rospy.Subscriber(
                'motor_states/%s' % self.motor_id, MotorState,
                self.state_callback)
            # setup message
            self.state = MotorState()

            self.RADIANS_PER_ENCODER_TICK = 2.0 * 3.1415926 / 4000.0
            self.ENCODER_TICKS_PER_RADIAN = 1.0 / self.RADIANS_PER_ENCODER_TICK
            #print(dir(self.odom_msg))
            # self.joint_state_msg.header.frame_id = self.odom_frame
            # self.joint_state_msg.child_frame_id = self.base_frame

        if not self.connect_on_startup:
            rospy.loginfo("ODrive node started, but not connected.")
            return

        if not self.connect_driver(None)[0]:
            return  # Failed to connect

        if not self.calibrate_on_startup:
            rospy.loginfo("ODrive node started and connected. Not calibrated.")
            return

        if not self.calibrate_motor(None)[0]:
            return

        if not self.engage_on_startup:
            rospy.loginfo("ODrive connected and configured. Engage to drive.")
            return

        if not self.engage_motor(None)[0]:
            return

        rospy.loginfo("ODrive connected and configured. Ready to drive.")

    def terminate(self):
        if self.driver:
            self.driver.release()
        self.timer.shutdown()

    # ROS services
    def connect_driver(self, request):
        if self.driver:
            rospy.logerr("Already connected.")
            return (False, "Already connected.")

        self.driver = ODriveInterfaceAPI(calibrate_axis=self.Calibrate_Axis,
                                         logger=ROSLogger())
        rospy.loginfo("Connecting to ODrive...")
        if not self.driver.connect(right_axis=self.axis_for_right):
            self.driver = False
            rospy.logerr("Failed to connect.")
            return (False, "Failed to connect.")

        rospy.loginfo("ODrive connected.")

        self.m_s_to_value = self.driver.encoder_cpr / self.tyre_circumference

        if self.publish_odom:
            self.old_pos_l = self.driver.left_axis.encoder.pos_cpr
            self.old_pos_r = self.driver.right_axis.encoder.pos_cpr

        return (True, "ODrive connected successfully")

    def disconnect_driver(self, request):
        if not self.driver:
            rospy.logerr("Not connected.")
            return (False, "Not connected.")
        if not self.driver.disconnect():
            return (False, "Failed disconnection, but try reconnecting.")
        return (True, "Disconnection success.")

    def calibrate_motor(self, request):
        if not self.driver:
            rospy.logerr("Not connected.")
            return (False, "Not connected.")

        if self.has_preroll:
            if not self.driver.preroll():
                return (False, "Failed preroll.")
        else:
            if not self.driver.calibrate():
                return (False, "Failed calibration.")

        return (True, "Calibration success.")

    def engage_motor(self, request):
        if not self.driver:
            rospy.logerr("Not connected.")
            return (False, "Not connected.")
        if not self.driver.engage():
            return (False, "Failed to engage motor.")
        return (True, "Engage motor success.")

    def release_motor(self, request):
        if not self.driver:
            rospy.logerr("Not connected.")
            return (False, "Not connected.")
        if not self.driver.release():
            return (False, "Failed to release motor.")
        return (True, "Release motor success.")

    def reset_odometry(self, request):
        self.x = 0.0
        self.y = 0.0
        self.theta = 0.0

        return (True, "Odometry reset.")

    # Helpers and callbacks

    def convert(self, forward, ccw):
        angular_to_linear = ccw * (self.wheel_track / 2.0)
        left_linear_val = int(
            (forward - angular_to_linear) * self.m_s_to_value)
        right_linear_val = int(
            (forward + angular_to_linear) * self.m_s_to_value)

        return left_linear_val, right_linear_val

    def cmd_vel_callback(self, msg):
        #rospy.loginfo("Received a /cmd_vel message!")
        #rospy.loginfo("Linear Components: [%f, %f, %f]"%(msg.linear.x, msg.linear.y, msg.linear.z))
        #rospy.loginfo("Angular Components: [%f, %f, %f]"%(msg.angular.x, msg.angular.y, msg.angular.z))

        # rostopic pub -r 1 /commands/motor/current std_msgs/Float64 -- -1.0

        # Do velocity processing here:
        # Use the kinematics of your robot to map linear and angular velocities into motor commands

        # 3600 ERPM = 360 RPM ~= 6 km/hr

        #angular_to_linear = msg.angular.z * (wheel_track/2.0)
        #left_linear_rpm  = (msg.linear.x - angular_to_linear) * m_s_to_erpm
        #right_linear_rpm = (msg.linear.x + angular_to_linear) * m_s_to_erpm

        x = max(min(msg.linear.x, self.max_speed), -self.max_speed)
        z = max(min(msg.linear.x, self.max_angular), -self.max_angular)

        left_linear_val, right_linear_val = self.convert(x, z)

        # if wheel speed = 0, stop publishing after sending 0 once. #TODO add error term, work out why VESC turns on for 0 rpm
        if self.last_speed == 0 and abs(left_linear_val) == 0 and abs(
                right_linear_val) == 0:
            return

        # Then set your wheel speeds (using wheel_left and wheel_right as examples)
        #self.left_motor_pub.publish(left_linear_rpm)
        #self.right_motor_pub.publish(right_linear_rpm)
        #wheel_left.set_speed(v_l)
        #wheel_right.set_speed(v_r)

        rospy.logdebug(
            "Driving left: %d, right: %d, from linear.x %.2f and angular.z %.2f"
            % (left_linear_val, right_linear_val, msg.linear.x, msg.angular.z))
        self.driver.drive(left_linear_val, right_linear_val)

        self.last_speed = max(abs(left_linear_val), abs(right_linear_val))
        dt = 0.0
        if self.last_cmd_vel_time:
            dt = rospy.Time.now().to_sec() - self.last_cmd_vel_time.to_sec()
        self.last_cmd_vel_time = rospy.Time.now()

        self.joint_state_msg.goal_pos = msg.linear.x * dt

    def state_callback(self, msg):
        if self.publish_joint_state:
            self.joint_state_msg.motor_temps = 0
            self.joint_state_msg.goal_pos = self.raw_to_rad(
                state.goal, self.initial_position_raw, self.flipped,
                self.RADIANS_PER_ENCODER_TICK)
            self.joint_state_msg.current_pos = self.raw_to_rad(
                state.position, self.initial_position_raw, self.flipped,
                self.RADIANS_PER_ENCODER_TICK)
            self.joint_state_msg.error = state.error * self.RADIANS_PER_ENCODER_TICK
            self.joint_state_msg.velocity = state.speed * self.VELOCITY_PER_TICK
            self.joint_state_msg.load = state.load
            self.joint_state_msg.is_moving = state.moving
            self.joint_state_msg.header.stamp = rospy.Time.from_sec(
                state.timestamp)

            self.joint_state_publisher.publish(self.joint_state_msg)

    def timer_current(self, event):
        if not self.driver or not hasattr(self.driver,
                                          'driver') or not hasattr(
                                              self.driver.driver, 'axis0'):
            return

        self.left_current_accumulator += self.driver.left_axis.motor.current_control.Ibus
        self.right_current_accumulator += self.driver.right_axis.motor.current_control.Ibus

        self.current_loop_count += 1
        if self.current_loop_count >= 9:
            # publish appropriate axis
            self.current_publisher_left.publish(self.left_current_accumulator)
            self.current_publisher_right.publish(
                self.right_current_accumulator)

            self.current_loop_count = 0
            self.left_current_accumulator = 0.0
            self.right_current_accumulator = 0.0
        #self.current_timer = rospy.Timer(rospy.Duration(0.02), self.timer_current) # publish motor currents at 10Hz, read at 50Hz
        #self.current_publisher_left  = rospy.Publisher('left_current', Float64, queue_size=2)
        #self.current_publisher_right = rospy.Publisher('right_current', Float64, queue_size=2)

    def timer_odometry(self, event):
        #check for driver connected
        if self.driver is None or not self.driver.connected:
            return
        # at ~10Hz,

        # poll driver for each axis position and velocity PLL estimates
        encoder_cpr = self.driver.encoder_cpr
        wheel_track = self.wheel_track  # check these. Values in m
        tyre_circumference = self.tyre_circumference
        # self.m_s_to_value = encoder_cpr/tyre_circumference set earlier

        # get values from ODrive
        odrive_poll_time = rospy.Time.now()
        vel_l = self.driver.left_axis.encoder.vel_estimate  # units: encoder counts/s
        vel_r = -self.driver.right_axis.encoder.vel_estimate  # neg is forward for right
        new_pos_l = self.driver.left_axis.encoder.pos_cpr  # units: encoder counts
        new_pos_r = -self.driver.right_axis.encoder.pos_cpr  # sign!

        # Twist: calculated from motor values only
        s = tyre_circumference * (vel_l + vel_r) / (2.0 * encoder_cpr)
        w = tyre_circumference * (vel_r - vel_l) / (
            wheel_track * encoder_cpr
        )  # angle: vel_r*tyre_radius - vel_l*tyre_radius
        self.odom_msg.twist.twist.linear.x = s
        self.odom_msg.twist.twist.angular.z = w

        #rospy.loginfo("vel_l: % 2.2f  vel_r: % 2.2f  vel_l: % 2.2f  vel_r: % 2.2f  x: % 2.2f  th: % 2.2f  pos_l: % 5.1f pos_r: % 5.1f " % (
        #                vel_l, -vel_r,
        #                vel_l/encoder_cpr, vel_r/encoder_cpr, self.odom_msg.twist.twist.linear.x, self.odom_msg.twist.twist.angular.z,
        #                self.driver.left_axis.encoder.pos_cpr, self.driver.right_axis.encoder.pos_cpr))

        delta_pos_l = new_pos_l - self.old_pos_l
        delta_pos_r = new_pos_r - self.old_pos_r

        self.old_pos_l = new_pos_l
        self.old_pos_r = new_pos_r

        # Check for overflow. Assume we can't move more than half a circumference in a single timestep.
        half_cpr = encoder_cpr / 2.0
        if delta_pos_l > half_cpr: delta_pos_l = delta_pos_l - encoder_cpr
        elif delta_pos_l < -half_cpr: delta_pos_l = delta_pos_l + encoder_cpr
        if delta_pos_r > half_cpr: delta_pos_r = delta_pos_r - encoder_cpr
        elif delta_pos_r < -half_cpr: delta_pos_r = delta_pos_r + encoder_cpr

        # counts to metres
        delta_pos_l_m = delta_pos_l / self.m_s_to_value
        delta_pos_r_m = delta_pos_r / self.m_s_to_value

        # Distance travelled
        d = (delta_pos_l_m + delta_pos_r_m) / 2.0  # delta_ps
        th = (delta_pos_r_m -
              delta_pos_l_m) / wheel_track  # works for small angles

        xd = math.cos(th) * d
        yd = -math.sin(th) * d

        # elapsed time = event.last_real, event.current_real
        elapsed = (event.current_real - event.last_real).to_sec()
        # calc_vel: d/elapsed, th/elapsed

        # Pose: updated from previous pose + position delta
        self.x += math.cos(self.theta) * xd - math.sin(self.theta) * yd
        self.y += math.sin(self.theta) * xd + math.cos(self.theta) * yd
        self.theta = (self.theta + th) % (2 * math.pi)

        #rospy.loginfo("dl_m: % 2.2f  dr_m: % 2.2f  d: % 2.2f  th: % 2.2f  xd: % 2.2f  yd: % 2.2f  x: % 5.1f y: % 5.1f  th: % 5.1f" % (
        #                delta_pos_l_m, delta_pos_r_m,
        #                d, th, xd, yd,
        #                self.x, self.y, self.theta
        #                ))

        # fill odom message and publish
        self.odom_msg.header.stamp = odrive_poll_time
        self.odom_msg.pose.pose.position.x = self.x
        self.odom_msg.pose.pose.position.y = self.y
        q = tf_conversions.transformations.quaternion_from_euler(
            0.0, 0.0, self.theta)
        self.odom_msg.pose.pose.orientation.z = q[2]  # math.sin(self.theta)/2
        self.odom_msg.pose.pose.orientation.w = q[3]  # math.cos(self.theta)/2

        #rospy.loginfo("theta: % 2.2f  z_m: % 2.2f  w_m: % 2.2f  q[2]: % 2.2f  q[3]: % 2.2f (q[0]: %2.2f  q[1]: %2.2f)" % (
        #                        self.theta,
        #                        math.sin(self.theta)/2, math.cos(self.theta)/2,
        #                        q[2],q[3],q[0],q[1]
        #                        ))

        #self.odom_msg.pose.covariance
        # x y z
        # x y z

        self.tf_msg.header.stamp = odrive_poll_time
        self.tf_msg.transform.translation.x = self.x
        self.tf_msg.transform.translation.y = self.y
        #self.tf_msg.transform.rotation.x
        #self.tf_msg.transform.rotation.x
        self.tf_msg.transform.rotation.z = q[2]
        self.tf_msg.transform.rotation.w = q[3]

        # ... and publish!
        self.odom_publisher.publish(self.odom_msg)
        if self.publish_tf:
            self.tf_publisher.sendTransform(self.tf_msg)

        if self.publish_joint_state:
            self.state.position = new_pos_r
            self.state.speed = vel_r
            self.joint_state_msg.motor_temps = 0
            self.joint_state_msg.goal_pos = self.raw_to_rad(
                self.state.goal, 0, False, self.RADIANS_PER_ENCODER_TICK)
            self.joint_state_msg.current_pos = self.raw_to_rad(
                self.state.position, 0, False, self.RADIANS_PER_ENCODER_TICK)
            self.joint_state_msg.error = 0  # state.error * self.RADIANS_PER_ENCODER_TICK
            self.joint_state_msg.velocity = self.state.speed * self.RADIANS_PER_ENCODER_TICK
            self.joint_state_msg.load = 0  # state.load
            self.joint_state_msg.is_moving = True
            self.joint_state_msg.header.stamp = rospy.Time.now().to_sec()
            self.joint_state_publisher.publish(self.joint_state_msg)

    def timer_check(self, event):
        """Check for cmd_vel 1 sec timeout. """
        if not self.driver:
            return

        if self.last_cmd_vel_time is None:
            return

        # if moving, and no cmd_vel received, stop
        if (event.current_real - self.last_cmd_vel_time).to_sec() > 1.0 and (
                self.last_speed > 0):
            rospy.logdebug("No /cmd_vel received in > 1s, stopping.")
            self.driver.drive(0, 0)
            self.last_speed = 0
            self.last_cmd_vel_time = event.current_real

    def rad_to_raw(self, angle, initial_position_raw, flipped,
                   encoder_ticks_per_radian):
        """ angle is in radians """
        #print 'flipped = %s, angle_in = %f, init_raw = %d' % (str(flipped), angle, initial_position_raw)
        angle_raw = angle * encoder_ticks_per_radian
        #print 'angle = %f, val = %d' % (math.degrees(angle), int(round(initial_position_raw - angle_raw if flipped else initial_position_raw + angle_raw)))
        return int(
            round(initial_position_raw -
                  angle_raw if flipped else initial_position_raw + angle_raw))

    def raw_to_rad(self, raw, initial_position_raw, flipped,
                   radians_per_encoder_tick):
        return (initial_position_raw - raw if flipped else raw -
                initial_position_raw) * radians_per_encoder_tick
class ODriveNode(object):

    last_speed = 0.0
    driver = None
    last_cmd_vel_time = None
    
    # Robot wheel_track params for velocity -> motor speed conversion
    wheel_track = .6477
    tyre_circumference = .5282438
    encoder_counts_per_rev = 24
    m_s_to_value = 45.433 #change this back to 0. Changed for testing purposes  
    axis_for_right = 0

    # Startup parameters
    connect_on_startup = True
    calibrate_on_startup = True
    engage_on_startup = True
    
    def __init__(self): 

        self.axis_for_right = float(rospy.get_param('~axis_for_right', 0)) # if right calibrates first, this should be 0, else 1
        self.wheel_track = float(rospy.get_param('~wheel_track', 0.6477)) # m, distance between wheel centres
        self.tyre_circumference = float(rospy.get_param('~tyre_circumference', 0.5282438)) # used to translate velocity commands in m/s into motor rpm
        
        
        self.connect_on_startup   = rospy.get_param('~connect_on_startup', True)
        self.calibrate_on_startup = rospy.get_param('~calibrate_on_startup', True)
        self.engage_on_startup    = rospy.get_param('~engage_on_startup', True)

        self.has_preroll = rospy.get_param('~use_preroll', False)        
        self.max_speed   = rospy.get_param('~max_speed', 176) #was set to .3144 but changed to 50 for testing
        self.max_angular = rospy.get_param('~max_angular', 270) 
        rospy.on_shutdown(self.terminate)

        rospy.Service('connect_driver',    std_srvs.srv.Trigger, self.connect_driver)
        rospy.Service('disconnect_driver', std_srvs.srv.Trigger, self.disconnect_driver)
    
        rospy.Service('calibrate_motors',  std_srvs.srv.Trigger, self.calibrate_motor)
        rospy.Service('engage_motors',     std_srvs.srv.Trigger, self.engage_motor)
        rospy.Service('release_motors',    std_srvs.srv.Trigger, self.release_motor)

        self.vel_subscribe = rospy.Subscriber("cmd_vel", Twist, self.cmd_vel_callback, queue_size=2) #subscirber node is good but do need to check the cmd_vel_callback method 
        
        self.timer = rospy.Timer(rospy.Duration(0.1), self.timer_check) # stop motors if no cmd_vel received > 1second

                
	if not self.connect_on_startup:
            rospy.loginfo("ODrive node started, but not connected.")
            return
        
        if not self.connect_driver(None)[0]:
            return # Failed to connect
        
        if not self.calibrate_on_startup:
            rospy.loginfo("ODrive node started and connected. Not calibrated.")
            return
        
        if not self.calibrate_motor(None)[0]:
            return
            
        if not self.engage_on_startup:
            rospy.loginfo("ODrive connected and configured. Engage to drive.")
            return
        
        if not self.engage_motor(None)[0]:
            return
        
        rospy.loginfo("ODrive connected and configured. Ready to drive.")
        
    def terminate(self):
        if self.driver:
            self.driver.release()
        self.timer.shutdown()
    
    # ROS services
    def connect_driver(self, request):
        if self.driver:
            rospy.logerr("Already connected.")
            return (False, "Already connected.")
        
        self.driver = ODriveInterfaceAPI(logger=ROSLogger())
        rospy.loginfo("Connecting to ODrive...")
        if not self.driver.connect(right_axis=self.axis_for_right):
            self.driver = False
            rospy.logerr("Failed to connect.")
            return (False, "Failed to connect.")
            
        rospy.loginfo("ODrive connected.")
        
        self.m_s_to_value = self.driver.encoder_cpr/self.tyre_circumference
                
        
        return (True, "ODrive connected successfully")
    
    def disconnect_driver(self, request):
        if not self.driver:
            rospy.logerr("Not connected.")
            return (False, "Not connected.")
        if not self.driver.disconnect():
            return (False, "Failed disconnection, but try reconnecting.")
        return (True, "Disconnection success.")
    
    def calibrate_motor(self, request):
        if not self.driver:
            rospy.logerr("Not connected.")
            return (False, "Not connected.")
            
        if self.has_preroll:
            if not self.driver.preroll():
                return (False, "Failed preroll.")        
        else:
            if not self.driver.calibrate():
                return (False, "Failed calibration.")
                
        return (True, "Calibration success.")
                    
    def engage_motor(self, request):
        if not self.driver:
            rospy.logerr("Not connected.")
            return (False, "Not connected.")
        if not self.driver.engage():
            return (False, "Failed to engage motor.")
        return (True, "Engage motor success.")
    
    def release_motor(self, request):
        if not self.driver:
            rospy.logerr("Not connected.")
            return (False, "Not connected.")
        if not self.driver.release():
            return (False, "Failed to release motor.")
        return (True, "Release motor success.")
    
    def reset_odometry(self, request):
        self.x = 0.0
        self.y = 0.0
        self.theta = 0.0
        
        return(True, "Odometry reset.")
    
    # Helpers and callbacks
    def constrain(self, val):
	return min(self.max_speed, max(-self.max_speed, val))

    def cmd_vel_callback(self, twist):
	
	motor_linear = self.constrain(twist.linear.x)
	
	motor_angular = self.constrain(twist.angular.z)

	angular_to_linear = motor_angular * (self.wheel_track/2.0)

	left_motor_val = int((motor_linear - angular_to_linear) * self.m_s_to_value)
	right_motor_val = int((motor_linear + angular_to_linear) * self.m_s_to_value)

        if self.last_speed < 0+0.001 and abs(left_motor_val) < 0+0.001 and abs(right_motor_val) < 0+0.001:
            return
                
        self.driver.drive(left_motor_val, right_motor_val)

        self.last_speed = max(abs(left_motor_val), abs(right_motor_val))
        self.last_cmd_vel_time = rospy.Time.now()
                
    def timer_current(self, event):
        if not self.driver or not hasattr(self.driver, 'driver') or not hasattr(self.driver.driver, 'axis0'):
            return
        
        self.left_current_accumulator += self.driver.left_axis.motor.current_control.Ibus
        self.right_current_accumulator += self.driver.right_axis.motor.current_control.Ibus
        
        self.current_loop_count += 1
        if self.current_loop_count >= 9:
            # publish appropriate axis
            self.current_publisher_left.publish(self.left_current_accumulator)
            self.current_publisher_right.publish(self.right_current_accumulator)
            
            self.current_loop_count = 0
            self.left_current_accumulator = 0.0
            self.right_current_accumulator = 0.0
        #self.current_timer = rospy.Timer(rospy.Duration(0.02), self.timer_current) # publish motor currents at 10Hz, read at 50Hz
        #self.current_publisher_left  = rospy.Publisher('left_current', Float64, queue_size=2)
        #self.current_publisher_right = rospy.Publisher('right_current', Float64, queue_size=2)
    
    def timer_check(self, event):
        """Check for cmd_vel 1 sec timeout. """
        if not self.driver:
            return
        
        if self.last_cmd_vel_time is None:
            return
        
        # if moving, and no cmd_vel received, stop
        if (event.current_real-self.last_cmd_vel_time).to_sec() > 1.0 and (self.last_speed > 0):
            rospy.logdebug("No /cmd_vel received in > 1s, stopping.")
            self.driver.drive(0,0)
            self.last_speed = 0
            self.last_cmd_vel_time = event.current_real