본문으로 바로가기

Python 기본 타입 메시지 Publisher / Subscriber 예제

category 강좌/ROS2 2026. 5. 26. 00:04

이번 실습에서는 직접 만든 메시지 타입을 사용해서 Publisher와 Subscriber가 통신하도록 구성합니다.

사용할 메시지 구조는 다음과 같습니다.

int32 id
float32 temperature
bool enabled
string name
 

최종 실행 구조는 다음과 같습니다.

basic_types_pub 노드
        ↓
/basic_types 토픽
        ↓
basic_types_sub 노드
 

1. 워크스페이스 생성

먼저 ROS 2 워크스페이스를 만듭니다.

 
mkdir -p ~/ros2_lab_ws/src
cd ~/ros2_lab_ws/src
 

구조는 다음과 같습니다.

~/ros2_lab_ws/
└── src/
 

 

 

 

 

2. 인터페이스 패키지 생성

사용자 정의 메시지는 일반 Python 패키지가 아니라 별도의 인터페이스 패키지에 두는 것이 정석입니다.

 
cd ~/ros2_lab_ws/src

ros2 pkg create robot_interfaces --build-type ament_cmake
 

생성 후 구조는 대략 다음과 같습니다.

ros2_lab_ws/src/
└── robot_interfaces/
    ├── CMakeLists.txt
    ├── package.xml
    └── include/
 
 
 
 
 
 
 

3. 메시지 파일 생성

robot_interfaces 패키지 안에 msg 폴더를 만듭니다.

 
mkdir -p ~/ros2_lab_ws/src/robot_interfaces/msg
 
 
 

 

메시지 파일을 생성합니다.

 
touch ~/ros2_lab_ws/src/robot_interfaces/msg/BasicTypes.msg
 
 
 
 
 
 

또는 VS Code를 사용하면 됩니다.

 
code ~/ros2_lab_ws/src/robot_interfaces/msg/BasicTypes.msg
 

 

다음 내용을 작성합니다.

int32 id
float32 temperature
bool enabled
string name
 

 

각 필드 의미는 다음과 같습니다.

 

                      필드                                        타입                                  의미
id int32 로봇 번호, 데이터 순번
temperature float32 온도, 거리, 전압 같은 실수 값
enabled bool 활성화 여부
name string 로봇 이름 또는 상태 이름

 

 

 

 

 

4. robot_interfaces 설정

1) package.xml 수정

 

파일을 엽니다.

 
gedit ~/ros2_lab_ws/src/robot_interfaces/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>
 

 

전체 구조에서 중요한 부분은 다음과 같습니다.

 
<package format="3">
  <name>robot_interfaces</name>
  <version>0.0.0</version>
  <description>Custom interfaces for ROS 2 practice</description>
  <maintainer email="user@example.com">user</maintainer>
  <license>Apache-2.0</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

  <build_depend>rosidl_default_generators</build_depend>
  <exec_depend>rosidl_default_runtime</exec_depend>

  <member_of_group>rosidl_interface_packages</member_of_group>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>
 

 

여기서 특히 중요합니다.

 
<member_of_group>rosidl_interface_packages</member_of_group>
 

 

이게 빠지면 인터페이스 패키지 빌드에서 문제가 날 수 있습니다.

 

 

 

 

 

2) CMakeLists.txt 수정

 

파일을 엽니다.

 
gedit ~/ros2_lab_ws/src/robot_interfaces/CMakeLists.txt
 

 

아래처럼 수정합니다.

 
cmake_minimum_required(VERSION 3.8)
project(robot_interfaces)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

find_package(ament_cmake REQUIRED)
find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/BasicTypes.msg"
)

ament_package()
 

 

핵심은 이 부분입니다.

 
rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/BasicTypes.msg"
)
 

 

이 설정이 있어야 ROS 2가 BasicTypes.msg를 실제 메시지 타입으로 생성합니다.

 

 

 

 

 

5. topic_practice 패키지 생성

이제 Publisher와 Subscriber를 작성할 Python 패키지를 만듭니다.

 
cd ~/ros2_lab_ws/src

ros2 pkg create topic_practice --build-type ament_python --dependencies rclpy robot_interfaces
 

생성 후 구조는 다음과 같습니다.

ros2_lab_ws/src/
├── robot_interfaces/
└── topic_practice/
    ├── package.xml
    ├── setup.py
    ├── resource/
    ├── test/
    └── topic_practice/
        └── __init__.py
 

 

 

 

 

 

 

6. Publisher 코드 작성

 

파일을 생성합니다.

 
cd ~/ros2_lab_ws/src/topic_practice/topic_practice
touch basic_types_pub.py
 

 

파일을 엽니다.

 
gedit basic_types_pub.py
 

 

 

아래 코드를 작성합니다.

 
import rclpy
from rclpy.node import Node

from robot_interfaces.msg import BasicTypes


class BasicTypesPublisher(Node):
    def __init__(self):
        super().__init__('basic_types_pub')

        self.publisher_ = self.create_publisher(
            BasicTypes,
            '/basic_types',
            10
        )

        self.timer = self.create_timer(1.0, self.timer_callback)
        self.count = 0

    def timer_callback(self):
        self.count += 1

        msg = BasicTypes()
        msg.id = self.count
        msg.temperature = 25.0 + self.count * 0.5
        msg.enabled = self.count % 2 == 1
        msg.name = f'robot_{self.count}'

        self.publisher_.publish(msg)

        self.get_logger().info(
            f'Publish: id={msg.id}, '
            f'temp={msg.temperature:.2f}, '
            f'enabled={msg.enabled}, '
            f'name={msg.name}'
        )


def main(args=None):
    rclpy.init(args=args)

    node = BasicTypesPublisher()

    rclpy.spin(node)

    node.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()
 
 
 
 

 

 

1) Publisher 코드 핵심

 

메시지 타입 가져오기

 
from robot_interfaces.msg import BasicTypes
 

 

robot_interfaces 패키지에 만든 BasicTypes.msg를 Python에서 가져옵니다.

 

 

 

 

2) Publisher 생성

 
self.publisher_ = self.create_publisher(
    BasicTypes,
    '/basic_types',
    10
)
 

 

의미는 다음과 같습니다.

BasicTypes 메시지를
/basic_types 토픽으로
큐 크기 10으로 발행한다
 
 
 
 

3) 1초마다 메시지 발행

 
self.timer = self.create_timer(1.0, self.timer_callback)
 

 

1초마다 timer_callback() 함수가 실행됩니다.

 

 

 

4) 메시지 값 대입

 
msg.id = self.count
msg.temperature = 25.0 + self.count * 0.5
msg.enabled = self.count % 2 == 1
msg.name = f'robot_{self.count}'
 

 

BasicTypes.msg에 작성한 필드 이름을 그대로 사용합니다.

BasicTypes.msg의 id          → msg.id
BasicTypes.msg의 temperature → msg.temperature
BasicTypes.msg의 enabled     → msg.enabled
BasicTypes.msg의 name        → msg.name
 

 

필드 이름이 다르면 에러가 납니다.

 

예를 들어 BasicTypes.msg에는 temperature가 있는데 코드에서 이렇게 쓰면 안 됩니다.

 
msg.temp = 25.5
 

 

올바른 코드는 다음입니다.

 
msg.temperature = 25.5
 
 

 

 

 

 

7. Subscriber 코드 작성

 

파일을 생성합니다.

 
cd ~/ros2_lab_ws/src/topic_practice/topic_practice
touch basic_types_sub.py
 

 

 

파일을 엽니다.

 
gedit basic_types_sub.py
 

 

아래 코드를 작성합니다.

 
import rclpy
from rclpy.node import Node

from robot_interfaces.msg import BasicTypes


class BasicTypesSubscriber(Node):
    def __init__(self):
        super().__init__('basic_types_sub')

        self.subscription = self.create_subscription(
            BasicTypes,
            '/basic_types',
            self.listener_callback,
            10
        )

    def listener_callback(self, msg):
        self.get_logger().info(
            f'Receive: id={msg.id}, '
            f'temp={msg.temperature:.2f}, '
            f'enabled={msg.enabled}, '
            f'name={msg.name}'
        )


def main(args=None):
    rclpy.init(args=args)

    node = BasicTypesSubscriber()

    rclpy.spin(node)

    node.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()
 

 

 

 

 

 

1) Subscriber 코드 핵심

 

Subscriber 생성

 
self.subscription = self.create_subscription(
    BasicTypes,
    '/basic_types',
    self.listener_callback,
    10
)
 

 

의미는 다음과 같습니다.

/basic_types 토픽에서
BasicTypes 메시지를 받으면
listener_callback 함수를 실행한다
 

 

 

 

2) 수신 데이터 확인

 
def listener_callback(self, msg):
    self.get_logger().info(
        f'Receive: id={msg.id}, '
        f'temp={msg.temperature:.2f}, '
        f'enabled={msg.enabled}, '
        f'name={msg.name}'
    )
 

 

Publisher가 보낸 메시지는 msg 변수로 들어옵니다.

 

 

 

 

8. setup.py 등록

ros2 run으로 실행하려면 setup.py에 실행 파일을 등록해야 합니다.

 

파일을 엽니다.

 
gedit ~/ros2_lab_ws/src/topic_practice/setup.py
 

 

entry_points 부분을 아래처럼 수정합니다.

 
entry_points={
    'console_scripts': [
        'basic_types_pub = topic_practice.basic_types_pub:main',
        'basic_types_sub = topic_practice.basic_types_sub:main',
    ],
},
 
 

 

 

전체 예시는 다음과 비슷합니다.

 
from setuptools import setup

package_name = 'topic_practice'

setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='user',
    maintainer_email='user@example.com',
    description='ROS 2 topic practice package',
    license='Apache-2.0',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'basic_types_pub = topic_practice.basic_types_pub:main',
            'basic_types_sub = topic_practice.basic_types_sub:main',
        ],
    },
)
 

 

 

 

 

 

9. topic_practice package.xml 확인

 

파일을 엽니다.

 
gedit ~/ros2_lab_ws/src/topic_practice/package.xml
 

 

아래 의존성이 들어 있어야 합니다.

 
<depend>rclpy</depend>
<depend>robot_interfaces</depend>
 

 

예시는 다음과 같습니다.

 
<package format="3">
  <name>topic_practice</name>
  <version>0.0.0</version>
  <description>ROS 2 topic practice package</description>
  <maintainer email="user@example.com">user</maintainer>
  <license>Apache-2.0</license>

  <depend>rclpy</depend>
  <depend>robot_interfaces</depend>

  <test_depend>ament_copyright</test_depend>
  <test_depend>ament_flake8</test_depend>
  <test_depend>ament_pep257</test_depend>
  <test_depend>python3-pytest</test_depend>

  <export>
    <build_type>ament_python</build_type>
  </export>
</package>
 
 
 
 
 
 
 
 

10. 빌드하기

 

워크스페이스 루트로 이동합니다.

 
cd ~/ros2_lab_ws
 

 

빌드합니다.

 
colcon build --symlink-install
 

 

빌드 후 환경 설정을 적용합니다.

 
source install/setup.bash
 

 

 

확실하게 하려면 메시지 인터페이스가 생성되었는지 확인합니다.

 
ros2 interface show robot_interfaces/msg/BasicTypes
 

 

 

정상 출력은 다음과 같습니다.

int32 id
float32 temperature
bool enabled
string name
 
 
 
 
 
 
 

11. 실행하기

 

터미널을 3개 열어서 확인하는 것이 좋습니다.

 

 

 

1) 터미널 1: Publisher 실행

 
cd ~/ros2_lab_ws
source install/setup.bash
ros2 run topic_practice basic_types_pub
 

 

예상 출력:

[INFO] [basic_types_pub]: Publish: id=1, temp=25.50, enabled=True, name=robot_1
[INFO] [basic_types_pub]: Publish: id=2, temp=26.00, enabled=False, name=robot_2
[INFO] [basic_types_pub]: Publish: id=3, temp=26.50, enabled=True, name=robot_3
 
 
 
 

2) 터미널 2: Subscriber 실행

 
cd ~/ros2_lab_ws
source install/setup.bash
ros2 run topic_practice basic_types_sub
 

 

예상 출력:

[INFO] [basic_types_sub]: Receive: id=1, temp=25.50, enabled=True, name=robot_1
[INFO] [basic_types_sub]: Receive: id=2, temp=26.00, enabled=False, name=robot_2
[INFO] [basic_types_sub]: Receive: id=3, temp=26.50, enabled=True, name=robot_3
 

 

Publisher에서 보낸 값과 Subscriber에서 받은 값이 같으면 정상입니다.

 

 

 

 

3) 터미널 3: CLI로 확인

 

토픽 목록 확인:

 
cd ~/ros2_lab_ws
source install/setup.bash
ros2 topic list
 

 

출력 목록에 아래 토픽이 있어야 합니다.

/basic_types
 

 

 

토픽 데이터 확인:

 
ros2 topic echo /basic_types
 

 

예상 출력:

 
id: 1
temperature: 25.5
enabled: true
name: robot_1
---
id: 2
temperature: 26.0
enabled: false
name: robot_2
---
 

 

 

토픽 정보 확인:

 
ros2 topic info /basic_types
 

 

예상 출력:

Type: robot_interfaces/msg/BasicTypes
Publisher count: 1
Subscription count: 1
 

 

 

여기서 봐야 할 것은 세 가지입니다.

 

 

 

 

12. 전체 폴더 구조

 

최종 구조는 다음과 비슷해야 합니다.

~/ros2_lab_ws/
└── src/
    ├── robot_interfaces/
    │   ├── CMakeLists.txt
    │   ├── package.xml
    │   └── msg/
    │       └── BasicTypes.msg
    │
    └── topic_practice/
        ├── package.xml
        ├── setup.py
        ├── resource/
        │   └── topic_practice
        └── topic_practice/
            ├── __init__.py
            ├── basic_types_pub.py
            └── basic_types_sub.py
 

 

 

 

 

 

 

13. 실습 과제

과제 1. voltage 필드 추가하기

 

BasicTypes.msg를 아래처럼 수정합니다.

int32 id
float32 temperature
bool enabled
string name
float32 voltage
 

 

Publisher에 추가합니다.

 
msg.voltage = 12.0 + self.count * 0.1
 

 

로그 출력도 수정합니다.

 
self.get_logger().info(
    f'Publish: id={msg.id}, '
    f'temp={msg.temperature:.2f}, '
    f'enabled={msg.enabled}, '
    f'name={msg.name}, '
    f'voltage={msg.voltage:.2f}'
)
 

 

Subscriber도 수정합니다.

 
self.get_logger().info(
    f'Receive: id={msg.id}, '
    f'temp={msg.temperature:.2f}, '
    f'enabled={msg.enabled}, '
    f'name={msg.name}, '
    f'voltage={msg.voltage:.2f}'
)
 

 

수정 후 반드시 다시 빌드합니다.

 
cd ~/ros2_lab_ws
colcon build --symlink-install
source install/setup.bash
 

 

 

 

과제 2. enabled 값에 따라 name 다르게 만들기

 

Publisher의 timer_callback() 안에서 아래 코드를 사용합니다.

 
if msg.enabled:
    msg.name = f'active_robot_{self.count}'
else:
    msg.name = f'inactive_robot_{self.count}'
 

 

예상 결과:

id=1, enabled=True, name=active_robot_1
id=2, enabled=False, name=inactive_robot_2
id=3, enabled=True, name=active_robot_3
id=4, enabled=False, name=inactive_robot_4
 

 

 

 

 

 

14. 최종 핵심 정리

 

1. 사용자 정의 메시지는 robot_interfaces에 만든다.
2. Publisher와 Subscriber는 같은 메시지 타입을 사용해야 한다.
3. Publisher와 Subscriber는 같은 토픽 이름을 사용해야 한다.
4. setup.py에 실행 파일을 등록해야 ros2 run이 된다.
5. 메시지 수정 후에는 반드시 colcon build와 source를 다시 한다.
 

 

최종 실행 명령어만 다시 정리하면 다음과 같습니다.

 
cd ~/ros2_lab_ws
colcon build --symlink-install
source install/setup.bash
 

 

Publisher:

 
ros2 run topic_practice basic_types_pub
 

 

Subscriber:

 
ros2 run topic_practice basic_types_sub
 

 

토픽 확인:

 
ros2 topic echo /basic_types
 

 

메시지 확인:

 
ros2 interface show robot_interfaces/msg/BasicTypes

 

728x90
728x90