ROS 2 Tutorials – Foxy Fitzroy

In the tutorials below, you will get hands-on experience with the most important parts of ROS 2 Foxy Fitzroy, the latest distribution of ROS 2. I have hand picked the core areas of ROS 2 that you will use again and again in your robotics applications. This tutorial is designed to save you time and get you comfortable with ROS 2 as quickly as possible.

To get the most out of ROS 2, I recommend going through each of the tutorials below. Don’t worry if everything seems complicated and doesn’t make sense. Don’t worry if you can’t understand how any of these abstract concepts connect to a real-world robot. 

ROS 2 is built in such a way that you need to work through the boring basics before you can use it to develop actual robotics projects. You have to walk before you learn how to run.

Without further ado, let’s get started!

Prerequisites

ROS 2 Foxy Fitzroy Tutorials

If you’ve made it through each of the tutorials above, congratulations! You now have a firm foundation in ROS 2 and can go out and confidently develop projects of your own or dive into an existing project.

Keep building!

Getting Started With OpenCV in ROS 2 Foxy Fitzroy (Python)

In this tutorial, we’ll learn the basics of how to interface ROS 2 with OpenCV, the popular computer vision library. These basics will provide you with the foundation to add vision to your robotics applications.

We’ll create an image publisher node to publish webcam data (i.e. video frames) to a topic, and we’ll create an image subscriber node that subscribes to that topic. 

Prerequisites

Create a Package

Open a new terminal window, and navigate to the src directory of your workspace:

cd ~/dev_ws/src

Now let’s create a package named cv_basics.

Type this command (this is all a single command):

ros2 pkg create --build-type ament_python cv_basics --dependencies rclpy image_transport cv_bridge sensor_msgs std_msgs opencv2

Modify Package.xml

Go to the dev_ws/src/cv_basics folder.

cd ~/dev_ws/src/cv_basics

Make sure you have a text editor installed. I like to use gedit.

sudo apt-get install gedit

Open the package.xml file. 

gedit package.xml

Fill in the description of the cv_basics package, your email address and name on the maintainer line, and the license you desire (e.g. Apache License 2.0).

<description>A minimal image publisher and subscriber node that uses OpenCV</description>
<maintainer email="automaticaddison@todo.todo">automaticaddison</maintainer>
<license>Apache License 2.0</license>

Save and close the file.

Create the Image Publisher Node (Python)

Move to the cv_basics package.

cd ~/dev_ws/src/cv_basics/cv_basics

Open a new Python file named webcam_pub.py.

gedit webcam_pub.py

Type the code below into it:

# Basic ROS 2 program to publish real-time streaming 
# video from your built-in webcam
# Author:
# - Addison Sears-Collins
# - https://automaticaddison.com
 
# Import the necessary libraries
import rclpy # Python Client Library for ROS 2
from rclpy.node import Node # Handles the creation of nodes
from sensor_msgs.msg import Image # Image is the message type
from cv_bridge import CvBridge # Package to convert between ROS and OpenCV Images
import cv2 # OpenCV library

class ImagePublisher(Node):
  """
  Create an ImagePublisher class, which is a subclass of the Node class.
  """
  def __init__(self):
    """
    Class constructor to set up the node
    """
    # Initiate the Node class's constructor and give it a name
    super().__init__('image_publisher')
     
    # Create the publisher. This publisher will publish an Image
    # to the video_frames topic. The queue size is 10 messages.
    self.publisher_ = self.create_publisher(Image, 'video_frames', 10)
     
    # We will publish a message every 0.1 seconds
    timer_period = 0.1  # seconds
     
    # Create the timer
    self.timer = self.create_timer(timer_period, self.timer_callback)
		
    # Create a VideoCapture object
    # The argument '0' gets the default webcam.
    self.cap = cv2.VideoCapture(0)
		
    # Used to convert between ROS and OpenCV images
    self.br = CvBridge()
  
  def timer_callback(self):
    """
    Callback function.
    This function gets called every 0.1 seconds.
    """
    # Capture frame-by-frame
    # This method returns True/False as well
    # as the video frame.
    ret, frame = self.cap.read()
         
    if ret == True:
      # Publish the image.
      # The 'cv2_to_imgmsg' method converts an OpenCV
      # image to a ROS 2 image message
      self.publisher_.publish(self.br.cv2_to_imgmsg(frame))

    # Display the message on the console
    self.get_logger().info('Publishing video frame')
 
def main(args=None):
 
  # Initialize the rclpy library
  rclpy.init(args=args)
 
  # Create the node
  image_publisher = ImagePublisher()
 
  # Spin the node so the callback function is called.
  rclpy.spin(image_publisher)
 
  # Destroy the node explicitly
  # (optional - otherwise it will be done automatically
  # when the garbage collector destroys the node object)
  image_publisher.destroy_node()
 
  # Shutdown the ROS client library for Python
  rclpy.shutdown()
 
if __name__ == '__main__':
  main()

Save and close the editor.

Modify Setup.py

Go to the following directory.

cd ~/dev_ws/src/cv_basics/

Open setup.py.

gedit setup.py

Add the following line between the ‘console_scripts’: brackets:

'img_publisher = cv_basics.webcam_pub:main',
    entry_points={
        'console_scripts': [
          'img_publisher = cv_basics.webcam_pub:main',
        ],
    },

Save the file, and close it.

Create the Image Subscriber Node (Python)

Move to the dev_ws/src/cv_basics/cv_basics folder.

cd ~/dev_ws/src/cv_basics/cv_basics

Now, let’s create the subscriber node. We’ll name it webcam_sub.py.

gedit webcam_sub.py

Type the code below into it:

# Basic ROS 2 program to subscribe to real-time streaming 
# video from your built-in webcam
# Author:
# - Addison Sears-Collins
# - https://automaticaddison.com
 
# Import the necessary libraries
import rclpy # Python library for ROS 2
from rclpy.node import Node # Handles the creation of nodes
from sensor_msgs.msg import Image # Image is the message type
from cv_bridge import CvBridge # Package to convert between ROS and OpenCV Images
import cv2 # OpenCV library

class ImageSubscriber(Node):
  """
  Create an ImageSubscriber class, which is a subclass of the Node class.
  """
  def __init__(self):
    """
    Class constructor to set up the node
    """
    # Initiate the Node class's constructor and give it a name
    super().__init__('image_subscriber')
     
    # Create the subscriber. This subscriber will receive an Image
    # from the video_frames topic. The queue size is 10 messages.
    self.subscription = self.create_subscription(
      Image, 
      'video_frames', 
      self.listener_callback, 
      10)
    self.subscription # prevent unused variable warning
     
    # Used to convert between ROS and OpenCV images
    self.br = CvBridge()
  
  def listener_callback(self, data):
    """
    Callback function.
    """
    # Display the message on the console
    self.get_logger().info('Receiving video frame')

    # Convert ROS Image message to OpenCV image
    current_frame = self.br.imgmsg_to_cv2(data)
   
    # Display image
    cv2.imshow("camera", current_frame)
   
    cv2.waitKey(1)
 
def main(args=None):
 
  # Initialize the rclpy library
  rclpy.init(args=args)
 
  # Create the node
  image_subscriber = ImageSubscriber()
 
  # Spin the node so the callback function is called.
  rclpy.spin(image_subscriber)
 
  # Destroy the node explicitly
  # (optional - otherwise it will be done automatically
  # when the garbage collector destroys the node object)
  image_subscriber.destroy_node()
 
  # Shutdown the ROS client library for Python
  rclpy.shutdown()
 
if __name__ == '__main__':
  main()

Save and close the editor.

Modify Setup.py

Go to the following directory.

cd ~/dev_ws/src/cv_basics/

Open setup.py.

gedit setup.py

Make sure the entry_points block looks like this:

    entry_points={
        'console_scripts': [
          'img_publisher = cv_basics.webcam_pub:main',
          'img_subscriber = cv_basics.webcam_sub:main',
        ],
    },

Save the file, and close it.

Build the Package 

Return to the root of your workspace:

cd ~/dev_ws/

We need to double check that all the dependencies needed are already installed.

rosdep install -i --from-path src --rosdistro foxy -y

If you get the following error:

ERROR: the following packages/stacks could not have their rosdep keys resolved to system dependencies: cv_basics: Cannot locate rosdep definition for [opencv2]

Follow these instructions to resolve the error.

Build the package:

colcon build --packages-select cv_basics

Run the Nodes

To run the nodes, open a new terminal window.

Make sure you are in the root of your workspace:

cd ~/dev_ws/

Run the publisher node. If you recall, its name is img_publisher.

ros2 run cv_basics img_publisher
2-publishing-video-frameJPG

A window will pop up with the streaming video.

1-outputJPG

Open a new terminal, and run the subscriber node.

ros2 run cv_basics img_subscriber
3-subscribing-to-video-frameJPG

Check out the active topics.

ros2 topic list -t
4-active-topicsJPG

See the relationship between the active nodes.

rqt_graph

Click the refresh button. The refresh button is that circular arrow at the top of the screen.

refresh

We can see that the image_publisher node is publishing images to the video_frames topic. The image_subscriber node is subscribing to those video frames that are published to the video_frames topic.

5-rqt-graphJPG

That’s it! Keep building!

How to Create a Service and Client (Python) | ROS2 Foxy

In this tutorial, we will learn how to create a service and a client in ROS 2 Foxy Fitzroy using Python. The client-service relationship in ROS 2 is a request-reply relationship. A client node sends a request for data to the service node. The service node then sends a reply to the client node.

A real-world example of this is a client node requesting sensor data from a service node. The service node then responds to the client node with the requested sensor data. 

A file called a .srv file defines the structure of the service-client node interaction.

service_client-1

The example we will use here is an addition system. We will create a client node that requests the sum of two integers. We will then create a service node that will respond to the client node with the sum of those two integers.

The official tutorial is located in the ROS 2 Foxy documentation, but we’ll run through the entire process step-by-step below.

You Will Need

In order to complete this tutorial, you will need:

Prerequisites

You have already created a workspace.

Create a Package

Open a new terminal window, and navigate to the src directory of your workspace:

cd ~/dev_ws/src

Now let’s create a package named py_srvcli.

Type this command:

ros2 pkg create --build-type ament_python py_srvcli --dependencies rclpy example_interfaces

Your package named py_srvcli has now been created.

You will note that we added the –dependencies command after the package creation command. Doing this automatically adds the dependencies to your package.xml file.

Modify Package.xml

Go to the dev_ws/src/py_srvcli folder.

cd ~/dev_ws/src/py_srvcli

Make sure you have a text editor installed. I like to use gedit.

sudo apt-get install gedit

Open the package.xml file. 

gedit package.xml

Fill in the description of the py_srvcli package, your email address and name on the maintainer line, and the license you desire (e.g. Apache License 2.0).

<description>A minimal Python client and service node</description>
<maintainer email="automaticaddison@todo.todo">automaticaddison</maintainer>
<license>Apache License 2.0</license>

Save and close the file.

Write a Service Node

Move to the dev_ws/src/py_srvcli/py_srvcli folder.

cd ~/dev_ws/src/py_srvcli/py_srvcli

Create a new Python file named add_two_ints_server.py

gedit add_two_ints_server.py

Write the following code. Don’t be intimidated. Just go one line at a time and read the comments to understand what each line does.

This service node adds two integers together.

# Import AddTwoInts service type from the example_interfaces package
from example_interfaces.srv import AddTwoInts

# ROS 2 Client Library for Python
import rclpy

# Handles nodes
from rclpy.node import Node

class MinimalService(Node):

    def __init__(self):
        # Initialize the node via this constructor
        super().__init__('minimal_service')
     
        # Create a service
        self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback)

    def add_two_ints_callback(self, request, response):
        # Receive the request data and sum it
        response.sum = request.a + request.b
        
        # Return the sum as the reply
        self.get_logger().info('Incoming request\na: %d b: %d' % (request.a, request.b))
        return response

def main(args=None):

    # Start ROS
    rclpy.init(args=args)

    # Create the service
    minimal_service = MinimalService()

    # Make the service available to the network
    rclpy.spin(minimal_service)

    # Shutdown ROS
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Save the file, and close it.

Modify Setup.py

Go to the following directory.

cd ~/dev_ws/src/py_srvcli/

Open setup.py.

gedit setup.py

Add the following line between the ‘console_scripts’: brackets:

'service = py_srvcli.add_two_ints_server:main',

Save the file, and close it.

Write a Client Node

Move to the dev_ws/src/py_srvcli/py_srvcli folder.

cd ~/dev_ws/src/py_srvcli/py_srvcli

Create a new Python file named add_two_ints_client.py

gedit add_two_ints_client.py

Write the following code. Don’t be intimidated. Just go one line at a time and read the comments to understand what each line does.

# Enables command line input
import sys

from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.node import Node

class MinimalClientAsync(Node):

    def __init__(self):
        # Create a client    
        super().__init__('minimal_client_async')
        self.cli = self.create_client(AddTwoInts, 'add_two_ints')
      
        # Check if the a service is available  
        while not self.cli.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('service not available, waiting again...')
        self.req = AddTwoInts.Request()

    def send_request(self):
        self.req.a = int(sys.argv[1])
        self.req.b = int(sys.argv[2])
        self.future = self.cli.call_async(self.req)


def main(args=None):
    rclpy.init(args=args)

    minimal_client = MinimalClientAsync()
    minimal_client.send_request()

    while rclpy.ok():
        rclpy.spin_once(minimal_client)
        # See if the service has replied
        if minimal_client.future.done():
            try:
                response = minimal_client.future.result()
            except Exception as e:
                minimal_client.get_logger().info(
                    'Service call failed %r' % (e,))
            else:
                minimal_client.get_logger().info(
                    'Result of add_two_ints: for %d + %d = %d' %
                    (minimal_client.req.a, minimal_client.req.b, response.sum))
            break

    minimal_client.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

Save the file, and close it.

Modify Setup.py

Go to the following directory.

cd ~/dev_ws/src/py_srvcli/

Open setup.py.

gedit setup.py

Make sure the entry_points block looks like this:

entry_points={
    'console_scripts': [
        'service = py_srvcli.add_two_ints_server:main',
        'client = py_srvcli.add_two_ints_client:main',
    ],
},

Save the file, and close it.

Build the Package 

Return to the root of your workspace:

cd ~/dev_ws/

We need to double check that all the dependencies needed are already installed.

rosdep install -i --from-path src --rosdistro foxy -y

Build the package:

colcon build --packages-select py_srvcli
1-build-the-packageJPG

Run the Nodes

To run the nodes, open a new terminal window.

Make sure you are in the root of your workspace:

cd ~/dev_ws/

Run the service node. 

ros2 run py_srvcli service

Open a new terminal, and run the client node. At the end of the command, put the two integers you would like to add.

ros2 run py_srvcli client 5 3
2-run-clientJPG

Go back to the service node terminal. Here is what I see:

3-requestJPG

When you’re done, press CTRL + C in all terminal windows to shut everything down.

That’s it. Keep building!