본 블로그 포스팅은 https://www.youtube.com/@pinklab_studio/playlists
에서 현재 강의 중에 있는
ROS2 무작정 따라하기강의의 내용을 필자가 다시 복기하여 기록하는 내용에 관한 것이다.
이전포스트 PinkLAB - 민형기 ROS2 응용 실습 4일차 - 사용자 정의 메세지 실습
에서
사용자 정의 토픽메세지를 생성했다면,
이번에는 사용자 정의 서비스메세지를 생성하고자 한다.
아래의 명령어를 통해서 ros2_workspace/src
경로로 이동한 뒤 신규 패키지를 생성하자
$ cd ros2_workspace/src #각자의 ros2작업공간 내 'src'폴더로 이동
$ ros2 pkg create --build-type ament_cmake my_sec_pkg_msgs --license Apache-2.0
#신규패키지 [my_sec_pkg_msgs] 생성
이때 예의상 --license Apache-2.0
라이선스 옵션을 기재했다.
라이선스 옵션을 기재하면 위 사진처럼 해당 라이선스에 맞는 파일이 패키지 내 자동생성된다
아무튼 이게 중요한게 아니고, 아래 명령어를 더 실행하여
my_sec_pkg_msgs 패키지 내 서비스 메세지를 관리하기 위한 srv
폴더를 생성한다.
$ mkdir srv #신규패키지 [my_sec_pkg_msgs] 내 에서 생성해야함
그리고 해당 폴더 내 MultiSpan.srv
파일을 생성한다.
이 메세지 파일의 생성시 이름 명명 규칙이 있는데 _(언더바)
는 들어가면 안된다.
_
가 이름에 포함되면 오류가 발생한다.
MultiSpan.srv
내 메세지 타입은 ---
하이픈 3개를 기준으로 하여
Requset 메세지 : int64 num
Response 메세지 : float64
배열형 x, y, theta
로 나누어 작성한다.
신규 서비스 메세지를 포함하여 패키지구조가 변경되었으니
이에 맞춰 빌드 의존성, 빌드 옵션을 설정해야한다.
1) 빌드 옵션 설정: CmakeList.txt
2) 빌드 의존성 설정 : Package.xml
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>
<!--추가해야 할 항목-->
각 항목별로 추가하는 빌드 의존성을 설명하면 아래와 같다.
rosidl_default_generators
: ROS2의 인터페이스 파일(토픽, 서비스, 액션)을 컴파일하는데 필요한 빌드 도구를 의미한다.
rosidl_default_runtime
: ROS2 인터페이스 파일을 실행 중에 사용할 수 있도록 도와주는 런타임 라이브러리를 의미한다.
rosidl_interface_packages
: 지금 설계하는 패키지가 ROS2 인터페이스 패키지 그룹에 속함을 명시함
CmakeList.txt
작업내용
# 추가해야할 항목
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"srv/MultiSpan.srv"
)
# 추가해야할 항목
Package.xml
에서 추가한 rosidl_default_generators
패키지를 찾아 CmakeList.txt
빌드 시스템에 추가하며,
설계한 서비스 파일MultiSpan.srv
을 추가하여 인터페이스 코드 빌드를 수행한다.
MultiSpan.srv
를 추가하고 빌드 의존성 및 빌드 옵션 설정을 완료한 후 패키지 빌드를 아래와 같이 수행한다.
$ colcon build --packages-select my_sec_pkg_msgs
#신규 설계한 [my_sec_pkg_msgs] 패키지 빌드
$ r2pkgsetup #설계한 패키지를 등록
패키지 빌드 및 등록을 완료한다면 아래와 같은 코드를 수행할 때 정상적으로 사용자 정의 서비스메세지타입이 출력될 것이다.
$ ros2 interface show my_sec_pkg_msgs/srv/MultiSpan
#신규패키지[my_sec_pkg_msgs] 내 커스텀 메세지 [MultiSpan] 구성 출력
작업 개요는 위 사진과 같으며, 사용자 정의 서비스메세지로 설계한 서비스 [/multi_span]를 service call
한 뒤 Request
데이터 "{num: i}"을 입력한다
Request
가 발생하면 이에 맞춰 서비스 서버(multi_span)로 기능중인 my_srv_servier노드가 해당 Request
를 처리하여 적절한 Response
을 출력한다.
이에 대한 코드는 아래와 같다.
라이브러리 import
import rclpy as rp
from rclpy.node import Node
#커스텀 서비스 메세지 타입 라이브러리
from my_sec_pkg_msgs.srv import MultiSpan
import random
main구문
def main(args=None):
rp.init(args=args)
# 서비스 노드는 클래스 MultiSpanNode의 설계도로 구성됨
service_node = MultiSpanNode()
rp.spin(service_node)
#Ctrl+C 입력시 노드 종료 및 코드 종료
service_node.destroy_node()
rp.shutdown()
if __name__ == '__main__':
main()
노드 구동방식(설계도) 클래스 설계 부
class MultiSpanNode(Node):
def __init__(self):
super().__init__("multi_span_seivce_node") #노드명 기입
# 서비스 서버
self.srv_servier = self.create_service(
srv_type=MultiSpan, #서비스의 메세지 타입
srv_name="multi_span", #서비스 이름(토픽명)
callback=self.callback_service
)
#서비스의 메세지타이은 리퀘스트, 리스폰 2가지 타입으로 됨
def callback_service(self, req, res):
print(f"input_req_num: {req.num}")
# 입력받은 num의 개수만큼 리스트를 원소값을 생성하며,
# x, y 는 0~11사이의 값을 theta는 -pi ~ pi값을 갖으면 된다.
res.x = [random.uniform(0, 11) for _ in range(req.num)]
res.y = [random.uniform(0, 11) for _ in range(req.num)]
res.theta = [random.uniform(-3.14, 3.14) for _ in range(req.num)]
# 리퀘스트값 출력이 지저분하니 여기서 출력문을 정리하자.
for i in range(req.num):
print(f"idx: {i}, pos:[{res.x[i]:.2f}, {res.y[i]:.2f}, {res.theta[i]:.4f}]")
return res
여기서 create_service
메서드를 통해 생성되는 서비스 서버 : multi_span는 요구하는 callback
함수가
인자값 Request
, Response
두가지를 받는 형식으로 설계해야 한다.
Request
는 int형 인자값 num
을 받으며,
이때 요청값 num
의 정보에 맞춰
Response
데이터를 생성하는데 배열 데이터 x
, y
, theta
의 원소 개수가 num
개수에 맞춰서 랜덤값으로 생성된다.
그러니까 num = 3
이면 x
, y
, theta
의 각 데이터별 원소값은 모두 3
이다.
이때 생성되는 x
, y
, theta
의 각 원소별 범위가 다른데
x
, y
는 0~11 사이의 실수형 데이터
theta
는 ~ 사이의 실수형 데이터가 생성되게 범위를 조정했다.
이는 response
데이터가 아래 turtlesim_node를 수행했을 때 출력되는 팝업창 내 터틀봇의 위치 좌표데이터와 Yaw(theta) 방향데이터의 범위를 나타내기 위함이라 보면 된다.
코드 작성을 완료했다면 아래와 같이 패키지 my_second_pkg에 신규 노드 my_service_server.py : my_srv_servier가 추가되었으니 이를 setup.py에 추가한 뒤 패키지를 빌드한다.
$ colcon build --packages-select my_second_pkg
#변경된 패키지 [my_second_pkg]를 리빌드
$ r2pkgsetup #리 빌드된 패키지 변경사항 등록
노드구동
1) bash 1번 스크립트 : 노드 구동
$ ros2 run my_second_pkg my_srv_servier
#서비스 서버 노드 구동
2) bash 2번 스크립트 : Request
수행
$ ros2 service call /multi_span my_sec_pkg_msgs/srv/MultiSpan "{num: 6}"
# [서비스명] [서비스 메세지타입] ["{req인자}"]
두개의 bash 스크립트 창에 노드실행, Request
수행을
진행하면 아래의 결과값을 확인할 수 있다.
추가된 작업내용은 기존 코드 my_service_server.py의 뼈대를 기반으로 확장기능을 추가하는 것이기에 변경된 부분만 하이라이트하겠다.
import rclpy as rp
from rclpy.node import Node
#커스텀 서비스 메세지 타입 라이브러리
from my_sec_pkg_msgs.srv import MultiSpan
import random
# ros2 run turtlesim turtlesim_node을 수행 후
# ros2 service list -t 명령어를 입력하면
# /spawn [turtlesim/srv/Spawn]
from turtlesim.srv import Spawn
# /turtle1/teleport_absolute [turtlesim/srv/TeleportAbsolute]
from turtlesim.srv import TeleportAbsolute
# 위 두개의 서비스 리스트를 확인할 수 있음
import time
class MultiSpanNode(Node):
def __init__(self):
super().__init__("multi_span_seivce_node") #노드명 기입
# 서비스 서버
self.srv_servier = self.create_service(
srv_type=MultiSpan, #서비스의 메세지 타입
srv_name="multi_span", #서비스 이름(토픽명)
callback=self.callback_service
)
# 서비스 클라이언트 - 스폰 클라이언트
self.spawn_client = self.create_client(
srv_type=Spawn,
srv_name= '/spawn',
)
# 서비스 준비 확인
# while not self.spawn_client.wait_for_service(timeout_sec=1.0):
# self.get_logger().info('Spawn 서비스 준비중...')
# 서비스 클라이언트 - 터틀심1번의 절대위치 조정 클라이언트
self.tele_abs_turtle_client = self.create_client(
srv_type=TeleportAbsolute,
srv_name='/turtle1/teleport_absolute'
)
# 서비스 준비 확인
# while not self.tele_abs_turtle_client.wait_for_service(timeout_sec=1.0):
# self.get_logger().info('TeleportAbsolute 서비스 준비중...')
#서비스의 메세지타이은 리퀘스트, 리스폰 2가지 타입으로 됨
def callback_service(self, req, res):
print(f"input_req_num: {req.num}")
# 입력받은 num의 개수만큼 리스트를 원소값을 생성하며,
# x, y 는 0~11사이의 값을 theta는 -pi ~ pi값을 갖으면 된다.
res.x = [random.uniform(0, 11) for _ in range(req.num)]
res.y = [random.uniform(0, 11) for _ in range(req.num)]
res.theta = [random.uniform(-3.14, 3.14) for _ in range(req.num)]
# 리퀘스트값 출력이 지저분하니 여기서 출력문을 정리하자.
for i in range(req.num):
print(f"idx: {i}, pos:[{res.x[i]:.2f}, {res.y[i]:.2f}, {res.theta[i]:.4f}]")
if i == 0: #첫번째 랜덤값은 1번 터틀봇의 절대 위치조정에 사용
self.tele_abs_turtle(res.x[i], res.y[i], res.theta[i])
else : #나머지 랜덤값은 신규 터틀봇의 스폰에 사용
self.spawn_turtle(res.x[i], res.y[i], res.theta[i], i)
return res
# ros2 interface show turtlesim/srv/TeleportAbsolute
def tele_abs_turtle(self, x, y, theta):
# TeleportAbsolute메세지타입의 리퀘스트만 객체화
req = TeleportAbsolute.Request()
req.x = x
req.y = y
req.theta = theta
#서비스 호출 및 응답 대기
future = self.tele_abs_turtle_client.call_async(req)
# rp.spin_until_future_complete(self, future)
time.sleep(0.1)
# 서비스 호출 결과물 확인하기
# if future.result() is not None:
# self.get_logger().info(f"메인터틀심 위치 이동 성공")
# else:
# self.get_logger().error('메인터틀심 위치 이동 실패')
# ros2 interface show turtlesim/srv/Spawn
def spawn_turtle(self, x, y, theta, idx):
# Spawn 메세지 타입의 리퀘스트만 객체화
req = Spawn.Request()
req.x = x
req.y = y
req.theta = theta
#서비스 호출 및 응답 대기
future = self.spawn_client.call_async(req)
# rp.spin_until_future_complete(self, future)
time.sleep(0.1)
# 서비스 호출 결과물 확인하기
# if future.result() is not None:
# self.get_logger().info(f"터틀심 {idx}:{future.result().name}번 스폰 성공")
# else:
# self.get_logger().error(f"터틀심 {idx}번 스폰 실패")
여기서 주요 항목만 설명하자면 아래와 같다.
서비스 클라이언트 spawn_client
, tele_abs_turtle_client
2개를 생성했으며,
각각의 Request
항목에 데이터 채우기 +
채운 Request
항목으로 Service call
을 수행하는 것은
2개 기능이 Sequential하게 흘러가야 하기에
2개 기능을 묶어서 함수 [spawn_turtle], 함수 [tele_abs_turtle]를 설계한다.
설계한 함수들은 서비스 서버에서 아래의 코드로 호출된다.
메인 서비스 서버 srv_servier
에 Request
항목으로 num
값이 입력되면
해당 num
값이 0번째 idx에 해당하는건 기본 turtle1의 위치를 조정하는 서비스 콜
나머지 idx에 해당하는 값 만큼 신규 turtlebot을 spawn 하는 서비스 콜
하는 방식으로 코드 기능이 설계되었다
my_srv_multispan.py파일 작성을 완료했으면, setup.py에 해당 코드와 노드명 [my_srv_multispon]으로 매핑하여 등록한다.
신규 노드를 작성했으니 아래의 명령어를 수행하여
패키지빌드+등록을 수행한다.
$ colcon build --packages-select my_sec_pkg_msgs
#신규 설계한 [my_sec_pkg_msgs] 패키지 빌드
$ r2pkgsetup #설계한 패키지를 등록
1) bash 1번 스크립트 : 터틀심 노드 구동
$ humble
$ ros2 run turtlesim turtlesim_node #터틀심 노드 구동
2) bash 2번 스크립트 : 서비스 노드 구동하기
$ r2pkgsetup
$ ros2 run my_second_pkg my_srv_multispon
#서비스 노드 [my_srv_multispon] 구동하기
3) bash 3번 스크립트 : 메인 서비스 서버에 Req 수행하기
$ r2pkgsetup
$ ros2 service call /multi_span my_sec_pkg_msgs/srv/MultiSpan "{num: 6}"
# 서비스[multi_span], [서비스 메세지 타입], Req인자["{num: i}"] 를 기입하여 Service Req에 의거한 Call수행
위 챕터 3까지 수행했다면 ROS2 무작정 따라하기강의의 [ROS2] 2-13. 다수의 서비스 클라이언트 구현하기
까지는 필자가 어느정도 복기했다 볼 수 있다.
그러나 https://docs.ros2.org/latest/api/rclpy/api/services.html
두 ROS2 공식 Document
의 서비스 부분 예제 코드와 상이한 부분이 있어 해당 부분을 적용해 my_srv_multispan.py를 개선하고자 한다.
1) 서비스 서버의 준비 확인하기
첫번째로 수행할 코드개선사항은 서비스 클라이언트인
spawn_client
, tele_abs_turtle_client
는 각각 서비스 서버인
Spawn
, TeleportAbsolute
이 구동되지 않으면 동작하지 않는다.
따라서 서비스 서버 Spawn
, TeleportAbsolute
가 구동될 때까지 기다리는 코드를 작성하고자 한다.
이에 대한 코드 구현은 아래와 같다.
def __init__(self):
super().__init__("multi_span_seivce_node") #노드명 기입
# 서비스 서버
self.srv_servier = self.create_service(
srv_type=MultiSpan, #서비스의 메세지 타입
srv_name="multi_span", #서비스 이름(토픽명)
callback=self.callback_service
)
# 서비스 클라이언트 - 스폰 클라이언트
self.spawn_client = self.create_client(
srv_type=Spawn,
srv_name= '/spawn',
)
# 서비스 클라이언트 - 터틀심1번의 절대위치 조정 클라이언트
self.tele_abs_turtle_client = self.create_client(
srv_type=TeleportAbsolute,
srv_name='/turtle1/teleport_absolute'
)
# 두 서비스 준비 확인
self.wait_for_service_server()
def wait_for_service_server(self):
srv_1, srv_2 = False, False
while not (srv_1 and srv_2): #두 서비스가 준비될 때까지 기다리기
srv_1 = self.tele_abs_turtle_client.wait_for_service(timeout_sec=1.0)
srv_2 = self.spawn_client.wait_for_service(timeout_sec=1.0)
self.get_logger().info('서비스 준비중...')
서비스 클라이언트
https://docs.ros2.org/foxy/api/rclpy/api/services.html?highlight=wait_for_service 에 대한 document
를 보면
해당 서비스 클라이언트의 하위 클래스 메서드로 wait_for_service
가 존재한다.
메서드의 설명을 보면 인자값으로 넣는 timeout_sec
시간동안 서비스 서버가 활성화 되기까지 기다리며,
시간을 넘어서까지 서비스 서버가 활성화 되지 않으면 False
를 반환하고, 시간 내로 서비스 서버가 활성화 되면 True
를 반환한다.
따라서 두 서버가 활성화 되는 교집합 조건이 될 때까지
While 구문으로 묶어서 서비스 서버가 활성화 되는것을 기다린다.
따라서 해당 구문을 삽입하여 코드를 개선하면 turtlesim_node
가 실행되어 관련 서비스 서버가 활성화 되기 전까지는
위 gif 처럼
서비스 준비중...
메세지를 출력한다
2) 서비스의 응답 결과 확인하기 + 즉각 다음 작업으로 넘어가기
위 사진처럼 서비스 콜 후 충분한 시간
을 기다린 후 다음 서비스 콜을 수행하는 것이 아닌
서비스 콜 후 즉각 응답을 반환 받고 다음 작업을 수행하는 방식으로 코드 개선을 수행하고자 한다.
이것을 수행하려면 먼저 call_async
함수에 대해 알아야 한다.
위 사진처럼 call_async
와 같이 비 동기식으로 동작하는 작업메서드는 대체로 Future
객체를 반환하며, 해당 객체를 동해 작업이 완료되었는지? 를 확인이 가능하다.
Future
객체가 제공하는 하위 메서드는 아래의 표로 정리할 수 있으며, 수행 결과에 따라 적절한 메서드를 사용할 수 있다.
메서드/속성 | 설명 |
---|---|
done() | 작업이 완료되었는지 확인. 완료되었으면 True , 그렇지 않으면 False 를 반환. |
result() | 작업이 완료될 때까지 대기 후, 결과를 반환. 작업이 완료되지 않으면 블로킹 |
add_done_callback(callback) | 작업이 완료되면 실행할 콜백을 등록 비동기 작업의 결과 처리가 필요할 때 사용 |
exception() | 작업이 실패했을 때 발생한 예외를 반환. 작업이 성공적으로 완료되면 None 을 반환 |
이 중 add_done_callback(callback)
메서드를 사용하여 작업이 완료되었을 시 콜백 함수를 호출 작업 수행 결과에 대한 메세지 출력을 수행
하는 기능을 추가한다.
# ros2 interface show turtlesim/srv/TeleportAbsolute
def tele_abs_turtle(self, x, y, theta):
# TeleportAbsolute메세지타입의 리퀘스트만 객체화
req = TeleportAbsolute.Request()
req.x = x
req.y = y
req.theta = theta
#서비스 호출 및 응답 확인
future = self.tele_abs_turtle_client.call_async(req)
# 비동기 서비스 호출이 완료된 후 콜백함수 호출 -> 수행결과 메세지 출력
future.add_done_callback(self.teleport_callback)
def teleport_callback(self, future):
try:
result = future.result() #서비스 요청 작업이 완료될때까지 대기
if result is not None:
self.get_logger().info("메인터틀심 위치 이동 성공")
else:
self.get_logger().error("메인터틀심 위치 이동 실패")
except Exception as e:
self.get_logger().error(f"서비스 호출 중 오류 발생: {e}")
# ros2 interface show turtlesim/srv/Spawn
def spawn_turtle(self, x, y, theta, idx):
# Spawn 메세지 타입의 리퀘스트만 객체화
req = Spawn.Request()
req.x = x
req.y = y
req.theta = theta
#서비스 호출 및 응답 확인
future = self.spawn_client.call_async(req)
# 비동기 서비스 호출이 완료된 후 콜백함수 호출 -> 수행결과 메세지 출력
future.add_done_callback(lambda f: self.spawn_callback(f, idx))
def spawn_callback(self, future, idx):
try:
result = future.result() #서비스 요청 작업이 완료될때까지 대기
if result is not None:
self.get_logger().info(f"터틀심 {idx+1}:{result.name}번 스폰 성공")
else:
self.get_logger().error(f"터틀심 {idx+1}번 스폰 실패")
except Exception as e:
self.get_logger().error(f"서비스 호출 중 오류 발생: {e}")
위 기능을 포함하여 코드를 수행한다면 아래의 결과로 서비스 수행 결과가 도출된다.
time.sleep(0.1)
옵션보다는 좀 더 빠르게 코드가 수행되며, 서비스 응답 결과도 명시적으로 확인이 가능하니
좀더 코드가 개선된 사항이라 볼 수 있다.
하이라이트 식으로 코드를 붙이면 전체 코드의 순서가 어떻게 되는지 확인이 어려우니
개선된 코드 : my_srv_multispan.py의 전체 코드를 첨부하도록 하겠다.
import rclpy as rp
from rclpy.node import Node
#커스텀 서비스 메세지 타입 라이브러리
from my_sec_pkg_msgs.srv import MultiSpan
import random
# ros2 run turtlesim turtlesim_node을 수행 후
# ros2 service list -t 명령어를 입력하면
# /spawn [turtlesim/srv/Spawn]
from turtlesim.srv import Spawn
# /turtle1/teleport_absolute [turtlesim/srv/TeleportAbsolute]
from turtlesim.srv import TeleportAbsolute
# 위 두개의 서비스 리스트를 확인할 수 있음
import time
class MultiSpanNode(Node):
def __init__(self):
super().__init__("multi_span_seivce_node") #노드명 기입
# 서비스 서버
self.srv_servier = self.create_service(
srv_type=MultiSpan, #서비스의 메세지 타입
srv_name="multi_span", #서비스 이름(토픽명)
callback=self.callback_service
)
# 서비스 클라이언트 - 스폰 클라이언트
self.spawn_client = self.create_client(
srv_type=Spawn,
srv_name= '/spawn',
)
# 서비스 클라이언트 - 터틀심1번의 절대위치 조정 클라이언트
self.tele_abs_turtle_client = self.create_client(
srv_type=TeleportAbsolute,
srv_name='/turtle1/teleport_absolute'
)
# 두 서비스 준비 확인
self.wait_for_service_server()
def wait_for_service_server(self):
srv_1, srv_2 = False, False
while not (srv_1 and srv_2): #두 서비스가 준비될 때까지 기다리기
srv_1 = self.tele_abs_turtle_client.wait_for_service(timeout_sec=1.0)
srv_2 = self.spawn_client.wait_for_service(timeout_sec=1.0)
self.get_logger().info('서비스 준비중...')
#서비스의 메세지타이은 리퀘스트, 리스폰 2가지 타입으로 됨
def callback_service(self, req, res):
print(f"input_req_num: {req.num}")
# 입력받은 num의 개수만큼 리스트를 원소값을 생성하며,
# x, y 는 0~11사이의 값을 theta는 -pi ~ pi값을 갖으면 된다.
res.x = [random.uniform(0, 11) for _ in range(req.num)]
res.y = [random.uniform(0, 11) for _ in range(req.num)]
res.theta = [random.uniform(-3.14, 3.14) for _ in range(req.num)]
# 리퀘스트값 출력이 지저분하니 여기서 출력문을 정리하자.
for i in range(req.num):
print(f"idx: {i}, pos:[{res.x[i]:.2f}, {res.y[i]:.2f}, {res.theta[i]:.4f}]")
if i == 0: #첫번째 랜덤값은 1번 터틀봇의 절대 위치조정에 사용
self.tele_abs_turtle(res.x[i], res.y[i], res.theta[i])
else : #나머지 랜덤값은 신규 터틀봇의 스폰에 사용
self.spawn_turtle(res.x[i], res.y[i], res.theta[i], i)
return res
# ros2 interface show turtlesim/srv/TeleportAbsolute
def tele_abs_turtle(self, x, y, theta):
# TeleportAbsolute메세지타입의 리퀘스트만 객체화
req = TeleportAbsolute.Request()
req.x = x
req.y = y
req.theta = theta
#서비스 호출 및 응답 확인
future = self.tele_abs_turtle_client.call_async(req)
# 비동기 서비스 호출이 완료된 후 콜백함수 호출 -> 수행결과 메세지 출력
future.add_done_callback(self.teleport_callback)
def teleport_callback(self, future):
try:
result = future.result() #서비스 요청 작업이 완료될때까지 대기
if result is not None:
self.get_logger().info("메인터틀심 위치 이동 성공")
else:
self.get_logger().error("메인터틀심 위치 이동 실패")
except Exception as e:
self.get_logger().error(f"서비스 호출 중 오류 발생: {e}")
# ros2 interface show turtlesim/srv/Spawn
def spawn_turtle(self, x, y, theta, idx):
# Spawn 메세지 타입의 리퀘스트만 객체화
req = Spawn.Request()
req.x = x
req.y = y
req.theta = theta
#서비스 호출 및 응답 확인
future = self.spawn_client.call_async(req)
# 비동기 서비스 호출이 완료된 후 콜백함수 호출 -> 수행결과 메세지 출력
future.add_done_callback(lambda f: self.spawn_callback(f, idx))
def spawn_callback(self, future, idx):
try:
result = future.result() #서비스 요청 작업이 완료될때까지 대기
if result is not None:
self.get_logger().info(f"터틀심 {idx+1}:{result.name}번 스폰 성공")
else:
self.get_logger().error(f"터틀심 {idx+1}번 스폰 실패")
except Exception as e:
self.get_logger().error(f"서비스 호출 중 오류 발생: {e}")
def main(args=None):
rp.init(args=args)
service_node = MultiSpanNode() #서비스 노드는 멀티스폰 기능을 수행함
rp.spin(service_node)
service_node.destroy_node()
rp.shutdown()
if __name__ == '__main__':
main()