개요
, , 간단한 게시자/구독자( / ) 및 서비스/클라이언트( / ) 노드 에 대해 배웠습니다 . 이러한 경우에 사용한 인터페이스는 미리 정의되어 있습니다.
미리 정의된 인터페이스 정의를 사용하는 것이 좋지만 때로는 자체 메시지와 서비스를 정의해야 할 수도 있습니다. 이 자습서에서는 사용자 지정 인터페이스 정의를 만드는 가장 간단한 방법을 소개합니다.
이 자습서는 게시자/구독자( 및 ) 및 서비스/클라이언트( 및 ) 자습서에서 생성된 패키지를 사용하여 새 사용자 지정 메시지를 시험해 봅니다.
실습
1. 새 패키지 만들기
이 자습서에서는 자체 패키지에 사용자 지정 .msg
및.srv
파일을 만든 다음 별도의 패키지에서 활용합니다. 두 패키지는 동일한 작업 공간에 있어야 합니다.
이전 자습서에서 만든 pub/sub 및 service/client 패키지를 사용하므로 해당 패키지( ros2_ws/src
)와 동일한 작업 공간에 있는지 확인한 후 다음 명령을 실행하여 새 패키지를 만듭니다.
Copy ros2 pkg create --build-type ament_cmake tutorial_interfaces
tutorial_interfaces는
새 패키지의 이름입니다. 이는 CMake 패키지이며 패키지일 수 있지만 메시지 및 서비스를 사용할 수 있는 패키지 유형을 제한하지는 않습니다. CMake 패키지에서 고유한 사용자 지정 인터페이스를 만든 다음 마지막 섹션에서 다룰 C++ 또는 Python 노드에서 사용할 수 있습니다.
.msg
및 .srv
파일 은 각각 및 msg srv
라는 디렉토리에 배치해야 합니다 . 다음 위치에 디렉토리를 생성합니다 .ros2_ws/src/tutorial_interfaces
2. 사용자 지정 정의 만들기
2.1 메시지 정의
방금 만든 tutorial_interfaces/msg
디렉터리 에서 데이터 구조를 선언하는 한 줄의 코드로 호출되는 새 파일Num.msg
을 만듭니다.
이것은 num
이라는 단일 64비트 정수를 전송하는 사용자 지정 메시지입니다.
또한 방금 만든 tutorial_interfaces/msg
디렉토리 에서 다음 내용으로 호출되는 새 파일Sphere.msg
을 만듭니다.
Copy geometry_msgs/Point center
float64 radius
이 사용자 지정 메시지는 다른 메시지 패키지( 이 경우geometry_msgs/Point
) 의 메시지를 사용합니다 .
2.2 서비스 정의
방금 만든 tutorial_interfaces/srv
디렉터리로 돌아가서 다음 요청 및 응답 구조로 호출되는 새 파일AddThreeInts.srv
을 만듭니다.
Copy int64 a
int64 b
int64 c
---
int64 sum
이것은a
,b,c
및 라는 세 개의 정수를 요청하고, sum
정수로 응답하는 사용자 지정 서비스입니다.
3. CMakeLists.txt
정의한 인터페이스를 해당 언어에서 사용할 수 있도록 언어별 코드(예: C++ 및 Python)로 변환하려면 다음 행을 CMakeLists.txt
에 추가하십시오.
Copy find_package(geometry_msgs REQUIRED)
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Num.msg"
"msg/Sphere.msg"
"srv/AddThreeInts.srv"
DEPENDENCIES geometry_msgs # Add packages that above messages depend on, in this case geometry_msgs for Sphere.msg
)
참고
4. package.xml
인터페이스는 언어별 코드를 생성하는데 의존하므로 이에 대한 빌드 도구 종속성을 선언해야 합니다. rosidl_default_generators rosidl_default_runtime은
나중에 인터페이스를 사용하는 데 필요한 런타임 또는 실행 단계 종속성입니다. rosidl_interface_packages
는 <member_of_group>
태그를 사용하여 선언된 tutorial_interfaces
패키지가 연결되어야 하는 종속성 그룹의 이름입니다 .
package.xml
의 <package>
요소 내에 다음 줄을 추가합니다 .
Copy <depend>geometry_msgs</depend>
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
5. tutorial_interfaces 패키지 빌드
이제 사용자 정의 인터페이스 패키지의 모든 부분이 준비되었으므로 패키지를 빌드할 수 있습니다. 작업 영역(~/ros2_ws
)의 루트에서 다음 명령을 실행합니다.
Copy colcon build --packages-select tutorial_interfaces
이제 다른 ROS2 패키지에서 인터페이스를 검색할 수 있습니다.
6. msg 및 srv 생성 확인
새 터미널에서 작업 공간(ros2_ws
) 내에서 다음 명령을 실행하여 소싱합니다.
Copy source install/setup.bash
이제 다음 명령ros2 interface show
을 사용하여 인터페이스 생성이 작동했는지 확인할 수 있습니다 .
Copy ros2 interface show tutorial_interfaces/msg/Num
다음을 반환해야 합니다.
그리고
Copy ros2 interface show tutorial_interfaces/msg/Sphere
다음을 반환해야 합니다.
Copy geometry_msgs/Point center
float64 x
float64 y
float64 z
float64 radius
그리고
Copy ros2 interface show tutorial_interfaces/srv/AddThreeInts
다음을 반환해야 합니다.
Copy int64 a
int64 b
int64 c
---
int64 sum
7. 새 인터페이스 테스트
이 단계에서는 이전 자습서에서 만든 패키지를 사용할 수 있습니다. CMakeLists.txt
와 package.xml
파일에 대한 몇 가지 간단한 수정으로 새 인터페이스를 사용할 수 있습니다.
7.1 pub/sub를 사용한 Num.msg
테스트
발행자(Publisher)
Copy import rclpy
from rclpy.node import Node
from tutorial_interfaces.msg import Num # CHANGE
class MinimalPublisher(Node):
def __init__(self):
super().__init__('minimal_publisher')
self.publisher_ = self.create_publisher(Num, 'topic', 10) # CHANGE
timer_period = 0.5
self.timer = self.create_timer(timer_period, self.timer_callback)
self.i = 0
def timer_callback(self):
msg = Num() # CHANGE
msg.num = self.i # CHANGE
self.publisher_.publish(msg)
self.get_logger().info('Publishing: "%d"' % msg.num) # CHANGE
self.i += 1
def main(args=None):
rclpy.init(args=args)
minimal_publisher = MinimalPublisher()
rclpy.spin(minimal_publisher)
minimal_publisher.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
구독자(Subscriber)
Copy import rclpy
from rclpy.node import Node
from tutorial_interfaces.msg import Num # CHANGE
class MinimalSubscriber(Node):
def __init__(self):
super().__init__('minimal_subscriber')
self.subscription = self.create_subscription(
Num, # CHANGE
'topic',
self.listener_callback,
10)
self.subscription
def listener_callback(self, msg):
self.get_logger().info('I heard: "%d"' % msg.num) # CHANGE
def main(args=None):
rclpy.init(args=args)
minimal_subscriber = MinimalSubscriber()
rclpy.spin(minimal_subscriber)
minimal_subscriber.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
CMakeLists.txt
다음 줄을 추가합니다(C++만 해당).
Copy #...
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(tutorial_interfaces REQUIRED) # CHANGE
add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp tutorial_interfaces) # CHANGE
add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp tutorial_interfaces) # CHANGE
install(TARGETS
talker
listener
DESTINATION lib/${PROJECT_NAME})
ament_package()
package.xml
Copy <exec_depend>tutorial_interfaces</exec_depend>
위의 편집을 수행하고 모든 변경 사항을 저장한 후 패키지를 빌드합니다.
Copy colcon build --packages-select py_pubsub
그런 다음 두 개의 새 터미널을 열고 각각 source 를 입력하고 다음을 실행합니다.
Copy ros2 run py_pubsub talker
Copy ros2 run py_pubsub listener
정수만 릴레이 하므로 talker는 이전에 게시한 문자열과 달리 정수 Num.msg
값만 게시해야 합니다.
Copy [INFO] [minimal_publisher]: Publishing: '0'
[INFO] [minimal_publisher]: Publishing: '1'
[INFO] [minimal_publisher]: Publishing: '2'
7.2 서비스/클라이언트 AddThreeInts.srv
테스트
서비스
Copy from tutorial_interfaces.srv import AddThreeInts # CHANGE
import rclpy
from rclpy.node import Node
class MinimalService(Node):
def __init__(self):
super().__init__('minimal_service')
self.srv = self.create_service(AddThreeInts, 'add_three_ints', self.add_three_ints_callback) # CHANGE
def add_three_ints_callback(self, request, response):
response.sum = request.a + request.b + request.c # CHANGE
self.get_logger().info('Incoming request\na: %d b: %d c: %d' % (request.a, request.b, request.c)) # CHANGE
return response
def main(args=None):
rclpy.init(args=args)
minimal_service = MinimalService()
rclpy.spin(minimal_service)
rclpy.shutdown()
if __name__ == '__main__':
main()
클라이언트
Copy from tutorial_interfaces.srv import AddThreeInts # CHANGE
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(AddThreeInts, 'add_three_ints') # CHANGE
while not self.cli.wait_for_service(timeout_sec=1.0):
self.get_logger().info('service not available, waiting again...')
self.req = AddThreeInts.Request() # CHANGE
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]) # CHANGE
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)
if minimal_client.future.done():
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' % # CHANGE
(minimal_client.req.a, minimal_client.req.b, minimal_client.req.c, response.sum)) # CHANGE
break
minimal_client.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
mai
package.xml
다음 줄을 추가합니다.
Copy <exec_depend>tutorial_interfaces</exec_depend>
위의 편집을 수행하고 모든 변경 사항을 저장한 후 패키지를 빌드합니다.
Copy colcon build --packages-select py_srvcli
그런 다음 두 개의 새 터미널을 열고 각각 source ros2_ws
를 입력하고 다음을 실행합니다.
Copy ros2 run py_srvcli service
Copy ros2 run py_srvcli client 2 3 1