Robot Operating System/Tutorial 따라하기

[Dashing][Client Libraries] 5. Creating custom ROS 2 msg and srv files (Python)

jstar0525 2021. 6. 18. 16:31
반응형

이 글은 아래의 자료를 참고로 만들어졌습니다.

 

https://docs.ros.org/en/dashing/Tutorials/Custom-ROS2-Interfaces.html

 

Creating custom ROS 2 msg and srv files — ROS 2 Documentation: Dashing documentation

For this step you can use the packages you created in previous tutorials. A few simple modifications to the nodes, CMakeLists and package files will allow you to use your new interfaces. 7.1 Testing Num.msg with pub/sub With some slight modifications to th

docs.ros.org


 

Creating custom ROS 2 msg and srv files

 

Goal: Define custom interface files (.msg and .srv) and use them with Python and C++ nodes.

 

Tutorial level: Beginner

 

Background

In previous tutorials you utilized message and service interfaces to learn about topicsservices, and simple publisher/subscriber (C++/Python) and service/client (C++/Python) nodes. The interfaces you used were predefined in those cases.

 

While it’s good practice to use predefined interface definitions, you will probably need to define your own messages and services sometimes as well. This tutorial will introduce you to the simplest method of creating custom interface definitions.

 

Prerequisites

You should have a ROS 2 workspace.

 

This tutorial also uses the packages created in the publisher/subscriber (C++ and Python) and service/client (C++ and Python) tutorials to try out the new custom messages.

 

Tasks

1 Create a new package

For this tutorial you will be creating custom .msg and .srv files in their own package, and then utilizing them in a separate package. Both packages should be in the same workspace.

 

Since we will use the pub/sub and service/client packages created in earlier tutorials, make sure you are in the same workspace as those packages (dev_ws/src), and then run the following command to create a new package:

 

$ cd ~/dev_ws/src/
$ ros2 pkg create --build-type ament_cmake tutorial_interfaces

package name: tutorial_interfaces
destination directory: /home/ros/dev_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['ros <jstar0525@gmail.com>']
licenses: ['TODO: License declaration']
build type: ament_cmake
dependencies: []
creating folder ./tutorial_interfaces
creating ./tutorial_interfaces/package.xml
creating source and include folder
creating folder ./tutorial_interfaces/src
creating folder ./tutorial_interfaces/include/tutorial_interfaces
creating ./tutorial_interfaces/CMakeLists.txt

 

tutorial_interfaces is the name of the new package. Note that it is a CMake package; there currently isn’t a way to generate a .msg or .srv file in a pure Python package. You can create a custom interface in a CMake package, and then use it in a Python node, which will be covered in the last section.

 

It is good practice to keep .msg and .srv files in their own directories within a package.
Create the directories in dev_ws/src/tutorial_interfaces:

 

$ cd ~/dev_ws/src/tutorial_interfaces/
$ mkdir msg
$ mkdir srv

 

2 Create custom definitions

2.1 msg definition

In the tutorial_interfaces/msg directory you just created, make a new file called Num.msg with one line of code declaring its data structure:

 

int64 num

 

This is your custom message that transfers a single 64-bit integer called num.

 

2.2 srv definition

Back in the tutorial_interfaces/srv directory you just created, make a new file called AddThreeInts.srv with the following request and response structure:

 

int64 a
int64 b
int64 c
---
int64 sum

 

This is your custom service that requests three integers named a, b, and c, and responds with an integer called sum.

 

3 CMakeLists.txt

To convert the interfaces you defined into language-specific code (like C++ and Python) so that they can be used in those languages, add the following lines to CMakeLists.txt:

 

find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/Num.msg"
  "srv/AddThreeInts.srv"
 )

 

4 package.xml

Because the interfaces rely on rosidl_default_generators for generating language-specific code, you need to declare a dependency on it.
Add the following lines to package.xml

 

<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>

 

5 Build the tutorial_interfaces package

Now that all the parts of your custom interfaces package are in place, you can build the package.
In the root of your workspace (~/dev_ws), run the following command:

 

$ cd ~/dev_ws/
$ colcon build --packages-select tutorial_interfaces

Starting >>> tutorial_interfaces
Finished <<< tutorial_interfaces [3.66s]                       

Summary: 1 package finished [3.75s]

 

Now the interfaces will be discoverable by other ROS 2 packages.

 

6 Confirm msg and srv creation

In a new terminal, run the following command from within your workspace (dev_ws) to source it:

$ cd ~/dev_ws/
$ . install/setup.bash

 

Now you can confirm that your interface creation worked by using the ros2 interface show command:

should return:

$ ros2 msg show tutorial_interfaces/msg/Num

int64 num

 

And

should return:

$ ros2 srv show tutorial_interfaces/srv/AddThreeInts

int64 a
int64 b
int64 c
---
int64 sum

 

7 Test the new interfaces

For this step you can use the packages you created in previous tutorials.
A few simple modifications to the nodes, CMakeLists and package files will allow you to use your new interfaces.

 

7.1 Testing Num.msg with pub/sub

With some slight modifications to the publisher/subscriber package created in a previous tutorial (C++ or Python), you can see Num.msg in action. Since you’ll be changing the standard string msg to a numerical one, the output will be slightly different.

 

Publisher:

import rclpy
from rclpy.node import Node

from tutorial_interfaces.msg import Num    # CHANGE


class MinimalPublisher(Node):

    def __init__(self):
        super().__init__('minimal_publisher')
        self.publisher_ = self.create_publisher(Num, 'topic', 10)     # CHANGE
        timer_period = 0.5
        self.timer = self.create_timer(timer_period, self.timer_callback)
        self.i = 0

    def timer_callback(self):
        msg = Num()                                           # CHANGE
        msg.num = self.i                                      # CHANGE
        self.publisher_.publish(msg)
        self.get_logger().info('Publishing: "%d"' % msg.num)  # CHANGE
        self.i += 1


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

    minimal_publisher = MinimalPublisher()

    rclpy.spin(minimal_publisher)

    minimal_publisher.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

 

Subscriber:

import rclpy
from rclpy.node import Node

from tutorial_interfaces.msg import Num        # CHANGE


class MinimalSubscriber(Node):

    def __init__(self):
        super().__init__('minimal_subscriber')
        self.subscription = self.create_subscription(
            Num,                                              # CHANGE
            'topic',
            self.listener_callback,
            10)
        self.subscription

    def listener_callback(self, msg):
            self.get_logger().info('I heard: "%d"' % msg.num) # CHANGE


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

    minimal_subscriber = MinimalSubscriber()

    rclpy.spin(minimal_subscriber)

    minimal_subscriber.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

 

CMakeLists.txt:

Add the following lines (C++ only):

 

package.xml:

Add the following line:

<exec_depend>tutorial_interfaces</exec_depend>

 

After making the above edits and saving all the changes, build the package:

$ colcon build --packages-select py_pubsub

Starting >>> py_pubsub
Finished <<< py_pubsub [0.42s]          

Summary: 1 package finished [0.52s]

 

 

Then open two new terminals, source dev_ws in each, and run:

$ cd ~/dev_ws
$ . install/setup.bash
$ ros2 run py_pubsub talker

[INFO] [minimal_publisher]: Publishing: "0"
[INFO] [minimal_publisher]: Publishing: "1"
[INFO] [minimal_publisher]: Publishing: "2"
...
[INFO] [minimal_publisher]: Publishing: "29"
[INFO] [minimal_publisher]: Publishing: "30"
[INFO] [minimal_publisher]: Publishing: "31"
...
$ cd ~/dev_ws
$ . install/setup.bash
$ ros2 run py_pubsub listener

[INFO] [minimal_subscriber]: I heard: "29"
[INFO] [minimal_subscriber]: I heard: "30"
[INFO] [minimal_subscriber]: I heard: "31"
...

 

Since Num.msg relays only an integer, the talker should only be publishing integer values, as opposed to the string it published previously:

 

7.2 Testing AddThreeInts.srv with service/client

With some slight modifications to the service/client package created in a previous tutorial (C++ or Python), you can see AddThreeInts.srv in action. Since you’ll be changing the original two integer request srv to a three integer request srv, the output will be slightly different.

 

Service:

from tutorial_interfaces.srv import AddThreeInts     # CHANGE

import rclpy
from rclpy.node import Node


class MinimalService(Node):

    def __init__(self):
        super().__init__('minimal_service')
        self.srv = self.create_service(AddThreeInts, 'add_three_ints', self.add_three_ints_callback)        # CHANGE

    def add_three_ints_callback(self, request, response):
        response.sum = request.a + request.b + request.c                                                  # CHANGE
        self.get_logger().info('Incoming request\na: %d b: %d c: %d' % (request.a, request.b, request.c)) # CHANGE

        return response

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

    minimal_service = MinimalService()

    rclpy.spin(minimal_service)

    rclpy.shutdown()

if __name__ == '__main__':
    main()

 

Client:

from tutorial_interfaces.srv import AddThreeInts       # CHANGE
import sys
import rclpy
from rclpy.node import Node


class MinimalClientAsync(Node):

    def __init__(self):
        super().__init__('minimal_client_async')
        self.cli = self.create_client(AddThreeInts, 'add_three_ints')       # CHANGE
        while not self.cli.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('service not available, waiting again...')
        self.req = AddThreeInts.Request()                                   # CHANGE

    def send_request(self):
        self.req.a = int(sys.argv[1])
        self.req.b = int(sys.argv[2])
        self.req.c = int(sys.argv[3])                  # CHANGE
        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)
        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_three_ints: for %d + %d + %d = %d' %                               # CHANGE
                    (minimal_client.req.a, minimal_client.req.b, minimal_client.req.c, response.sum)) # CHANGE
            break

    minimal_client.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

 

CMakeLists.txt:

Add the following lines (C++ only):

 

package.xml:

Add the following line:

<exec_depend>tutorial_interfaces</exec_depend>

 

After making the above edits and saving all the changes, build the package:

$ cd ~/dev_ws/
$ colcon build --packages-select py_srvcli

Starting >>> py_srvcli
Finished <<< py_srvcli [0.43s]

Summary: 1 package finished [0.54s]

 

Then open two new terminals, source dev_ws in each, and run:

$ cd ~/dev_ws
$ . install/setup.bash
$ ros2 run py_srvcli service

[INFO] [minimal_service]: Incoming request
a: 2 b: 3 c: 1
$ cd ~/dev_ws
$ . install/setup.bash
$ ros2 run py_srvcli client 2 3 1

[INFO] [minimal_client_async]: Result of add_three_ints: for 2 + 3 + 1 = 6

 

Summary

In this tutorial, you learned how to create custom interfaces in their own package and how to utilize those interfaces from within other packages.

 

This is a simple method of interface creation and utilization. You can learn more about interfaces here.

 

Next steps

The next tutorial covers more ways to use interfaces in ROS 2.

 

반응형