본 블로그 포스팅은 https://www.youtube.com/@pinklab_studio/playlists
에서 현재 강의 중에 있는

ROS2 무작정 따라하기강의의 내용을 필자가 다시 복기하여 기록하는 내용에 관한 것이다.


1. 커스텀 서비스 메세지 만들기

이전포스트 PinkLAB - 민형기 ROS2 응용 실습 4일차 - 사용자 정의 메세지 실습에서

사용자 정의 토픽메세지를 생성했다면,
이번에는 사용자 정의 서비스메세지를 생성하고자 한다.

1.1 서비스메세지 : my_sec_pkg_msgs 만들기

아래의 명령어를 통해서 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.2 빌드 옵션 및 의존성 설정

신규 서비스 메세지를 포함하여 패키지구조가 변경되었으니
이에 맞춰 빌드 의존성, 빌드 옵션을 설정해야한다.

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을 추가하여 인터페이스 코드 빌드를 수행한다.


1.3 패키지 빌드 및 디버그

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] 구성 출력



2. 사용자 정의 서비스메세지 기반 패키지 만들기

작업 개요는 위 사진과 같으며, 사용자 정의 서비스메세지로 설계한 서비스 [/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()

노드 구동방식(설계도) \rightarrow 클래스 설계 부

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π-\pi ~ π\pi 사이의 실수형 데이터가 생성되게 범위를 조정했다.

이는 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 수행을
진행하면 아래의 결과값을 확인할 수 있다.



3. 신규 서비스로 터틀봇 MultiSpawn하기

추가된 작업내용은 기존 코드 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_servierRequest항목으로 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 #설계한 패키지를 등록

3.1 MultiSpawn 서비스 구동

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수행


4. 코드 개선

위 챕터 3까지 수행했다면 ROS2 무작정 따라하기강의의 [ROS2] 2-13. 다수의 서비스 클라이언트 구현하기까지는 필자가 어느정도 복기했다 볼 수 있다.

그러나 https://docs.ros2.org/latest/api/rclpy/api/services.html

https://docs.ros.org/en/foxy/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Py-Service-And-Client.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) 메서드를 사용하여 작업이 완료되었을 시 콜백 함수를 호출 \rightarrow 작업 수행 결과에 대한 메세지 출력을 수행

하는 기능을 추가한다.

    # 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()
profile
자율차 공부중

0개의 댓글

관련 채용 정보

Powered by GraphCDN, the GraphQL CDN