[Dashing][Client Libraries] 5. Creating custom ROS 2 msg and srv files (Python)
이 글은 아래의 자료를 참고로 만들어졌습니다.
https://docs.ros.org/en/dashing/Tutorials/Custom-ROS2-Interfaces.html
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 topics, services, 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.