1. 서비스(Service)란?

서비스 개념도
- 서비스에는 서버와 클라이언트가 존재
- 클라이언트: 서버에 어떠한 연산을 해달라고 요청을 보냄
- 서버: 클라이언트의 요청을 받아 어떤 연산을 수행하고, 그 결과를 돌려보냄
- 동작 순서 예시
- 클라이언트가 서버에 요청을 보냄
- 서버가 요청을 받으면 콜백함수가 동작하여 특정 작업을 수행함
- 작업이 완료되면 서버에서 클라이언트로 그 결과가 송신됨
- 클라이언트에서 작업 결과를 받아 활용
2. 서비스 서버(Server), 클라이언트(Client) 소스코드 작성
- ROS 노드 작업 시, 일반적인 작업 순서
- 패키지 생성(노드의 소스코드 패키지, 인터페이스 등)
- 노드 소스코드와 인터페이스 파일 작성
- 환경 설정 파일 수정(package.xml, setup.py, setup.cfg 등)
- 빌드 및 실행
인터페이스 파일 생성
$ ros2 pkg create srv_interface_example --build-type ament_cmake
$ cd ~/my_ws/src/srv_interface_example
$ mkdir srv
$ cd srv
- 서비스 인터페이스 파일 작성
- 이때, 인터페이스 파일에서 Request와 Response 부분은 "---"로 구분한다
- 나중에 서비스 노드 소스코드에서 Request와 Response는
request=PlusService.Request(), response=PlusService.Response() 와 같이 자료형처럼 선언하여 사용한다.
$ code PlusService.srv
int64 a
int64 b
int64 c
---
int64 sum
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>srv_interface_example</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="koyeongmin@todo.todo">koyeongmin</maintainer>
<license>TODO: License declaration</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
cmake_minimum_required(VERSION 3.8)
project(srv_interface_example)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rosidl_default_generators REQUIRED)
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
################################################################################
# Declare ROS messages, services and actions
################################################################################
set(srv_files
"srv/PlusService.srv"
)
rosidl_generate_interfaces(${PROJECT_NAME}
${srv_files}
DEPENDENCIES builtin_interfaces
)
################################################################################
# Macro for ament package
################################################################################
ament_export_dependencies(rosidl_default_runtime)
ament_package()
서비스 노드 소스코드 작성
$ ros2 pkg create my_srv_package --build-type ament_python --dependencies rclpy std_msgs srv_interface_example
$ cd ~/my_ws/src/my_srv_package/my_srv_package
$ code service_server_test.py
from srv_interface_example.srv import PlusService
import rclpy
from rclpy.node import Node
class MinimalService(Node):
def __init__(self):
super().__init__('minimal_service')
self.srv = self.create_service(PlusService, 'plus', self.add_two_ints_callback)
def add_two_ints_callback(self, request, response):
response.sum = request.a + request.b + request.c
self.get_logger().info('Incoming request\na: %d b: %d c: %d' % (request.a, request.b, request.c))
return response
def main(args=None):
rclpy.init(args=args)
minimal_service = MinimalService()
rclpy.spin(minimal_service)
rclpy.shutdown()
if __name__ == '__main__':
main()
$ code service_client_test.py
from srv_interface_example.srv import PlusService
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(PlusService, 'plus')
while not self.cli.wait_for_service(timeout_sec=1.0):
self.get_logger().info('service not available, waiting again...')
self.req = PlusService.Request()
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])
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' %
(minimal_client.req.a, minimal_client.req.b, minimal_client.req.c, response.sum))
break
minimal_client.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
$ cd ~/my_ws/src/my_test_package
$ code setup.py
from setuptools import find_packages, setup
package_name = 'my_srv_package'
setup(
name=package_name,
version='0.0.0',
packages=find_packages(exclude=['test']),
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='koyeongmin',
maintainer_email='koyeongmin@todo.todo',
description='TODO: Package description',
license='TODO: License declaration',
tests_require=['pytest'],
entry_points={
'console_scripts': [
'service_server_test = my_srv_package.service_server_test:main',
'service_client_test = my_srv_package.service_client_test:main',
],
},
)
빌드 및 실행
$ cd ~/my_ws
$ colcon build --symlink-install
$ source install/local_setup.bash
Terminal 1: $ ros2 run my_srv_package service_server_test
Terminal 2: $ ros2 run my_srv_package service_client_test

실행 예시
3. 유용한 명령어
- ros2 service list : 존재하는 서비스 목록 출력

- ros2 service type <서비스 이름> : 해당 서비스의 타입을 출력

- ros2 service list –t: 각 서비스타입과 함께 목록 출력

- ros2 service find <서비스 타입>: 해당 서비스 타입을 사용하는 서비스 목록 출력

- ros2 interface show <서비스 타입>: 해당 인터페이스의 자료 구조를 출력 (다른 인터페이스(msg, action) 등에도 사용 가능)

- ros2 service call <요청하고자 하는 서비스 이름> <해당 서비스의 타입> <서비스 타입의 request 구조> : 커맨드 라인에서 서비스를 요청
