How to Save Your ROS 2 Project on GitHub

In this tutorial, I will show you how to store your ROS 2 project in a repository on GitHub.

Follow along with me click by click, keystroke by keystroke.

In case you’re not familiar with GitHub, GitHub is an online platform specifically designed for software development. It offers several key functionalities:

  • Version control: This allows you to track changes made to code over time. Think of it like a time machine for your code, letting you revert to previous versions if necessary.
  • Code storage: GitHub acts as a secure and centralized location to store and manage code projects. Think of it like cloud storage specifically for your code.
  • Collaboration: Teams can work together on projects by sharing code, discussing changes, and merging different contributions seamlessly.
  • Open-source contribution: GitHub is a hub for open-source projects, where developers can publicly share their code, contribute to existing projects, and learn from others.

Prerequisites

  • You have created a ROS 2 package inside a workspace.
  • I have created a ROS 2 workspace that is at this path /home/ubuntu/ros2_ws/.

My package is called ros2_fundamentals_examples and is at this path: /home/ubuntu/ros2_ws/src/ros2_fundamentals_examples/

You can see the complete repository here on GitHub.

Install Git

The first thing you need to do is install Git. Open a new terminal window, and type:

sudo apt-get update
sudo apt-get install git -y

Check the git version you have.

git --version

Configure Git

Configure your git username and email.

git config --global user.name "John Doe"
git config --global user.email "johndoe@example.com"
git config --global init.defaultBranch main

Initialize Git

Move to inside your project folder.

cd  ~/ros2_ws/src/ros2_fundamentals_examples/

Initialize the folder as a Git repository by running:

git init

This command allows you to start tracking changes to the files within that directory.

1-git-init

Add and Commit the Files to Your Local Repository

Add the files in your folder to the repository with the following command:

git add .

Commit your staged files to your local repository with:

git commit -m "Initial commit"

Create the Remote Repository on GitHub

Go to GitHub and log in.

Click on the “+” icon in the upper right corner and select “New repository.”

Name your repository.

Add a description (optional).

Choose whether the repository will be public or private.

Ignore the rest of the options.

Click “Create repository.”

Link the Local Repository to the Remote Repository

After creating your repository on GitHub, you’ll get a URL for that repository that looks like this: “https://github.com/…./ros2_fundamentals.git” 

Go back to a terminal window, and link your local repository to GitHub with this command:

cd  ~/ros2_ws/src/ros2_fundamentals_examples/
git remote add origin <repository-URL>

Now log in to your GitHub account again.

Creating a Fine-Grained Personal Access Token

A personal access token is like a special key that lets you access your GitHub account from other apps or scripts. It’s more secure than using your password because you can control what the token is allowed to do. 

Here are the official steps, but we will walk through everything together now.

First, verify your email address, if it hasn’t been verified yet.

In the top-right corner of the GitHub page, click on your profile picture.

From the menu, click “Settings.”

In the left sidebar of the settings page, scroll down until you see “Developer settings.”

Click on “Developer settings.”

In the left sidebar, under Personal access tokens, click Fine-grained tokens.

Click Generate new token.

Follow the steps to authenticate your identity on GitHub.

Under Token name, enter a name for the token.

Under Resource owner, select a resource owner. The token will only be able to access resources owned by the selected resource owner.

Under Expiration, select an expiration for the token.

Optionally, under Description, add a note to describe the purpose of the token.

Under Repository access, select which repositories you want the token to access.

If you selected Only select repositories in the previous step, under the Selected repositories dropdown, select the repositories that you want the token to access.  I will select the ros2_fundamentals_examples repository.

Under Permissions, select which permissions to grant the token. Depending on which resource owner and which repository access you specified, there are repository and account permissions. You should choose the minimal permissions necessary for your needs. 

I will grant “Read and write” permissions to all of the resources. If “Read and write” is not available for a particular resource, select “Read-only”.

Click Generate token.

Copy the personal access token that you see, and save it somewhere safe.

Push Code to GitHub

Finally, push your code from your local repository (the folder on your computer) to GitHub (the remote folder) with:

cd  ~/ros2_ws/src/ros2_fundamentals_examples/
git push -u origin main

Enter your GitHub Username.

Then, when it asks for your password, enter the personal access token you created in the previous section.

With this command we have created a two-way connection between your local code and GitHub.

The git push -u origin main command uploads your local code to GitHub while simultaneously setting up a tracking relationship between your local and remote branches. 

The -u (or –set-upstream) flag tells Git to remember this connection, so in the future you can simply use git push or git pull without needing to specify where to push to or pull from. 

origin refers to your GitHub repository, and main is the name of your branch. 

Now if you go back to GitHub, you can see your repository.

Git Command Overview

When you make changes to your code on your computer, and want to get these code changes saved to GitHub, here is how you do it:

git add . 

This command picks up all your new code changes and gets them ready to be saved. It’s like gathering up all your files into a neat pile.

git commit -m "description of changes" 

This command saves these changes on your computer with a message explaining what you did. It’s like putting that pile of files into a labeled folder.

git push 

This command sends all your saved changes to GitHub. Think of it like uploading your folder to the cloud so others can see it and you have a backup.

You’ll repeat these three steps each time you want to update your code on GitHub. 

And if you’re working with other people, you should always download their changes first using git fetch, git status, and then git pull (one command right after the other) before you start working.

git fetch checks GitHub to see what changes exist – it’s like looking at a list of updates available but not downloading them yet. It tells you what’s different between your code and GitHub’s code.

git status shows you exactly what’s different between your local code and the code on GitHub after you’ve fetched. It’s like comparing your version with GitHub’s version to see what’s changed.

git pull actually downloads those changes from GitHub to your computer, bringing your local code up to date. It’s like clicking “download” on those updates you found with fetch and status.

These three commands help you safely check for and get any updates before you start working, which helps avoid conflicts with other people’s code changes.

Remove the Need to Use a Username and Password

If you want to not have to use a username and password every time you run “git push”, you can use SSH keys. This page has the official instructions on how to do that.

Here is the process…

Generate an SSH key pair (if you don’t already have one) by running:

ssh-keygen -t ed25519 -C "your_email@example.com"

When you get prompted for a password or saving location, just keep pressing Enter, which will accept the default.

Start the ssh-agent in the background.

eval "$(ssh-agent -s)"

Add your SSH private key to the ssh-agent:

ssh-add ~/.ssh/id_ed25519

Add the SSH public key to your Git server.

cat ~/.ssh/id_ed25519.pub

Copy the entire result to your clipboard by highlighting everything and copying it.

Go to your GitHub account “Settings” by clicking your profile icon in the upper right of the website.

Look for “SSH and GPG keys”.

Add a New SSH key.

Paste the copied key as an “Authentication Key”. Also add a title (you can make it whatever you want).

Click Add SSH key.

Go back to the main page of your repository on GitHub.

Find the SSH URL by clicking the green button labeled “Code”.

Copy the SSH URL that is in there.

Switch your repository’s remote URL to SSH by going to your Ubuntu Linux terminal window, and moving to the directory of your repository.

cd <path to your your local repository>

For example:

cd  ~/ros2_ws/src/ros2_fundamentals_examples/
git remote set-url origin git@github.com:username/repository.git

To confirm everything is setup properly, type:

git pull

If you get asked about the authenticity of the host, just type yes and press Enter.

That’s it.

Using SSH keys is a more secure and convenient method for machines where you regularly push changes, as it doesn’t require entering your credentials after the initial setup.

Keep building!

How to Create a Subscriber Node in C++ – Jazzy

In this tutorial, we will create a C++ subscriber for ROS 2.

In ROS 2 (Robot Operating System 2), a C++ subscriber is a program (written in C++) 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 C++ Style Guide.

Let’s get started!

Follow along with me click by click, keystroke by keystroke.

Prerequisites

Write the Code

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

cd ~/ros2_ws && code .

Right-click on the src/ros2_fundamentals_examples/src folder to create a new file called “cpp_minimal_subscriber.cpp”.

Type the following code inside cpp_minimal_subscriber.cpp:

/**
 * @file cpp_minimal_subscriber.cpp
 * @brief Demonstrates subscribing to string messages on a ROS 2 topic.
 *
 * Description: Demonstrates the basics of subscribing to messages within
 *   the ROS 2 framework. The core functionality of this subscriber is to
 *   display output to the terminal window when a message is received over
 *   a topic.
 *
 * -------
 * Subscription Topics:
 *   String message
 *   /cpp_example_topic - std_msgs/String
 * -------
 * Publishing Topics:
 *   None
 * -------
 * @author Addison Sears-Collins
 * @date November 5, 2024
 */

#include "rclcpp/rclcpp.hpp" // ROS 2 C++ client library
#include "std_msgs/msg/string.hpp" // Handle tring messages
using std::placeholders::_1; // Placeholder for callback function argument

/**
 * @class MinimalCppSubscriber
 * @brief Defines a minimal ROS 2 subscriber node.
 *
 * This class inherits from rclcpp::Node and
 * demonstrates creating a subscriber and
 * subscribing to messages.
 */
class MinimalCppSubscriber : public rclcpp::Node
{
public:
    /**
     * @brief Constructs a MinimalCppSubscriber node.
     *
     * Sets up a subscriber for 'std_msgs::msg::String' messages
	 * on the "/cpp_example_topic" topic.
     */
    MinimalCppSubscriber() : Node("minimal_cpp_subscriber")
    {
        // Create a subscriber object for listening to string messages on
        // with a queue size of 10.
        subscriber_ = create_subscription<std_msgs::msg::String>
        (
            "/cpp_example_topic",
            10,
            std::bind(
                &MinimalCppSubscriber::topicCallback,
                this,
                _1
            )
        );
    }

    /**
     * @brief This function runs every time a message is received on the topic.
     *
     * This is the callback function of the subscriber. It publishes a
	 * string message every time a message is received on the topic.
     *
     * @param msg The string message received on the topic
     * @return void
     */
    void topicCallback(const std_msgs::msg::String &msg) const
    {
        // Write a message every time a new message is received on the topic.
        RCLCPP_INFO_STREAM(get_logger(), "I heard: " << msg.data.c_str());

    }

private:
    // Member variables.
    rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscriber_;
};

/**
 * @brief Main function.
 *
 * Initializes the ROS 2 system and runs the minimal_cpp_subscriber node.
 * It keeps the node alive until it is manually terminated.
 */
int main(int argc, char * argv[])
{

  // Initialize ROS 2.
  rclcpp::init(argc, argv);

  // Create an instance of the MinimalCppSubscriber node and keep it running.
  auto minimal_cpp_subscriber_node = std::make_shared<MinimalCppSubscriber>();
  rclcpp::spin(minimal_cpp_subscriber_node);

  // Shutdown ROS 2 upon node termination.
  rclcpp::shutdown();

  // End of program.
  return 0;
}

Configure CMakeLists.txt

Now we need to modify the CMakeLists.txt file inside the package so that the ROS 2 system will be able to find the code we just wrote.

Open up the CMakeLists.txt file that is inside the package.

Make it look like this:

cmake_minimum_required(VERSION 3.8)
project(ros2_fundamentals_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 cpp_minimal_publisher
# from the source file src/cpp_minimal_publisher.cpp. Also make sure CMake
# knows about the program's dependencies.
add_executable(cpp_minimal_publisher src/cpp_minimal_publisher.cpp)
ament_target_dependencies(cpp_minimal_publisher ${dependencies})

add_executable(cpp_minimal_subscriber src/cpp_minimal_subscriber.cpp)
ament_target_dependencies(cpp_minimal_subscriber ${dependencies})

# Copy necessary files to designated locations in the project
install (
  DIRECTORY ros2_fundamentals_examples scripts
  DESTINATION share/${PROJECT_NAME}
)

install(
  DIRECTORY include/
  DESTINATION include
)

# Install cpp executables
install(
  TARGETS
  cpp_minimal_publisher
  cpp_minimal_subscriber
  DESTINATION lib/${PROJECT_NAME}
)

# Install Python modules for import
ament_python_install_package(${PROJECT_NAME})

# Add this section to install Python scripts
install(
  PROGRAMS
  ros2_fundamentals_examples/py_minimal_publisher.py
  ros2_fundamentals_examples/py_minimal_subscriber.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

To build the workspace, open a terminal window, and type:

build

If this command doesn’t work, type these commands:

echo "alias build='cd ~/dev_ws/ && colcon build && source ~/.bashrc'" >> ~/.bashrc
build

Run the Nodes

First run your publisher node.

ros2 run ros2_fundamentals_examples cpp_minimal_publisher 

Now run your subscriber node in another terminal window.

ros2 run ros2_fundamentals_examples cpp_minimal_subscriber
1-cpp-minimal-subscriber

An Important Note on Subscribers and Publishers

In the example above, we published a string message to a topic named /cpp_example_topic using a C++ node, and we subscribed to that topic using a C++node. 

ROS 2 is language agnostic, so we could have also used a Python node to do the publishing and a C++ node to do the subscribing, and vice versa.

That’s it for now. Keep building!

How to Create a ROS 2 Python Subscriber – Jazzy

In this tutorial, we will create a Python subscriber for ROS 2.

In ROS 2 (Robot Operating System 2), a subscriber is a program written that listens for messages being published on a specific topic.

Remember, 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.

Follow along with me click by click, keystroke by keystroke.

Let’s get started!

Prerequisites

Write the Code

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

cd ~/ros2_ws && code .

Right-click on src/ros2_fundamentals_examples/ros2_fundamentals_examples, and create a new file called “py_minimal_subscriber.py

1-py-minimal-subscriber

Type the following code inside py_minimal_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
    /py_example_topic - std_msgs/String
-------
Author: Addison Sears-Collins
Date: November 5, 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 MinimalPySubscriber(Node):
    """Create Minimal Subscriber node.

    """

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

        """

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

        # Creates a subscriber
        self.subscriber_1 = self.create_subscription(
            String,
            '/py_example_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(f'I heard: "{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 MinimalPySubscriber node
    minimal_py_subscriber = MinimalPySubscriber()

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

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

    # Shutdown ROS 2 communication
    rclpy.shutdown()


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

The node we just wrote subscribes to a topic named /py_example_topic that contains String messages.

Configure CMakeLists.txt

Now let’s configure the CMakeLists.txt file. Here is what it should look like:

cmake_minimum_required(VERSION 3.8)
project(ros2_fundamentals_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 cpp_minimal_publisher
# from the source file src/cpp_minimal_publisher.cpp. Also make sure CMake
# knows about the program's dependencies.
add_executable(cpp_minimal_publisher src/cpp_minimal_publisher.cpp)
ament_target_dependencies(cpp_minimal_publisher ${dependencies})

#add_executable(cpp_minimal_subscriber src/cpp_minimal_subscriber.cpp)
#ament_target_dependencies(cpp_minimal_subscriber ${dependencies})

# Copy necessary files to designated locations in the project
install (
  DIRECTORY ros2_fundamentals_examples
  DESTINATION share/${PROJECT_NAME}
)

install(
  DIRECTORY include/
  DESTINATION include
)

# Install cpp executables
install(
  TARGETS
  cpp_minimal_publisher
#  cpp_minimal_subscriber
  DESTINATION lib/${PROJECT_NAME}
)

# Install Python modules for import
ament_python_install_package(${PROJECT_NAME})

# Add this section to install Python scripts
install(
  PROGRAMS
  ros2_fundamentals_examples/py_minimal_publisher.py
  ros2_fundamentals_examples/py_minimal_subscriber.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

To build the workspace, open a terminal window, and type:

build

If this command doesn’t work, type these commands:

echo "alias build='cd ~/dev_ws/ && colcon build && source ~/.bashrc'" >> ~/.bashrc
build

Run the Node 

Let’s run our subscriber node. 

Open a terminal window, and type the following command to run the publisher node:

ros2 run ros2_fundamentals_examples py_minimal_publisher.py

Open another terminal window, and type the following command to run the subscriber node:

ros2 run ros2_fundamentals_examples py_minimal_subscriber.py

Now, press Enter.

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
2-output

Close the Nodes

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

Creating a Bash Script to Launch Nodes in Sequence

Let’s create a bash script to launch the publisher and subscriber nodes in sequence. Launching in this way gives you more control over the order in which your programs launch.

Right-click on src/ros2_fundamentals_examples/ and create a new folder called scripts.

3-create-scripts-folder

Right-click on src/ros2_fundamentals_examples/scripts, and create a new file called “minimal_pub_sub_launch.sh

Type the following code inside minimal_pub_sub_launch.sh: 

#!/bin/bash
# Launch publisher and subscriber nodes with cleanup handling

cleanup() {
    echo "Restarting ROS 2 daemon to cleanup before shutting down all processes..."
    ros2 daemon stop
    sleep 1
    ros2 daemon start
    echo "Terminating all ROS 2-related processes..."
    kill 0
    exit
}

trap 'cleanup' SIGINT

# Launch the publisher node
ros2 run ros2_fundamentals_examples py_minimal_publisher.py &
sleep 2

# Launch the subscriber node
ros2 run ros2_fundamentals_examples py_minimal_subscriber.py

Save the file and close it.

Go into the terminal, and change the permissions on the file to make it executable:

sudo chmod +x ~/ros2_ws/src/ros2_fundamentals_examples/scripts/minimal_pub_sub_launch.sh

Configure CMakeLists.txt

Now let’s configure the CMakeLists.txt file to add the scripts folder. Change this line:

# Copy necessary files to designated locations in the project
install (
  DIRECTORY ros2_fundamentals_examples 
  DESTINATION share/${PROJECT_NAME}
)

To this:

# Copy necessary files to designated locations in the project
install (
  DIRECTORY ros2_fundamentals_examples scripts
  DESTINATION share/${PROJECT_NAME}
)

Launch

Build the workspace.

build

Now we launch the nodes.

cd ~/ros2_ws/src/ros2_fundamentals_examples/scripts
./minimal_pub_sub_launch.sh

You could have also written:

bash minimal_pub_sub_launch.sh
4-running-bash-script

Close the Nodes

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, py_minimal_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.

That’s it. Keep building!