지난 장에서 우리는 로봇 기능을 여러 노드로 나누는 이유를 살펴보았다. 센서를 읽는 노드, 위치를 추정하는 노드, 경로를 계획하는 노드, 바퀴를 제어하는 노드가 각자 맡은 일에 집중하면 시스템은 훨씬 이해하기 쉬워진다. 그런데 이렇게 나눈 노드들이 서로 아무 말도 하지 않는다면 로봇은 움직일 수 없다. LiDAR 노드가 장애물을 보아도 제어 노드가 모르면 소용없고, 경로 계획 노드가 멋진 경로를 계산해도 바퀴 구동 노드가 듣지 못하면 로봇은 제자리에서 멈춰 있을 뿐이다.
이때 노드와 노드 사이에 데이터를 흘려보내는 길이 바로 토픽이다. ROS 2 공식 문서에서는 토픽을 노드들이 메시지를 교환하기 위한 ROS 그래프의 핵심 요소, 즉 일종의 데이터 버스로 설명한다.1 이 표현은 아주 중요하다. 토픽은 단순한 변수도 아니고, 함수 호출도 아니며, 어느 한 노드의 소유물도 아니다. 여러 노드가 같은 도로 위에 메시지를 올리고, 필요한 노드가 그 메시지를 받아 가는 구조에 가깝다.
이번 장에서는 ROS 2 로봇 시스템의 혈관과도 같은 토픽을 제대로 이해한다. 특히 /scan, /odom, /imu, /cmd_vel 같은 이름이 더 이상 낯선 문자열로 보이지 않게 만드는 것이 목표다. 이 장을 지나면 RViz나 터미널에서 토픽 목록을 보았을 때 "아, 지금 로봇 안에서 데이터가 이렇게 흐르고 있구나" 하고 읽어낼 수 있어야 한다.

토픽은 로봇 안의 데이터 도로다
토픽을 가장 쉽게 떠올리는 방법은 도로다. 도로에는 이름이 있다. /scan이라는 도로에는 LiDAR 스캔 데이터가 흐르고, /odom이라는 도로에는 바퀴 회전으로 추정한 위치 정보가 흐른다. /cmd_vel이라는 도로에는 "앞으로 얼마만큼 가라", "얼마나 회전하라"는 속도 명령이 흐른다.
노드는 이 도로에 데이터를 올릴 수도 있고, 도로 위의 데이터를 받아 갈 수도 있다. 데이터를 올리는 쪽을 Publisher, 우리말로 발행자라고 한다. 데이터를 받아 가는 쪽을 Subscriber, 구독자라고 한다.
예를 들어 키보드 조종 노드가 /cmd_vel 토픽에 속도 명령을 발행한다. 바퀴 제어 노드는 /cmd_vel 토픽을 구독하다가 새 명령이 오면 모터를 움직인다. 이때 키보드 조종 노드는 바퀴 제어 노드의 내부 구조를 알 필요가 없다. 바퀴 제어 노드도 키보드 조종 노드가 어떤 코드로 작성되었는지 알 필요가 없다. 둘은 오직 같은 토픽 이름과 같은 메시지 타입을 기준으로 연결된다.
이 구조 덕분에 ROS 2 시스템은 유연해진다. 처음에는 키보드 조종 노드가 /cmd_vel을 발행하다가, 나중에는 Nav2의 컨트롤러가 같은 /cmd_vel을 발행할 수 있다. 바퀴 제어 노드 입장에서는 누가 명령을 만들었는지보다, 정해진 형식의 속도 명령이 도착했는지가 더 중요하다. 바로 이 점이 ROS 2식 모듈화의 핵심이다.
토픽 통신은 반드시 1:1만 가능한 것이 아니다. 하나의 노드가 여러 토픽에 데이터를 발행할 수 있고, 동시에 여러 토픽을 구독할 수도 있다. 또한 하나의 토픽에 여러 발행자와 여러 구독자가 붙을 수도 있다.
1:1 통신은 하나의 센서 노드가 하나의 처리 노드에 데이터를 보내는 경우다.
1:N 통신은 하나의 카메라 노드가 영상 처리 노드, 저장 노드, 시각화 노드에 동시에 이미지를 보내는 경우다.
N:1 통신은 여러 센서 노드가 하나의 융합 노드로 데이터를 보내는 경우다.
N:N 통신은 여러 발행자와 여러 구독자가 같은 종류의 데이터를 공유하는 분산 시스템에서 나타난다.
이것을 가능하게 하는 이유는 토픽이 특정 노드 하나와 직접 묶이지 않기 때문이다. Publisher와 Subscriber는 서로의 이름을 몰라도 된다. 같은 토픽 이름을 사용하고, 같은 메시지 타입을 사용하며, QoS 설정이 호환되면 ROS 2 미들웨어가 연결을 만들어 준다.
Publisher와 Subscriber는 서로를 직접 부르지 않는다
일반적인 프로그래밍에서는 어떤 기능이 필요할 때 함수를 직접 호출한다. 예를 들어 move_robot(1.0, 0.0) 같은 함수를 부르면 로봇이 움직인다고 생각할 수 있다. 하지만 로봇 시스템에서는 이런 방식이 곧 복잡함의 시작이 된다.
센서 노드가 제어 노드의 함수를 직접 호출하고, 제어 노드가 경로 계획 노드의 함수를 직접 호출하고, 경로 계획 노드가 다시 위치 추정 노드의 내부 값을 직접 읽는다면 어떻게 될까? 처음에는 빠르게 만들 수 있을지 몰라도, 조금만 기능이 늘어나면 거미줄처럼 얽힌 코드가 된다. 센서 하나를 바꾸려 해도 여러 노드의 코드를 고쳐야 한다.
토픽은 이 문제를 느슨한 결합으로 해결한다. 느슨한 결합이란 각 모듈이 서로에게 지나치게 의존하지 않는 구조를 말한다. Publisher는 "나는 /scan에 sensor_msgs/msg/LaserScan 형식의 데이터를 발행한다"까지만 책임진다. Subscriber는 "나는 /scan에서 sensor_msgs/msg/LaserScan 형식의 데이터를 받겠다"까지만 선언한다. 둘이 실제로 연결되는 과정은 ROS 2가 처리한다.
이 구조는 실제 이동 로봇에서 매우 강력하다. 예를 들어 LiDAR 제조사를 바꾸어도 새 드라이버 노드가 기존과 같은 /scan 토픽에 같은 LaserScan 메시지를 발행한다면, 뒤쪽의 SLAM 노드나 장애물 회피 노드는 크게 바뀌지 않아도 된다. 키보드 조종을 자율주행으로 바꾸고 싶을 때도 마찬가지다. Nav2가 /cmd_vel을 발행하도록 만들면 바퀴 제어 노드는 여전히 같은 토픽을 구독하면 된다.
토픽 이름과 메시지 타입은 그래서 일종의 계약서다. "이 도로에는 이런 모양의 데이터가 흐른다." 이 계약이 맞으면 노드는 서로 협력할 수 있고, 맞지 않으면 연결되지 않거나 데이터를 제대로 해석하지 못한다.
turtlesim으로 토픽 흐름 보기
토픽을 머리로만 이해하면 금방 흐릿해진다. 직접 눈으로 보는 것이 가장 빠르다. 여기서는 ROS 2 튜토리얼에서 자주 사용하는 turtlesim으로 토픽 흐름을 확인해 보자.
첫 번째 터미널에서 거북이 시뮬레이터를 실행한다.
ros2 run turtlesim turtlesim_node두 번째 터미널에서는 키보드 조종 노드를 실행한다.
ros2 run turtlesim turtle_teleop_key이제 방향키를 누르면 거북이가 움직인다. 겉으로 보면 키보드 입력이 곧바로 거북이를 움직이는 것처럼 보인다. 하지만 ROS 2 안에서는 조금 더 흥미로운 일이 일어난다. /teleop_turtle 노드가 키보드 입력을 읽고, 그 결과를 /turtle1/cmd_vel 토픽에 발행한다. /turtlesim 노드는 /turtle1/cmd_vel을 구독하다가 속도 명령이 들어오면 거북이를 움직인다.
이 연결을 그림으로 보고 싶다면 rqt_graph를 실행한다.
ros2 run rqt_graph rqt_graph그래프 창에서 /teleop_turtle, /turtlesim, /turtle1/cmd_vel 사이의 관계를 확인할 수 있다. 복잡한 로봇 프로젝트에서는 노드와 토픽이 수십 개 이상으로 늘어나기 때문에 rqt_graph는 단순한 그림 도구가 아니라 구조를 읽는 지도 역할을 한다. 특히 노드를 추가했는데 연결이 생기지 않는다면 토픽 이름이 다른지, 메시지 타입이 다른지, 네임스페이스가 예상과 다른지 확인하는 첫 번째 창구가 된다.
ros2 topic list: 지금 어떤 도로가 열려 있는가
현재 ROS 2 시스템에서 활성화된 토픽 목록을 보려면 다음 명령을 사용한다.
ros2 topic listturtlesim과 키보드 조종 노드를 실행한 상태라면 대략 다음과 같은 토픽을 볼 수 있다.
/parameter_events
/rosout
/turtle1/cmd_vel
/turtle1/color_sensor
/turtle1/pose여기서 /rosout은 로그 메시지와 관련된 토픽이고, /parameter_events는 파라미터 변화와 관련된 토픽이다. 지금 우리가 집중할 것은 /turtle1/cmd_vel과 /turtle1/pose다. 전자는 거북이를 움직이는 속도 명령이고, 후자는 거북이의 현재 위치와 자세 정보다.
토픽 이름만으로는 데이터 구조를 알 수 없다. 메시지 타입까지 함께 보려면 -t 옵션을 붙인다.
ros2 topic list -t예상 출력은 다음과 비슷하다.
/turtle1/cmd_vel [geometry_msgs/msg/Twist]
/turtle1/pose [turtlesim/msg/Pose]이 한 줄에는 중요한 정보가 담겨 있다. /turtle1/cmd_vel 토픽은 geometry_msgs/msg/Twist 타입의 메시지를 운반한다. 즉 이 토픽을 구독하려는 노드는 같은 타입으로 구독해야 한다. std_msgs/msg/String으로 구독한다고 해서 속도 명령을 문자열처럼 받을 수 있는 것이 아니다.
실제 로봇에서는 다음과 같은 이름을 자주 만난다.
/scan [sensor_msgs/msg/LaserScan]
/odom [nav_msgs/msg/Odometry]
/imu [sensor_msgs/msg/Imu]
/cmd_vel [geometry_msgs/msg/Twist]
/goal_pose [geometry_msgs/msg/PoseStamped]처음에는 복잡해 보이지만, 읽는 법은 같다. 앞쪽은 토픽 이름이고, 대괄호 안은 그 토픽에 흐르는 메시지 타입이다.
ros2 topic info: 누가 발행하고 누가 구독하는가
특정 토픽에 대해 더 알고 싶다면 ros2 topic info를 사용한다.
ros2 topic info /turtle1/cmd_vel출력은 다음과 같은 형태다.
Type: geometry_msgs/msg/Twist
Publisher count: 1
Subscription count: 1Type은 메시지 타입이다. Publisher count는 이 토픽에 데이터를 발행하는 발행자의 수이고, Subscription count는 이 토픽을 구독하는 구독자의 수다.
여기서 중요한 점은 토픽이 꼭 한 명의 발행자와 한 명의 구독자만 연결하는 통로가 아니라는 것이다. 예를 들어 /cmd_vel을 구독하는 노드는 바퀴 제어 노드 하나일 수도 있지만, 동시에 안전 감시 노드나 로깅 노드가 같은 명령을 관찰할 수도 있다. 반대로 여러 명령 후보가 하나의 속도 명령 토픽으로 모일 수도 있다. 이 경우에는 보통 우선순위 선택기나 mux 노드가 중간에서 어떤 명령을 최종적으로 내보낼지 결정한다.
더 자세한 정보를 보고 싶다면 --verbose 옵션을 붙인다.
ros2 topic info /turtle1/cmd_vel --verbose이 명령은 발행자와 구독자의 노드 이름, 네임스페이스, 엔드포인트 정보, QoS 설정까지 보여준다. 특히 "분명히 토픽 이름과 타입이 맞는데 메시지가 안 온다"는 상황에서는 QoS가 호환되지 않는 경우도 있으므로 이 정보를 확인할 필요가 있다.
ros2 topic echo: 실제 메시지를 들여다보기
토픽 목록과 타입을 확인했다면, 이제 실제로 어떤 값이 오가는지 보고 싶어진다. 이때 사용하는 명령이 echo다.
ros2 topic echo /turtle1/cmd_vel처음에는 아무 출력도 없을 수 있다. 고장 난 것이 아니다. /turtle1/cmd_vel은 키보드 입력이 있을 때 속도 명령이 발행된다. turtle_teleop_key가 실행 중인 터미널로 돌아가 방향키를 눌러 보자. 그러면 echo를 실행한 터미널에 다음과 비슷한 메시지가 나타난다.
linear:
x: 2.0
y: 0.0
z: 0.0
angular:
x: 0.0
y: 0.0
z: 0.0
---이 출력은 "앞으로 2.0만큼 가고, 회전은 하지 말라"는 뜻으로 읽을 수 있다. 정확한 단위와 해석은 시뮬레이터나 로봇의 구현에 따라 달라질 수 있지만, 이동 로봇에서는 보통 linear.x를 전진 속도, angular.z를 제자리 회전 속도로 많이 사용한다.
이번에는 거북이의 현재 위치를 보자.
ros2 topic echo /turtle1/pose거북이가 움직일 때마다 x, y, theta 같은 값이 계속 바뀐다. theta는 거북이가 바라보는 방향이다. 실제 로봇에서 이 역할에 가까운 토픽은 /odom이다. /odom은 로봇이 얼마나 움직였는지를 추정한 결과를 담고, 위치 추정이나 경로 제어의 중요한 입력이 된다.
echo를 실행하면 ROS 2는 내부적으로 임시 구독 노드를 만든다. 그래서 rqt_graph에서 디버그 항목을 보이게 하면 /_ros2cli_... 같은 이름의 노드가 나타날 수 있다. 터미널 명령도 ROS 그래프 안에서는 하나의 노드처럼 동작한다는 점을 기억해 두자.
ros2 topic hz: 데이터가 얼마나 자주 오는가
로봇에서 메시지 내용만큼 중요한 것이 주기다. LiDAR 데이터가 10Hz로 온다고 생각했는데 실제로는 1Hz라면 장애물 회피가 늦어진다. /cmd_vel 명령이 너무 드물게 오면 로봇이 덜컥거리며 움직일 수 있고, 너무 늦게 도착하면 위험할 수 있다.
토픽의 발행 주기를 보려면 hz를 사용한다.
ros2 topic hz /turtle1/pose출력은 다음과 비슷하다.
average rate: 59.354
min: 0.005s max: 0.027s std dev: 0.00284s window: 58average rate는 초당 평균 메시지 수다. 60Hz라면 1초에 약 60번 메시지가 온다는 뜻이다. min, max는 메시지 사이 간격의 최소·최대값이고, std dev는 간격이 얼마나 흔들리는지를 보여준다.
이 명령은 디버깅에서 자주 쓰인다. 예를 들어 LiDAR 노드가 실행 중인데 SLAM이 제대로 동작하지 않는다면 먼저 /scan이 실제로 나오는지 echo로 확인하고, 그다음 hz로 주기가 정상인지 확인한다.
ros2 topic echo /scan
ros2 topic hz /scan만약 메시지가 전혀 나오지 않는다면 드라이버, 장치 연결, 토픽 이름을 의심해야 한다. 메시지는 나오지만 주기가 너무 낮다면 센서 설정, CPU 부하, 네트워크 상태를 확인해야 한다.
ros2 interface show: 메시지 타입의 설계도 보기
토픽은 이름만으로 완성되지 않는다. 토픽에 흐르는 데이터는 반드시 정해진 메시지 구조를 가져야 한다. 이 구조를 확인하는 명령이 ros2 interface show다.
turtlesim의 속도 명령 타입인 Twist를 살펴보자.
ros2 interface show geometry_msgs/msg/Twist출력은 다음과 같다.
Vector3 linear
float64 x
float64 y
float64 z
Vector3 angular
float64 x
float64 y
float64 zTwist 메시지는 크게 linear와 angular로 나뉜다. linear는 직선 운동 속도, angular는 회전 운동 속도다. 각각은 x, y, z 세 축 값을 가진다. 차동 구동 이동 로봇, 즉 양쪽 바퀴 속도 차이로 움직이는 로봇에서는 보통 linear.x와 angular.z를 주로 사용한다. 옆으로 미끄러지듯 움직일 수 없는 로봇이라면 linear.y는 대부분 0이다.
명령줄에서 직접 토픽을 발행할 수도 있다. 다음 명령은 거북이를 앞으로 움직이면서 회전하게 만든다.
ros2 topic pub /turtle1/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 2.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 1.8}}"기본적으로 ros2 topic pub은 반복해서 메시지를 발행한다. 한 번만 보내고 싶다면 --once를 붙인다.
ros2 topic pub --once /turtle1/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 2.0}, angular: {z: 1.8}}"일정 주기로 보내고 싶다면 --rate를 사용한다.
ros2 topic pub --rate 10 /turtle1/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 0.2}, angular: {z: 0.0}}"이 명령은 초당 10번, 즉 10Hz로 전진 명령을 발행한다. 실제 로봇에서 이런 명령을 실행할 때는 반드시 바퀴가 공중에 떠 있거나, 안전한 공간에서 낮은 속도로 테스트해야 한다. /cmd_vel은 장난감 스위치가 아니라 실제 모터로 이어질 수 있는 명령 토픽이다.
표준 메시지는 로봇의 공통 언어다
ROS 2에는 자주 쓰이는 메시지 타입이 이미 많이 준비되어 있다. 개발자는 가능하면 이 표준 메시지를 사용하는 것이 좋다. 그래야 다른 패키지와 쉽게 연결된다. geometry_msgs는 점, 자세, 속도 같은 기하학 정보를 담고, sensor_msgs는 카메라, IMU, LiDAR 같은 센서 데이터를 담으며, nav_msgs는 오도메트리나 경로처럼 내비게이션에 필요한 데이터를 담는다.2
이동 로봇에서 자주 만나는 메시지 몇 가지를 살펴보자.
LaserScan은 2D LiDAR 데이터에 사용된다. 보통 /scan 토픽에 실린다. 이 메시지에는 스캔 각도 범위, 각도 간격, 거리 배열 등이 들어 있다. 쉽게 말해 로봇 주변을 부채꼴로 훑었을 때 각 방향에 장애물이 얼마나 떨어져 있는지를 담는다. SLAM과 장애물 회피에서 핵심 입력으로 사용된다.
ros2 interface show sensor_msgs/msg/LaserScanOdometry는 로봇의 위치와 속도 추정값을 담는다. 보통 /odom 토픽에 실린다. 바퀴 엔코더, IMU, 기타 센서 정보를 이용해 "로봇이 출발 지점으로부터 얼마나 이동했는가"를 표현한다. 이 메시지에는 위치, 자세, 선속도, 각속도, 그리고 추정 불확실성을 나타내는 covariance 정보가 포함된다.
ros2 interface show nav_msgs/msg/OdometryImu는 관성 측정 장치 데이터를 담는다. 보통 /imu 토픽에 실린다. 각속도, 선가속도, 방향 추정값 등이 포함될 수 있다. 로봇이 회전하거나 흔들릴 때 바퀴 오도메트리만으로는 놓치기 쉬운 움직임을 보완하는 데 도움을 준다.
ros2 interface show sensor_msgs/msg/ImuTwist는 속도 명령이나 속도 상태를 표현한다. /cmd_vel에서 가장 자주 보게 된다. Nav2의 컨트롤러가 목표 지점으로 가기 위해 계산한 속도 명령도 결국 이 형태로 바퀴 제어 쪽에 전달되는 경우가 많다.
ros2 interface show geometry_msgs/msg/TwistPoseStamped는 특정 시각과 좌표계에서의 위치와 자세를 표현한다. 목표 지점, 로봇의 추정 위치, 클릭한 위치 등을 표현할 때 자주 사용된다. Stamped라는 말이 붙으면 대개 헤더가 있고, 헤더에는 시간과 좌표계 정보가 들어 있다.
ros2 interface show geometry_msgs/msg/PoseStamped메시지 타입을 볼 때는 필드 이름만 보지 말고 "이 데이터가 어떤 의미로 쓰이는가"까지 함께 봐야 한다. float64 x라는 숫자 하나만으로는 아무 의미가 없다. 그것이 linear.x인지, position.x인지, orientation.x인지에 따라 완전히 다른 뜻이 된다.
커스텀 메시지는 언제 필요할까
표준 메시지가 많다고 해서 모든 상황을 다 담을 수 있는 것은 아니다. 로봇 프로젝트를 하다 보면 나만의 데이터 구조가 필요할 때가 있다. 예를 들어 창고 로봇이 선반의 식별 번호, 적재 무게, 목적 구역, 작업 우선순위를 한 번에 보내야 한다고 해 보자. 이것을 억지로 문자열 하나에 `
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.