간단한 publisher/subscriber 작성 (Python)

목표: Python을 사용하여 퍼블리셔와 서브스크라이버 노드를 만들고 실행합니다.

배경 지식

Nodes 는 ROS 그래프를 통해 통신하는 실행 가능한 프로세스입니다. 이 튜토리얼에서는 노드들이 topic 을 통해 서로에게 문자열 메시지 형식의 정보를 전달할 것입니다. 여기서 사용되는 예제는 간단한 “talker” 및 “listener” 시스템입니다. 한 노드는 데이터를 게시하고, 다른 노드는 해당 주제를 구독하여 해당 데이터를 수신합니다.

이 예제에 사용된 코드는 여기에서 찾을 수 있습니다.

전제 조건

이전 튜토리얼에서는 작업 공간을 만드는 방법패키지를 만드는 방법 을 배웠습니다.

Python에 대한 기본적인 이해는 권장되지만 완전히 필수는 아닙니다.

작업

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>

위의 줄 다음에, 패키지의 코드가 실행될 때 패키지가 rclpystd_msgs 가 필요하다는 것을 나타내는 다음 종속성을 추가합니다.

<exec_depend>rclpy</exec_depend>
<exec_depend>std_msgs</exec_depend>

이렇게 하면 코드가 실행될 때 패키지가 rclpystd_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 시스템의 일부로 rclpystd_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 를 입력하여 노드의 실행을 중지할 수 있습니다.

요약

데이터를 주고받기 위해 두 노드를 생성하고 토픽을 통해 데이터를 게시하고 구독하는 방법을 배웠습니다. 노드를 실행하기 전에 종속성을 추가하고 패키지 구성 파일에 엔트리 포인트를 추가했습니다.

다음 단계

다음으로, 서비스/클라이언트 모델을 사용하여 더 간단한 ROS 2 패키지를 만들 예정입니다. 다시 한번 선택하여 C++ 또는 Python 로 작성할 수 있습니다.

관련 콘텐츠

Python으로 퍼블리셔와 서브스크라이버를 작성하는 다양한 방법이 있습니다. ros2/examples 리포지토리의 minimal_publisherminimal_subscriber 패키지를 확인해보세요.