Tutorials:Drive forwards

From RacecarWiki
Jump to: navigation, search

In this tutorial, you will create a very simple node that drives the car forwards autonomously.

Creating a ROS node

Create a file named drive.py and open it in your favorite editor. Begin with the following code:

1 #!/usr/bin/env python2
2 
3 import rospy
4 
5 if __name__ == "__main__":
6   rospy.init_node("drive")
7   rospy.spin()

This code doesn't do much, but you will see it a lot. It is the simplest possible ROS node that you can write. Line 1 establishes that this code should be interpreted as Python 2 code. It is worth noting that ROS is not necessarily compatible with Python 3 so stick with Python 2.

In line 3 you import the main ROS library for python, rospy. Line 5 is Python syntax for "execute the code below this". In line 6 you initialize a ROS node. An individual ROS node is nothing more than some independent piece of code. What makes nodes useful is that registering code as a node allows it to communicate with other ROS nodes. These other nodes can serve a variety of functions. For example some nodes may communicate directly with pieces of hardware while others perform higher level tasks like mapping or path plannning.

The final line "spins" the node. Spinning a node keeps it running even though you've reached the end of the script. This may not seem useful now, but you will see shortly that it is necessary if we have other processes running in the background of the script that we want to keep alive. Make the code executable with chmod and then run it.

$ chmod +x drive.py
$ ./drive.py

Nothing should happen! Because the code is spinning, the code will not stop on its own, so press Ctrl+C to end it.

Driving

ROS topics

Now its time to turn this blank node template into a useful piece of code that drives the car forward. To do this you will publish driving commands to another ROS node that communicates with the hardware. There are several different nodes in the base Racecar software that communicate with the car hardware and most of them can be started by running

$ teleop

In another terminal, check out what "topics" are active with rostopic list. Your output should be similar to below:

$ rostopic list

These topics are the channels that nodes communicate over. For example, suppose node A subscribes to a topic named /example. When any other node publishes a message to the topic named /example, node A will receive that message and can process it. Multiple nodes can subscribe to the same topic and multiple nodes can publish to the same topic.

ROS handles how all of these messages are passed around so that the programs can run in realtime. For example if a subscriber is spending too much time processing a message, other messages may be dropped so that nodes do not get too far out of sync.

Initializing a publisher

The topic that we are interested in is called /vesc/high_level/ackermann_cmd_mux/input/default which you should have seen in the list of topics. Anytime that you publish a message to this topic, a node on the other end will parse the message and use it to control the motor and steering servo of the car.

You will begin by creating a class to house your code and within that class creating a publisher. The publisher should publish to the /vesc/high_level/ackermann_cmd_mux/input/default topic. You also need to give this publisher a type. To find out what type of messages you should publish, run

$ rostopic info /vesc/high_level/ackermann_cmd_mux/input/default
Type: ackermann_msgs/AckermannDriveStamped
...

You should see that the topic type is AckermannDriveStamped. You will learn more about this type soon. Put these pieces together and write the class as follows:

 2 ...
 3 import rospy
 4 from ackermann_msgs.msg import AckermannDriveStamped
 5 
 6 class Drive:
 7   
 8   def __init__(self):
 9 
10     # Initialize a publisher
11     self.drive_pub = rospy.Publisher(
12         "/vesc/high_level/ackermann_cmd_mux/input/default",
13         AckermannDriveStamped,
14         queue_size=1)
15 ...

If you are unfamiliar with python classes you can read more about them here. The queue_size parameter defines how many messages the publisher should build up before it begins dropping the oldest ones. A queue size of 1 is for this application.

Publishing driving commands

Now that you have a publisher you need to actually publish messages to it. First, you should determine what exactly You can do that with rosmsg info:

$ rosmsg info AckermannDriveStamped

Alternatively you can use the web interface. As you can see the message is composed of a header which includes an ID and a timestamp. It also includes an AckermannDrive message called drive which includes several fields including speed and steering_angle.

To construct this message in python and assign the fields in parameter with the following:

1     # Construct the drive command
2     msg = AckermannDriveStamped()
3     msg.drive.steering_angle = 0.
4     msg.drive.speed = 1.

This code creates a message to drive forwards at [math] 1\text{m/s}[/math]

Complete code

Your completed code should look like the following

 1 #!/usr/bin/env python2
 2 
 3 import rospy
 4 from ackermann_msgs.msg import AckermannDriveStamped
 5 
 6 class Drive:
 7 
 8   def __init__(self):
 9 
10     # Initialize a publisher
11     self.drive_pub = rospy.Publisher(
12         "/vesc/high_level/ackermann_cmd_mux/input/default",
13         AckermannDriveStamped,
14         queue_size=1)
15 
16     # Initialize a timer
17     self.timer = rospy.Timer(
18         1/40., 
19         self.callback)
20 
21   def callback(self, _):
22     
23     # Construct the drive command
24     msg = AckermannDriveStamped()
25     msg.drive.steering_angle = 0.
26     msg.drive.speed = 1.
27 
28     # Publish the drive command
29     self.drive_pub.publish(msg)
30 
31 if __name__ == "__main__":
32   rospy.init_node("drive")
33   d = Drive()
34   rospy.spin()