포스트

ROS 토픽 통신이 안 보일 때: Publisher, Subscriber 확인 순서

ROS 토픽 예제가 동작하는지 확인하는 가장 짧은 길은 마스터를 띄우고 Publisher를 실행한 뒤, 토픽 목록, 정보, 메시지를 확인하고 마지막에 Subscriber를 붙이는 것입니다. 토픽이 등록됐다는 사실과 실제 메시지가 흐른다는 사실, Subscriber callback이 실행된다는 사실은 각각 다릅니다. list → info → echo 순서로 관측하면 빌드, 발행, 구독 문제를 한꺼번에 고치지 않아도 됩니다.

노드, 토픽, 서비스를 먼저 구분하기

이 기록에서 ROS는 로봇 소프트웨어를 여러 실행 단위로 나누고 메시지로 연결하는 프레임워크입니다.

  • 노드(node): 실행 가능한 최소 프로세스
  • 패키지(package): 여러 노드를 묶은 단위
  • 메시지(message): 노드 사이에서 주고받는 데이터 형식
  • 토픽(topic): Publisher에서 Subscriber로 보내는 단방향 통신
  • 서비스(service): 요청과 응답이 오가는 양방향 통신
  • 파라미터(parameter): 노드가 읽고 쓰는 설정값

핵심 흐름은 다음과 같습니다.

1
2
3
4
5
ROS Master
   │
   ├─ 노드 1: Publisher ── topic ──> 노드 2: Subscriber
   │
   └─ 두 노드의 접속 정보를 중개

토픽이 보이지 않을 때 코드를 먼저 크게 고치기보다 마스터, 노드 이름, 토픽 이름, 메시지 형식이 같은지 순서대로 확인해야 합니다.

사용자 메시지와 두 노드의 핵심 조각

원문 환경은 Raspberry Pi 3 B+의 Ubuntu Mate와 ROS Kinetic 설치 스크립트를 전제로 합니다.

1
2
3
4
5
6
sudo apt-get update
sudo apt-get upgrade

wget https://raw.githubusercontent.com/ROBOTIS-GIT/robotis_tools/master/install_ros_kinetic.sh
chmod 755 ./install_ros_kinetic.sh
bash ./install_ros_kinetic.sh

이 설치 명령은 특정 배포판과 스크립트에 묶인 과거 기록입니다. 현재 ROS 전체 설치법으로 일반화하지 않고, 아래 패키지 구조를 이해하기 위한 환경 메모로 보는 편이 안전합니다.

패키지는 메시지 생성, 표준 메시지, C++ 클라이언트 의존성을 지정해 만들었습니다.

1
2
cd ~/catkin_ws/src
catkin_create_pkg ros_tutorials_topic message_generation std_msgs roscpp

사용자 메시지 파일 MsgTutorial.msg의 내용은 두 필드입니다.

1
2
time stamp
int32 data

Publisher의 핵심은 같은 토픽 이름으로 메시지를 광고하고 10Hz로 값을 발행하는 부분입니다. 아래는 전체 빌드 파일을 포함하지 않은 노드 핵심 조각입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include "ros/ros.h"
#include "ros_tutorials_topic/MsgTutorial.h"

int main(int argc, char **argv)
{
    ros::init(argc, argv, "topic_publisher");
    ros::NodeHandle nh;

    ros::Publisher publisher =
        nh.advertise<ros_tutorials_topic::MsgTutorial>(
            "ros_tutorial_msg", 100
        );

    ros::Rate loop_rate(10);
    ros_tutorials_topic::MsgTutorial msg;
    int count = 0;

    while (ros::ok())
    {
        msg.stamp = ros::Time::now();
        msg.data = count;

        ROS_INFO("send stamp.sec = %d", msg.stamp.sec);
        ROS_INFO("send stamp.nsec = %d", msg.stamp.nsec);
        ROS_INFO("send data = %d", msg.data);

        publisher.publish(msg);
        loop_rate.sleep();
        ++count;
    }
    return 0;
}

Subscriber는 ros_tutorial_msg를 구독하고 메시지가 들어올 때 콜백을 실행합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "ros/ros.h"
#include "ros_tutorials_topic/MsgTutorial.h"

void msgCallback(
    const ros_tutorials_topic::MsgTutorial::ConstPtr& msg
)
{
    ROS_INFO("receive stamp.sec = %d", msg->stamp.sec);
    ROS_INFO("receive stamp.nsec = %d", msg->stamp.nsec);
    ROS_INFO("receive data = %d", msg->data);
}

int main(int argc, char **argv)
{
    ros::init(argc, argv, "topic_subscriber");
    ros::NodeHandle nh;

    ros::Subscriber subscriber =
        nh.subscribe("ros_tutorial_msg", 100, msgCallback);

    ros::spin();
    return 0;
}

두 조각에서 반드시 같아야 하는 값은 메시지 타입 ros_tutorials_topic/MsgTutorial과 토픽 이름 ros_tutorial_msg입니다.

빌드부터 메시지 확인까지

패키지와 CMake 설정을 작성한 뒤 workspace 루트에서 빌드합니다.

1
2
cd ~/catkin_ws
catkin_make

실행은 여러 터미널로 나눕니다. 첫 터미널에서 마스터를 시작합니다.

1
roscore

둘째 터미널에서 Publisher를 실행합니다.

1
rosrun ros_tutorials_topic topic_publisher

Subscriber를 붙이기 전에 ROS가 토픽을 알고 있는지 확인합니다.

1
2
3
rostopic list
rostopic info /ros_tutorial_msg
rostopic echo /ros_tutorial_msg

마지막으로 셋째 터미널에서 Subscriber를 실행합니다.

1
rosrun ros_tutorials_topic topic_subscriber

판단 기준은 분명합니다.

  1. 목록에 토픽이 없다면 Publisher 실행과 토픽 이름을 확인합니다.
  2. 토픽은 있지만 echo가 비어 있다면 Publisher의 발행 루프와 메시지 빌드를 확인합니다.
  3. echo에는 값이 있지만 Subscriber 로그가 없다면 구독 토픽 이름과 메시지 타입을 확인합니다.

이 글이 완전한 실행법은 아닌 이유

기존 글의 package.xml과 CMakeLists.txt에는 의존성, 타깃 설정이 길게 기록돼 있지만, CMake 주석 일부가 코드처럼 끊겨 있어 그대로 복사할 수 있는 완전한 파일은 아닙니다. 여기서는 오류를 숨기지 않고 Publisher와 Subscriber의 연결 원리를 보여 주는 핵심 조각만 남겼습니다.

또한 SSH 설정, root 비밀번호 변경, 카메라, turtlesim, Arduino serial 예제는 토픽 통신 하나를 검증하는 데 필수적이지 않아 절차에서 분리했습니다. 이 글의 범위는 ROS Kinetic 당시 환경에서 사용자 메시지 하나가 두 C++ 노드 사이를 이동하는지 확인하는 것까지입니다.

연결 상태를 어떤 증거로 나눠 기록해야 하나

첫 번째 증거는 package와 executable이 빌드되는지입니다. 사용자 메시지를 바꿨다면 message generation 의존성과 빌드 순서를 확인하고, 새 terminal에서도 같은 workspace를 source합니다. 실행 파일을 찾지 못하는 오류와 토픽이 비어 있는 오류는 이 단계에서 구분됩니다.

두 번째 증거는 Publisher 단독 상태입니다. Subscriber를 켜기 전에 rostopic list로 이름을, rostopic info로 publisher와 타입을, rostopic echo로 실제 값을 확인합니다. Echo에 예상한 필드가 반복해 나오면 메시지 생성과 발행 루프까지는 통과한 것입니다.

세 번째 증거는 Subscriber callback입니다. 토픽 이름이 같아 보여도 namespace와 절대, 상대 이름에 따라 다른 경로가 될 수 있고, 메시지 타입이 다르면 연결되지 않습니다. Callback 첫 줄에 받은 값을 출력해 통신과 이후 로봇 동작을 분리하면 제어 코드가 실패해도 구독 성공 여부를 알 수 있습니다.

함께 읽으면 이해가 이어지는 글

자주 묻는 질문

ROS에서 Publisher와 Subscriber보다 roscore를 먼저 실행해야 하나요?

네. 이 기록의 ROS Kinetic 흐름에서는 노드와 토픽 연결을 관리할 master가 먼저 필요합니다. Master가 없으면 노드 코드가 맞아도 서로를 발견하지 못합니다.

토픽 목록에는 있는데 rosrun echo가 비어 있으면 무엇을 보나요?

Publisher의 발행 루프가 실제로 돌고 있는지, 메시지 값이 채워지는지, 같은 ROS 환경을 source했는지 확인합니다. 토픽 등록과 메시지 발행은 서로 다른 성공 단계입니다.

Subscriber 로그만 없으면 메시지를 다시 빌드해야 하나요?

먼저 rostopic echo에 값이 있는지 확인합니다. 값이 있다면 Subscriber가 구독하는 토픽 이름과 사용자 메시지 타입, callback 실행 여부를 보는 것이 우선입니다.

사용자 메시지를 바꾼 뒤 왜 전체 흐름을 다시 빌드해야 하나

메시지 파일은 C++ header처럼 코드 생성의 입력이 됩니다. 필드를 추가하거나 타입을 바꿨다면 message generation 설정과 이 메시지를 사용하는 두 node가 같은 결과를 보고 있어야 합니다. 한 terminal에 이전 build 산출물이 남아 있으면 Publisher와 Subscriber가 서로 다른 정의를 기대할 수 있습니다.

Package 설정에서는 메시지 생성에 필요한 의존성과 실행 target의 의존을 구분합니다. CMake의 긴 주석을 그대로 복사하기보다 실제로 필요한 find_package, message file 등록, generate 단계, executable과 link 설정을 하나씩 확인합니다. Build 오류의 첫 줄과 마지막 요약을 함께 보고, 앞선 오류가 만든 연쇄 메시지를 모두 별도 문제로 취급하지 않습니다.

빌드 뒤에는 새 terminal에서 workspace를 source하고 메시지 타입을 확인합니다. Publisher를 단독 실행해 topic type과 echo field가 새 정의와 같은지 봅니다. 이 결과가 맞은 뒤 Subscriber를 다시 빌드, 실행해야 callback 코드와 생성 header의 불일치를 찾기 쉽습니다.

여러 ROS 환경이 섞였는지 확인하는 법

각 terminal에서 master 주소, package 검색 경로, 실행 중인 node와 topic을 확인합니다. Raspberry Pi와 다른 컴퓨터가 같은 master를 보려면 네트워크 설정도 일치해야 하지만, 이 글의 범위에서는 우선 한 환경에서 두 node가 연결되는지 검증합니다. 원격 연결은 로컬 성공 뒤 별도 단계로 추가합니다.

같은 topic 이름이 namespace 아래 여러 개 생길 수 있으므로 rostopic info의 publisher, subscriber node 이름을 봅니다. 예상하지 않은 node가 값을 보내면 echo는 정상처럼 보여도 자신의 Publisher가 작동하는지 알 수 없습니다. 테스트 중에는 node 이름과 실행 terminal을 고정하고 불필요한 node를 종료합니다.

메시지 빈도도 확인합니다. Publisher가 한 번만 보내고 Subscriber가 나중에 시작됐다면 일반적인 실행 흐름에서 값을 못 볼 수 있습니다. 반복 발행 예제에서는 loop와 spin, sleep이 실제로 도는지 로그로 확인하고, 종료 직전 한 번의 출력만 보고 지속 통신으로 오해하지 않습니다.

Subscriber 뒤의 로봇 동작을 분리하는 이유

Callback이 호출된 뒤 모터나 카메라 코드가 막히면 통신 자체도 멈춘 것처럼 보일 수 있습니다. 먼저 callback에서 메시지만 출력하고, 실제 장치 동작은 그 다음 단계로 연결합니다. 긴 작업이 callback을 막는다면 메시지 수신과 처리 구조를 별도로 설계해야 합니다.

알 수 없는 메시지나 범위를 벗어난 값에 대한 기본 동작도 정합니다. 사용자 메시지가 전달됐다는 사실만으로 값이 안전하다는 뜻은 아닙니다. Subscriber에서 검증한 값만 장치 제어로 넘기고, 오류 시 어떤 로그와 상태를 남길지 정해야 토픽 예제가 실제 로봇으로 확장될 수 있습니다.

THE END / OPSOAI

여기까지 읽었습니다

핵심 장면을 한 번 더 떠올려 보세요. 이해가 남았다면 이 책은 제 역할을 다했습니다.

다른 책 고르기
표지 1

키와 좌우 스와이프를 지원합니다. 읽던 페이지는 이 기기에 저장됩니다.

CONTENTS

이 책의 목차

    11개 장 17 분읽는 시간