[ROS Q&A] 195 – How to know if robot has moved one meter using Odometry

[ROS Q&A] 195 – How to know if robot has moved one meter using Odometry

Learn how to know if the robot has moved one-meter using Odometry.  You’ll learn how to:

  • create a Python program for calculating the distance moved by the robot using the Odometry topic and publish it into a topic.
  • create a Python program for testing that we can move the robot depending on the Odometry data.

Let’s go!


Related Resources


Step 1: Get your development environment ready and grab a copy of the code

  1. Click here to grab a copy of the ROSject already containing the project. This requires an account on the ROS Development Studio (ROSDS), an online platform for developing for ROS within a PC browser. If you don’t have an account, you will be asked to create one.
  2. To open a “terminal” on ROSDSpick the Shell app from the Tools menu.
  3. You can find the IDE app on the Tools menu.

Step 2: Start a Simulation

We are going to use the TurtleBot 2 simulation.

  1. Click on Simulations from the main menu.
  2. Under Launch a provided simulation, leave “Empty world” selected and click on Choose a robot.
  3. Type “turtle” into the search box and select TurtleBot 2.
  4. Click Start Simulation.
  5. A window showing the TurtleBot 2 in an empty world should show up.

Launch a provided sim

Step 3: Examine the structure of Odometry messages

Let’s see the structure of an Odom message. Open a terminal and run:

user:~$ rostopic echo /odom -n1
header:
  seq: 14929
  stamp:
    secs: 748
    nsecs: 215000000
  frame_id: "odom"
child_frame_id: "base_footprint"
pose:
  pose:
    position:
      x: 0.00668370211388
      y: 0.00010960687178
      z: -0.000246865753431
    orientation:
      x: 0.000389068511716
      y: -0.00720626927928
      z: 0.000354481463316
      w: 0.999973895985
  covariance: [1e-05, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1e-05, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1000000000000.0, 0.0, 0.0, 0.0, 0.0,0.0, 0.0, 1000000000000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1000000000000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.001]
twist:
  twist:
    linear:
      x: 2.30945682841e-05
      y: 0.000390977104083
      z: 0.0
    angular:
      x: 0.0
      y: 0.0
      z: -0.000507764995516
  covariance: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
---

The part of the message that stores the current position of the robot is the pose.pose.position:

user:~$ rostopic echo /odom -n1
# ...
pose:
  pose:
    position:
      x: 0.00668370211388
      y: 0.00010960687178
      z: -0.000246865753431
# ...

This part is what is used in the Python script that calculates the distance moved.

Step 4: Understand the Python scripts

1. Open the IDE and browse to the src folder. You will find two packages odom_movement_detector and test_movement. Each of these projects contains a Python script, each explained with comments in the code

odom_movement_detector/src/odom_movement_detector.py

#!/usr/bin/env python

import rospy
import math
from nav_msgs.msg import Odometry
from geometry_msgs.msg import Point
from std_msgs.msg import Float64

class MovementDetector(object):
    def __init__(self):
        """Initialize an object of the MovementDetector class."""
        # _mved_distance is for stored distance moved
        # create and initialize it here. Initial value is 0.0
        self._mved_distance = Float64()
        self._mved_distance.data = 0.0

        # Get the inital position. This will be a reference point for calculating
        # the distance moved 
        self.get_init_position()

        # Create a publisher for publishing the distance moved into the topic '/moved_distance'
        self.distance_moved_pub = rospy.Publisher('/moved_distance', Float64, queue_size=1)

        # create a subscriber for getting new Odometry messages
        rospy.Subscriber("/odom", Odometry, self.odom_callback)

    def get_init_position(self):
        """Get the initial position of the robot."""
        """
        Structure of the odom position message:
        user:~$ rostopic echo /odom -n1
        header:
        seq: 14929
        stamp:
            secs: 748
            nsecs: 215000000
        frame_id: "odom"
        child_frame_id: "base_footprint"
        pose:
        pose:
            position:
            x: 0.00668370211388
            y: 0.00010960687178
            z: -0.000246865753431
        """
        data_odom = None
        # wait for a message from the odometry topic and store it in data_odom when available
        while data_odom is None:
            try:
                data_odom = rospy.wait_for_message("/odom", Odometry, timeout=1)
            except:
                rospy.loginfo("Current odom not ready yet, retrying for setting up init pose")
        
        # Store the received odometry "position" variable in a Point instance 
        self._current_position = Point()
        self._current_position.x = data_odom.pose.pose.position.x
        self._current_position.y = data_odom.pose.pose.position.y
        self._current_position.z = data_odom.pose.pose.position.z

    def odom_callback(self, msg):
        """Process odometry data sent by the subscriber."""
        # Get the position information from the odom message
        # See the structure of an /odom message in the `get_init_position` function
        NewPosition = msg.pose.pose.position

        # Calculate the new distance moved, and add it to _mved_distance and 
        self._mved_distance.data += self.calculate_distance(NewPosition, self._current_position)
        
        # Update the current position of the robot so we have a new reference point
        # (The robot has moved and so we need a new reference for calculations)
        self.updatecurrent_positin(NewPosition)
        
        # If distance moved is big enough, publish it to the designated topic
        # Otherwise publish zero
        if self._mved_distance.data < 0.000001:
            aux = Float64()
            aux.data = 0.0
            self.distance_moved_pub.publish(aux)
        else:
            self.distance_moved_pub.publish(self._mved_distance)

    def updatecurrent_positin(self, new_position):
        """Update the current position of the robot."""
        self._current_position.x = new_position.x
        self._current_position.y = new_position.y
        self._current_position.z = new_position.z

    def calculate_distance(self, new_position, old_position):
        """Calculate the distance between two Points (positions)."""
        x2 = new_position.x
        x1 = old_position.x
        y2 = new_position.y
        y1 = old_position.y
        dist = math.hypot(x2 - x1, y2 - y1)
        return dist

    def publish_moved_distance(self):
        # spin() simply keeps python from exiting until this node is stopped
        rospy.spin()

if __name__ == '__main__':
    # create a node for running the program
    rospy.init_node('movement_detector_node', anonymous=True)

    # create an instance of the MovementDetector class and set the code
    # in motion
    movement_obj = MovementDetector()
    movement_obj.publish_moved_distance()

test_movement/src/test_movement.py

#!/usr/bin/env python

import rospy
from geometry_msgs.msg import Twist
from std_msgs.msg import Float64

def my_callback(msg):
    """Callback function that processes messages from the subscriber."""

    # get the distance moved from the message
    distance_moved = msg.data

    # If distance is less than 2, continue moving the robot
    # Otherwise, stop it (by pubishing `0`)
    if msg.data < 2:
        move.linear.x = 0.1

    if msg.data > 2:
        move.linear.x = 0

    pub.publish(move)

# create a node for running the program
rospy.init_node('test_movement')

# create a subscriber that gets the distance moved
sub = rospy.Subscriber('/moved_distance', Float64, my_callback)

# Create a publisher that moves the robot
pub = rospy.Publisher('/cmd_vel', Twist, queue_size="1")

# Create a global variable for publising a Twist ("cmd_vel") message 
move = Twist()

# Keep the program running
rospy.spin()

Step 5: See the Python scripts in action

Open a terminal and run the movement detector:

user:~$ rosrun odom_movement_detector odom_movement_detector.py

Open another terminal and check the distance being published to the /moved_distance:

user:~$ rostopic echo /moved_distance
data: 0.00280330822873
---
data: 0.00280530307334

You can see from above that the distance is not changing (more or less) because the robot is stationary. Now let’s move the robot:

user:~$ rosrun test_movement test_movement.py

Now you should see the robot moving and moved_distance increasing. It should stop when moved_instance > 2.0.

user:~$ rostopic echo /moved_distance
data: 0.00280330822873
---
data: 0.00280330822873
---
data: 0.00280330822873
---
data: 0.00280330822873
---
data: 0.00280330822873
---
# ...
data: 1.00280330822873
---
# ...
data: 2.00280330822873
---

And that’s it. I hope you had some fun!

Extra: Video of the post

Here below you have a “sights and sounds” version of this post, just in case you prefer it that way. Enjoy!

Feedback

Did you like this post? Do you have any questions about the explanations? Whatever the case, please leave a comment on the comments section below, so we can interact and learn from each other.

If you want to learn about other ROS or ROS2 topics, please let us know in the comments area and we will do a video or post about it.


Edited by Bayode Aderinola

[ROS Q&A] 135 – How to rotate a robot to a desired heading using feedback from odometry?

 

In this video we are going to see how to rotate our robot based on data from odometry. We will subscribe to /odom topic to get the heading of our robot, process it, and then send a command over the /cmd_vel topic.

This is a video based on the following post on ROS Answers: https://answers.ros.org/question/290534/rotate-a-desired-degree-using-feedback-from-odometry

// RELATED LINKS

▸ Original question: https://goo.gl/GSHi42
▸ ROS Development Studio (ROSDS): https://goo.gl/bUFuAq
▸ Robot Ignite Academy: https://goo.gl/6nNwhs

[irp posts=”7999″ name=”ROS Q&A – How to convert quaternions to Euler angles”]

Step 1. Create a project in ROS Development Studio(ROSDS)

We can do any ROS development we want easily, without setting up environment locally in ROSDS and that’s the reason why we will use ROSDS for this tutorial. If you haven’t had an account yet. You can create one here for free now. After logging in, let’s begin our journey by clicking on the create new project and call it rotate_robot.

Step 2. Create package

At first, we launch the simulation from Simulations->Turtlebot 2

We’ll create a package for the rotating robot task with the following command

cd ~/catkin_ws/src
catkin_create_pkg rotate_robot rospy

Then we’ll create a script folder and a main.py script inside it with the following content

#!/usr/bin/env python
import rospy
from nav_msgs.msg import Odometry
from tf.transformations import euler_from_quaternion, quaternion_from_euler

roll = pitch = yaw = 0.0

def get_rotation (msg):
    global roll, pitch, yaw
    orientation_q = msg.pose.pose.orientation
    orientation_list = [orientation_q.x, orientation_q.y, orientation_q.z, orientation_q.w]
    (roll, pitch, yaw) = euler_from_quaternion (orientation_list)
    print yaw

rospy.init_node('my_quaternion_to_euler')

sub = rospy.Subscriber ('/odom', Odometry, get_rotation)

r = rospy.Rate(1)
while not rospy.is_shutdown():
    quat = quaternion_from_euler (roll, pitch,yaw)
    print quat
    r.sleep()

Then we can run the script with the following command to see the topic publishing in quaternion and Euler angle.

cd rotate_robot/script
chmod +x main.py
rosrun rotate_robot main.py

You can also run the teleop script to move the robot and see the value changing with this command

rosrun  teleop_twist_keyboard teleop_twist_keyboard.py

Step 3. Rotate the robot

We’ll use the easiest way to do it by applying the controller. Please save the script with a new name rotate.py and change it as the following content.

#!/usr/bin/env python
import rospy
from nav_msgs.msg import Odometry
from tf.transformations import euler_from_quaternion, quaternion_from_euler
from geometry_msgs.msg import Twist
import math

roll = pitch = yaw = 0.0
target = 90
kp=0.5

def get_rotation (msg):
    global roll, pitch, yaw
    orientation_q = msg.pose.pose.orientation
    orientation_list = [orientation_q.x, orientation_q.y, orientation_q.z, orientation_q.w]
    (roll, pitch, yaw) = euler_from_quaternion (orientation_list)
    print yaw

rospy.init_node('rotate_robot')

sub = rospy.Subscriber ('/odom', Odometry, get_rotation)
pub = rospy.Publisher('cmd_vel', Twist, queue_size=1)
r = rospy.Rate(10)
command =Twist()

while not rospy.is_shutdown():
    #quat = quaternion_from_euler (roll, pitch,yaw)
    #print quat
    target_rad = target*math.pi/180
    command.angular.z = kp * (target_rad-yaw)
    pub.publish(command)
    print("taeget={} current:{}", target,yaw)
    r.sleep()

After executing it, you should see the robot turn to the desired orientation.

 

Want to learn more?

Applying the controller is not the only way to do it. There are more advanced and better ways to do it. If you are interested, please check our ROS Basic and TF ROS 101 courses  in  Robot Ignite Academy for more information.

 

 

Edit by: Tony Huang

 


 

Feedback

Did you like this video? Do you have questions about what is explained? Whatever the case, please leave a comment on the comments section below, so we can interact and learn from each other.

If you want to learn about other ROS topics, please let us know on the comments area and we will do a video about it.

Pin It on Pinterest