왕다방

인공지능 & 로봇 사용자들의 자유게시판입니다. 인공지능, 가정용로봇, 서비스로봇, 인공지능스피커 등 자유롭게 올려주세요. 질문과답변도 이곳에 올려주세요.

제목ROS2: 서비스(service) 만들기2021-12-21 22:41
작성자user icon Level 4

88x31.png


이번 글에서는 서비스(service)를 만들어 보겠습니다. 간단하게 두 수 a, b를 덧셈하는 서비스를 만들겠습니다.


서비스는 앞서 말했듯이 클라이언트(client)의 요청(request)과 서버(server)의 응답(response)으로 이루어진 통신방식입니다.


터미널을 열고 오버레이 작업공간 src 폴더로 이동하세요.

  $ cd ~/dev_ws/src


파이썬 패키지를 만들겠습니다.  패키지 이름은 add_service_py로 하고 의존성 패키지로 rclpy와 example_interfaces를 포함시키겠습니다. 

  $ ros2 pkg create add_service_py --build-type ament_python --dependencies rclpy example_interfaces


서비스 인터페이스를 직접 작성해도 되지만 이번 글에서는 example_interfaces 패키지에 있는 AddTwoInts.srv 파일을 사용하겠습니다. AddTowInts 타입의 구조는 다음과 같습니다.

 int64 a

 int64 b

 ---

 int64 sum


요청 메시지는 int형 a, b를 변수로 가지고 응답 메시지는 int형 sum을 변수로 가집니다.


package.xml 파일을 열어 versiondescriptionmaintainerlicense를 수정하고 저장합니다.

mb-file.php?path=2021%2F12%2F21%2FF4411_1.png

setup.py 파일을 열고 파이썬 패키지 빌드 설정을 합니다. entry_points 에는 server 노드와 client 노드에 대한 스크립트를 설정합니다.

mb-file.php?path=2021%2F12%2F21%2FF4412_2.png

서버 노드와 클라이언트 노드를 생성합니다.

  $ touch ~/dev_ws/src/add_service_py/add_service_py/{server.py,client.py}


서버 노드부터 작성합시다.

mb-file.php?path=2021%2F12%2F21%2FF4413_3.png

rclpy 모듈과 Node 모듈, AddTwoInts 모듈을 import 합니다.

 from example_interfaces.srv import AddTwoInts


 import rclpy

 from rclpy.node import Node


서버 노드 클래스를 작성합니다. 클래스 이름은 Server이고, Node 클래스를 상속합니다.

 class Server(Node):


Server 클래스 생성자를 작성합니다. super 함수를 통해 부모 클래스인 Node 클래스의 생성자에 노드 이름을 매개변수로 작성합니다. 노드 이름은 'server' 입니다.

     def __init__(self):

         super().__init__('server')


create_service 함수를 통해 서비스를 만들어주세요. 매개변수로 메시지 타입, 서비스 이름, 콜백 함수를 넘겨줍니다.

         self.srv = self.create_service(AddTwoInts, 'service', callback)


콜백 함수는 첫번째 인수로 요청 메시지 객체를, 두번째 인수로 응답 메시지 객체를 가지도록 작성합니다.

서비스 인터페이스에 작성된 것처럼 요청 메시지 객체는 a, b를 프로퍼티로 가지고 응답 메시지 객체는 sum을 프로퍼티로 가집니다. sum 프로퍼티를 작성하여 응답 메시지 객체를 리턴해줍시다.

     def callback(self, req, res):

         res.sum = req.a + req.b

         self.get_logger().info('Incoming request\na: %d b: %d' % (req.a, req.b))


         return res


메인함수를 작성합니다. 

 def main(args=None):

     rclpy.init(args=args)

 

     server = Server()


     rclpy.spin(server)


     rclpy.shutdown()



클라이언트 노드를 작성합시다.

mb-file.php?path=2021%2F12%2F21%2FF4414_4.png

rclpy 모듈과 Node 모듈, AddTwoInts 모듈을 import 합니다. 터미널 명령어로부터 인수를 받기위해 sys 모듈도 import 하세요.

 import sys


 from example_interfaces.srv import AddTwoInts


 import rclpy

 from rclpy.node import Node


클라이언트 노드 클래스를 작성합니다. 클래스 이름은 Client이고, Node 클래스를 상속합니다.

 class Client(Node):


Client 클래스 생성자를 작성합니다. super 함수를 통해 부모 클래스인 Node 클래스의 생성자에 노드 이름을 매개변수로 작성합니다. 노드 이름은 'client' 입니다.

     def __init__(self):

         super().__init__('client')


create_client 함수를 통해 클라이언트를 생성합니다. 매개변수로 메시지 타입서비스 이름을 넘겨줍니다.

         self.cli = self.create_client(AddTwoInts, 'service')


클라이언트의 wait_for_service 함수를 통해 서비스가 존재하는지 확인합니다.

         while not self.cli.wait_for_service(timeout_sec=1.0):

             self.get_logger().info('service not available, waiting again...')


요청 메시지 객체를 req 프로퍼티에 설정해줍시다.

         self.req = AddTwoIntes.Request()


요청 메시지를 보내는 함수를 정의합니다. 요청 메시지 객체의 a, b 프로퍼티에는 sys 모듈을 통해 터미널에 입력한 변수를 전달합니다. 그리고 클라이언트의 call_async 함수를 통해 서버에 요청 메시지를 보내고 응답을 기다립니다.

     def send_request(self):

         self.req.a = int(sys.argv[1])

         self.req.b = int(sys.argv[2])

         self.future = self.cli.call_async(self.req)



메인함수를 작성합니다. 서버에 요청 메시지를 보내고 응답메시지를 받습니다. 구독자 노드와 달리 spin_once 함수를 통해 한번만 실행합니다.

 def main(args=None):

     rclpy.init(args=args)

 

     client = Client()

     client.send_request()


     while rclpy.ok():

         rclpy.spin_once(client)

         if client.future.done():

             try:

                 response = client.future.result()

             except Exception as e:

                 client.get_logger().info('Service call failed % r' % (e, ))

             else:

                 client.get_logger().info('Result of add_two_ints: for %d + %d = %d' % (client.req.a, client.req.b, response.sum)

             break


     client.destroy_node()

     rclpy.shutdown()


colcon으로 패키지를 빌드해주세요.

  $ cd ~/dev_ws && colcon build --symlink-install --packages-select add_service_py


오버레이 환경설정을 해야하는데 .bashrc에 alias로 'overlay'라고 설정하겠습니다. 이제부터는 overlay라고 입력하면 오버레이 환경설정으로 이해하시면 됩니다.

mb-file.php?path=2021%2F12%2F21%2FF4415_5.png

클라이언트를 먼저 실행해보겠습니다.

 $ overlay && ros2 run add_service_py client 123 456

mb-file.php?path=2021%2F12%2F21%2FF4417_6.gif


service not available, waiting again... 이라는 문구가 1초에 한번씩 출력됩니다. 서버를 기다리는 중입니다.

이제 새로운 터미널을 열고 서버를 실행해봅시다.

mb-file.php?path=2021%2F12%2F21%2FF4418_6.png

클라이언트에 덧셈 결과가 출력되고 클라이언트 노드가 종료됩니다.


이제 C++ 패키지를 만들겠습니다. 빌드타입은 ament_cmake, 의존성 패키지는 rclcpp와 example_interfaces를 포함합니다. 패키지 이름은 add_service_cpp로 하겠습니다.

  $ ros2 pkg create add_service_cpp --build-type ament_cmake --dependencies rclcpp example_interfaces


package.xml 파일을 열어서 versiondescriptionmaintainerlicense를 수정하고 저장합니다.

mb-file.php?path=2021%2F12%2F21%2FF4419_8.png

CMakeLists.txt 파일을 열고 다음과 같이 수정하고 저장합니다.

mb-file.php?path=2021%2F12%2F21%2FF4420_9.png

서버 노드와 클라이언트 노드를 생성합니다.

  $ touch ~/dev_ws/src/add_service_cpp/src/{server.cpp,client.cpp}


서버 노드를 작성합니다. 기본적인 구조는 파이썬과 같습니다. 노드를 만들고 서비스를 생성합니다.

mb-file.php?path=2021%2F12%2F21%2FF4421_10.png


클라이언트 노드 또한 파이썬과 같습니다. 서버가 있는지 확인하고 요청 메시지를 서버에 보내면 됩니다.

mb-file.php?path=2021%2F12%2F21%2FF4422_11.png


#ROS2# 노드# 패키지# server# client# service# 파이썬# C++
댓글
자동등록방지
(자동등록방지 숫자를 입력해 주세요)