대상: 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를 처음 시작하면 대부분 다음 순서로 마주칩니다.
roscore실행- 예제
talker/listener돌려보기 - “이걸 내 패키지 / 내 코드로 어떻게 옮기지?”라는 고민
- “토픽 메시지 타입이 뭐고,
.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를 열고, 아래 부분을 추가합니다.
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})
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에서 다음 항목들을 추가/확인합니다.
find_package부분에message_generation추가:
find_package(catkin REQUIRED COMPONENTS
roscpp
std_msgs
message_generation
)
add_message_files섹션 추가:
add_message_files(
FILES
MyMsg.msg
)
generate_messages섹션 추가:
generate_messages(
DEPENDENCIES
std_msgs
)
catkin_package에message_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. 추가 팁 / 자주 하는 실수
setup.bash를 안 불러온 경우rosrun시unknown package에러 발생- 새 터미널을 열 때마다
source ~/catkin_ws/devel/setup.bash필요
- CMakeLists / package.xml 수정 후
catkin_make안한 경우- 새로 만든
.msg타입을 C++에서 include하면 헤더 파일을 못 찾는 컴파일 에러 발생 - 메시지/서비스 추가 후에는 반드시 다시
catkin_make
- 새로 만든
- 패키지 이름과 메시지 include 경로 불일치
- 메시지는
패키지명/메시지명.h형태로 include - 예:
cpp_topic_tutorial/MyMsg.h→ 패키지명이 다르면 include 경로도 달라짐
- 메시지는
- 토픽 이름 불일치
- Publisher에서
"chatter", Subscriber에서"chater"(오타) 같은 경우 서로 통신 안 됨 rostopic list로 실제 생성된 토픽 이름을 꼭 확인
- Publisher에서
- Node 이름 중복
- 같은 이름의 노드를 여러 번 띄우면 이전 노드가 자동으로 종료됨
- 디버깅 중이라면 노드 이름을 다르게 주거나
roslaunch로 관리
5. 정리
- ROS Melodic에서 C++로 토픽 통신을 하려면
ros::NodeHandle,ros::Publisher,ros::Subscriber, 메시지 타입 구조를 이해하면 된다. std_msgs/String같은 기본 메시지 타입은rosmsg show로 구조를 쉽게 확인할 수 있고,.msg파일을 정의해 커스텀 메시지 타입도 만들 수 있다.- 메시지 타입을 추가할 때는
package.xml,CMakeLists.txt에message_generation,message_runtime,add_message_files,generate_messages를 빠짐없이 설정해야 한다. rostopic list,rostopic echo,rosmsg show를 함께 사용하면 토픽 메시지를 디버깅하고 구조를 이해하는 데 큰 도움이 된다.