로봇운영체제 실습 - Service

고영민·2024년 9월 25일

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
# Request
int64 a
int64 b
int64 c
---
# Response
int64 sum
  • package.xml 수정
<?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>
  • CMakeLists.txt 수정
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 # 정의한 srv를 import 

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) # spin을 통해 서비스 서버가 계속 켜져 있도록 함
    rclpy.shutdown()

if __name__ == '__main__':
    main()
  • 서비스 클라이언트 소스코드 작성
$ code service_client_test.py
from srv_interface_example.srv import PlusService # 정의한 srv를 import 

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): # 서비스 서버가 켜져 있는지 체크(1초마다)
            self.get_logger().info('service not available, waiting again...')
        self.req = PlusService.Request() # 정의한 srv 파일의 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]) # request 부분에 데이터 삽입
        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) # 서비스 요청은 한번만(spin_once)
        if minimal_client.future.done(): # 서비스 응답을 기다리며 while문을 돌다가, 응답이 오면 종료
            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()
  • setup.py 수정
$ 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 구조> : 커맨드 라인에서 서비스를 요청

0개의 댓글