ROS2 Launch Files

ROS2 allows you to run individual nodes with the command:

$ ros2 run <package_name> <node_name>

This is nice and fun if you are just running a couple of nodes at the same time, but imagine you need to run 10-20 nodes like this. That would be really cumbersome to do so. Instead, you can use a so-called launch file. These files allow you to run multiple nodes with a single command.

$ ros2 launch <package_name> <launch_file_name.launch.py>

Launch files have another advantage: you can also set different ROS2 parameters that are taken over to the individual nodes, and you can also specify to change topic names. Another nice feature is that you can group multiple nodes into a logical group, especially when these nodes are always started together. For example, if you have a service server that subscribes to a camera topic and saves the image when being called, you want to start the camera node and the service server always together. n this case, a launch file will do the trick.

Create a launch file for a Python package

You have already seen how to start a ROS program by using the ros2 run command. The ros2 run command allows you to start one single program at a time. In most cases, one single program will not be enough to get your robot up and running. In these cases, a launch file will make your life easier.

First, create a directory called launch to organize your package. Therefore, you must be inside your my_turtlesim folder.

$ cd ~/ros2_ws/src/my_turtlesim/
$ mkdir launch

Now, enter the new directory to create a new file. You can do this with your file browser or with the terminal:

$ cd launch

In ROS2, there are 3 different types of launch files: Python files, XML files and YAML files. In ROS1, there were only XML files. Since Python files provide the most flexibility, they are often seen as the default type of launch files in ROS2.

You can create a new Python launch file and open it with your terminal with the following command:

$ gedit my_turtlesim.launch.py

Now, a new window should open up. It shows an empty text file. The launch.py extension is used as a convention to show that it is a launch file and not simply a Python script.

Type the following code into the launch file and save the file:

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='turtlesim',
            executable='turtlesim_node',
            name='my_turtlesim_node',
            output='screen',
        ),
       Node(
            package='my_turtlesim',
            executable='my_simple_publisher',
            name='my_simple_publisher',
            output='screen',
        )
    ])

You can now save the file and close it.

The first lines import the necessary modules to launch ROS2 nodes.

from launch import LaunchDescription
from launch_ros.actions import Node

Next, a launch description is defined, including all the nodes that you want to launch. In this example, we would like to launch the turtlesim node inside the package turtlesim. The name of the executable is turtlesim_node. This is also the name that you would use when using ros2 run. The name in this case can be used to overwrite the actual node name as defined inside the node. This can be interesting if you launch the same node multiple times and you want to avoid collisions caused by two nodes with the exact same name. The output will define where the ROS2 log entries, which usually appear in the terminal, will be sent to. A launch file will default simply write them in a log file and not display anything on the screen. Here, we actually tell ROS2 to write these log entries on the screen.

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='turtlesim',
            executable='turtlesim_node',
            name='my_turtlesim_node',
            output='screen',
        ),

Inside the launch description, you can also define a second node you want to call. In this case, you can add the simple publisher that makes the turtle move in a circle. This was a package that you created by yourself. The syntax is exactly the same. You indicate the package name, the executable name and the node name.

       Node(
            package='my_turtlesim',
            executable='my_simple_publisher',
            name='my_simple_publisher',
            output='screen',
        )
    ])

Before you can use the new launch file, you need to modify the package.xml file and the setup.py file. Open the package.xml file:

$ gedit ~/ros2_ws/src/my_turtlesim/package.xml

And add the following line to the file:

<exec_depend>ros2launch</exec_depend>

Save the file and close it. Now, open the setup.py file:

$ gedit ~/ros2_ws/src/my_turtlesim/setup.py

And modify the data_files part by adding the following entry:

(os.path.join('share', package_name, 'launch'), glob('launch/*.py')),

You also need to add some imports at the beginning of the file:

from glob import glob
import os

The file should now look as follows:

from setuptools import setup
from glob import glob
import os

package_name = 'my_turtlesim'

setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
        (os.path.join('share', package_name, 'launch'), glob('launch/*.py')),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='dave',
    maintainer_email='dave@todo.todo',
    description='TODO: Package description',
    license='TODO: License declaration',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'my_first_node = my_turtlesim.my_first_program:main',
            'my_simple_publisher = my_turtlesim.my_simple_publisher:main',
            'my_simple_subscriber = my_turtlesim.my_simple_subscriber:main',
            'my_service_server = my_turtlesim.my_service_server:main',
            'my_service_client = my_turtlesim.my_service_client:main',
        ],
    },
)

Save the file and close it. With these modifications, we tell the ROS2 package to look for launch files and where to look for them. Now, re-build your workspace and source it again:

$ cd ~/ros2_ws/
$ colcon build --symlink-install
$ source install/local_setup.bash

Now, you can launch your new launch file with the following command:

$ ros2 launch my_turtlesim my_turtlesim.launch.py

As you can see, this command is now starting the turtlesim node and at the same time, it makes the turtle move in a circle. This means, two ROS2 nodes have been started with a single command.

Other Launch File Formats

As mentioned before, there are three types of launch files: Python, XML and YAML. ROS1 programmers will be most familiar with XML launch files. You can create one by opening a new text file:

$ gedit ~/ros2_ws/src/my_turtlesim/launch/my_turtlesim.launch.xml

Now, insert the following code:

<launch>  
	<node pkg="turtlesim" exec="turtlesim_node" output="screen" />  
	<node pkg="my_turtlesim" exec="my_simple_publisher" output="screen" />  
</launch>

Save the file and close it. As you can see, this code is quite different but also much shorter. At the same time, since XML is not a scripting language, it provides less flexibility and functionality than the Python version.

In some cases, it can be interesting to write down the entries in multiple lines, especially when more elements are added to the file:

<launch>  
	<node pkg="turtlesim" 
          exec="turtlesim_node" 
          output="screen" />  
	<node pkg="my_turtlesim" 
          exec="my_simple_publisher" 
          output="screen" />  
</launch>

This can add some clarity as all the elements are in a block.

Let’s also create a YAML launch file:

$ gedit ~/ros2_ws/src/my_turtlesim/launch/my_turtlesim.launch.yaml

Add the following code:

launch:
    - node: {pkg: "turtlesim", exec: "turtlesim_node", output: "screen"}  
    - node: {pkg: "my_turtlesim", exec: "my_simple_publisher", output: "screen"} 

Save the document and close it.

As you can see, the YAML file is even shorter than the XML file. The YAML file has a bit more of a symbol-based syntax where you need to pay attention to put all the brackets, colons and commas in the right position. XML uses tags that make the file more verbose but also more explicit.

Now, you also need to modify the setup.py file again:

$ gedit ~/ros2_ws/src/my_turtlesim/setup.py

Just as with the Python files, you need to tell ROS2 where to look for the launch files. This includes the file extensions .launch.xml and .launch.yaml.

        (os.path.join('share', package_name, 'launch'), glob('launch/*.xml')),
        (os.path.join('share', package_name, 'launch'), glob('launch/*.yaml')),

The entire file should then look like this:

from setuptools import setup
from glob import glob
import os

package_name = 'my_turtlesim'

setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
        (os.path.join('share', package_name, 'launch'), glob('launch/*.py')),
        (os.path.join('share', package_name, 'launch'), glob('launch/*.xml')),
        (os.path.join('share', package_name, 'launch'), glob('launch/*.yaml')),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='dave',
    maintainer_email='dave@todo.todo',
    description='TODO: Package description',
    license='TODO: License declaration',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'my_first_node = my_turtlesim.my_first_program:main',
            'my_simple_publisher = my_turtlesim.my_simple_publisher:main',
            'my_simple_subscriber = my_turtlesim.my_simple_subscriber:main',
            'my_service_server = my_turtlesim.my_service_server:main',
            'my_service_client = my_turtlesim.my_service_client:main',
        ],
    },
)

Save the file and close it. Head back to the root directory of the workspace, build the package and source the workspace again:

$ cd ~/ros2_ws/
$ colcon build --symlink-install
$ source install/local_setup.bash

You can launch the XML launch file with the following command:

ros2 launch my_turtlesim my_turtlesim.launch.xml

And the YAML file with the following command:

ros2 launch my_turtlesim my_turtlesim.launch.yaml

The XML and the YAML files will start the turtlesim node and make it move in a circle, just like the Python version.

ROS2 Services

Services in ROS2 are another type of communication between different ROS2 nodes. After you learned how to use Topics, you might wonder, why you would need something else for communication, right?

As explained in the overview, different communication types are used for different purposes. While Topics are mainly used for continuous streams of data, Services will be used for tasks where a constant data stream would be overkill, especially when the data exchange is less frequent. Another use case would be when your program relies on receiving feedback from a sent information, for example when your robot finished a task.

You can find more information about Services in the official ROS2 documentation.

What are Services?

A Service is a type of communication that adopts the idea of a handshake protocol as it is implemented by having a client application that will send a request to the server to perform a task. After the server has finished the task, it will send a message back to the client to notify it of the result. The client will patiently wait for a response before it will continue with other tasks. Especially this last behaviour is one of the major drawbacks of using services as the client is basically unresponsive while waiting for the response. The advantage is that it provides a simple and elegant solution that receives a response rather than broadcasting into the unknown while producing a massive amount of unnecessary data traffic.

If you would like to reproduce the same strategy with Topics, you would need to have two publishers and two subscribers that constantly send the request and the answer to make sure the message has been sent and the result has been received. This would create unnecessary data traffic to obtain the same result.

After all these explanations, let’s have a look at a service message. As already mentioned, there is two-way communication which means the messages have two components. An example is the service message /turtle1/set_pen which is a message for the TurtleSim program seen earlier.

Launch the turtlesim node with:

$ ros2 run turtlesim turtlesim_node

Now, you can have a look at the services that are available with the following command:

$ ros2 service list

The following list will appear:

ros2 service list with the turtlesim application running

We can see there is a service called /turtle1/set_pen. We can have a closer look at this service with the following command:

$ ros2 service type /turtle1/set_pen

This command will return the message type for this service. In this case, the service type is turtlesim/srv/SetPen.

/turtle1/set_pen message type

To get more information about this service message type, use the interface command:

$ ros2 interface show turtlesim/srv/SetPen

This command will show you the service message description.

turtlesim/srv/SetPen interface description

For the /turtle1/set_pen message, this message looks as follows:

uint8 r
uint8 g
uint8 b
uint8 width
uint8 off
---

In general, the service message has a request and a response part, with their respective data types (which can be any type such as int8, uint16, string, bool, …), that are separated by three dashes as seen below:

requestType request
---
responseType response

Note: The request can be composed of several components or it can be empty, like in the SetPen interface. The same holds true for the response. In the above example, the values of the three base colours are given as part of the request while the response is an empty message. This means that there is a response, but it does not contain any data. Pay attention that, even though the response is empty, it needs to be sent as otherwise, the client will keep waiting for a response forever.

The message type of the /turtle1/set_pen message is a /turtlesim/srv/SetPen message, a custom message for this application. There are only a few standard message types for services as they are often very specific for a task. It is fairly common to create a new service message for each service used, unlike topics that usually rely on standard message types.

Using Services in Terminal

In case you only want to get some information about existing Services or you just want to test the function of a service, there is no need to write a ROS2 program to do that. Instead, you can simply use the terminal to do the job.

Starting TurtleSim

If you have no turtlesim node running, you can start it with the following command:

$ ros2 run turtlesim turtlesim_node

The first thing you need to know is, how to find which Services are already available. This allows you to use the Services that already exist.

Finding Information about the Services

As seen above, you can show a list of the existing Services, use the following command:

$ ros2 service list

This command will list all the Services that are already available. The Services available with the turtesim node are the following:

/clear
/kill
/reset
/spawn
/turtle1/set_pen
/turtle1/teleport_absolute
/turtle1/teleport_relative
/turtlesim/describe_parameters
/turtlesim/get_parameter_types
/turtlesim/get_parameters
/turtlesim/list_parameters
/turtlesim/set_parameters
/turtlesim/set_parameters_atomically

Let’s have a closer look at the /spawn service:

$ ros2 service type /spawn

This Service is of the type turtlesim/srv/Spawn. Let’s have a look at its description:

float32 x
float32 y
float32 theta
string name # Optional.  A unique name will be created and returned if this is empty
---
string name

Looks like this Service requires 4 different fields to be called.

Calling the Service

You can call the Service as follows:

$ ros2 service call /spawn turtlesim/srv/Spawn "{x: 2, y: 2, theta: 0, name: 'foxy_turtle'}"

When you execute this command, you will see the following response:

waiting for service to become available...
requester: making request: turtlesim.srv.Spawn_Request(x=2.0, y=2.0, theta=0.0, name='foxy_turtle')

response:
turtlesim.srv.Spawn_Response(name='foxy_turtle')

And you will see that a second turtle has appeared in the turtlesim node:

spawning a new turtle with the spawn service

Moreover, the new turtle has its own set of Services. You can find them with the following command:

$ ros2 service list

The following new Services are available now:

/foxy_turtle/set_pen
/foxy_turtle/teleport_absolute
/foxy_turtle/teleport_relative

These new Services use the name specified in the Service call: /foxy_turtle.

ros2 service list to see new services

In the previous part about Topics, you learned how to Subscribe to Topics and how to Publish them. In this case, though a Service also consists of two parts, you can only use one part in the terminal: the Service Client. The Client sends a request (like you did through the terminal) to a Service Server. The Server performs an algorithm with a return value that is returned back to the Client. As the Server part is usually more complex, it cannot be done in the terminal.

As with the Topics, using the terminal to interact with Services is usually only done for testing or quickly checking if the Services are running.

Using Services in Python

The usual method of using Services is through ROS2 nodes. In the next parts, you will see how to use Services with Python.

As mentioned earlier, there are two parts of the Service: the Client and the Server. These two parts can be split up into two separate Python nodes.

Service Servers

The Server is the part of a Service that is being called and performs an action as a result. When the action is finished, the Server provides a response to the Client that sent the Request.

First, go to the directory containing the Python programs that you made earlier.

cd ~/ros2_ws/src/my_turtlesim/my_turtlesim/

Now, create a new empty file and open it with your text editor.

gedit my_service_server.py

Before you can type the code for running your Service Server node, you want to decide which message type you will use for calling the Service. There are some standard message types that you can use (see here). Let’s say you want to create a service that will allow you to make the turtle move in a circle after the service call. The message will not contain any data except for the information on when to start but you do want to know if the message has been received correctly. Therefore, the Trigger Service message seems ideal as there is no input data and you receive feedback.

Have a look at the Trigger message description:

ros2 interface show std_srvs/srv/Trigger

The Trigger message contains the following information:

---
bool success   # indicate successful run of triggered service
string message # informational, e.g. for error messages

The idea is that the client can send a message to the server to request that the turtle starts moving. As soon as the service call is received the server will reply and make the turtle move in circles.

The code for the Service Server will look as seen here below. Note that the code also contains code to publish to a Topic.

import rclpy
from rclpy.node import Node
from std_srvs.srv import Trigger
from geometry_msgs.msg import Twist


class MyServiceServer(Node):

    def __init__(self):
        super().__init__('my_service_server')
        self.my_service = self.create_service(Trigger, 'draw_circle', self.draw_circle_callback)
        self.publisher_ = self.create_publisher(Twist, 'turtle1/cmd_vel', 10)

    def draw_circle_callback(self, request, response):
        request # request must be specified even if it is not used
        self.get_logger().info('Received request to draw a circle!')
        response.success = True
        response.message = "Starting to draw a circle!"
        timer_period = 0.5  # seconds
        self.timer = self.create_timer(timer_period, self.publish_velocity_callback)
        self.i = 0

        return response

    def publish_velocity_callback(self):
        my_velocity = Twist()
        my_velocity.linear.x = 0.5
        my_velocity.angular.z = 0.5
        self.publisher_.publish(my_velocity)
        self.get_logger().info(f"Publishing velocity: \
            \n\t linear.x: {my_velocity.linear.x}; \
            \n\t linear.z: {my_velocity.linear.x}")
        self.i += 1


def main(args=None):
    rclpy.init(args=args)

    my_service_server = MyServiceServer()

    rclpy.spin(my_service_server)
    rclpy.shutdown()


if __name__ == '__main__':
    main()

In the first part, you only tell ROS2 which modules you import. In this case, you need to import the Service message of the type std_srvs/srv/Trigger and also the geometry_msgs/msg/Twist message for publishing the velocity in a Topic.

import rclpy
from rclpy.node import Node
from std_srvs.srv import Trigger
from geometry_msgs.msg import Twist

Next, you create a class that inherits from the Node class. In its __init__() function, you define the node name, the publisher for the velocity commands and the Service server. The Service takes the message type, the Service name and the callback function as arguments.

class MyServiceServer(Node):

    def __init__(self):
        super().__init__('my_service_server')
        self.my_service = self.create_service(Trigger, 'draw_circle', self.draw_circle_callback)
        self.publisher_ = self.create_publisher(Twist, 'turtle1/cmd_vel', 10)

The moment that a service call is received, it will trigger a callback function in the code. In this case, the function is responsible to make the TurtleSim move in circles. This is done by creating a publisher and then sending messages to the topic /turtle1/cmd_vel. In this implementation, the topic is published repeatedly in a timer callback function. The function finishes by returning the service response. At this point, the turtle starts drawing circles.

    def draw_circle_callback(self, request, response):
        request # request must be specified even if it is not used
        self.get_logger().info('Received request to draw a circle!')
        response.success = True
        response.message = "Starting to draw a circle!"
        timer_period = 0.5  # seconds
        self.timer = self.create_timer(timer_period, self.publish_velocity_callback)
        self.i = 0

        return response

The callback function of the timer simply sets the velocity command to be published and prints the command message to the screen.

    def publish_velocity_callback(self):
        my_velocity = Twist()
        my_velocity.linear.x = 0.5
        my_velocity.angular.z = 0.5
        self.publisher_.publish(my_velocity)
        self.get_logger().info(f"Publishing velocity: \
            \n\t linear.x: {my_velocity.linear.x}; \
            \n\t linear.z: {my_velocity.linear.x}")
        self.i += 1

The main function initialises the ROS2 communication and creates an instance of our MyServiceServer class. Then, it keeps the node alive until CTRL+C is pressed.

def main(args=None):
    rclpy.init(args=args)

    my_service_server = MyServiceServer()

    rclpy.spin(my_service_server)
    rclpy.shutdown()


if __name__ == '__main__':
    main()

Save the code with your text editor and close it. Don’t forget to make your Python program executable:

$ chmod +x my_service_server.py

You need to add the new ROS2 Python script to the setup.py file. Open it with:

$ gedit ~/ros2_ws/src/my_turtlesim/setup.py

Add an entry for your new Python program in the entry_points so it should look as follows:

entry_points={
    'console_scripts': [
        'my_first_node = my_turtlesim.my_first_program:main',
        'my_simple_publisher = my_turtlesim.my_simple_publisher:main',
        'my_simple_subscriber = my_turtlesim.my_simple_subscriber:main',
        'my_service_server = my_turtlesim.my_service_server:main',
    ],
},

Now go to the root directory of your workspace and build the package:

$ cd ~/ros2_ws/
$ colcon build --packages-select my_turtlesim --symlink-install
$ source install/local_setup.bash

The colcon build command should build the package without any issues.

colcon build the my_turtlesim package

If you want to run and test the new service server, you will need to have 3 terminals: one for the turtlesim node, one for the Service Server node and one to call the Service.

In the first terminal, start the turtlesim node:

$ ros2 run turtlesim turtlesim_node

In the second terminal, start the Service Server:

$ ros2 run my_turtlesim my_service_server

In the third terminal, call the Service:

$ ros2 service call /draw_circle std_srvs/srv/Trigger {}

You will see that the turtle will start moving after you called the Service:

turtle making circles after calling the /draw_circle service

After executing the command, you can see how the turtle starts moving in a circle.

Calling the Service from the terminal is useful to test if the Service s working properly. On a real robot, it is more likely that the Service is called by another ROS2 program. This is called a Service Client.

Service Clients

The Client is the part that calls the Service. This means it sends a request to the Server and then waits for a response. One node can consist of several Clients that call different Services.

To start, you can go to the package you already made earlier:

$ cd ~/ros2_ws/src/my_turtlesim/my_turtlesim/

Now you can create a new file called my_service_client.py in which you will write the Python code to create a Service Client node:

$ gedit my_service_client.py

Now, an empty text editor window will pop up where you can type down the following code:

import rclpy
from rclpy.node import Node
from std_srvs.srv import Trigger


class MyServiceClient(Node):

    def __init__(self):
        super().__init__('my_service_client')
        self.my_client = self.create_client(Trigger, 'draw_circle')
        while not self.my_client.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('Waiting for service to become avilable...')
        self.req = Trigger.Request()

    def send_request(self):
        self.future = self.my_client.call_async(self.req)
        rclpy.spin_until_future_complete(self, self.future)
        return self.future.result()


def main(args=None):
    rclpy.init(args=args)

    my_service_client = MyServiceClient()
    response = my_service_client.send_request()
    my_service_client.get_logger().info(
        f'Result for triggering "Draw Circle: \
        \n\tSuccessful: {response.success} \
        \n\tMessage: {response.message}')

    my_service_client.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

What this code does, is first import the rclpy library and the Service message type Trigger from the subfolder srv from the ROS package called std_srvs.

import rclpy
from rclpy.node import Node
from std_srvs.srv import Trigger

The next part creates a class inherited from the Node class. This class, called MyServiceClient initiates the node with the node name my_service_client and creates a Service Client instance using the Service message type and the name of the Service. Next, we create an empty Service request message.

class MyServiceClient(Node):

    def __init__(self):
        super().__init__('my_service_client')
        self.my_client = self.create_client(Trigger, 'draw_circle')
        while not self.my_client.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('Waiting for service to become avilable...')
        self.req = Trigger.Request()

The function send_request() defines a variable called self.future which receives the response from the Service Server. Then, the program spins until the response has been received with the rclpy.spin_until_future_complete() function. In this function, you need to specify which variable contains the received response. The variable name self.future can be chosen freely, but for clarity, it contains the word future to specify it is the response that will be obtained in the near future after making the request. The function ends with returning the received answer.

    def send_request(self):
        self.future = self.my_client.call_async(self.req)
        rclpy.spin_until_future_complete(self, self.future)
        return self.future.result()

The main function initialises the ROS2 communication and then creates an instance of the node class. Then, we added a log entry to display in the terminal that we created a service request for the service /draw_circle. We end the program by destroying the node and shutting down the program.

def main(args=None):
    rclpy.init(args=args)

    my_service_client = MyServiceClient()
    response = my_service_client.send_request()
    my_service_client.get_logger().info(
        f'Result for triggering "Draw Circle: \
        \n\tSuccessful: {response.success} \
        \n\tMessage: {response.message}')

    my_service_client.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

Save the code and then make the file executable with the following command:

$ chmod +x my_service_client.py

Now, add the new node to the setup.py file:

$ gedit ~/ros2_ws/src/my_turtlesim/setup.py

And add a new entry:

entry_points={
    'console_scripts': [
        'my_first_node = my_turtlesim.my_first_program:main',
        'my_simple_publisher = my_turtlesim.my_simple_publisher:main',
        'my_simple_subscriber = my_turtlesim.my_simple_subscriber:main',
        'my_service_server = my_turtlesim.my_service_server:main',
        'my_service_client = my_turtlesim.my_service_client:main',
    ],
},

Save the file and close it. Now go to the root directory of your workspace, build the package and source the workspace:

$ cd ~/ros2_ws/
$ colcon build --packages-select my_turtlesim --symlink-install
$ source install/local_setup.bash

Now, you can test the Service Client. For this, you need 3 terminals: one for the turtlesim node, one for the Service Server node and one to call the Service with your new node.

In the first terminal, start the turtlesim node:

$ ros2 run turtlesim turtlesim_node

In the second terminal, start the Service Server:

$ ros2 run my_turtlesim my_service_server

In the third terminal, run the Service Client:

$ ros2 run my_turtlesim my_service_client

You will see that the turtle will start moving after you run the Service Client node:

run service client

If everything went correctly, the turtle should start moving and a message should appear that the Service call was successful.

Since the program is only spinning until the response has been received, the program will stop automatically. This also means that the program is not responsive until the response has been received. If the server is taking longer or is not responding at all, the client node can be stuck. This could disrupt the behaviour of the robot. Therefore, Services should only be used for tasks where the server will finish quickly such as taking a picture, saving a map or maybe closing or opening a gripper. For tasks that take longer, it is recommended to use ROS2 Actions. Since ROS2 Actions are a bit more advanced, they will not be covered here.

ROS2 topics

You learned how to use ROS2 packages to start one or several nodes. You also learned how to create your own ROS2 programs with Python. In this article, you will learn how to subscribe to a Topic and how to publish to a Topic.

There are many sources covering ROS2 Topics such as the official ROS2 documentation.

What are Topics?

As already mentioned earlier, a Topic is a way of communication between ROS2 nodes. This protocol created a data stream from a Publisher to a Subscriber. It is possible that several Publishers are sending data to a Topic at the same time and several Subscribers can listen to a Topic simultaneously.

Each Topic consists of a Topic name and a message type. The name is used to refer to a specific Topic while the message type defines the actual structure of the content. A fairly common Topic name is /cmd_vel which contains a Twist message. Twist messages describe the three velocity parameters for the translation and rotation of a robot. Twist belongs to a category of ROS2 messages called geometry_msgs. This is simply the ROS2 package that contains these message definitions. Twist is defined as follows:

Vector3  linear:
    float64 x
    float64 y
    float64 z
Vector3  angular:
    float64 x
    float64 y
    float64 z

Note: You can find more references to the geometry_msgs Twist messages here and here.

This means that you can access the properties of a Twist object in the following way in Python:

my_message = Twist()

my_message.linear.x = 0
my_message.linear.y = 0
my_message.linear.z = 0
my_message.angular.x = 0
my_message.angular.y = 0
my_message.angular.z = 0

First, you define the name of the variable and set it to the variable type of Twist() which is a constructor that creates a Twist object. It initializes all the values to zero.

Topics can also be less complex data types such as Int or String which then only contain a simple integer or string value. These message types belong to the ROS package called std_msgs. Another very common type is sensor_msgs for IMU data, camera data or laser scanner data.

Using Topics in Terminal

In case you only want to see the content of a topic or see what topics are available, you don’t need to write a ROS2 program to listen to a Topic. You can do this in the terminal as well. You can even publish some data into a Topic. This is mainly used for testing purposes and not really used for actual robot control.

Starting TurtleSim

Open a terminal and start the turtlesim node with the following command:

$ ros2 run turtlesim turtlesim_node

A new window with the turtlesim application will appear.

Starting turtlesim node

The first thing you need to know is, how to find which Topics are already used by a robot. This is useful as you can use the Topics that are already available rather then creating a new Topic even though, it is not necessary.

Finding Information about the Topics

The following command (again in a new terminal) will show you a list of the Topics that are either being published or subscribed to by a node:

$ ros2 topic list

The output in your terminal should look like this:

Now, you know which topics are currently available. You can get more information about these Topics with the following command:

$ ros2 topic info /turtle1/pose

This command will provide the following information:

The information you get is that this topic is of type turtlesim/msg/Pose which means it is a message type inside the package called turtlesim. The message type is Pose and it contains the following information:

float32 x
float32 y
float32 theta

float32 linear_velocity
float32 angular_velocity

The Pose messages contain information about the current position and orientation of the turtle and the linear and angular velocity. This block of information is published by the turtlesim regularly.

Subscriber

So the first thing you want to learn is how to see what is inside a Topic. Let’s take /turtle1/pose for example. You can listen to this Topic by using your terminal with the following command:

$ ros2 topic echo /turtle1/pose

Now, you will see something like the following:

You can stop incoming messages by hitting CRTL+c on your keyboard. The Topic that you are looking at is showing you the position of the little turtle on the canvas.

Publisher

Just like you can listen to a Topic through the terminal, you can also write messages to a Topic through the terminal. Therefore, you can use the following command to write to /turtle1/cmd_vel:

$ ros2 topic pub /turtle1/cmd_vel geometry_msgs/Twist "{linear: {x: 0.25, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.25}}" -r 10

Your terminal will look like this:

TOS2 topic pub to publish cmd_vel messages

You will see that the terminal is sending messages to the turtlesim. In this case, it tells the turtle to move forward and to the left. As a result, the turtle is making a circle. The units in the command are given in m/s and rad/s. The general command is:

$ ros2 topic pub <topic_name> <message_type> "<topic_message>"

You might have noticed that we added a -r 10 at the end of the message. Many ROS2 commands have additional arguments that you can provide. The argument -r 10 instructs the command to repeat the message at a rate of 10 Hz.

Using the terminal is mainly used for quick verification or testing of a system or for a single event that doesn’t need repetition. For controlling a robot, you will probably write a program that will perform the same tasks autonomously.

More information on ROS2 message types can be found on the ROS2 overview page.

Using Topics in Python

Publisher

To start, you can go to the package you already made earlier:

$ cd ~/ros2_ws/src/my_turtlesim/my_turtlesim/

Now you can create a new file called my_simple_publisher.py in which you will write the Python code to create a Publisher node:

$ gedit my_simple_publisher.py

Add the following code to the file:

import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist


class MySimplePublisher(Node):

    def __init__(self):
        super().__init__('my_simple_publisher')
        self.get_logger().info('Creating simple publisher!')
        self.publisher_ = self.create_publisher(Twist, 'turtle1/cmd_vel', 10)
        timer_period = 0.5  # seconds
        self.timer = self.create_timer(timer_period, self.publish_velocity_callback)
        self.i = 0

    def publish_velocity_callback(self):
        my_velocity = Twist()
        my_velocity.linear.x = 0.5
        my_velocity.angular.z = 0.5
        self.publisher_.publish(my_velocity)
        self.get_logger().info(f"Publishing velocity: \n\t linear.x: {my_velocity.linear.x}; \n\t linear.z: {my_velocity.linear.x}")
        self.i += 1


def main(args=None):
    # initiate ROS2
    rclpy.init(args=args)

    # create an instance of the node
    my_simple_publisher = MySimplePublisher()

    # keep the node alive intil pressing CTRL+C
    rclpy.spin(my_simple_publisher)
    
    # destroy the node when it is not used anymore
    my_simple_publisher.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

You start with importing the rclpy module, the Node class, and the Twist message as we will use this message to publish to the topic “/turtle1/cmd_vel”.

import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist

Next, you need to create a class that inherits from the Node class object. Let’s call it MySimplePublisher. It initialises a ROS2 node to allow using ROS2 communication methods like topics. Next, we need a publisher object that we call self.publisher. It defines the topic name, the message type and the queue size. Since we want the node to publish the messages at a regular time interval, we define a timer that waits for 0.5 seconds and then we create a timer that triggers a callback function based on this 0.5 seconds.

class MySimplePublisher(Node):

    def __init__(self):
        super().__init__('my_simple_publisher')
        self.get_logger().info('Creating simple publisher!')
        self.publisher_ = self.create_publisher(Twist, 'turtle1/cmd_vel', 10)
        timer_period = 0.5  # seconds
        self.timer = self.create_timer(timer_period, self.publish_velocity_callback)
        self.i = 0

A callback is a function that is triggered by an event rather than a specified sequence in the program code. In this case, the timer will trigger the callback function every 0.5 seconds, or at a frequency of 2 Hz.

This callback function then creates a message of the type Twist and defines the value for the forward direction (linear.x) and the rotation to the left (angular.z). Lastly, the callback publishes the Twist message and writes a message to the terminal.

    def publish_velocity_callback(self):
        my_velocity = Twist()
        my_velocity.linear.x = 0.5
        my_velocity.angular.z = 0.5
        self.publisher_.publish(my_velocity)
        self.get_logger().info(f"Publishing velocity: \n\t linear.x: {my_velocity.linear.x}; \n\t linear.z: {my_velocity.linear.x}")
        self.i += 1

Now, in the main function of the program, you initialise the ROS2 library and create an instance of your custom node class. The rclpy.spin() function makes sure that this instance keeps running until it gets shut down.

def main(args=None):
    # initiate ROS2
    rclpy.init(args=args)

    # create an instance of the node
    my_simple_publisher = MySimplePublisher()

    # keep the node alive intil pressing CTRL+C
    rclpy.spin(my_simple_publisher)
    
    # destroy the node when it is not used anymore
    my_simple_publisher.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

You need to make the Python program executable before you can build the package again and run it:

$ chmod +x my_simple_publisher.py

Also, you need to add the new node to the setup.py file. Make sure the entry_point looks as follows:

    entry_points={
        'console_scripts': [
            'my_first_node = my_turtlesim.my_first_program:main',
            'my_simple_publisher = my_turtlesim.my_simple_publisher:main',
        ],
    },

To build the package, go to the root directory of your workspace and build it:

$ cd ~/ros2_ws/
$ colcon build --packages-select my_turtlesim --symlink-install

This time, we use two additional flags compared to the last time we built the workspace. --packages-select <package_name> allows you to build only a single package. In this case, it does not affect anything, since we only have a single package in our workspace, but if you have multiple packages, it allows you to build only the ones that you are currently interested in. The flag --symlink-install allows you to modify the Python code without rebuilding the package. By default, colcon makes a copy of the source code and then runs that copy when executing the program. Since this package contains Python files, it should be possible to change the code and run it without building the package. This flag makes this possible. This allows us to quickly make changes to the code.

The package should have been built without issues.

Building the ROS2 topic publisher

Before running the new node, you need to source the workspace again:

$ source install/local_setup.bash

Make sure you have a turtlesim node running. If not, you can start one with the command:

$ ros2 run turtlesim turtlesim_node

In a separate terminal, you can start your publisher node with the following command:

$ ros2 run my_turtlesim my_simple_publisher.py

You will notice that the turtle will start to move in a circle.

Simple publisher making the turtle move

You can also verify the published messages with the following command:

$ ros2 topic echo /turtle1/cmd_vel

This command will show you the messages sent to the topic /turtle1/cmd_vel, which are the Twist messages that your my_simple_publisher.py program is publishing. The command above will show the following:

Subscriber

When making a robotic system, you are more likely to create a ROS2 node that will take the role of a Subscriber. This has the advantage that you can automatically listen to a Topic and then act depending on the data your program receives.

Let’s make a node that listens to the “/turtle1/pose” topic to get the position of the turtlesim.

To start, you can go to the package you already made earlier:

$ cd ~/ros2_ws/src/my_turtlesim/my_turtlesim/

Now you can create a new file called my_simple_subscriber.py in which you will write the Python code to create a Subscriber node:

$ gedit my_simple_subscriber.py

Now, an empty text editor window will pop up where you can type down the following code:

import rclpy
from rclpy.node import Node
from turtlesim.msg import Pose


class MySimpleSubscriber(Node):

    def __init__(self):
        super().__init__('minimal_subscriber')
        self.my_subscriber = self.create_subscription(
            Pose,
            'turtle1/pose',
            self.listener_callback,
            10)
        # prevent warning that self.my_subscriber is not used
        self.my_subscriber

    def listener_callback(self, msg):
        self.get_logger().info(f'Turtle found at x: {msg.x}; y: {msg.y}')


def main(args=None):
    rclpy.init(args=args)

    my_simple_subscriber = MySimpleSubscriber()
    rclpy.spin(my_simple_subscriber)

     # destroy the node when it is not used anymore
    my_simple_subscriber.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

First, you need to import the rclpy and its Node class. Then, you also import the Pose message type.

import rclpy
from rclpy.node import Node
from turtlesim.msg import Pose

You have to create a new class that inherits from the Node class. Let’s call it MySimpleSubscriber. In its init function, you need to give it a node name. Also, you can create a subscriber object that defines the topic message type, the topic name, the callback function and the queue size. This callback function gets triggered every time a new message is coming in from the “/turtle1/pose” topic. Here, we also call the self.subscriber object once so that the program will not complain about an unused variable.

class MySimpleSubscriber(Node):

    def __init__(self):
        super().__init__('minimal_subscriber')
        self.my_subscriber = self.create_subscription(
            Pose,
            'turtle1/pose',
            self.listener_callback,
            10)
        # prevent warning that se.fmy_subscriber is not used
        self.my_subscriber

The callback for the subscriber is very simple as we just want to print the message in the terminal, using the self.get_logger().info() function.

    def listener_callback(self, msg):
        self.get_logger().info(f'Turtle found at x: {msg.x}; y: {msg.y}')

In the main function, we can find the initialisation of the rclpy and the instance of our subscriber node class. Then, the node is kept alive with the rclpy.spin() function. Lastly, we clean up the node when the program is finished and close the program.

def main(args=None):
    rclpy.init(args=args)

    my_simple_subscriber = MySimpleSubscriber()
    rclpy.spin(my_simple_subscriber)

     # destroy the node when it is not used anymore
    my_simple_subscriber.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Save the code and then make the file executable with the following command:

$ chmod +x ~/ros2_ws/src/my_turtlesim/my_turtlesim/my_simple_subscriber.py

Add the new so it will look as followsnode to the setup.py file:

    entry_points={
        'console_scripts': [
            'my_first_node = my_turtlesim.my_first_program:main',
            'my_simple_publisher = my_turtlesim.my_simple_publisher:main',
            'my_simple_subscriber = my_turtlesim.my_simple_subscriber:main',
        ],
    },

You need to return to the workspace root directory to build the package once again. This is necessary since we added a new file. Also here, we can use the --symlink-install flag to allow changing the code later if necessary:

$ cd ~/ros2_ws/
$ colcon build --symlink-install

Now, source the package and run the new node:

$ source install/local_setup.bash
$ ros2 run my_turtlesim my_simple_subscriber.py

The subscriber will print the pose data of the turtle in the terminal. If this is not the case, make sure that the turtlesim node is running in another terminal.

My simple subscriber receiving the pose messages

Currently, the message will always be the same unless you make the turtle move. For this, you could use the publisher from earlier.

When using Python to access Topics, you can have two different kinds of programs: a Subscriber or a Publisher. In addition, you can also have a program that implements several Subscribers or several Publishers or even both.

A common use case is that a publisher is being called in the callback function of a subscriber. As a result, the node will send a new message for each incoming message. This can be handy when one topic contains information related to the subscribed topic. This could be an image filter or a node that verifies the distance towards an object.

That’s it. Now you are able to write nodes that can subscribe or publish to topics. In some cases, you need to combine both in a single program. For now, you can continue with Services and how to set up a Service Server and how to create a Service Client.

Creating ROS2 Packages

After you learned how to use a ROS2 package, you will learn how to create your own ROS2 package.

To start, first, make sure ROS2 Foxy is installed. If this is the case, you can activate the ROS2 Foxy installation with:

$ source /opt/ros/foxy/setup.bash

Note: You need to repeat this command for each new terminal. If you write this command into your .bashrc file, you do not have to repeat it each time.

Create a Package

Your own ROS2 packages should all be placed inside your ROS2 workspace, which you probably named ros2_ws. ROS2 has its own command to create new packages. Open a terminal and type to enter your workspace’s source code directory:

$ cd ~/ros2_ws/src/

You can create a new ROS package with the following command:

$ ros2 pkg create <package_name>

Here, the <package_name> is the name of your package. Note that you can not easily change that name after you create it, as you will need to modify the CMakeLists.txt file and the package.xml file.

Next to the package name, you will also need to specify whether it will be a C++ package or a Python package. For this, you need to add --build-type ament_python to the command. Let’s create a Python package called my_turtlesim:

$ ros2 pkg create --build-type ament_python my_turtlesim

It is a convention to name ROS2 packages with lowercase names combined with an underscore. This notation is also called snake case.

The terminal will show some details of what files are being created inside the new package.

Creating a new ROS2 Python Package

Enter the folder my_turtlesim. Here, you will find several files and directories. You can enter the package and show its content with the following commands:

$ cd ~/ros2_ws/src/my_turtlesim/
$ ls

The following picture shows you the output of the list command.

Content of a new ROS2 Python Package

You can see that the my_turtlesim package has the following folders and files:

  • my_turtlesim: contains source code for the ROS2 Python programs
  • package.xml: contains package-specific elements such as dependencies and author information
  • resource: for libraries files
  • setup.cfg: contains instructions to build the ROS2 package in general
  • setup.py: contains instructions to build the ROS2 package such as which programs to build and where the launch files are located
  • test: contains test files to test the code

You can create additional folders such as a launch folder for launch files or a config folder for configuration files containing ROS2 parameters.

Create a ROS Python Program

Now, you can create your first ROS2 program with Python. Enter the my_turtlesim directory with your terminal:

$ cd my_turtlesim/

You need to create a new file with the file extension .py in order to have a Python file. You can create a new file and open it with your terminal with the following command:

$ gedit my_first_program.py

You will see that a new window will open on your screen with an empty text file. This is where you will write your first ROS2 program in Python.

Write the following code in your file and save the file. This code will simply create a ROS2 node that displays a message I n the terminal. It does nothing else than that. This node is an absolute minimum working example for a ROS2 Python node.

import rclpy
from rclpy.node import Node


class MyFirstProgram(Node):

    def __init__(self):
        super().__init__('my_first_program')
        self.get_logger().info('Hello, World')


def main(args=None):
    rclpy.init(args=args)

    my_first_program = MyFirstProgram()

    my_first_program.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

To use the ROS2 tools inside a Python script, you need to import the ROS Client Library for Python with import rclpy.

Every ROS2 program is running as one or multiple ROS2 nodes. To create these nodes, you need to import the Node class from the rclpy module and create a class that inherits from the Node class.

class MyFirstProgram(Node):

    def __init__(self):
        super().__init__('my_first_program')
        self.get_logger().info('Hello, World')

Inside the class definition, you need to specify the name of the node. In this case, it is defined as my_first_program. The node name should give clear information on what the node is doing.

The main function will initiate the ROS2 module and read any ROS2 arguments. Then, it creates an instance of our custom Node class. Then, The code makes sure the node is closed properly after finishing its job and the program exits.

def main(args=None):
    rclpy.init(args=args)

    my_first_program = MyFirstProgram()

    my_first_program.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

Before you can run your program, you need to make the Python file executable, this means giving this file permissions from your system to be executed as a program. Usually, files only have permission to be read as a file or to be modified. This is also known as read-write permission. To add the permission to execute the file, make sure you are in the my_turtlesim directory and type the following command in your terminal and press ENTER:

$ chmod +x my_first_program.py

Making Python files executable is required for each Python file you create but you only need to do this process once for each Python file.

Next, you need to tel the ROS2 build system, called colcon, which Python program to build. For this, go to the package root folder and open the file setup.py. Add the following piece of code into the entry_points={} part:

entry_points={
        'console_scripts': [
            'my_first_node = my_turtlesim.my_first_program:main',
        ],

Open the file with the following commands:

$ cd ~/ros2_ws/src/my_turtlesim/
$ gedit setup.py

The entire file should look as follows:

from setuptools import setup

package_name = 'my_turtlesim'

setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='dave',
    maintainer_email='dave@todo.todo',
    description='TODO: Package description',
    license='TODO: License declaration',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'my_first_node = my_turtlesim.my_first_program:main',
        ],
    },
)

Save the file and close it.

Add the new node to the setup.py file

When you build the package later, this line will make sure that the Python script will be seen as a valid ROS2 node with the name my_first_node which can be found inside the my_turtlesim package and the source code is saved in the file my_first_program.py.

Note: the node name and the Python file name are not the same in this example. Many ROS2 developers actually use the same name for both to keep their packages a little bit more organised. Here, the names are different to make it clear which name refers to which element.

After that, go to the main directory of the ros2_ws and build your package:

$ cd ~/ros2_ws/
$ colcon build

The terminal will show you some details about the build process such as which package is being built and how long it takes.

Colcon build your first ROS2 Python node

Next, you need to source this workspace so that ROS2 will take into account all the packages in this workspace:

$ source install/local_setup.bash

Now, everything is ready to run your new program. For this, use the following command:

$ ros2 run my_turtlesim my_first_node

The terminal should output the following text:

[INFO] [1663744547.799484529] [my_first_program]: Hello, World!

This is it, you just grated your first Python program in ROS2! Congratulations! Of course, this program is not doing anything useful yet. But this is the absolute minimum you need to run a proper ROS2 Python program.

Tip: you don’t need to type the entire command by yourself. If you start typing a word, you can double-tap the TAB key on your keyboard to auto-complete the commands in your terminal.

Note: if your computer doesn’t show your ROS2 package, first make sure you actually have spelt the names correctly and that the package actually exists. Also, if the package is new, ROS2 might not know about it yet. Therefore, type the following to list all the packages on your system, after that, ROS2 should be able to auto-complete the name of your package as well:

$ ros2 pkg list

The above command will list all the installed ROS2 packages on your computer. As ROS2 is going through the entire system, it will probably find your package and add it to its known packages.

Next, you will learn how to create a node that can actually interact with other ROS2 nodes by using ROS2 topics.

Using ROS2 Packages

The following article will explain what a ROS2 package is and how you can use them to run programs in ROS2. Make sure you have a basic understanding of ROS2 by looking at the overview page.

What are ROS Packages?

As mentioned earlier, every program in ROS2 is delivered as a package. A package can contain various types of files where the most important files are the CMakeLists.txt file and the package.xml file. These two files are automatically generated when you create a package. These files contain information about the package so it can be built, which means the source code can be compiled so that you can run the programs. This means, that packages usually also contain the source code of the programs you want to run.

Now that you have an idea of what a package is, you can see how you can run them.

Running a ROS2 package

Before running a ROS program, you need to have ROS2 installed as explained in the installation tutorial.

Activating ROS2

When you just open a terminal, you can’t use ROS2 right away. You have to source the ROS2 installation first. This needs to be done for each terminal that you use. This is done with the following command:

$ source /opt/ros/foxy/setup.bash

In the installation tutorial, this has already been explained and also that you can add this line to the .bashrc file. This file is executed each time you open a new terminal.

Note: this step is necessary as it is possible to have multiple ROS installations installed at the same time, such as ROS1 and ROS2. The computer needs to know which version you actually want to use.

Running a ROS2 program

Now, it is time to start a real ROS program. Therefore, you need to open a new terminal and then you can run a program with the following syntax:

$ ros2 run <ros2_package> <ros2_program>

Of course, the <ros2_package> and the <ros2_program> are placeholders and need to be replaced by an actual package and program name. For example, you can run the turtlesim program which is an animated 2D turtle that can be controlled with ROS commands just like a real robot.

$ ros2 run turtlesim turtlesim_node

Now, a little window should open on your screen and you should see a little turtle in the middle of a coloured canvas.

Next, you can interact with the turtle by starting another node by opening a new terminal and typing:

$ ros2 run turtlesim draw_square

The turtle will start moving in a square shape and it will draw a line on the canvas where it is moving:

At this point, you have two ROS programs running that interact with each other. As mentioned earlier, each ROS program is running as a node. These nodes can be visualized with a program called RQT. You can show these nodes with the following command in a new terminal:

$ ros2 run rqt_graph rqt_graph

The RQT graph will look as follows:

You have two nodes running and they communicate by using Topics. In the next article, you will learn how to create your own package and how you can simplify the process of starting multiple ROS2 programs at the same time.

Creating ROS Packages

After you learned how to use a ROS package, you will learn how to create your own ROS package.

Create a Package

Creating a package is done by using the catkin environment as each ROS package is following the catkin format. First, you need to go to your catkin workspace which is called catkin_ws. Open a terminal and type:

cd ~/catkin_ws

Next, you need to enter the src directory which stores the source code of your own packages:

cd src

You can create a new ROS package with the following command:

catkin_create_pkg <package_name> <dependencie_1> <dependencie_2> <dependencie_3> <...>

Here, the <package_name> is the name of your package. Note that you can not simply change that name after you created it as you will need to modify the CMakeLists.txt file and the package.xml file. The <depenencies> are entries in the CMakeLists.txt and package.xml files that allow ROS to include some libraries that you can use. For Python programs, you need to add the dependency rospy and for C++ programs, you need to add roscpp. If you use libraries for navigation, sensor data collection or something else, you can also add these dependencies here.

For you package, you will simply add the rospy dependency and you can call the package my_turtlesim.

catkin_create_pkg my_turtlesim rospy

It is a convention to name packages with lower case names combined with an underscore. This notation is also called snake case.

Now, a new directory has been created containing a CMakeLists.txt file and the package.xml file and a folder called src. This folder is meant to contain the C++ source code. This is why many people and tutorials make a new directory for Python scripts called scripts. Therefore, type the following in your terminal:

cd ~/catkin_ws/src/my_turtlesim/
mkdir scripts

If you also have a robot model, configuration files or documentation to your package, you can create more folders and store the files in the correct folder.

Create a ROS Python Program

Now, you can create your first ROS program with Python. Enter the scripts directory with your terminal:

cd scripts

You need to create a new file with the file extension .py in order to have a Python file. You can create a new file and open it with your terminal with the following command:

gedit my_first_program.py

You will see that a new window will open on your screen with an empty text file. This is where you will write your first ROS program in Python.

Write the following code in your file and save the file:

#!/usr/bin/env python

import rospy

if __name__ == "__main__":
    rospy.init_node("my_first_node")
    rospy.loginfo("Hello World!")

Note: in order to use the ROS tools inside a Python script, you need to import rospy. Also, as every ROS program is running as a node, you need to declare a name for your node with the rospy.init_node(“my_first_node”) function. Each ROS node needs to have a name as otherwise the roscore doesn’t know which node is executing code. This means, one of the first things you want to do in your programs is to declare the name of the node.

Before you can run your program, you need to make the Python file executable, this means to give this file permissions from your system to be executed as a program. Usually, files only have the permission to be read as a file or to be modified. This is also known as read-write permission. To add the permission to execute the file, make sure you are in the scripts directory and type the following command in your terminal and press ENTER:

chmod +x my_first_program.py

Making Python files executable is required for each Python file you create but you only need to do this process once for each Python file. You can run your Python program with the following command:

rosrun my_turtlesim my_first_program.py

The terminal in which you started the Python program should output some text as you can see below:

Tip: you don’t need to type the entire command by yourself. If you start typing a word, you can double tap the TAB key on your keyboard to auto-complete the commands in your terminal.

Note: if your computer doesn’t show your ROS package, first make sure you actually have spelled the names correctly and that the package actually exists. Also, if the package is new, ROS might not know about it yet. Therefore, type the following to list all the packages on your system, after that, ROS should be able to auto-complete the name of your package as well:

rospack list

The above command will list all the installed ROS packages on your computer. As ROS is going through the entire system, it will probably find your package and add it to its known packages.

Create a launch file

You have already seen how to start a ROS program by using the rosrun command. The rosrun command allows you to start one single program at a time. In most cases, one single program will not be enough to get your robot up and running. In these cases, a launch file will make your life easier.

First, create a directory called launch to organize your package. Therefore, you must be inside your my_turtlesim folder.

cd ~/catkin_ws/src/my_turtlesim/
mkdir launch

Even though, there is no requirement to call this directory launch, it is a widely used convention. The best idea is, to stick to these conventions as they make life easier for you and your team.

Now, enter the new directory to create a new file. You can do this with your file browser or with the terminal:

cd launch

You can create a new file and open it with your terminal with the following command:

gedit turtlesim.launch

Now, a new window should open up. It shows an empty text file. The launch extension is actually a XML file type that is used to create and structure a launch file for ROS.

Type the following code into the launch file and save the file:

<?xml version="1.0" encoding="UTF-8"?>

<launch>

    <node
        name="my_node"
        pkg="my_turtlesim"
        type="my_first_program"
        output="screen"/>

</launch>

Note: unlike Python programs, you do not need to make the launch file executable.

You can simply start it with the following command in a new terminal:

roslaunch my_turtlesim turtlesim.launch

The above command will give you the following result:

You can see that the roslaunch method of starting a program outputs much more in the terminal. This is because there is much more going on. At first, ROS will check if there is already a roscore running and if not, it will start the roscore. Next, it will start the programs that are listed in the launch file.

As you added the line output=”screen” to the launch file, it will print the output on the terminal, otherwise it would not show anything from the started programs.

You can now change the content of the launch file into the following code:

<?xml version="1.0" encoding="UTF-8"?>

<launch>

    <node
        name="turtlesim_node"
        pkg="turtlesim"
        type="turtlesim_node"
        output="screen"/>

    <node
        name="draw_square"
        pkg="turtlesim"
        type="draw_square"
        output="screen"/>

</launch>

Save the file and launch it again with:

roslaunch my_turtlesim turtlesim.launch

You should now see that the turtlesim window is opening and the turtle is immediately starting to move in a square pattern. The launch file has now started two ROS programs at the same time. This makes many things much easier. On top of that, you do not need to first start the roscore as the launch file is already starting it for you.

Explanation of the Launch File

The launch file is written in XML syntax. This means you have tags that are indicating the type of content. The following tag tells your computer what type of file the launch file actually is:

<?xml version="1.0" encoding="UTF-8"?>

The content of your launch file will then be written inside the launch tags:

<launch>

</launch>

Lastly, the node tags describe what ROS program you want to run. This is indicated with the following lines:

    <node
        name="turtlesim_node"
        pkg="turtlesim"
        type="turtlesim_node"
        output="screen"/>

Here, the syntax is as follows:

    <node
        name="<name_of_the_node>"
        pkg="<name_of_the_package>"
        type="name_of_the_program_file"
        output="screen"/>

In this context, the <name_of_the_node> refers to the name you used inside the rospy.init_node() statement. As you do not know the exact name of the turtlesim_node source file, you can just assume it has the same name as the program name. The <name_of_the_package> is the name of the ROS package that contains the wanted ROS program. The <name_of_the_program_file> is the name of the Python file with the .py extension or the name of the compiled C++ program which doesn’t have any extension in Linux.

The output=”screen” will make sure that all text will still be shown on the terminal. On a robot, you do not need this but for running programs on your computer with a screen, this is helpful to see what is happening. In case you create a launch file and you don’t see output in your terminal, check if this is missing.

With this, you have learned how to run and how to create ROS packages. Next, you will learn how to subscribe and publish to Topics.

Using ROS Packages

The following article will explain what a ROS package is and how you can use them to run programs in ROS, the Robot Operating System.

What are ROS Packages?

As mentioned earlier, every program in ROS is delivered as a package. A package can contain various types of files where the most important files are the CMakeLists.txt file and the package.xml file. These two files are automatically generated when you create a package. These files contain information about the package so it can be build, which means the source code can be compiled so that you can run the programs. This means, that packages usually also contain the source code of the programs you want to run.

Now that you have an idea what a package is, you can see how you can run them.

Running a ROS package

Before running a ROS program, you need to do a couple of extra steps in advance. These steps are to build the catkin workspace, start the roscore and then starting the actual program.

The catkin workspace

A catkin workspace is a directory on your computer that follows some specific guidelines. These make it easier for the software using the directory and its files to find what it needs, in this case, the source code of your software.

To build your catkin workspace which you have created during the installation of ROS, you type the following two commands, assuming you called your catkin workspace also catkin_ws:

cd ~/catkin_ws
catkin_make

After catkin has finished, you can now start a roscore and then run the software.

Starting ROS

Before you can run any ROS program, you need to start a roscore. This can be done by opening a terminal and typing the following text and then hitting ENTER on the keyboard:

roscore

Starting the roscore in the terminal should output something similar to the following image:

Now, the roscore should be running and the computer is ready to run ROS programs. Note: only one single roscore can run in the same robotic system at the same time, so you only need to do this step once.

If the roscore dies, you need to restart it. You can also stop the roscore by pressing CTRL+c on your keyboard.

Running a ROS program

Now, it is time to start a real ROS program. Therefore, you need to open a new terminal and then you can run a program with the following syntax:

rosrun <ros_package> <ros_program>

Of course, the <ros_package> and the <ros_program> are placeholders and need to be replaced by an actual package and program name. For example, you can run the turtlesim program which is an animated 2D turtle that can be controlled with ROS commands just like a real robot.

rosrun turtlesim turtlesim_node

Now, a little window should open on your screen and you should see a little turtle in the middle of a colored canvas.

Next, you can interact with the turtle by starting another node by opening a new terminal and typing:

rosrun turtlesim draw_square

The turtle will start moving in a square shape and it will draw a line on the canvas where it is moving:

At this point, you have two ROS programs running that interact with each other. As mentioned earlier, each ROS program is running as a node. These nodes can be visualized with a program called RQT. You will learn more about this software later, but here is the graph that this software will generate for your current setup:

You have two nodes running and they communicate by using Topics. In the next article, you will learn how to create your own package and how you can simplify the process of starting multiple ROS programs at the same time.