ROS2 Humble에서 C++ Topic 예제 코드 정리

대상: 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 설정 누락

자주 하는 실수:

  1. add_executable는 했는데, ament_target_dependencies를 안 적은 경우
  2. 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 등으로 발전시킬 수 있습니다.

댓글 남기기