Python for Robotics: ROS2

In the world of robotics, there’s a constant need for adaptable and efficient software that can effectively communicate with the hardware. As a result, the Robot Operating System (ROS) was created to simplify the task of designing and building robust robotic applications. In this article, we will explore ROS2, the latest version of ROS and how it can be used with Python to create powerful robotic systems.

Introduction to ROS2

ROS2, or Robot Operating System 2, is the second generation of the popular open-source robotics middleware. It is designed to provide a common framework for developing, maintaining and deploying robotic applications on different platforms. ROS2 has been built with a focus on improving the limitations of ROS, such as scalability, reliability and real-time capabilities. It also adopts modern software engineering practices, making it easier for developers to create more robust and maintainable systems.

Why Use Python for Robotics?

Python is a versatile and widely-used programming language that has found its way into various domains, including robotics. Its readability, ease of use and a vast ecosystem of libraries make it a popular choice among robotics developers. Python’s key advantages for robotics include:

  1. Readability: Python’s syntax is clean and easy to understand, making it perfect for quickly grasping complex algorithms and logic.
  2. Ecosystem: Python has an extensive library ecosystem that caters to a wide range of applications, including robotics. This enables developers to build upon existing tools and libraries to accelerate development.
  3. Productivity: Python’s high-level nature allows developers to write less code, making it faster to prototype and implement new ideas.
  4. Cross-platform: Python can run on various platforms, including Windows, macOS and Linux, allowing for seamless integration with other systems.

Setting Up Your Environment

Before diving into ROS2 with Python, you need to set up your development environment. This involves installing ROS2 and configuring your workspace.

  1. Install ROS2: Follow the [official ROS2 installation guide](https://index.ros.org/doc/ros2/Installation/) for your operating system. Make sure to install the latest distribution.
  2. Set Up Your Workspace: To create a new ROS2 workspace, follow these steps:

mkdir -p ~/ros2_ws/src
cd ~/ros2_ws
colcon build

After building the workspace, source the setup file:

source install/setup.bash

Now that your environment is set up, let’s get started with the basics of ROS2.

ROS2 Basics: Nodes, Topics and Services

ROS2 applications are built around three core concepts: nodes, topics and services.

  • Nodes: A node is an executable that performs a specific task. In a robotic system, nodes can represent various components like sensors, actuators, or algorithms that need to communicate with each other.
  • Topics: Topics are named channels through which nodes can send and receive messages. Nodes can publish messages to a topic or subscribe to it to receive updates. Topics enable many-to-many communication between nodes.
  • Services: Services are another way for nodes to communicate. They allow for request-response communication between nodes, where one node sends a request message and the other node replies with a response message.

Creating a Python Package in ROS2

To create a Python package in ROS2, follow these steps:

  • Navigate to your workspace’s src directory:

cd ~/ros2_ws/src

    • Use the ros2 pkg create command to generate a new Python package:

    ros2 pkg create --build-type ament_python my_robot_python_pkg

    Writing a Simple Python Publisher and Subscriber

    In this section, we will create a simple Python publisher and subscriber using ROS2.

    • Create a Python Publisher: Inside your package’s my_robot_python_pkg directory, create a new file called publisher.py:

    import rclpy
    from rclpy.node import Node
    from std_msgs.msg import String
    
    class [PublisherNode]
    def init(self):
    super().init('publisher_node')
    self.publisher = self.create_publisher(String, 'hello_topic', 10)
    self.timer = self.create_timer(1.0, self.publish_message)
    self.message_count = 0
    
    def publish_message(self):
        message = String()
        message.data = f'Hello, world! {self.message_count}'
        self.publisher.publish(message)
        self.message_count += 1
        self.get_logger().info(f'Published: "{message.data}"')
    def main(args=None):
    rclpy.init(args=args)
    publisher_node = PublisherNode()
    rclpy.spin(publisher_node)
    
    publisher_node.destroy_node()
    rclpy.shutdown()
    if name == 'main':
    main()

    Create a Python Subscriber: Similarly, create a new file called subscriber.py inside your package’s my_robot_python_pkg directory:

    import rclpy
    from rclpy.node import Node
    from std_msgs.msg import String
    
    class SubscriberNode(Node):
        def __init__(self):
            super().__init__('subscriber_node')
            self.subscription = self.create_subscription(String, 'hello_topic', self.receive_message, 10)
    
        def receive_message(self, message):
            self.get_logger().info(f'Received: "{message.data}"')
    
    def main(args=None):
        rclpy.init(args=args)
        subscriber_node = SubscriberNode()
        rclpy.spin(subscriber_node)
    
        subscriber_node.destroy_node()
        rclpy.shutdown()
    
    if __name__ == '__main__':
        main()

    Now, run the publisher and subscriber in separate terminals:

    Terminal 1:

    ros2 run my_robot_python_pkg publisher

    Terminal 2:

    ros2 run my_robot_python_pkg subscriber

    You should see the publisher sending messages and the subscriber receiving them.

    Using ROS2 Services with Python

    In this section, we will create a simple Python service and client using ROS2.

    First, create a new file called add_two_ints.srv inside your package’s srv directory. Define the service as follows:

    int64 a
    int64 b
    ---
    int64 sum

    Modify your package.xml file to include the following dependencies:

    <depend>builtin_interfaces</depend>
    <depend>rosidl_default_generators</depend>
    <depend>rosidl_default_runtime</depend>

    Next, edit the CMakeLists.txt file to generate service code for Python:

    find_package(rosidl_default_generators REQUIRED)
    
    rosidl_generate_interfaces(${PROJECT_NAME}
      "srv/AddTwoInts.srv"
    )

    After making these changes, build your package:

    cd ~/ros2_ws
    colcon build
    source install/setup.bash

    Now, create a Python service in a new file called add_two_ints_server.py:

    import rclpy
    from rclpy.node import Node
    from my_robot_python_pkg.srv import AddTwoInts
    
    class AddTwoIntsServer(Node):
        def __init__(self):
            super().__init__('add_two_ints_server')
            self.service = self.create_service(AddTwoInts, 'add_two_ints', self.handle_add_two_ints)
    
        def handle_add_two_ints(self, request, response):
            response.sum = request.a + request.b
            self.get_logger().info(f'{request.a} + {request.b} = {response.sum}')
            return response
    
    def main(args=None):
        rclpy.init(args=args)
        server = AddTwoIntsServer()
        rclpy.spin(server)
    
        server.destroy_node()
        rclpy.shutdown()
    
    if __name__ == '__main__':
        main()

    Finally, create a Python service client in a new file called add_two_ints_client.py:

    import sys
    import rclpy
    from rclpy.node import Node
    from my_robot_python_pkg.srv import AddTwoInts
    
    class AddTwoIntsClient(Node):
        def __init__(self, a, b):
            super().__init__('add_two_ints_client')
            self.client = self.create_client(AddTwoInts, 'add_two_ints')
            request = AddTwoInts.Request()
            request.a = a
            request.b = b
    
            while not self.client.wait_for_service(timeout_sec=1.0):
                self.get_logger().info('Waiting for service to become available...')
    
            self.future = self.client.call_async(request)
            self.future.add_done_callback(self.handle_response)
    
        def handle_response(self, future):
            response = future.result()
            self.get_logger().info(f'{response.sum}')
            rclpy.shutdown()
    
    def main(args=None):
        if len(sys.argv) != 3:
            print('Usage: ros2 run my_robot_python_pkg add_two_ints_client <value a> <value b>')
            sys.exit(1)
    
    a = int(sys.argv[1])
    b = int(sys.argv[2])
    
    rclpy.init(args=args)
    add_two_ints_client = AddTwoIntsClient(a, b)
    rclpy.spin(add_two_ints_client)
    
    add_two_ints_client.destroy_node()
    if name == 'main':
    main()

    Now, run the service and client in separate terminals:

    Terminal 1:

    ros2 run my_robot_python_pkg add_two_ints_server

    Terminal 2:

    ros2 run my_robot_python_pkg add_two_ints_client 5 7

    Conclusion

    In this guide, we have explored ROS2 and its integration with Python for creating powerful robotic systems. We have covered the basics of ROS2, such as nodes, topics and services and demonstrated how to create Python packages, publishers, subscribers and services in ROS2. With this knowledge, you can now start building more advanced robotic applications using ROS2 and Python.