[ROS2 Mastery 4편] ROS2 Topic 프로그래밍 (1) – Publisher로 거북이 제어하기
안녕하세요! 자율주행과 로봇 공학의 세계를 탐구하시는 대학생 및 연구원 여러분.
지난 3편에서는 비어있는 ROS2 패키지를 생성하고, 파이썬(Python)으로 시간에 따라 로그를 출력하는 아주 기본적인 노드(Node)를 만들어 보았습니다. 이번 4편부터는 본격적으로 로봇 제어의 핵심인 ‘통신(Communication)’을 다루게 됩니다. 그 첫 번째 시간으로 토픽(Topic) 프로그래밍을 통해 Turtlesim의 거북이가 원을 그리며 스스로 움직이도록 제어하는 Publisher(발행자) 노드를 직접 구현해 보겠습니다.

제어 대상 분석: 토픽(Topic)과 메시지(Message) 파악하기
로봇을 제어하는 코드를 작성하기 전에 가장 먼저 해야 할 일은 ‘내가 어떤 토픽에 어떤 형태의 데이터를 보내야 로봇이 움직이는가?’를 파악하는 것입니다. ROS2의 강력한 CLI(Command Line Interface) 도구들을 활용하여 이를 먼저 조사해 보겠습니다.
터미널을 열고 거북이 시뮬레이터를 실행해 주세요.
ros2 run turtlesim turtlesim_node
이제 새로운 터미널을 열고 거북이를 움직이기 위한 토픽의 정보를 확인합니다. 거북이를 움직이는 토픽의 이름은 /turtle1/cmd_vel입니다. ros2 topic info 명령어를 사용하여 이 토픽의 데이터 타입을 조회해 봅니다.
ros2 topic info /turtle1/cmd_vel
출력 결과를 보면 /turtle1/cmd_vel 토픽은 geometry_msgs/msg/Twist라는 데이터 타입을 사용한다는 것을 알 수 있습니다. 그렇다면 이 Twist라는 메시지 안에는 구체적으로 어떤 변수들이 들어있을까요? 이를 확인하기 위해 ros2 interface show 명령어를 사용합니다.
ros2 interface show geometry_msgs/msg/Twist
명령어를 실행하면 터미널에 다음과 같은 구조가 출력됩니다.
Vector3 linear
float64 x
float64 y
float64 z
Vector3 angular
float64 x
float64 y
float64 z
분석해 보면 Twist 메시지는 3차원 벡터인 linear(직선 이동 속도)와 angular(회전 이동 속도)라는 두 가지 내부 구조체 데이터를 가지고 있습니다.
- linear.x (Forward): 로봇이 앞으로 전진하는 속도입니다.
- angular.z (Yaw): 로봇이 제자리에서 회전하는 속도(각속도)입니다.
따라서 우리가 작성할 파이썬 노드는 geometry_msgs/msg/Twist 타입의 객체를 생성한 뒤, linear.x와 angular.z에 적절한 값을 넣어 /turtle1/cmd_vel 토픽으로 주기적으로 발행(Publish)해주면 됩니다!
Publisher 노드 코드 작성: draw_circle.py
이제 본격적으로 코드를 작성해 봅시다. 지난 3편에서 만들었던 my_robot_controller 패키지의 소스 코드 디렉토리로 이동하여 새로운 파이썬 파일 draw_circle.py를 생성하고, 시스템이 이 스크립트를 실행할 수 있도록 실행 권한(chmod +x)을 부여합니다.
# 노드 디렉토리로 이동
cd ~/ros2_ws/src/my_robot_controller/my_robot_controller
# 파일 생성 및 실행 권한 부여
touch draw_circle.py
chmod +x draw_circle.py
VS Code 등의 에디터로 draw_circle.py를 열고 아래의 코드를 작성합니다. 코드를 타이핑하면서 각 부분이 어떤 역할을 하는지 고민해 보세요.
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist # 분석했던 Twist 메시지 타입을 import 합니다.
class DrawCircleNode(Node):
def __init__(self):
super().__init__('draw_circle') # 노드의 이름을 'draw_circle'로 지정합니다.
# 1. Publisher 생성
# 타입: Twist, 토픽명: '/turtle1/cmd_vel', 큐 사이즈: 10
self.cmd_vel_pub_ = self.create_publisher(Twist, '/turtle1/cmd_vel', 10)
# 2. 타이머 생성
# 0.5초마다 send_velocity_command 콜백 함수를 실행합니다.
self.timer_ = self.create_timer(0.5, self.send_velocity_command)
self.get_logger().info("거북이 원 그리기 노드가 시작되었습니다!")
def send_velocity_command(self):
# 3. 메시지 객체 생성 및 데이터 할당
msg = Twist()
msg.linear.x = 2.0 # 초당 2.0의 속도로 전진
msg.angular.z = 1.0 # 초당 1.0 라디안의 속도로 회전
# 4. 토픽 발행(Publish)
self.cmd_vel_pub_.publish(msg)
def main(args=None):
rclpy.init(args=args)
node = DrawCircleNode()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
[코드 심층 분석]
- 모듈 Import: ROS2의 파이썬 핵심 패키지인
rclpy와 더불어, 앞서 우리가 CLI로 확인했던 데이터 타입인geometry_msgs.msg에서Twist패키지를 import 합니다. - Publisher 생성:
self.create_publisher메쏘드를 사용하여 발행자를 만듭니다. 첫 번째 인자는 데이터 타입(Twist), 두 번째 인자는 발행할 토픽의 이름(/turtle1/cmd_vel), 세 번째 인자(10)는 통신 지연 시 메시지를 보관할 큐(Queue)의 사이즈(버퍼 크기)를 의미합니다. - 타이머(Timer) 설정: ROS2 프로그래밍은 무한 루프(
while True:)를 지양하고 타이머 콜백을 권장합니다.create_timer(0.5, ...)를 통해 0.5초마다 등록된 콜백 함수가 실행되도록 설정합니다. - 콜백 함수 및 데이터 발행: 0.5초마다 호출되는
send_velocity_command함수 내부에서Twist메시지 인스턴스를 생성합니다.linear.x에 2.0,angular.z에 1.0을 입력하여 앞으로 전진하면서 회전하도록 데이터를 채워준 뒤,publish()메쏘드로 토픽을 ROS2 네트워크에 쏘아 올립니다.
설정 파일 수정: package.xml 및 setup.py
파이썬 코드 작성을 마쳤다고 해서 끝이 아닙니다! ROS2의 빌드 시스템(Colcon)이 이 패키지를 올바르게 빌드하고 실행할 수 있도록 두 가지 설정 파일을 수정해야 합니다.
1) package.xml 의존성(Dependency) 추가
우리의 파이썬 코드 안에서 geometry_msgs라는 외부 메시지 패키지를 새롭게 import 하여 사용했습니다. 시스템이 빌드될 때 해당 패키지가 필요하다는 것을 명시해 주어야 에러가 발생하지 않습니다. 패키지의 루트 디렉토리(~/ros2_ws/src/my_robot_controller)에 있는 package.xml 파일을 열고 아래 코드를 추가합니다.
또한 거북이 시뮬레이터를 제어할 것이므로 기존의 turtlesim 패키지에 대한 의존성도 함께 추가해 주는 것이 좋습니다.
<!-- 기타 기존 의존성 태그들 아래에 추가 -->
<depend>geometry_msgs</depend>
<depend>turtlesim</depend>
2) setup.py 진입점(Entry Point) 추가
터미널에서 ros2 run my_robot_controller draw_circle 명령어를 입력했을 때, 시스템이 방금 작성한 파이썬 파일의 main 함수를 찾아 실행하도록 매핑해주어야 합니다. 패키지 루트 디렉토리의 setup.py 파일을 열고 console_scripts 배열에 다음 줄을 추가합니다.
entry_points={
'console_scripts': [
'test_node = my_robot_controller.my_first_node:main',
# 방금 만든 노드의 진입점을 추가합니다.
'draw_circle = my_robot_controller.draw_circle:main'
],
},
draw_circle: 터미널에서 실행할 노드의 이름입니다.my_robot_controller.draw_circle:main:my_robot_controller디렉토리 안의draw_circle.py스크립트 내부에 있는main함수를 가리킵니다.
빌드 및 실행 테스트
모든 코딩과 환경 설정이 완벽하게 마무리되었습니다! 이제 작업 공간의 최상위 루트 디렉토리로 이동하여 작성한 패키지를 새롭게 빌드해 봅시다.
# 작업 공간 루트로 이동
cd ~/ros2_ws
# 전체 패키지 빌드
colcon build --symlink-install
에러 없이 정상적으로 빌드가 완료되었다면, 노드를 실행할 준비가 된 것입니다.
[실행 단계]
- 첫 번째 터미널: 거북이 시뮬레이터를 실행합니다.
- 두 번째 터미널: 새 터미널을 열고 우리가 방금 만든 오버레이 환경을 소싱한 후
draw_circle노드를 실행합니다.
성공입니다! 키보드로 조종하지 않아도 우리가 만든 Publisher 노드가 0.5초마다 이동 속도 명령(Topic)을 보내기 때문에, 거북이가 아름다운 원을 그리며 무한히 회전하는 것을 볼 수 있습니다.
💡연구원 팁: RQT Graph로 통신 구조 확인하기
로봇 시스템이 거대해지면 데이터가 어디서 어디로 흐르는지 시각적으로 파악하는 것이 디버깅의 핵심입니다. 거북이가 돌고 있는 상태에서 완전히 새로운 터미널을 열고 rqt_graph 명령어를 실행해 보세요.
rqt_graph
화면을 보면 /draw_circle 노드(우리가 만든 발행자)에서 /turtlesim 노드(구독자)를 향해 /turtle1/cmd_vel 이라는 이름의 토픽 데이터가 실시간으로 흘러가고 있는 명확한 통신 구조를 시각적으로 확인할 수 있습니다.
마무리하며
이번 4편에서는 ROS2 프로그래밍의 꽃이라고 할 수 있는 ‘토픽 발행(Topic Publish)’의 전체 과정을 직접 코딩하며 익혀보았습니다.
- 명령어(
ros2 topic info,ros2 interface show)를 통해 제어하고자 하는 데이터 형식을 분석하고, rclpy.node.Node를 상속받아 Publisher 객체와 주기적인 Timer를 생성한 뒤,- 올바른 데이터(
Twist)를 할당하여 지속적으로 송신하는 구조를 만들었습니다.
여러분은 이제 어떤 로봇 플랫폼이 주어지더라도, 로봇이 구독(Subscribe)하고 있는 토픽의 이름과 데이터 구조만 알아낸다면 여러분만의 알고리즘으로 자율주행 명령을 내릴 수 있는 기본적인 능력을 갖추게 되었습니다!
다음 [ROS2 Mastery 5편]에서는 이번과 반대되는 개념으로, 거북이의 현재 위치(x, y 좌표) 센서 데이터를 실시간으로 수신받아 터미널에 출력하는 “Subscriber 노드 만들기”에 대해 알아보도록 하겠습니다.
여러분의 성공적인 로봇 공학 연구를 응원하며, 다음 연재에서 뵙겠습니다. 감사합니다!

작성자: 에이든(Aiden), 쿼드(QUAD) 드론연구소 마케팅팀
기고일: 2026.05.10
