ROS2 with Python and YAML: A Step-by-Step Guide from Creating a Workspace to Running Multiple Nodes

If you’re diving into the world of robotics with ROS2 (Robot Operating System 2), setting up a solid foundation is crucial. A well-organized workspace and properly structured nodes are the backbone of any successful ROS project. Whether you’re a beginner or a seasoned developer, this guide will walk you through creating and managing a ROS2 workspace, setting up packages, implementing nodes, and leveraging launch files effectively.

Outline:

Setting Up Your Package in a ROS Workspace

The very first step is to setup a ROS workspace if you don’t already have one. The ROS workspace is a folder where you implement and build your ROS packages. ROS packages includes implementations of nodes and/or resources and scripts.

Creating a Workspace Folder

  1. Begin by creating a workspace folder, for example, ros_workspace, under your root directory.
  2. Add the following line to your shell setup script (e.g., .bashrc, .zshrc) to define the workspace:
export ROS_WORKSPACE=~/ros_workspace

Creating a Package

  1. Navigate to your ROS workspace or any sub-folder. The folder depth doesn’t matter.
  2. Use the following command to create a package:
ros2 pkg create my-pkg

This command creates a folder named my-pkg, containing the minimum files needed for your package. To specify a build type, use the --build-type option. Valid options are: cmake, ament_cmake (default), and ament_python. For example, to create a Python package:

ros2 pkg create --build-type ament_python my-pkg

In the remainder of this article, we’ll go with the default (ament_cmake). We want to create packages that support both python
and C++ nodes
.

Implementing Nodes in Python

Writing Python Nodes

  • Use the rclpy (ROS Client Library for the Python language) to implement your node scripts. See examples below.
  • Ensure your scripts are executable (chmod +x) to avoid the ”No executable found” error.
  • Place node scripts in the scripts sub-folder of your package.
  • Ensure the package.xml file includes dependencies like rclpy and any additional libraries, such as ones provided topics or service types used in your nodes. For example:
<depend>rclpy</depend>
  <depend>std_msgs</depend>
  • Add installation details to CMakeLists.txt for each script:
install(PROGRAMS scripts/my-node-script.py
   DESTINATION lib/${PROJECT_NAME})

Example Publisher Node

#!/usr/bin/python3
  import rclpy
  from rclpy.node import Node
  from std_msgs.msg import UInt16
  
  class CounterNode(Node):
      def __init__(self):
          rclpy.init()
          super().__init__('counter')
          self.publisher = self.create_publisher(UInt16, 'count', 10)
          self.timer = self.create_timer(1, self.publishCount)
          self.count = 0
          rclpy.spin(self)
          self.destroy_node()
          rclpy.shutdown()
  
      def publishCount(self):
          self.count += 1
          msg = UInt16()
          msg.data = self.count
          print(f'Publishing {msg}')
          self.publisher.publish(msg)
  
  if __name__ == "__main__":
      CounterNode()

Example Subscriber Node

#!/usr/bin/python3
  import rclpy
  from rclpy.node import Node
  from std_msgs.msg import UInt16
  
  class DoubleNode(Node):
      def __init__(self):
          rclpy.init()
          super().__init__('double')
          self.subscriber = self.create_subscription(UInt16, 'count', self.displayDouble, 10)
          rclpy.spin(self)
          self.destroy_node()
          rclpy.shutdown()
  
      def displayDouble(self, countMsg):
          count = countMsg.data
          double = count * 2
          print(f'Received {count} which double is {double}')
  
  if __name__ == "__main__":
      DoubleNode()

Node Parameters Example in Python

A given node can have parameters passed during the execution. The node script has to first declare the parameters before reading them. Here are some examples:

  • Declare parameters:
node.declare_parameter('my_array', rclpy.Parameter.Type.DOUBLE_ARRAY)
  rclpy.Param
  node.declare_parameter('my_int', 42) # 42 is the default value
  • Read provided parameter values:
node.get_parameter('my_int').value
  node.get_parameters(['my_int', 'my_array'])

You can learn more about node parameters in:

Building Your Package

Build Before Running

Whenever you change any file, rebuild the package using this command:

colcon build

The above command checks all packages inside your ROS workspaces and rebuilds all the dirty ones. This sometimes can take too much time. To build specific packages, use:

colcon build --paths my-pkg some/folder/*

Build Once, Modify and Re-Run Several Times

Building with option --symlink-install allows to modify and re-run nodes implemented in python, without rebuilding. This is true for all python scripts that were referenced in the CMakeList.txt upon the build.

Update the Environment

We recommand sourcing the setup.bash file in the install folder of the ROS workspace. It updates the environment with all built packages, enabling completion when running your nodes. In the workspace folder evaluate the following command:

source ./install/setup.bash

Running ROS Nodes

Once you have built a package, you can run a single node using the following command:

ros2 run my_package my_node

Running a Node with Parameters

If your node has parameters, you can set them using the following command:

ros2 run my_package my_node --ros-args -p my_int:=2 -p my_array:=[1.0,2.0]

Use Launch Files to Run Multiple Nodes at Once

Launch files allow grouping multiple nodes, as well as other executables, and run them all using a single command. Launch files can be written in multiple languages You can find in the ROS2 HowTo Guide the syntax, and various options for supported languages. In the following we provide a the bare minimum you need to get started with launch files in YAML.

  • Launch files are stored in the launch folder of your package.
  • Add the following to CMakeLists.txt to include launch files during installation:
install(DIRECTORY launch
    DESTINATION share/${PROJECT_NAME}/
  )

Example Launch File

launch:
  
  # Start a node with parameters
    - node:
        pkg: "demo"
        exec: "counter"
        name: "my_counter"
        param:
        -
            name: 'delta'
            value: 100
  
  # Start a node with commandline arguments
  - node:
      pkg: "rviz2"
      exec: "rviz2"
      name: "rviz2"
      args: "-d $(dirname)/../config/minimal.rviz"
  
  # Run another launch file
    - include:
        file: "$(find-pkg-share other-package)/launch/other-launch-file.yaml"
  
  # Run a random executatble
  # Here we start a terminal where we run a command line 
  # that starts an interactive node. This is a workaround to 
  # enable sending keyboard events to an interactive node.
  - executable:
      cmd: gnome-terminal --tab -e 'ros2 run  teleop_twist_keyboard teleop_twist_keyboard'

Adding Configuration Files to a CMake-based Package

In the above example, the launch file relies on a configuration file for rviz2 node. In such cases where extra files are required, you need to reference them in your project.

  • Put your configuration files into config sub-folder
  • Tell CMake to copy config sub-folder to the install folder of the workspace where the
    execution takes place. To do so, add to CMakeLists.txt the following:
install(DIRECTORY config
    DESTINATION share/${PROJECT_NAME}/
  )

Substitutions

A launch file might need to retrieve files from some folders, or use some local or environment variable. This is where susbstitutions come to play. Substitutions allow to make launch files that adapt to the setup of the system hosting your ROS packae. Two very useful susbtitutions are used in our example:

  • $(find-pkg-share): Substituted by the share directory path of the given package.
  • $(dirname): Substituted by the current launch file directory name. Be careful to use $(dirname) before any $(find-pkg-share). Because, $(dirname) is affected by previous$(find-pkg-share).

Running Launch Files

Use the following command to run a launch file:

ros2 launch my_package my_launch_file

If your launch file accepts parameters, you can append them to the command line:

ros2 launch my_package my_launch_file param_name:=param_value

Conclusion

By following this guide, you’ve set up a ROS2 workspace, created and managed packages, implemented Python nodes, and leveraged launch files. Whether you’re building simple robotics projects or complex systems, you’ll need to to go through these foundational steps.

However, this article barely scratches the surface. Each ROS tool has a lot of options to address various cases. There are also alternative supported languages and tools that we didn’t cover. We provide some links, throughtout the document. You’ll find below some more, that we find useful. They’ll help you in your journey creating amazing robotic applications.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.