이번에는 ROS 2에서 Service를 이용해 노드 간 요청과 응답을 주고받는 방법을 실습했다.
앞서 Topic에서는 Publisher가 메시지를 발행하고 Subscriber가 이를 구독하는 방식으로 통신했다면, Service는 Client가 Server에게 특정 작업을 요청하고 그 결과를 응답받는 방식으로 동작한다.
ROS 2의 Service는 요청(Request)과 응답(Response)을 주고받는 통신 방식이다.
Service를 사용하는 노드는 크게 두 가지 역할로 나뉜다.
전체적인 구조는 다음과 같다.
Service Client
│
│ Request
↓
Service Server
│
│ Response
↓
Service Client
Topic이 지속적으로 데이터를 주고받는 데 적합하다면, Service는 특정 작업을 요청하고 그 결과를 받는 상황에서 사용할 수 있다.
현재 ROS 2 시스템에서 사용할 수 있는 Service를 확인하기 위해 다음 명령어를 사용한다.
ros2 service list
Turtlesim을 실행한 상태에서 명령어를 실행하면 다음과 같은 Service들을 확인할 수 있다.
/clear
/kill
/reset
/spawn
/turtle1/set_pen
/turtle1/teleport_absolute
/turtle1/teleport_relative
이를 통해 현재 실행 중인 노드에서 어떤 Service를 제공하고 있는지 확인할 수 있다.
Service의 이름뿐만 아니라 어떤 인터페이스를 사용하는지도 확인할 수 있다.
ros2 service list -t
예를 들어 다음과 같이 확인할 수 있다.
/clear [std_srvs/srv/Empty]
/kill [turtlesim/srv/Kill]
/spawn [turtlesim/srv/Spawn]
특정 Service의 타입만 확인하려면 다음과 같이 사용할 수 있다.
ros2 service type /spawn
실행 결과:
turtlesim/srv/Spawn
즉, /spawn Service는 turtlesim/srv/Spawn이라는 Service 인터페이스를 사용한다.
특정 Service 인터페이스를 사용하는 Service를 찾을 수도 있다.
예를 들어 std_srvs/srv/Empty 타입을 사용하는 Service를 찾으려면:
ros2 service find std_srvs/srv/Empty
실행 결과:
/clear
/reset
또한 turtlesim/srv/Kill 타입을 사용하는 Service를 찾을 수도 있다.
ros2 service find turtlesim/srv/Kill
결과:
/kill
이처럼 ros2 service find 명령어를 사용하면 특정 Service 타입이 실제로 어떤 Service에서 사용되고 있는지 확인할 수 있다.
ros2 service call 명령어를 사용하면 Service에 직접 요청을 보낼 수 있다.
먼저 /clear Service를 호출해 보았다.
ros2 service call /clear std_srvs/srv/Empty
/clear는 별도의 요청 데이터를 필요로 하지 않기 때문에 Empty 타입을 사용한다.
호출이 정상적으로 이루어지면 다음과 같은 요청과 응답을 확인할 수 있다.
requester: making request: std_srvs.srv.Empty_Request()
response:
std_srvs.srv.Empty_Response()
이번에는 /reset Service를 호출해 보았다.
ros2 service call /reset std_srvs/srv/Empty
Service가 호출되면 Turtlesim의 상태가 초기화된다.
또한 /kill Service를 이용하면 특정 거북이를 삭제할 수 있다.
ros2 service call /kill turtlesim/srv/Kill "name: 'turtle1'"
여기서는 turtle1이라는 이름을 가진 거북이를 삭제하도록 Service Server에 요청한다.
즉,
Client
│
│ name: turtle1
↓
/kill Service
│
↓
turtle1 삭제
와 같은 방식으로 동작한다.
Service에서는 srv 인터페이스를 사용한다.
Service 인터페이스는 일반적으로 다음과 같이 구성된다.
Request
---
Response
---는 Request와 Response를 구분하는 역할을 한다.
Turtlesim의 /spawn Service에서 사용하는 인터페이스를 확인해 보았다.
ros2 interface show turtlesim/srv/Spawn.srv
결과는 다음과 같은 구조로 되어 있다.
float32 x
float32 y
float32 theta
string name
---
string name
여기서 ---를 기준으로 위쪽은 Request, 아래쪽은 Response이다.
Request
├─ x
├─ y
├─ theta
└─ name
│
↓
Service
│
↓
Response
└─ name
즉, Client가 x, y, theta, name 정보를 전달하면 Service Server가 새로운 거북이를 생성하고, 생성된 거북이의 이름을 Response로 반환하는 구조이다.
앞에서 실습한 Topic과 이번에 실습한 Service를 비교하면 다음과 같다.
| 구분 | Topic | Service |
|---|---|---|
| 통신 방식 | Publisher → Subscriber | Client → Server → Client |
| 데이터 흐름 | 단방향 | 요청/응답 |
| 인터페이스 | msg | srv |
| 주요 용도 | 지속적인 데이터 전달 | 특정 작업 요청 및 결과 확인 |
Topic에서는 메시지를 계속 발행하고 구독하는 반면, Service에서는 필요한 시점에 작업을 요청하고 그에 대한 응답을 받는 방식으로 동작한다.
이번 실습에서는 ROS 2의 Service 통신 방식을 직접 확인했다.
먼저 ros2 service list를 이용해 현재 실행 중인 Service를 확인하고, ros2 service type을 통해 Service의 인터페이스 타입을 확인했다.
또한 ros2 service find를 이용해 특정 인터페이스를 사용하는 Service를 찾고, ros2 service call을 이용해 실제 Service에 요청을 보내는 과정까지 실습했다.
마지막으로 turtlesim/srv/Spawn의 srv 인터페이스를 확인하면서 Request와 Response가 어떻게 정의되어 있는지도 확인했다.
전체적인 구조를 정리하면 다음과 같다.
ROS 2 Service
Service Client
│
│ Request
↓
Service Server
│
│ Response
↓
Service Client
그리고 Service에서 사용하는 인터페이스는 다음과 같이 Request와 Response로 구성된다.
Request
---
Response
따라서 Topic이 지속적인 데이터 전달에 사용된다면, Service는 특정 작업을 요청하고 그 결과를 받는 통신 방식이라고 정리할 수 있다.