How to Create a ROS2 Python Publisher – Iron

In this tutorial, we will go over how to create a Python publisher for ROS 2.

In ROS 2 (Robot Operating System 2), a Python publisher is a program or script (written in Python) that sends messages across the ROS network to other parts of the system.

The official instructions for creating a publisher are here, but I will walk you through the entire process, step by step.

We will be following the ROS 2 Python Style Guide.

Let’s get started!

Prerequisites

Directions

Open a terminal, and type these commands to open VS Code.

cd ~/ros2_ws
code .

You can close any pop ups that appear.

Let’s set our default indentation to 4 spaces.

Here are the steps to set a default 4-space indentation in VS Code:

1. Access Settings:

  • Using the menu: Go to File > Preferences > Settings (or press Ctrl+, on Windows/Linux or Cmd+, on macOS).

2. Modify Settings:

  • Search for “Indentation” in the settings panel.
  • Change the following values:
    • “Editor: Tab Size”: Set this to 4 to control the width of a tab character.
    • “Editor: Insert Spaces”: Set this to true to ensure that pressing Tab inserts spaces instead of a literal tab character.
    • (Optional) “Editor: Detect Indentation”: Set this to false if you want to prevent VS Code from automatically adjusting indentation based on existing code.

3. Apply Changes:

  • The changes should take effect immediately. You can test by opening a file and pressing Tab to see if it inserts 4 spaces.

Write the Code

Right-click on src/cobot_arm_examples, and type “scripts” to create a new folder for our Python script.

Right-click on the scripts folder to create a new file called “minimal_py_publisher.py”.

10-scripts
11-minimal-py-publisher

Type the following code inside minimal_py_publisher.py:

#! /usr/bin/env python3

"""
Description:
    This ROS 2 node periodically publishes "Hello World" messages on a topic.
    It demonstrates basic ROS concepts such as node creation, publishing, and
    timer usage.
-------
Publishing Topics:
    The channel containing the "Hello World" messages
    /topic - std_msgs/String
-------
Subscription Topics:
    None    
-------
Author: Addison Sears-Collins
Date: January 31, 2024
"""

import rclpy # Import the ROS 2 client library for Python
from rclpy.node import Node # Import the Node class for creating ROS 2 nodes

from std_msgs.msg import String # Import the String message type for publishing


class MinimalPublisher(Node):
    """Create MinimalPublisher node.

    """
    def __init__(self):
        """ Create a custom node class for publishing messages

        """

        # Initialize the node with a name
        super().__init__('minimal_publisher')

        # Creates a publisher on the topic "topic" with a queue size of 10 messages
        self.publisher_1 = self.create_publisher(String, '/topic', 10)

        # Create a timer with a period of 0.5 seconds to trigger the callback function
        timer_period = 0.5  # seconds
        self.timer = self.create_timer(timer_period, self.timer_callback)   

        # Initialize a counter variable for message content
        self.i = 0

    def timer_callback(self):
        """Callback function executed periodically by the timer.

        """
        # Create a new String message object
        msg = String()

        # Set the message data with a counter
        msg.data = 'Hello World: %d' % self.i

        # Publish the message on the topic
        self.publisher_1.publish(msg)

        # Log a message indicating that the message has been published
        self.get_logger().info('Publishing: "%s"' % msg.data)

        # Increment the counter for the next message
        self.i = self.i + 1


def main(args=None):
    """Main function to start the ROS 2 node.

    Args:
        args (List, optional): Command-line arguments. Defaults to None.
    """

    # Initialize ROS 2 communication
    rclpy.init(args=args)

    # Create an instance of the MinimalPublisher node
    minimal_publisher = MinimalPublisher()

    # Keep the node running and processing events.
    rclpy.spin(minimal_publisher)

    # Destroy the node explicitly
    # (optional - otherwise it will be done automatically
    # when the garbage collector destroys the node object)
    minimal_publisher.destroy_node()

    # Shutdown ROS 2 communication
    rclpy.shutdown()

if __name__ == '__main__':
    # Execute the main function if the script is run directly
    main()

To generate the comments for each class and function, you follow these steps for the autoDocstring package.

What we are going to do in this node is publish the string “Hello World” to a topic named /topic. The string message will also contain a counter that keeps track of how many times the message has been published.

We chose the name /topic for the topic, but you could have chosen any name.

Configure the Package

Create the __init__.py file

Now, we need to configure our package so that ROS 2 can discover this Python node we just created.

Right-click on src/cobot_arm_examples, and create a folder that has the same name as the package. This folder is required in order to run Python scripts.

ROS 2’s build system, ament, relies on this structure to correctly locate and build Python modules within a package. The folder with the same name as the package serves as the root for Python code, allowing ament to accurately generate and install the package’s Python modules.

Now right-click on the name of this folder, and create an empty script called

__init__.py.

Here is what the file should look like:

# Required to import Python modules
12-init-py

The presence of _ _init_ _.py explicitly designates a directory as a Python package. This enables Python’s import machinery to recognize and treat it as a cohesive collection of modules. 

Create a README.md

Now let’s create a README.md file. Right-click on the name of the package, and name the file README.md.

A README.md file is a plain text file that serves as an introduction and explanation for a project, software, or package. It’s like a welcome mat for anyone encountering your work, providing essential information and guidance to get them started.

You can find a syntax guide on how to write a README.md file here on GitHub.

# cobot_arm_examples

This package provides some basic examples to get you started with ROS 2 manipulation.

To see what the README.md file looks like, you can right-click on README.md on the left pane and click “Open Preview”.

Modify the package.xml File

Now let’s open the package.xml file. Make sure it looks like this.

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>cobot_arm_examples</name>
  <version>0.0.0</version>
  <description>Basic examples demonstrating ROS 2</description>
  <maintainer email="automaticaddison@example.com">Addison Sears-Collins</maintainer>
  <license>Apache-2.0</license>

  <!--Specify build tools that are needed to compile the package-->
  <buildtool_depend>ament_cmake</buildtool_depend>
  <buildtool_depend>ament_cmake_python</buildtool_depend>

  <!--Declares package dependencies that are required for building the package-->
  <depend>rclcpp</depend>
  <depend>rclpy</depend>
  <depend>std_msgs</depend>

  <!--Specifies dependencies that are only needed for testing the package-->
  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

The package.xml file is an important part of any ROS 2 package. It serves as the package’s manifest, holding essential metadata that ROS 2 tools use to build, install, and manage the package.

Here’s a breakdown of the key elements you’ll find in a typical package.xml file:

1. Basic Information:

  • name: The unique identifier for the package, often corresponding to the folder name.
  • version: The package’s semantic version, indicating its maturity and compatibility.
  • description: A brief explanation of the package’s purpose and functionality.

2. Dependencies:

  • build_depend: Packages and libraries required for building the current package.
  • buildtool_depend: Build tools (like compilers) needed for building the package.
  • run_depend: Packages and libraries required for running the package’s executables.

3. Build Configuration:

  • build_type: Specifies the build system (e.g., cmake, catkin).
  • export: Defines properties and settings used during package installation.

4. Maintainers and License:

  • maintainer: Information about the package’s primary developers and maintainers.
  • license: The license under which the package is released (e.g., Apache 2.0).

Modify the CMakeLists.txt File

Now let’s configure the CMakeLists.txt file. A CMakeLists.txt file in ROS 2 defines how a ROS 2 package should be built. It contains instructions for building and linking the package’s executables, libraries, and other artifacts. The standard sections of a CMakeLists.txt file for ROS 2 are as follows:

1. cmake_minimum_required:

cmake_minimum_required(VERSION 3.5)

Specifies the minimum required version of CMake for building the package. This is typically set to a version that is known to be compatible with ROS 2.

2. project:

project(my_package)

Specifies the name of the project (ROS 2 package). This sets up various project-related variables and settings.

3. find_package:

find_package(ament_cmake REQUIRED)

Finds and loads the necessary dependencies for the ROS 2 package. `ament_cmake` is a key package used in ROS 2 build systems.

4. ament_package:

ament_package()

Configures the package to use the appropriate ROS 2 build and install infrastructure. This line should be present at the end of the `CMakeLists.txt` file.

5. add_executable or add_library:

add_executable(my_node src/my_node.cpp)

Defines an executable or a library to be built. This command specifies the source files associated with the target.

6. ament_target_dependencies:

ament_target_dependencies(my_node rclcpp)

Declares the dependencies for a target (executable or library). In this example, `my_node` depends on the `rclcpp` library.

7. install:

install(TARGETS 

           my_node

           DESTINATION lib/${PROJECT_NAME})

Specifies the installation rules for the built artifacts. It defines where the executable or library should be installed.

8. ament_export_dependencies:

ament_export_dependencies(ament_cmake)

Exports the dependencies of the package. This is used to inform downstream packages about the dependencies of the current package.

9. ament_export_include_directories:

ament_export_include_directories(include)

Exports the include directories of the package. This is used to inform downstream packages about the include directories.

10. ament_export_libraries:

ament_export_libraries(my_library)

Exports the libraries of the package. This is used to inform downstream packages about the libraries.

11. ament_package_config_dependency:

ament_package_config_dependency(rclcpp)

Declares a dependency on another package for the purpose of package configuration. This is used when configuring the package for building against other packages.

These sections collectively define the build process, dependencies, and installation rules for a ROS 2 package. The specific content within each section will vary depending on the package’s requirements and structure. 

Build the Workspace

Now that we have created our script and configured our build files, we need to build everything into executables so that we can run our code.

Open a new terminal window, and type the following commands:

cd ~/ros2_ws/
colcon build
13-colcon-build
source ~/.bashrc

Run the Node 

In this section, we will finally run our node.

Here’s the general syntax for running a node:

ros2 run <package_name> <python_script_name>.py

Here’s a breakdown of the components:

  • <package_name>: Replace this with the name of your ROS 2 package containing the Python script.
  • <python_script_name>.py: Replace this with the name of your Python script file that contains the ROS 2 node.

Note that, you can use the tab button to autocomplete a partial command. For example, type the following and then press the TAB button on your keyboard. 

ros2 run cobot_arm_examples min

After autocompletion, the command looks like this:

ros2 run cobot_arm_examples minimal_py_publisher.py

Now, press Enter.

Here is what the output looks like:

15-publishing

Open a new terminal window.

Let’s see a list of all currently active topics.

ros2 topic list
16-ros2-topic-list

We see we have three active topics:

/parameter_events
/rosout
/topic

/parameter_events and /rosout topics appear even when no nodes are actively running due to the presence of system-level components and the underlying architecture of the ROS 2 middleware.

The /parameter_events topic facilitates communication about parameter changes, and the /rosout topic provides a centralized way to access log messages generated by different nodes within the ROS 2 network.

/topic is the topic we created with our Python node. Let’s see what data is being published to this topic.

ros2 topic echo /topic
17-ros2-topic-echo

You can see the string message that is being published to this topic, including the counter integer we created in the Python script.

Press CTRL + C in the terminal to stop the output.

At what frequency is data being published to this topic?

ros2 topic hz /topic

Data is being published at 2Hz, or every 0.5 seconds.

18-ros2-topic-hz

Press CTRL + C in the terminal to stop the output.

What type of data is being published to this topic, and how many nodes are publishing to this topic?

ros2 topic info /topic
19-ros2-topic-info

To get more detailed information about the topic, you could have typed:

ros2 topic info /topic --verbose

What are the currently active nodes?

ros2 node list
20-ros2-node-list

Let’s find out some more information about our node.

ros2 node info /minimal_publisher
21-ros2-node-info

Close the Node

Now go back to the terminal where your minimal_py_publisher.py script is running and press CTRL + C to stop its execution.

To clear the terminal window, type:

clear

Congratulations! You have written your first publisher in ROS 2.

In this example, you have written a publisher to publish a basic string message. On a real robot, you will write many different publishers that publish data that gets shared by the different components of a robot: strings, LIDAR scan readings, ultrasonic sensor readings, camera frames, 3D point cloud data, integers, float values, battery voltage readings, odometry data, and much more. 

The code you wrote serves as a template for creating these more complex publishers. All publishers in ROS 2 are based on the basic framework as minimal_py_publisher.py.

That’s it. Keep building!

ROS 2 Communication Basics: Publishers, Subscribers, Topics

The foundation of ROS 2 communication is made up of three elements, publishers, subscribers, and topics.

Let’s use an analogy to explain these three elements.

Imagine our robot is like a superhero team with lots of helpers. Each helper has a special superpower, like seeing far away with LIDAR, seeing close-up with the camera, and detecting nearby objects with the ultrasonic sensor.

Publishers are like the robot’s messengers. They take the information from each helper (like the distance LIDAR measured or the picture the camera saw) and shout it out loud on a special channel called a topic. Think of it like a walkie-talkie channel just for robot information!

Subscribers are like the robot’s listeners. They tune into the topic and listen for the messages the publishers shout. When they hear something, they understand what it means and tell the robot what’s happening around it. For example, if the LIDAR subscriber hears “Obstacle 5 meters ahead!” the robot knows to stop.

So, the publisher is the one who shares the information, the subscriber is the one who listens and learns, and the topic is the special channel they use to talk to each other. This way, all the robot’s helpers can work together and keep it safe and aware of its surroundings, just like a superhero team!

Remember, just like superheroes have different powers, publishers and subscribers have different jobs, but they all work together to make the robot super smart.

Publishers and subscribers are functionalities within processes called nodes. Nodes are the fundamental building blocks of a ROS 2 system, each representing an independent unit of computation with specific tasks.

Nodes:

  • Individual processes that perform specific tasks within a ROS 2 system.
  • Each node has a unique name and can host multiple publishers and subscribers.
  • Nodes communicate with each other through topics.

Publishers:

  • Functionalities within a node that publish data via topics.
  • Topics are named channels like “sensor_data” or “motor_commands” where data is published and made available to interested subscribers.
  • Any node can subscribe to a topic, regardless of the publisher node.

Subscribers:

  • Functionalities within a node that receive data published to a topic.
  • They can process the received data and utilize it for their specific tasks.
  • Multiple subscribers can listen to the same topic simultaneously.

Here is a graphic from the ROS 2 website that puts all this together.

publish_subscribe_architecture

You don’t need to memorize the information above. Just refer back to it whenever you get confused on the ROS 2 jargon.

Keep building!

How to Create a ROS 2 Package – Iron

In this tutorial, we will go over how to create a ROS 2 package.

A ROS 2 package is the fundamental building block of robot software in ROS 2. Imagine it as a Lego brick in your robot’s software architecture. Each package contains a specific piece of functionality or capability, like motor control, sensor processing, or communication with other systems.

Remember, just like Legos, you can combine multiple ROS 2 packages to create complex robot functionalities. Each package plays its specific role, and together they build the complete software system for your robot.

The official instructions for creating a package are here, but I will walk you through the entire process, step by step.

Let’s get started!

Prerequisites

Directions

Open a terminal, and type these commands (note: we are using the Apache 2.0 license, but you can remove that piece if you want):

cd ~/ros2_ws/src
ros2 pkg create --build-type ament_cmake --license Apache-2.0 cobot_arm_examples

Now let’s build our new package:

cd ~/ros2_ws
colcon build 

Let’s see if our new package is recognized by ROS 2.

Either open a new terminal window or source the bashrc file like this:

source ~/.bashrc
ros2 pkg list

You can see the newly created package right there at the top.

8-cobot-arm-examples

Install Useful Packages (Optional)

In a month or so, I will begin building a robotic arm. The package in this tutorial will serve as the foundation for the ROS 2 software.

Let’s install some useful packages that will help us along the way.

Begin by installing a tool called Terminator. Terminator will enable you to have multiple terminal panes within a single interface.

Open a terminal window, and type the following commands:

sudo apt-get update -y
sudo apt-get upgrade -y
sudo apt-get install terminator

To open terminator, you can either click the 9 dots in the bottom left of your Desktop and search for “terminator”….or you can type terminator in a regular terminal window.

You can use CTRL + Shift + E or CTRL + Shift + O to split the terminal window into different panels.

Let’s install some useful ROS 2 packages. Open a terminal window, and type the following:

sudo apt-get install ros-$ROS_DISTRO-gazebo-ros
sudo apt-get install ros-$ROS_DISTRO-gazebo-ros2-control
sudo apt-get install ros-$ROS_DISTRO-joint-state-publisher-gui
sudo apt-get install ros-$ROS_DISTRO-moveit
sudo apt-get install ros-$ROS_DISTRO-xacro
sudo apt-get install ros-$ROS_DISTRO-ros2-control
sudo apt-get install ros-$ROS_DISTRO-ros2-controllers
sudo apt-get install libserial-dev
sudo apt-get install python3-pip
pip install pyserial

If you want to use Amazon Alexa Voice Assistant down the road to control your robotic arm, install the following:

pip install ask-sdk
pip install flask
pip install flask-ask-sdk

Configure Colcon

colcon is the primary command-line tool for building, testing, and installing ROS packages.

In your terminal window, type the following command:

echo "source /usr/share/colcon_argcomplete/hook/colcon-argcomplete.bash" >> ~/.bashr

That’s it! Keep building!