대상: ROS1(특히 Melodic/Noetic) 경험이 있거나, ROS2 Humble에서 C++로 토픽 Publisher/Subscriber를 처음 만드는 개발자
환경: Ubuntu 22.04, ROS2 Humble, colcon, C++17, ament_cmake
1. 문제/주제 요약
이 글에서는 ROS2 Humble에서 C++ 노드로 토픽 통신을 하는 가장 기본적인 예제를 다룹니다.
- 패키지 생성 (ament_cmake 기반)
- C++ Publisher 노드 작성
- C++ Subscriber 노드 작성
- colcon build 후 실행
- ROS1과 다른 점(예: rclcpp, QoS, ament_cmake 설정 등)까지 간단히 설명
2. 배경 / 개념 설명
2-1. ROS1 vs ROS2 토픽 구조 핵심 차이
ROS1 (roscpp)와 비교해서 ROS2(rclcpp)의 큰 차이는:
- 초기화 함수/스핀 방식이 다름
- ROS1: ros::init, ros::NodeHandle, ros::spin()
- ROS2: rclcpp::init, rclcpp::Node 상속, rclcpp::spin(node)
- QoS (Quality of Service) 개념 도입
- ROS1은 기본적인 TCP/UDP 통신 방식
- ROS2는 DDS 기반으로, Reliability, History, Depth 등 QoS를 선택 가능
- 빌드 시스템
- ROS1: catkin_make / catkin_tools
- ROS2: colcon build + ament_cmake
하지만 기본적인 토픽 통신 개념은 동일합니다.
- Publisher: 특정 토픽 이름에 메시지를 발행
- Subscriber: 같은 토픽 이름/타입으로 메시지를 수신
3. C++ 토픽 통신 예제 – 단계별
예제 구조:
- 패키지 이름: cpp_topic_example
- 메시지 타입: std_msgs/msg/String
- 노드:
- minimal_publisher
- minimal_subscriber
3-1. 패키지 생성
워크스페이스 구조 (예시):
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
패키지 생성:
ros2 pkg create \
--build-type ament_cmake \
cpp_topic_example \
--dependencies rclcpp std_msgs
- –build-type ament_cmake : CMake 기반 C++ 패키지
- –dependencies rclcpp std_msgs : C++ 노드 + 표준 메시지 사용
생성 후 구조:
cpp_topic_example/
├─ CMakeLists.txt
├─ package.xml
└─ src/
3-2. C++ Publisher 노드 작성
src/minimal_publisher.cpp 생성:
cd ~/ros2_ws/src/cpp_topic_example/src
gedit minimal_publisher.cpp
#include <chrono>
#include <memory>
#include <string>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using namespace std::chrono_literals;
class MinimalPublisher : public rclcpp::Node
{
public:
MinimalPublisher()
: Node("minimal_publisher"), count_(0)
{
// Publisher 생성: 토픽 이름 "topic", 큐 사이즈 10
publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
// 500ms 주기로 타이머 콜백 실행
timer_ = this->create_wall_timer(
500ms, std::bind(&MinimalPublisher::timer_callback, this));
}
private:
void timer_callback()
{
auto message = std_msgs::msg::String();
message.data = "Hello, ROS2! " + std::to_string(count_++);
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
publisher_->publish(message);
}
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
rclcpp::TimerBase::SharedPtr timer_;
size_t count_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
auto node = std::make_shared<MinimalPublisher>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
핵심 포인트
- class MinimalPublisher : public rclcpp::Node
- ROS2에서는 보통 Node 클래스를 상속해 노드를 구현
- create_publisher(“topic”, 10)
- T : 메시지 타입
- 10 : history depth (큐 사이즈 느낌)
- create_wall_timer(500ms, …)
- 타이머 콜백으로 주기적인 작업 수행 (여기서는 메시지 publish)
3-3. C++ Subscriber 노드 작성
src/minimal_subscriber.cpp 생성:
cd ~/ros2_ws/src/cpp_topic_example/src
gedit minimal_subscriber.cpp
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
class MinimalSubscriber : public rclcpp::Node
{
public:
MinimalSubscriber()
: Node("minimal_subscriber")
{
subscription_ = this->create_subscription<std_msgs::msg::String>(
"topic", // 토픽 이름
10, // QoS depth
std::bind(&MinimalSubscriber::topic_callback, this, std::placeholders::_1));
}
private:
void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
{
RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
auto node = std::make_shared<MinimalSubscriber>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
핵심 포인트
- create_subscription(“topic”, 10, callback)
- 토픽 이름 “topic”이 Publisher와 동일해야 통신 가능
- 세 번째 인자는 콜백 함수 (C++11 std::bind 사용)
- 콜백 시그니처:
- void callback(const msg_type::SharedPtr msg)
- ROS2에서는 보통 SharedPtr로 메시지를 받음
3-4. CMakeLists.txt 수정 (빌드 설정)
cpp_topic_example/CMakeLists.txt 열어서 아래 부분을 추가/수정합니다.
3-4-1. find_package 확인
패키지 생성 시 이미 있을 텐데, 없으면 추가:
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
3-4-2. 실행 파일/타겟 등록
CMakeLists.txt 하단에 다음 내용 추가:
add_executable(minimal_publisher src/minimal_publisher.cpp)
ament_target_dependencies(minimal_publisher rclcpp std_msgs)
add_executable(minimal_subscriber src/minimal_subscriber.cpp)
ament_target_dependencies(minimal_subscriber rclcpp std_msgs)
install(TARGETS
minimal_publisher
minimal_subscriber
DESTINATION lib/${PROJECT_NAME})
이제 colcon이 두 개의 실행 파일을 빌드하고, 설치 경로에 복사하게 됩니다.
3-5. package.xml 의존성 확인
cpp_topic_example/package.xml에서 rclcpp, std_msgs가 로 들어있는지 확인:
<depend>rclcpp</depend>
<depend>std_msgs</depend>
패키지 생성 시 –dependencies 옵션을 줬다면 이미 있을 가능성이 높습니다.
3-6. 빌드 및 실행
워크스페이스 루트에서 빌드:
cd ~/ros2_ws
colcon build
빌드 완료 후 새 터미널 또는 현재 터미널에서 환경 설정:
source install/setup.bash
3-6-1. Publisher 실행
터미널 1:
source ~/ros2_ws/install/setup.bash
ros2 run cpp_topic_example minimal_publisher
출력 예:
[INFO] [minimal_publisher]: Publishing: 'Hello, ROS2! 0'
[INFO] [minimal_publisher]: Publishing: 'Hello, ROS2! 1'
...
3-6-2. Subscriber 실행
터미널 2:
source ~/ros2_ws/install/setup.bash
ros2 run cpp_topic_example minimal_subscriber
출력 예:
[INFO] [minimal_subscriber]: I heard: 'Hello, ROS2! 0'
[INFO] [minimal_subscriber]: I heard: 'Hello, ROS2! 1'
...
3-6-3. 토픽 확인
다른 터미널에서:
source ~/ros2_ws/install/setup.bash
# 현재 토픽 목록
ros2 topic list
# 특정 토픽 정보
ros2 topic info /topic
# 토픽 메시지 내용 확인
ros2 topic echo /topic
4. 토픽 통신 코드 상세 설명 / 자주 하는 실수
4-1. QoS (Quality of Service)
Publisher/Subscriber 생성 시 두 번째 인자로 10을 넣은 부분:
create_publisher<std_msgs::msg::String>("topic", 10);
create_subscription<std_msgs::msg::String>("topic", 10, callback);
여기서 10은 내부적으로 **rclcpp::QoS(10)**로 해석됩니다.
원래는 다음처럼 QoS 객체를 쓰는 게 더 명시적입니다.
auto qos = rclcpp::QoS(rclcpp::KeepLast(10));
publisher_ = this->create_publisher<std_msgs::msg::String>("topic", qos);
subscription_ = this->create_subscription<std_msgs::msg::String>(
"topic", qos, std::bind(...));
QoS 설정 예:
- reliability : Reliable vs Best Effort
- durability : Volatile vs Transient Local
센서 토픽, 라이프사이클 노드 등에서 나중에 QoS 설정을 바꿔야 할 수 있으니, 기본 예제에서도 QoS 객체를 쓰는 패턴에 익숙해지는 게 좋습니다.
4-2. 네임스페이스 / 토픽 이름
ROS2에서는 노드 이름, 네임스페이스에 따라 실제 토픽 이름이 바뀔 수 있습니다.
- 노드가 “topic”으로 publish 하더라도,
- 네임스페이스 /robot1 아래에서 실행하면 실제 토픽은 /robot1/topic이 됩니다.
ros2 topic list로 실제 이름을 항상 확인하고, ros2 topic echo 또는 Subscriber에서도 동일한 전체 이름을 사용해야 합니다.
4-3. source install/setup.bash 누락
ROS2에서는 빌드 후 항상 install/setup.bash를 source 해야 합니다.
- source ~/ros2_ws/install/setup.bash
- 새 터미널을 열 때마다 필요
ros2 run cpp_topic_example minimal_publisher 해도 “패키지를 찾을 수 없다”고 나올 때는 대부분 이 문제입니다.
4-4. colcon build 후 코드 변경
코드를 변경한 뒤에는 반드시 다시 빌드해야 합니다.
cd ~/ros2_ws
colcon build --packages-select cpp_topic_example
source install/setup.bash
ROS1과 마찬가지로, 빌드하지 않으면 실행 파일이 이전 버전 그대로입니다.
4-5. CMake 설정 누락
자주 하는 실수:
- add_executable는 했는데, ament_target_dependencies를 안 적은 경우
- install(TARGETS …)를 안 적어서 ros2 run이 실행 파일을 못 찾는 경우
CMakeLists.txt에 세 줄 세트를 항상 넣는 습관:
add_executable(node_name src/node_name.cpp)
ament_target_dependencies(node_name rclcpp std_msgs)
install(TARGETS node_name DESTINATION lib/${PROJECT_NAME})
5. 정리
- ROS2 Humble에서 C++ 토픽 통신은 rclcpp::Node를 상속한 클래스를 만들고, 그 안에서 create_publisher / create_subscription으로 구현합니다.
- QoS, colcon build, ament_cmake 설정 등 ROS1과 다른 요소들이 있지만, 기본 예제를 한 번 따라 해 보면 전체 흐름이 자연스럽게 익힙니다.
- 이후에는 이 패턴을 그대로 확장해서, 사용자 정의 메시지 타입, 다양한 QoS 설정, compose node, lifecycle node 등으로 발전시킬 수 있습니다.