간단한 publisher/subscriber 작성 (Python)
목표: Python을 사용하여 퍼블리셔와 서브스크라이버 노드를 만들고 실행합니다.
배경 지식
Nodes 는 ROS 그래프를 통해 통신하는 실행 가능한 프로세스입니다. 이 튜토리얼에서는 노드들이 topic 을 통해 서로에게 문자열 메시지 형식의 정보를 전달할 것입니다. 여기서 사용되는 예제는 간단한 “talker” 및 “listener” 시스템입니다. 한 노드는 데이터를 게시하고, 다른 노드는 해당 주제를 구독하여 해당 데이터를 수신합니다.
이 예제에 사용된 코드는 여기에서 찾을 수 있습니다.
작업
1. 패키지 생성
새 터미널을 열고 ROS 2 설치를 소스로 지정 하여 ros2 명령이 작동하도록합니다.
이전 튜토리얼 에서 만든 ros2_ws 디렉토리로 이동합니다.
패키지는 워크스페이스의 루트가 아닌 src 디렉토리에서 생성해야 합니다.
따라서 ros2_ws/src 로 이동하고 다음 명령을 실행하여 패키지를 생성합니다.
ros2 pkg create --build-type ament_python --license Apache-2.0 py_pubsub
터미널은 패키지 py_pubsub 및 모든 필요한 파일 및 폴더를 생성했음을 확인하는 메시지를 반환합니다.
2. 퍼블리셔 노드 작성
ros2_ws/src/py_pubsub/py_pubsub 로 이동합니다.
이 디렉토리는 ROS 2 패키지와 동일한 이름을 가진 Python 패키지 입니다.
다음 명령을 사용하여 예제 토커 코드를 다운로드합니다.
wget https://raw.githubusercontent.com/ros2/examples/humble/rclpy/topics/minimal_publisher/examples_rclpy_minimal_publisher/publisher_member_function.py
이제 __init__.py 옆에 publisher_member_function.py 라는 새 파일이 있어야 합니다.
원하는 텍스트 편집기를 사용하여 파일을 엽니다.
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class MinimalPublisher(Node):
def __init__(self):
super().__init__('minimal_publisher')
self.publisher_ = self.create_publisher(String, 'topic', 10)
timer_period = 0.5 # seconds
self.timer = self.create_timer(timer_period, self.timer_callback)
self.i = 0
def timer_callback(self):
msg = String()
msg.data = 'Hello World: %d' % self.i
self.publisher_.publish(msg)
self.get_logger().info('Publishing: "%s"' % msg.data)
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()
2.1. 코드 검토
주석 이후의 코드의 첫 줄은 rclpy 를 가져와서 해당 Node 클래스를 사용할 수 있게 합니다.
import rclpy
from rclpy.node import Node
다음 문장은 노드에서 토픽에 전달할 데이터를 구조화하는 데 사용되는 내장 문자열 메시지 형식을 가져옵니다.
from std_msgs.msg import String
이러한 줄은 노드의 종속성을 나타냅니다.
이전 튜토리얼에서 언급했듯이 종속성은 package.xml 에 추가해야 합니다. 다음 섹션에서 이 작업을 수행하게 됩니다.
다음으로, MinimalPublisher 클래스가 만들어집니다. 이 클래스는 Node 에서 상속받거나 (또는 Node 의 하위 클래스) 되었습니다.
class MinimalPublisher(Node):
다음은 클래스의 생성자 정의입니다.
super().__init__ 은 Node 클래스의 생성자를 호출하고 노드 이름을 지정합니다. 이 경우 minimal_publisher 입니다.
create_publisher 는 노드가 topic 이라는 이름의 주제에서 메시지 형식이 String 인 메시지를 게시한다고 선언하며, queue size 가 10임을 나타냅니다.
대기열 크기는 구독자가 메시지를 충분히 빨리 수신하지 않는 경우 메시지의 대기열 크기를 제한하는 필수 QoS (서비스 품질) 설정입니다.
다음으로, 0.5초마다 실행되는 타이머가 생성됩니다.
self.i 는 콜백에서 사용되는 카운터입니다.
def __init__(self):
super().__init__('minimal_publisher')
self.publisher_ = self.create_publisher(String, 'topic', 10)
timer_period = 0.5 # seconds
self.timer = self.create_timer(timer_period, self.timer_callback)
self.i = 0
timer_callback 는 카운터 값을 추가한 메시지를 생성하고, get_logger().info 를 사용하여 콘솔에 메시지를 게시합니다.
def timer_callback(self):
msg = String()
msg.data = 'Hello World: %d' % self.i
self.publisher_.publish(msg)
self.get_logger().info('Publishing: "%s"' % msg.data)
self.i += 1
마지막으로, main 함수가 정의됩니다.
def main(args=None):
rclpy.init(args=args)
minimal_publisher = MinimalPublisher()
rclpy.spin(minimal_publisher)
# 노드를 명시적으로 삭제
# (선택 사항 - 그렇지 않으면 가비지 수집기가 노드 객체를 파괴할 때 자동으로 수행됩니다)
minimal_publisher.destroy_node()
rclpy.shutdown()
첫 번째로 rclpy 라이브러리를 초기화하고, 그런 다음 노드를 만들고, 노드가 콜백을 호출하도록합니다.
2.2. 종속성 추가
ros2_ws/src/py_pubsub 디렉토리로 한 단계 뒤로 이동하면 이미 패키지를 위한 setup.py, setup.cfg, package.xml 파일이 생성된 것을 확인할 수 있습니다.
이전 튜토리얼에서 언급했듯이 <description>, <maintainer>, <license> 태그를 채우도록 주의하세요.
<description>Examples of minimal publisher/subscriber using rclpy</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache License 2.0</license>
위의 줄 다음에, 패키지의 코드가 실행될 때 패키지가 rclpy 와 std_msgs 가 필요하다는 것을 나타내는 다음 종속성을 추가합니다.
<exec_depend>rclpy</exec_depend>
<exec_depend>std_msgs</exec_depend>
이렇게 하면 코드가 실행될 때 패키지가 rclpy 및 std_msgs 가 필요하다고 선언됩니다.
파일을 저장하도록합니다.
2.3. 엔트리 포인트 추가
setup.py 파일을 엽니다.
다시 한 번 package.xml 파일에 있는 maintainer, maintainer_email, description, license 필드와 일치하도록 변경합니다.
maintainer='YourName',
maintainer_email='you@email.com',
description='rclpy를 사용한 간단한 퍼블리셔/서브스크라이버 예제',
license='Apache License 2.0',
entry_points 필드의 console_scripts 괄호 내에 다음 줄을 추가합니다.
entry_points={
'console_scripts': [
'talker = py_pubsub.publisher_member_function:main',
],
},
저장을 잊지 마세요.
2.4. setup.cfg 확인
setup.cfg 파일의 내용은 다음과 같이 자동으로 올바르게 채워져 있어야 합니다.
[develop]
script_dir=$base/lib/py_pubsub
[install]
install_scripts=$base/lib/py_pubsub
이것은 단순히 setuptools에게 실행 파일을 lib 에 넣도록 지시하는 것입니다. 왜냐하면 ros2 run 명령이 해당 위치에서 실행 파일을 찾을 것이기 때문입니다.
이제 패키지를 빌드하고 로컬 설정 파일을 소스로 지정한 다음 실행할 수 있지만, 작동하는 전체 시스템을 보기 위해 먼저 서브스크라이버 노드를 생성하겠습니다.
3 서브스크라이버 노드 작성
다음 노드를 만들려면 ros2_ws/src/py_pubsub/py_pubsub 로 돌아갑니다.
터미널에서 다음 명령을 입력합니다.
wget https://raw.githubusercontent.com/ros2/examples/humble/rclpy/topics/minimal_subscriber/examples_rclpy_minimal_subscriber/subscriber_member_function.py
이제 디렉토리에 다음 파일이 있어야 합니다.
__init__.py publisher_member_function.py subscriber_member_function.py
3.1. 코드 검토
텍스트 편집기를 열고 subscriber_member_function.py 파일을 엽니다.
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class MinimalSubscriber(Node):
def __init__(self):
super().__init__('minimal_subscriber')
self.subscription = self.create_subscription(
String,
'topic',
self.listener_callback,
10)
self.subscription # 사용하지 않은 변수 경고 방지
def listener_callback(self, msg):
self.get_logger().info('I heard: "%s"' % msg.data)
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()
서브스크라이버 노드의 코드는 거의 퍼블리셔와 동일합니다. 생성자는 퍼블리셔와 동일한 인수로 서브스크라이버를 만듭니다. topics 튜토리얼 에서 배운 대로 퍼블리셔와 서브스크라이버가 통신하려면 사용할 주제와 메시지 형식이 일치해야 합니다.
self.subscription = self.create_subscription(
String,
'topic',
self.listener_callback,
10)
서브스크라이버의 생성자 및 콜백에는 타이머 정의가 없습니다. 왜냐하면 필요하지 않기 때문입니다. 서브스크라이버의 콜백은 메시지를 수신하자마자 호출됩니다.
콜백 정의는 메시지를 콘솔에 인쇄하고 받은 데이터와 함께 정보 메시지를 출력하는 것입니다.
퍼블리셔가 msg.data = 'Hello World: %d' % self.i 를 정의했다는 것을 기억하세요.
def listener_callback(self, msg):
self.get_logger().info('I heard: "%s"' % msg.data)
main 정의는 거의 동일하며 퍼블리셔의 생성 및 스핀을 서브스크라이버로 교체합니다.
minimal_subscriber = MinimalSubscriber()
rclpy.spin(minimal_subscriber)
이 노드가 퍼블리셔와 동일한 종속성을 가지므로 package.xml 에 추가해야 할 새로운 내용은 없습니다.
setup.cfg 파일도 변경할 필요가 없습니다.
3.2. 엔트리 포인트 추가
setup.py 파일을 다시 열고 서브스크라이버 노드를 퍼블리셔 노드와 동일한 방식으로 추가합니다.
entry_points={
'console_scripts': [
'talker = py_pubsub.publisher_member_function:main',
'listener = py_pubsub.subscriber_member_function:main',
],
},
이제 두 노드가 모두 있는 패키지를 빌드하고 로컬 설정 파일을 소스로 지정합니다.
4 빌드 및 실행
일반적으로 ROS 2 시스템의 일부로 rclpy 와 std_msgs 패키지가 이미 설치되어 있을 것입니다.
빌드하기 전에 빠진 종속성을 확인하기 위해 워크스페이스 루트(ros2_ws)에서 rosdep 를 실행하는 것이 좋습니다.
rosdep install -i --from-path src --rosdistro {DISTRO} -y
여전히 워크스페이스 루트인 ros2_ws 에서 새 패키지를 빌드합니다.
colcon build --packages-select py_pubsub
새 터미널을 열고, ros2_ws 로 이동한 다음 설정 파일을 소스화합니다.
source install/setup.bash
이제 퍼블리셔 노드를 실행합니다.
ros2 run py_pubsub talker
터미널은 다음과 같이 0.5초마다 정보 메시지를 게시하기 시작합니다.
[INFO] [minimal_publisher]: Publishing: "Hello World: 0"
[INFO] [minimal_publisher]: Publishing: "Hello World: 1"
[INFO] [minimal_publisher]: Publishing: "Hello World: 2"
[INFO] [minimal_publisher]: Publishing: "Hello World: 3"
[INFO] [minimal_publisher]: Publishing: "Hello World: 4"
...
다른 터미널에서 다시 ros2_ws 내에서 설정 파일을 소스화하고 서브스크라이버 노드를 시작합니다.
ros2 run py_pubsub listener
서브스크라이버는 퍼블리셔가 해당 시점에서의 메시지 수신을 시작하는 것부터 메시지를 콘솔에 출력하기 시작합니다.
[INFO] [minimal_subscriber]: I heard: "Hello World: 10"
[INFO] [minimal_subscriber]: I heard: "Hello World: 11"
[INFO] [minimal_subscriber]: I heard: "Hello World: 12"
[INFO] [minimal_subscriber]: I heard: "Hello World: 13"
[INFO] [minimal_subscriber]: I heard: "Hello World: 14"
각 터미널에서 Ctrl+C 를 입력하여 노드의 실행을 중지할 수 있습니다.
요약
데이터를 주고받기 위해 두 노드를 생성하고 토픽을 통해 데이터를 게시하고 구독하는 방법을 배웠습니다. 노드를 실행하기 전에 종속성을 추가하고 패키지 구성 파일에 엔트리 포인트를 추가했습니다.
관련 콘텐츠
Python으로 퍼블리셔와 서브스크라이버를 작성하는 다양한 방법이 있습니다. ros2/examples 리포지토리의 minimal_publisher 와 minimal_subscriber 패키지를 확인해보세요.