In this tutorial, we will go over how to create a Python subscriber for ROS 2.
In ROS 2 (Robot Operating System 2), a Python subscriber is a program or script (written in Python) that listens for messages being published on a specific topic.
Topics in ROS 2 are channels of communication named according to the type of information they carry, such as “/robot/speed” for speed information or “/camera/image” for vision information. Each subscriber in ROS 2 declares its interest in a particular topic and is programmed to react or process the messages received on that topic.
The official instructions for creating a subscriber 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
- You have created a ROS 2 workspace.
- You have created a ROS 2 package.
- You have Visual Studio code installed.
- You have created a ROS 2 publisher.
Directions
Open a terminal, and type these commands to open VS Code.
cd ~/ros2_ws
code .
Write the Code
Right-click on src/cobot_arm_examples/scripts, and create a new file called “minimal_py_subscriber.py”
Type the following code inside minimal_py_subscriber.py:
#! /usr/bin/env python3
"""
Description:
This ROS 2 node subscribes to "Hello World" messages on a topic.
It demonstrates basic ROS concepts such as node creation and subscribing.
-------
Publishing Topics:
None
-------
Subscription Topics:
The channel containing the "Hello World" messages
/topic - std_msgs/String
-------
Author: Addison Sears-Collins
Date: February 15, 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
class MinimalSubscriber(Node):
"""Create Minimal Subscriber node.
"""
def __init__(self):
""" Create a custom node class for subscribing
"""
# Initialize the node with a name
super().__init__('minimal_subscriber')
# Creates a subscriber
self.subscriber_1 = self.create_subscription(
String,
'/topic',
self.listener_callback,
10)
def listener_callback(self, msg):
"""Call this function every time a new message is published on
the topic.
"""
# Log a message indicating that the message has been published
self.get_logger().info('I heard: "%s"' % msg.data)
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 MinimalSubscriber node
minimal_subscriber = MinimalSubscriber()
# Keep the node running and processing events.
rclpy.spin(minimal_subscriber)
# Destroy the node explicitly
# (optional - otherwise it will be done automatically
# when the garbage collector destroys the node object)
minimal_subscriber.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 subscribe to a topic named /topic that contains String messages.
Configure the Package
Modify the CMakeLists.txt File
Now let’s configure the CMakeLists.txt file. Here is what it should look like:
cmake_minimum_required(VERSION 3.8)
project(cobot_arm_examples)
# Check if the compiler being used is GNU's C++ compiler (g++) or Clang.
# Add compiler flags for all targets that will be defined later in the
# CMakeLists file. These flags enable extra warnings to help catch
# potential issues in the code.
# Add options to the compilation process
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# Locate and configure packages required by the project.
find_package(ament_cmake REQUIRED)
find_package(ament_cmake_python REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclpy REQUIRED)
find_package(std_msgs REQUIRED)
# Define a CMake variable named dependencies that lists all
# ROS 2 packages and other dependencies the project requires.
set(dependencies
rclcpp
std_msgs
)
# Add the specified directories to the list of paths that the compiler
# uses to search for header files. This is important for C++
# projects where you have custom header files that are not located
# in the standard system include paths.
include_directories(
include
)
# Tells CMake to create an executable target named minimal_cpp_publisher
# from the source file src/minimal_cpp_publisher.cpp. Also make sure CMake
# knows about the program's dependencies.
add_executable(minimal_cpp_publisher src/minimal_cpp_publisher.cpp)
ament_target_dependencies(minimal_cpp_publisher ${dependencies})
add_executable(minimal_cpp_subscriber src/minimal_cpp_subscriber.cpp)
ament_target_dependencies(minimal_cpp_subscriber ${dependencies})
# Copy necessary files to designated locations in the project
install (
DIRECTORY cobot_arm_examples scripts
DESTINATION share/${PROJECT_NAME}
)
install(
DIRECTORY include/
DESTINATION include
)
# Install cpp executables
install(
TARGETS
minimal_cpp_publisher
minimal_cpp_subscriber
DESTINATION lib/${PROJECT_NAME}
)
# Install Python modules for import
ament_python_install_package(${PROJECT_NAME})
# Install Python executables
install(
PROGRAMS
scripts/minimal_py_publisher.py
scripts/minimal_py_subscriber.py
#scripts/example3.py
#scripts/example4.py
#scripts/example5.py
#scripts/example6.py
#scripts/example7.py
DESTINATION lib/${PROJECT_NAME}
)
# Automates the process of setting up linting for the package, which
# is the process of running tools that analyze the code for potential
# errors, style issues, and other discrepancies that do not adhere to
# specified coding standards or best practices.
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# comment the line when a copyright and license is added to all source files
set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# comment the line when this package is in a git repo and when
# a copyright and license is added to all source files
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
# Used to export include directories of a package so that they can be easily
# included by other packages that depend on this package.
ament_export_include_directories(include)
# Generate and install all the necessary CMake and environment hooks that
# allow other packages to find and use this package.
ament_package()
Build the Workspace
Open a new terminal window, and type the following commands:
cd ~/ros2_ws/
colcon build
source ~/.bashrc
Run the Node
In this section, we will finally run our node. Open a terminal window, and type:
ros2 run cobot_arm_examples minimal_py_publisher.py
Open another terminal window, and type:
ros2 run cobot_arm_examples minimal_py_subscriber.py
Now, press Enter.
Here is what the output looks like:
Open a new terminal window.
Let’s see a list of all currently active topics.
ros2 topic list
What are the currently active nodes?
ros2 node list
Close the Node
Now go back to the terminals where your scripts are running and press CTRL + C to stop the execution.
To clear the terminal window, type:
clear
Congratulations! You have written your first subscriber in ROS 2.
In this example, you have written a subscriber to listen to a basic string message. On a real robot, you will write many different subscribers that subscribe to data that gets published 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 subscribers. All subscribers in ROS 2 are based on the basic framework as the subscriber you just wrote, minimal_py_subscriber.py.
In fact, even after over a decade of working with ROS, I still refer back to this template when I am building new subscriber nodes for my robots.