September 15, 2026
hands-on-with-ros-2-nodes-topics-and-services

Imagine being tasked with integrating a dozen engineering teams, each responsible for a specific subsystem of a large, complex humanoid robot. One team develops the intricate arm and hand movements, another handles locomotion for balance, while a third manages sensory input from cameras and motion detectors. The challenge lies in orchestrating seamless communication and data exchange between these disparate components, ensuring they act in concert to achieve the robot’s overall mission. Historically, this meant engineers spent an inordinate amount of time crafting bespoke messaging schemes and underlying frameworks for every new robotic project, often reinventing the wheel and delaying actual development. This fundamental inefficiency in robotics development was precisely the problem the Robot Operating System (ROS) was designed to solve.

The Genesis of ROS: Addressing a Fundamental Challenge

Hands On with ROS 2: Nodes, Topics, and Services

Before the advent of ROS in the late 2000s, roboticists faced a fragmented landscape where each new robot project often necessitated the creation of entirely new communication protocols and software architectures. This "reinventing the wheel" approach was not only time-consuming but also hindered collaboration and the reusability of code, significantly slowing down progress in the burgeoning field of robotics. The lack of a standardized middleware meant that integrating different hardware components and software modules from various developers was an arduous task, riddled with compatibility issues.

The solution emerged from Stanford University’s Salisbury Robotics Lab in 2006, where Ph.D. students Eric Berger and Keenan Wyrobek began developing what would become the Robot Operating System. Their vision was to create a standardized framework that would abstract away the complexities of inter-process communication, allowing roboticists to focus on the unique challenges of their specific applications rather than the underlying messaging infrastructure. This foundational work quickly caught the attention of Scott Hassan, founder of the Willow Garage incubator, a pivotal organization dedicated to advancing robotics research and open-source development. Hassan invited Berger and Wyrobek to continue their work at Willow Garage, providing the resources and collaborative environment needed to mature ROS into a robust, widely adopted platform.

Over the subsequent three years, the team at Willow Garage embarked on developing the PR2 robot, an advanced research platform designed as a successor to Stanford’s PR1. The PR2 project served as a crucial testbed for fleshing out ROS, transforming it into the comprehensive underlying software framework that powered the robot’s sophisticated capabilities. The PR2 robot, famously demonstrated at events like Maker Faire Bay Area in 2011, showcased the practical utility of ROS in enabling complex human-robot interaction and navigation within dynamic environments. It became a testament to the power of a standardized middleware in accelerating robotics innovation.

Hands On with ROS 2: Nodes, Topics, and Services

Evolution to ROS 2: A Leap Forward in Robotics Middleware

While ROS 1 revolutionized the field, its initial architecture presented certain technical limitations, particularly concerning real-time performance, security, and suitability for multi-robot systems and embedded platforms. Recognizing these constraints, the ROS team initiated the development of ROS 2 in 2014. This new iteration was not merely an update but a significant redesign, addressing the shortcomings of its predecessor and expanding its applicability to a broader range of industrial and research scenarios. Key drivers for ROS 2 included enhanced support for distributed systems, improved quality of service (QoS) configurations for reliability and latency control, and a more robust security framework inherent in its design.

The transition from ROS 1 to ROS 2 culminated in a significant milestone: ROS 1 officially reached its end-of-life status on May 31, 2025. This marked the cessation of official updates and support, signaling a full mandate for developers to migrate to ROS 2. The ROS team, now primarily supported by the Open Robotics Foundation (OSRF), follows a structured release cycle, offering new distributions approximately once a year. Each release is given a whimsical, alliterative name, often featuring a turtle and progressing through the alphabet. For instance, the latest release, Kilted Kaiju, was unveiled in May 2025. For stability and long-term projects, developers often opt for Long-Term Support (LTS) distributions, such as Jazzy Jalisco, which benefits from support until 2029. These distributions are meticulously pinned to specific versions of underlying operating systems, typically Ubuntu, to guarantee compatibility and optimal performance of all integrated libraries. This commitment to long-term support and regular, well-defined releases provides a stable ecosystem for both academic research and commercial deployment.

Hands On with ROS 2: Nodes, Topics, and Services

Understanding ROS 2 Architecture: Nodes, Topics, and Services

At its core, ROS is an open-source robotics middleware framework and a collection of libraries, not a standalone operating system like Windows or Linux. It operates on top of a conventional OS (most commonly Linux) and provides a structured way for independent processes—called nodes—to communicate and share data. This design facilitates complex robot applications by breaking them down into manageable, modular components. For instance, libraries like the Transform Library 2 (TF2) handle complex coordinate frame transformations, a crucial aspect of robot navigation and manipulation.

ROS 2’s power lies in its scalability. While it might be overkill for simple, single-purpose robots like basic vacuum cleaners or maze solvers, it excels in environments with multiple, complex components that need to operate synergistically. Its architecture streamlines development, saving hundreds of hours of work and frustration for teams building advanced robotic systems. This scalability has led to significant adoption beyond academia, with ROS 2 now powering real-world commercial robots, including some of Amazon’s warehouse automation systems, Avidbots’ commercial-grade cleaning robots, and Omron’s sophisticated TM manipulator arms.

Hands On with ROS 2: Nodes, Topics, and Services

ROS 2 supports nodes written in several programming languages, including native support for Python and C++. The open-source community further extends this, offering support for languages like Ada, C, Java, .NET (e.g., C#), Node.js (JavaScript), Rust, and Flutter (Dart). The beauty of this multi-language support is that nodes written in one language can seamlessly communicate with nodes written in others, fostering diverse development teams and leveraging specialized language strengths. C++ is typically favored for low-level drivers and computationally intensive tasks requiring fast execution, while Python’s rapid development cycle and rich ecosystem (e.g., OpenCV for vision, PyTorch/TensorFlow for machine learning) make it ideal for prototyping and complex algorithmic development.

ROS relies heavily on object-oriented programming principles. Individual nodes are typically subclasses of the base Node class, inheriting core functionalities. Within these nodes, communication mechanisms like publishers, subscribers, clients, and servers are instantiated as objects, allowing a single node to manage multiple communication channels simultaneously.

Topics: The Publish/Subscribe Model

Hands On with ROS 2: Nodes, Topics, and Services

The primary communication method in ROS 2 is the topic, which employs a publish/subscribe messaging model. A publisher node sends data to a named topic, and the underlying ROS system efficiently delivers that message to any node subscribed to that specific topic. This asynchronous, one-to-many communication pattern is ideal for continuous data streams, such as sensor readings (e.g., LiDAR scans, camera feeds), odometry data, or motor commands that are broadcast regularly. For example, a camera node might publish raw image data to a /camera/image_raw topic, and multiple subscriber nodes—one for object detection, another for visual odometry, and perhaps a third for recording—can all receive copies of this data simultaneously without direct knowledge of each other.

Services: The Client/Server Model

While topics are excellent for continuous data flow, they are less suited for situations requiring a direct, one-time request-response interaction. For this, ROS 2 provides services, which follow a client/server model. A server node waits to receive incoming requests for a specific service, processes them, and then sends a response back to the requesting client node. This synchronous, point-to-point communication pattern is perfect for triggering actions, setting parameters, or requesting specific data on demand. For example, a navigation client node might request a motion server node to move the robot forward by a specific distance, or a diagnostic client might query a sensor server for its current calibration status. The service ensures that the client receives a response, indicating the success or failure of the requested operation, and often includes relevant data.

Hands On with ROS 2: Nodes, Topics, and Services

Setting Up Your ROS 2 Development Environment with Docker

For newcomers and seasoned developers alike, setting up a consistent ROS 2 environment can be simplified through containerization. While installing Ubuntu on a dedicated small laptop or single-board computer (like a Raspberry Pi) is common for real robot deployments, a Docker image provides a platform-agnostic, pre-configured environment perfect for tutorials and development. This approach ensures that all necessary dependencies and package versions are correctly managed, regardless of the host operating system (macOS, Windows, or various Linux distributions).

To begin, ensure Docker Desktop is installed on your host computer by downloading it from docker.com and following the default installation steps. Once Docker is ready, the next step involves obtaining the ROS Docker image and an example repository, typically from a GitHub source like github.com/ShawnHymel/introduction-to-ros. After downloading and unzipping the repository, navigate to its root directory in a command-line terminal (e.g., zsh, bash, PowerShell).

Hands On with ROS 2: Nodes, Topics, and Services

The Docker image can then be built using the command: docker build -t env-ros2 .. This process downloads a full instance of Ubuntu 24.04 with a graphical interface and pre-installs the Jazzy Jalisco ROS 2 distribution, which provides long-term support until 2029. The build may take some time due to the size of the image.

Once built, the Docker container is launched with specific commands tailored to your operating system. For macOS or Linux, the command is:
docker run --rm -it -e PUID=$(id -u) -e PGID=$(id -g) -p 22002:22 -p 3000:3000 -v "$PWD/workspace:/config/workspace" env-ros2
For Windows (using PowerShell), the command is slightly adjusted:
docker run --rm -it -e PUID=$(wsl id -u) -e PGID=$(wsl id -g) -p 22002:22 -p 3000:3000 -v "$PWDworkspace:/config/workspace" env-ros2

These commands ensure that the container runs interactively, maps necessary ports (e.g., 3000 for the graphical interface, 22002 for SSH), and mounts the host’s workspace directory into the container’s /config/workspace path. This mounting is crucial as it allows any changes made to files within the workspace directory inside the container to be persistently saved on the host computer. Upon successful execution, you will see a KasmVNC welcome message in your terminal, and you can access a full Ubuntu desktop environment by navigating to https://localhost:3000 in your web browser. This env-ros2 Docker image, based on the XFCE Ubuntu webtop image maintained by the LinuxServer.io group, provides a clean and consistent development environment for ROS 2.

Hands On with ROS 2: Nodes, Topics, and Services

Building Your First ROS 2 Package: A Practical Walkthrough

Within the Docker container’s Ubuntu desktop, Visual Studio Code (VS Code) is preconfigured with ROS 2 extensions. Opening VS Code and its integrated terminal (View > Terminal) will place you in the development environment. All ROS 2 code resides within packages, which are organized within a workspace. The workspace/ folder, mounted from your host, acts as the ROS 2 workspace.

To create a new package, navigate to /config/workspace/src in the VS Code terminal and execute:
ros2 pkg create --build-type ament_python my_first_pkg
This command generates a directory structure for my_first_pkg with ament_python as the specified build type, indicating that Python will be used for node development. The my_first_pkg/my_first_pkg/ subdirectory is where your Python source code will reside, while other files like package.xml and setup.py manage metadata and build configurations.

Hands On with ROS 2: Nodes, Topics, and Services

Creating Publisher and Subscriber Nodes

For demonstrating topic-based communication, two Python nodes are required: a publisher and a subscriber.

  1. Publisher Node (my_publisher.py): Create this file in my_first_pkg/my_first_pkg/. The code defines a MinimalPublisher class, subclassing rclpy.node.Node. Inside its constructor, it creates a publisher object for the my_topic topic using self.create_publisher() and sets up a timer to call _timer_callback() every 0.5 seconds. The callback constructs a "Hello world: [counter]" string and publishes it. The main() function initializes rclpy, instantiates MinimalPublisher, and spins the node, ensuring continuous operation.
  2. Subscriber Node (my_subscriber.py): Create this file in the same directory. The MinimalSubscriber class, also a subclass of rclpy.node.Node, creates a subscription object for my_topic with _listener_callback() as its handler. This callback simply prints the received message to the console. Its main() function mirrors the publisher’s, initializing rclpy and spinning the node.

Configuring Package Manifest and Build System

Hands On with ROS 2: Nodes, Topics, and Services

Before building, ROS needs to know about the new nodes and their dependencies.

  1. package.xml: Open my_first_pkg/package.xml. This package manifest declares metadata and dependencies. Add <depend>rclpy</depend> after the <license> tag to inform ROS that the package relies on the rclpy library.
  2. setup.py: Open my_first_pkg/setup.py. This file configures the Python build system. Under the entry_points dictionary, within the console_scripts key, add entries for both nodes:
    entry_points=
        'console_scripts': [
            'my_publisher = my_first_pkg.my_publisher:main',
            'my_subscriber = my_first_pkg.my_subscriber:main',
        ],
    ,

    This tells ROS how to execute these Python scripts as nodes.

Finally, navigate back to the workspace directory (cd /config/workspace/) and build the package using the colcon build tool:
colcon build --packages-select my_first_pkg
colcon is the standard build tool for ROS 2, managing the compilation and installation of packages within the workspace. A successful build indicates the package is ready for execution.

Hands On with ROS 2: Nodes, Topics, and Services

Demonstrating Inter-Node Communication: Topics in Action

To observe the publisher and subscriber nodes in action, open three separate terminal windows within the Docker container (Applications menu > Terminal Emulator). These distinct terminals will host the individual processes of the publisher, the subscriber, and a visualization tool.

In the first terminal, navigate to the workspace and source the setup script to make the newly built package accessible to ROS. Then, launch the publisher node:

Hands On with ROS 2: Nodes, Topics, and Services
cd /config/workspace/
source install/setup.bash
ros2 run my_first_pkg my_publisher

This command starts the my_publisher node, which will begin printing its "Hello world: [count]" messages to this terminal and publishing them on my_topic.

In the second terminal, repeat the sourcing of the workspace setup script and then launch the subscriber node:

cd /config/workspace/
source install/setup.bash
ros2 run my_first_pkg my_subscriber

Immediately, you should see messages appearing in this second terminal, identical to those in the publisher’s terminal. This confirms that the subscriber node is successfully receiving the messages broadcast by the publisher over my_topic.

Hands On with ROS 2: Nodes, Topics, and Services

For a visual representation of this communication, use the third terminal to run rqt_graph:
rqt_graph
rqt_graph is a powerful graphical interface tool within ROS 2 that provides a dynamic visualization of the nodes and the topics connecting them. After launching, you may need to click the "Refresh" button in the top-left corner of the rqt_graph window. You will then see two distinct nodes, minimal_publisher and minimal_subscriber, connected by an arrow representing my_topic. This visual feedback is invaluable for debugging and understanding the architecture of complex ROS 2 applications, particularly as the number of nodes and topics grows. To stop the nodes, simply press Ctrl+C in their respective terminals or close all terminal windows.

Implementing Request-Response with ROS 2 Services

To illustrate the client/server communication model, we’ll create a service server and client.

Hands On with ROS 2: Nodes, Topics, and Services
  1. Server Node (my_server.py): Create this file in my_first_pkg/my_first_pkg/. The MinimalServer class, inheriting from rclpy.node.Node, creates a service named add_ints using self.create_service(). It specifies AddTwoInts (an example interface from example_interfaces.srv) as the service type and attaches _server_callback() as the handler. This callback receives two integers (req.a, req.b), calculates their sum, logs the operation, and returns the result in the response object (res.sum).
  2. Client Node (my_client.py): Create this file in the same directory. The MinimalClient class creates a client object for the add_ints service with the AddTwoInts interface. It uses a timer to call _timer_callback() every 2 seconds. This callback generates two random integers, constructs an AddTwoInts.Request message, and sends it to the server using self.cli.call_async(). The call_async() method returns a future object, a placeholder for the asynchronous response. A callback is attached to this future to process the server’s response (the sum) once it arrives, printing it to the console.

Updating Package Configuration and Rebuilding

Similar to the topic example, setup.py needs to be updated to include the new service nodes:

entry_points=
    'console_scripts': [
        'my_publisher = my_first_pkg.my_publisher:main',
        'my_subscriber = my_first_pkg.my_subscriber:main',
        'my_client = my_first_pkg.my_client:main',
        'my_server = my_first_pkg.my_server:main',
    ],
,

After saving setup.py, rebuild the package from the workspace directory:
colcon build --packages-select my_first_pkg
This ensures the new executables are installed and recognized by ROS.

Hands On with ROS 2: Nodes, Topics, and Services

Running Your Server and Client

Open three new terminal windows in the container.

  1. In the first terminal, source the workspace environment and run the server node:
    cd /config/workspace/
    source install/setup.bash
    ros2 run my_first_pkg my_server

    The server will start, announcing it is ready to receive requests.

    Hands On with ROS 2: Nodes, Topics, and Services
  2. In the second terminal, source the environment and run the client node:
    cd /config/workspace/
    source install/setup.bash
    ros2 run my_first_pkg my_client

    The client will periodically send requests (two random integers between 0 and 10) to the server. You will observe the server terminal logging the received requests and sending responses, while the client terminal prints the calculated sums received from the server.

  3. While rqt_graph can be run in the third terminal, it will only show the client and server nodes as independent entities, without visualizing the service calls as direct connecting lines like topics. This is because services are a request-response mechanism, not a continuous broadcast channel, and rqt_graph primarily visualizes persistent connections.

Mighty Middleware: The Broader Impact and Future of ROS 2

While the initial demonstrations of inter-node communication via topics and services might seem deceptively simple, these fundamental concepts form the bedrock of ROS 2’s immense power and utility. ROS 2 is not merely a collection of robot drivers or sensor libraries; it is, at its core, a sophisticated middleware messaging layer that addresses a critical challenge in modern robotics: enabling developers to effectively scale software projects for large, complex robotic systems. For smaller, single-purpose robotics projects, the overhead of ROS 2 might be considerable, but its value becomes undeniable when orchestrating dozens, or even hundreds, of interdependent software components.

Hands On with ROS 2: Nodes, Topics, and Services

The introduction and continued development of ROS, particularly its evolution into ROS 2, have had a profound impact on the global robotics landscape. It has democratized robotics development, lowering the barrier to entry for researchers, startups, and hobbyists by providing a common, open-source framework. This collaborative ecosystem fosters innovation, allowing developers to build upon existing solutions rather than constantly starting from scratch. By standardizing communication, ROS 2 facilitates the integration of diverse hardware and software, accelerating the pace of research and the deployment of commercial robots.

From its origins at Stanford and pivotal development at Willow Garage, ROS has grown into a cornerstone of robotics engineering worldwide. The Open Robotics Foundation (OSRF) now oversees its development and maintenance, ensuring its continued evolution and stability. Beyond the core messaging system, ROS 2 ships with a comprehensive suite of tools and libraries. These include TF2 for robust coordinate frame transformations, critical for precise robot navigation and manipulation; advanced diagnostic tools for monitoring system health; and powerful visualizers like RViz, which allow developers to visualize robot models, sensor data, and planning outputs in 3D. Topics and services are just the beginning; the entire ROS ecosystem provides a rich environment for building, testing, and deploying highly sophisticated robot software.

The widespread adoption of ROS 2 in both academia and industry underscores its significance. Universities globally use it as a standard platform for teaching robotics and conducting cutting-edge research. In the commercial sector, its use extends from warehouse automation and logistics to advanced manufacturing and service robotics. The ongoing commitment to open-source development, coupled with its robust architecture and a vibrant community, ensures that ROS 2 will continue to be a driving force in the future of robotics. As robots become more intelligent, autonomous, and integrated into our daily lives, ROS 2 stands ready to provide the essential software infrastructure needed to bring these complex visions to fruition. This brief introduction merely scratches the surface; further exploration through official documentation, community forums, and hands-on projects will reveal the true depth and versatility of this mighty middleware.