ROS Melodic에서 C++ 코드 작성 가이드

대상: ROS1(Melodic)에서 C++로 토픽(pub/sub) 노드 샘플을 직접 만들어보고 싶은 개발자
환경: Ubuntu 18.04, ROS Melodic, C++ (catkin, roscpp 기준)


1. 문제/주제 요약

이 글에서는 다음 내용을 한 번에 정리합니다.

  • ROS Melodic에서 C++ 패키지 생성하는 방법
  • 가장 기본적인 Publisher / Subscriber 샘플 코드 (C++)
  • std_msgs/String 같은 기본 메시지 타입 구조 설명
  • .msg 파일을 이용한 사용자 정의 메시지 타입 생성 + C++에서 사용하는 방법
  • rostopic echo/info 등으로 메시지 구조 확인하는 방법

바로 따라 하면 토픽 통신이 되는 C++ 노드를 직접 만들어서 실행해볼 수 있는 수준까지 정리합니다.


2. 원인/배경 설명

ROS를 처음 시작하면 대부분 다음 순서로 마주칩니다.

  1. roscore 실행
  2. 예제 talker / listener 돌려보기
  3. “이걸 내 패키지 / 내 코드로 어떻게 옮기지?”라는 고민
  4. “토픽 메시지 타입이 뭐고, .msg 파일은 어떻게 만드는 거지?”라는 의문

특히 C++ 기준으로:

  • ros::init, ros::NodeHandle, ros::Publisher, ros::Subscriber 의 역할
  • 메시지 타입(std_msgs::String, geometry_msgs::Twist, 커스텀 메시지 등)의 구조
  • CMakeLists.txt, package.xml에 무엇을 추가해야 메시지가 제대로 빌드되는지

이 부분이 처음엔 헷갈리기 쉽습니다.
그래서 이 글에서는 실제 코드 + 빌드 설정까지 모두 같이 보면서 이해할 수 있도록 정리합니다.


3. 해결 순서 / 샘플 코드 작성 순서

3-1. catkin 패키지 생성

먼저 작업용 catkin workspace가 있다고 가정합니다.

# workspace 생성 (없다면)
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws
catkin_make

# 환경 설정
echo "source ~/catkin_ws/devel/setup.bash" >> ~/.bashrc
source ~/.bashrc

이제 C++ 노드용 패키지를 하나 만듭니다.

cd ~/catkin_ws/src

catkin_create_pkg cpp_topic_tutorial roscpp std_msgs
  • 패키지 이름: cpp_topic_tutorial
  • 의존성: roscpp, std_msgs

생성 후 구조는 대략:

cpp_topic_tutorial/
  CMakeLists.txt
  package.xml
  src/
  include/

3-2. 가장 기본적인 Publisher 샘플 (talker.cpp)

src 폴더에 talker.cpp 파일을 만듭니다.

cd ~/catkin_ws/src/cpp_topic_tutorial/src
gedit talker.cpp   # 또는 좋아하는 에디터

내용:

#include "ros/ros.h"
#include "std_msgs/String.h"

int main(int argc, char **argv)
{
  // 1. ROS 초기화
  ros::init(argc, argv, "talker");   // 노드 이름: talker

  // 2. NodeHandle 생성 (ROS와의 통신 핸들)
  ros::NodeHandle nh;

  // 3. Publisher 생성
  //    - 토픽 이름: "chatter"
  //    - 큐 사이즈: 10
  ros::Publisher chatter_pub = nh.advertise<std_msgs::String>("chatter", 10);

  // 4. 루프 주기 설정 (10Hz)
  ros::Rate loop_rate(10);

  int count = 0;
  while (ros::ok())
  {
    // 5. 메시지 객체 생성
    std_msgs::String msg;

    // 6. 실제 데이터 채우기
    std::stringstream ss;
    ss << "hello world " << count;
    msg.data = ss.str();

    // 7. publish
    ROS_INFO("Publish: %s", msg.data.c_str());
    chatter_pub.publish(msg);

    // 8. 콜백 처리 및 슬립
    ros::spinOnce();
    loop_rate.sleep();
    ++count;
  }

  return 0;
}

핵심 포인트

  • advertise<타입>("토픽이름", 큐사이즈)
  • 메시지 타입: std_msgs::String → 필드: string data 하나
  • msg.data에 문자열을 넣어서 전송

3-3. 기본 Subscriber 샘플 (listener.cpp)

이번에는 src/listener.cpp를 만듭니다.

cd ~/catkin_ws/src/cpp_topic_tutorial/src
gedit listener.cpp

내용:

#include "ros/ros.h"
#include "std_msgs/String.h"

// 콜백 함수: 메시지 수신 시 자동 호출
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
  ROS_INFO("I heard: [%s]", msg->data.c_str());
}

int main(int argc, char **argv)
{
  // 1. ROS 초기화
  ros::init(argc, argv, "listener");  // 노드 이름: listener

  // 2. NodeHandle 생성
  ros::NodeHandle nh;

  // 3. Subscriber 생성
  //    - 토픽 이름: "chatter"
  //    - 큐 사이즈: 10
  //    - 콜백 함수: chatterCallback
  ros::Subscriber sub = nh.subscribe("chatter", 10, chatterCallback);

  // 4. 콜백 대기 루프
  ros::spin();

  return 0;
}

핵심 포인트

  • nh.subscribe("토픽명", 큐사이즈, 콜백함수)
  • 콜백 함수의 인자 타입: const std_msgs::String::ConstPtr&
  • msg->data로 실제 데이터 접근

3-4. CMakeLists.txt 수정 (C++ 노드 빌드 설정)

cpp_topic_tutorial/CMakeLists.txt를 열고, 아래 부분을 추가합니다.

  1. add_executable, target_link_libraries, add_dependencies 설정
## 기존의 find_package(catkin REQUIRED COMPONENTS ...)는 이미 생성되어 있음

## talker 노드
add_executable(talker src/talker.cpp)
target_link_libraries(talker
  ${catkin_LIBRARIES}
)
add_dependencies(talker ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

## listener 노드
add_executable(listener src/listener.cpp)
target_link_libraries(listener
  ${catkin_LIBRARIES}
)
add_dependencies(listener ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
  1. catkin_package() 위/아래 위치만 크게 신경 쓰지 않아도 되지만, 일반적으로 catkin_package() 이후에 위 코드를 둡니다.

3-5. 빌드 & 실행

cd ~/catkin_ws
catkin_make

빌드가 성공했다면, 터미널 두 개를 띄워서 실행합니다.

1번 터미널:

roscore

2번 터미널:

source ~/catkin_ws/devel/setup.bash
rosrun cpp_topic_tutorial talker

3번 터미널:

source ~/catkin_ws/devel/setup.bash
rosrun cpp_topic_tutorial listener

talker 터미널:

[INFO] [..] Publish: hello world 0
[INFO] [..] Publish: hello world 1
...

listener 터미널:

[INFO] [..] I heard: [hello world 0]
[INFO] [..] I heard: [hello world 1]
...

이제 가장 기본적인 C++ 토픽 통신이 잘 되는 것을 확인할 수 있습니다.


3-6. 토픽 메시지 타입 구조 보기 (std_msgs/String 예시)

토픽 메시지 구조는 rosmsg show로 바로 확인할 수 있습니다.

rosmsg show std_msgs/String

출력:

string data

즉, std_msgs::String은 필드가 딱 하나인 메시지 타입입니다.

C++에서:

std_msgs::String msg;
msg.data = "hello";

이렇게 사용하고, Subscriber 쪽 콜백에서는:

void cb(const std_msgs::String::ConstPtr& msg)
{
  ROS_INFO("%s", msg->data.c_str());
}

처럼 msg->data로 접근합니다.


3-7. 커스텀 메시지 타입 만들기 (예: MyMsg.msg)

실제 프로젝트에서는 std_msgs만으로 부족하고, 나만의 메시지 타입을 만드는 경우가 많습니다.

(1) msg 파일 생성

cpp_topic_tutorial/msg 폴더를 만들고 .msg 파일을 생성합니다.

cd ~/catkin_ws/src/cpp_topic_tutorial
mkdir -p msg
gedit msg/MyMsg.msg

예시 내용:

int32 id
string name
float32 value

(2) package.xml 수정

package.xml에 메시지 관련 의존성을 추가합니다.

<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>

(기존 의존성 roscpp, std_msgs는 그대로 두고 추가)

(3) CMakeLists.txt 수정 – 메시지 생성 설정

CMakeLists.txt에서 다음 항목들을 추가/확인합니다.

  1. find_package 부분에 message_generation 추가:
find_package(catkin REQUIRED COMPONENTS
  roscpp
  std_msgs
  message_generation
)
  1. add_message_files 섹션 추가:
add_message_files(
  FILES
  MyMsg.msg
)
  1. generate_messages 섹션 추가:
generate_messages(
  DEPENDENCIES
  std_msgs
)
  1. catkin_packagemessage_runtime 추가:
catkin_package(
  CATKIN_DEPENDS roscpp std_msgs message_runtime
)

이제 catkin_make를 다시 실행하면 MyMsg 타입에 대한 C++ 헤더가 자동으로 생성됩니다.

cd ~/catkin_ws
catkin_make

빌드가 끝난 후, 아래 명령으로 구조를 확인할 수 있습니다.

rosmsg show cpp_topic_tutorial/MyMsg

3-8. 커스텀 메시지 C++ 코드에서 사용하기

이제 MyMsg 타입을 사용하는 Publisher/Subscriber를 만들어보겠습니다.

(1) custom_talker.cpp

cd ~/catkin_ws/src/cpp_topic_tutorial/src
gedit custom_talker.cpp

내용:

#include "ros/ros.h"
#include "cpp_topic_tutorial/MyMsg.h"  // 패키지명/메시지명

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

  ros::Publisher pub = nh.advertise<cpp_topic_tutorial::MyMsg>("my_topic", 10);
  ros::Rate loop_rate(5);

  int count = 0;
  while (ros::ok())
  {
    cpp_topic_tutorial::MyMsg msg;
    msg.id = count;
    msg.name = "sample_name";
    msg.value = 0.1f * count;

    ROS_INFO("Publish MyMsg: id=%d, name=%s, value=%f",
             msg.id, msg.name.c_str(), msg.value);

    pub.publish(msg);

    ros::spinOnce();
    loop_rate.sleep();
    ++count;
  }

  return 0;
}

(2) custom_listener.cpp

cd ~/catkin_ws/src/cpp_topic_tutorial/src
gedit custom_listener.cpp

내용:

#include "ros/ros.h"
#include "cpp_topic_tutorial/MyMsg.h"

void myCallback(const cpp_topic_tutorial::MyMsg::ConstPtr& msg)
{
  ROS_INFO("Received MyMsg: id=%d, name=%s, value=%f",
           msg->id, msg->name.c_str(), msg->value);
}

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

  ros::Subscriber sub = nh.subscribe("my_topic", 10, myCallback);

  ros::spin();
  return 0;
}

(3) CMakeLists.txt에 executable 등록

CMakeLists.txt에 아래 내용을 추가합니다.

add_executable(custom_talker src/custom_talker.cpp)
target_link_libraries(custom_talker
  ${catkin_LIBRARIES}
)
add_dependencies(custom_talker ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

add_executable(custom_listener src/custom_listener.cpp)
target_link_libraries(custom_listener
  ${catkin_LIBRARIES}
)
add_dependencies(custom_listener ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

다시 빌드:

cd ~/catkin_ws
catkin_make

(4) 실행 및 메시지 확인

# 터미널 1
roscore

# 터미널 2
source ~/catkin_ws/devel/setup.bash
rosrun cpp_topic_tutorial custom_talker

# 터미널 3
source ~/catkin_ws/devel/setup.bash
rosrun cpp_topic_tutorial custom_listener

또는 rostopic echo로도 확인 가능:

rostopic echo /my_topic

출력 예:

id: 0
name: "sample_name"
value: 0.0
---
id: 1
name: "sample_name"
value: 0.1
...

3-9. 토픽/메시지 디버깅에 유용한 명령어 정리

  • 현재 활성 토픽 목록
  • rostopic list
  • 특정 토픽의 타입 확인
  • rostopic type /my_topic
  • 메시지 구조 확인
  • rosmsg show cpp_topic_tutorial/MyMsg
  • 토픽의 publish rate 확인
  • rostopic hz /chatter

4. 추가 팁 / 자주 하는 실수

  1. setup.bash를 안 불러온 경우
    • rosrununknown package 에러 발생
    • 새 터미널을 열 때마다 source ~/catkin_ws/devel/setup.bash 필요
  2. CMakeLists / package.xml 수정 후 catkin_make 안한 경우
    • 새로 만든 .msg 타입을 C++에서 include하면 헤더 파일을 못 찾는 컴파일 에러 발생
    • 메시지/서비스 추가 후에는 반드시 다시 catkin_make
  3. 패키지 이름과 메시지 include 경로 불일치
    • 메시지는 패키지명/메시지명.h 형태로 include
    • 예: cpp_topic_tutorial/MyMsg.h → 패키지명이 다르면 include 경로도 달라짐
  4. 토픽 이름 불일치
    • Publisher에서 "chatter", Subscriber에서 "chater"(오타) 같은 경우 서로 통신 안 됨
    • rostopic list로 실제 생성된 토픽 이름을 꼭 확인
  5. Node 이름 중복
    • 같은 이름의 노드를 여러 번 띄우면 이전 노드가 자동으로 종료됨
    • 디버깅 중이라면 노드 이름을 다르게 주거나 roslaunch로 관리

5. 정리

  • ROS Melodic에서 C++로 토픽 통신을 하려면 ros::NodeHandle, ros::Publisher, ros::Subscriber, 메시지 타입 구조를 이해하면 된다.
  • std_msgs/String 같은 기본 메시지 타입은 rosmsg show로 구조를 쉽게 확인할 수 있고, .msg 파일을 정의해 커스텀 메시지 타입도 만들 수 있다.
  • 메시지 타입을 추가할 때는 package.xml, CMakeLists.txtmessage_generation, message_runtime, add_message_files, generate_messages를 빠짐없이 설정해야 한다.
  • rostopic list, rostopic echo, rosmsg show를 함께 사용하면 토픽 메시지를 디버깅하고 구조를 이해하는 데 큰 도움이 된다.

댓글 남기기