티스토리 뷰

반응형

ROS 2 Jazzy C++ 패키지 만들기: rclcpp Publisher·Subscriber 실습

ROS 2 Jazzy 실전 시리즈 4편 · Ubuntu 24.04 · C++17 · 검토 기준 2026년 8월 25일

이번에는 같은 /chatter 통신을 C++로 구현합니다. ament_cmake 패키지, rclcpp::Node, 명시적 QoS, CMake 의존성, 설치 규칙을 함께 작성해 “빌드는 되는데 ros2 run이 못 찾는” 문제까지 예방합니다.

1. ament_cmake 패키지 생성

source /opt/ros/jazzy/setup.bash
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src

ros2 pkg create --build-type ament_cmake \
  --license Apache-2.0 \
  --dependencies rclcpp std_msgs \
  jazzy_cpp_pubsub

주요 파일은 다음과 같습니다.

jazzy_cpp_pubsub/
├── CMakeLists.txt
├── package.xml
├── include/jazzy_cpp_pubsub/
└── src/
왜 C++17인가? Jazzy의 rclcpp 예제를 작성할 때 C++17을 프로젝트 기본값으로 명시하면 컴파일러·CI 환경에 따른 표준 차이를 줄일 수 있습니다.

2. Publisher 구현

src/publisher_node.cpp를 작성합니다.

#include <chrono>
#include <functional>
#include <memory>
#include <string>

#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/string.hpp>

using namespace std::chrono_literals;

class TextPublisher : public rclcpp::Node
{
public:
  TextPublisher()
  : Node("text_publisher"), count_(0)
  {
    const auto period = this->declare_parameter<double>(
      "publish_period", 1.0);

    const auto qos = rclcpp::QoS(rclcpp::KeepLast(10)).reliable();
    publisher_ = this->create_publisher<std_msgs::msg::String>(
      "chatter", qos);

    timer_ = this->create_wall_timer(
      std::chrono::duration<double>(period),
      std::bind(&TextPublisher::publish_message, this));

    RCLCPP_INFO(
      this->get_logger(),
      "text_publisher started: period=%.2fs", period);
  }

private:
  void publish_message()
  {
    std_msgs::msg::String message;
    message.data = "Hello Jazzy: " + std::to_string(count_++);
    publisher_->publish(message);
    RCLCPP_INFO(this->get_logger(), "Published: %s", message.data.c_str());
  }

  rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
  rclcpp::TimerBase::SharedPtr timer_;
  std::size_t count_;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<TextPublisher>());
  rclcpp::shutdown();
  return 0;
}

3. Subscriber 구현

src/subscriber_node.cpp를 작성합니다. callback 인자는 읽기 전용 ConstSharedPtr로 받습니다.

#include <memory>

#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/string.hpp>

class TextSubscriber : public rclcpp::Node
{
public:
  TextSubscriber()
  : Node("text_subscriber")
  {
    const auto qos = rclcpp::QoS(rclcpp::KeepLast(10)).reliable();

    subscription_ = this->create_subscription<std_msgs::msg::String>(
      "chatter",
      qos,
      [this](std_msgs::msg::String::ConstSharedPtr message) {
        RCLCPP_INFO(
          this->get_logger(), "Received: %s", message->data.c_str());
      });

    RCLCPP_INFO(this->get_logger(), "text_subscriber started");
  }

private:
  rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<TextSubscriber>());
  rclcpp::shutdown();
  return 0;
}

메시지를 수정하지 않는 Subscriber라면 mutable pointer보다 const pointer가 의도를 정확히 표현합니다. 객체를 멤버 변수에 저장해 노드가 실행되는 동안 subscription 수명을 유지합니다.

4. CMakeLists.txt 설정

생성된 CMakeLists.txt를 아래 핵심 구성으로 정리합니다.

cmake_minimum_required(VERSION 3.8)
project(jazzy_cpp_pubsub)

if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 17)
endif()

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

add_executable(publisher src/publisher_node.cpp)
ament_target_dependencies(publisher rclcpp std_msgs)

add_executable(subscriber src/subscriber_node.cpp)
ament_target_dependencies(subscriber rclcpp std_msgs)

install(TARGETS
  publisher
  subscriber
  DESTINATION lib/${PROJECT_NAME}
)

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  ament_lint_auto_find_test_dependencies()
endif()

ament_package()
핵심 설치 규칙: add_executable()만 작성하면 컴파일은 될 수 있지만 ros2 run이 실행 파일을 찾지 못합니다. 반드시 install(TARGETS ... DESTINATION lib/${PROJECT_NAME})가 필요합니다.

5. package.xml 확인

ros2 pkg create --dependencies가 의존성을 추가하지만, 직접 import한 패키지가 manifest에도 있는지 확인합니다.

<?xml version="1.0"?>
<package format="3">
  <name>jazzy_cpp_pubsub</name>
  <version>0.0.0</version>
  <description>ROS 2 Jazzy rclcpp pub/sub example</description>
  <maintainer email="you@example.com">Your Name</maintainer>
  <license>Apache-2.0</license>

  <buildtool_depend>ament_cmake</buildtool_depend>
  <depend>rclcpp</depend>
  <depend>std_msgs</depend>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

6. 빌드와 실행

cd ~/ros2_ws
source /opt/ros/jazzy/setup.bash

rosdep install --from-paths src --ignore-src -y
colcon build --packages-select jazzy_cpp_pubsub \
  --cmake-args -DCMAKE_BUILD_TYPE=Release

source install/setup.bash
ros2 pkg executables jazzy_cpp_pubsub

두 터미널에서 각각 실행합니다.

# 터미널 1
source /opt/ros/jazzy/setup.bash
source ~/ros2_ws/install/setup.bash
ros2 run jazzy_cpp_pubsub publisher
# 터미널 2
source /opt/ros/jazzy/setup.bash
source ~/ros2_ws/install/setup.bash
ros2 run jazzy_cpp_pubsub subscriber

Parameter override와 Topic 검증을 실행합니다.

ros2 run jazzy_cpp_pubsub publisher \
  --ros-args -p publish_period:=0.25

ros2 topic info /chatter -v
ros2 topic hz /chatter
ros2 topic echo /chatter --once

7. rclcpp 코드의 핵심

코드역할
rclcpp::initROS 인자 처리와 런타임 초기화
rclcpp::spinexecutor가 callback을 실행하도록 대기
create_wall_timerwall clock 기준으로 주기 callback 생성
RCLCPP_INFO노드 logger를 통한 구조화된 로그 출력
SharedPtrpublisher·subscription·timer 객체의 수명 관리

callback에서 오래 걸리는 계산을 하면 SingleThreadedExecutor의 다른 callback도 지연됩니다. 실제 시스템에서는 callback group과 MultiThreadedExecutor, worker thread, 별도 노드 중 어떤 경계를 사용할지 처리 시간과 공유 상태를 기준으로 정합니다.

8. 데이터에 맞는 QoS 사용

// 카메라, LiDAR처럼 최신 데이터가 중요한 경우
auto sensor_qos = rclcpp::SensorDataQoS();

// 명령, 상태처럼 신뢰성이 중요한 경우
auto command_qos = rclcpp::QoS(rclcpp::KeepLast(10)).reliable();

// 정적 지도처럼 late joiner가 마지막 값을 받아야 하는 경우
auto map_qos = rclcpp::QoS(rclcpp::KeepLast(1))
  .reliable()
  .transient_local();
PublisherSubscriber연결
RELIABLERELIABLE가능
RELIABLEBEST_EFFORT가능
BEST_EFFORTBEST_EFFORT가능
BEST_EFFORTRELIABLE불가

9. 빌드·실행 오류 해결

증상확인할 것
헤더를 찾지 못함find_package, ament_target_dependencies, package.xml 의존성 확인
undefined reference필요 library가 target에 연결됐는지 확인
빌드 성공, 실행 파일 없음install(TARGETS ...)와 overlay source 확인
코드 수정이 반영 안 됨노드를 종료하고 대상 패키지를 다시 빌드
메시지가 수신되지 않음Topic 타입·이름·domain·QoS를 ros2 topic info -v로 비교
# 상세 빌드 로그
colcon build --packages-select jazzy_cpp_pubsub \
  --event-handlers console_direct+

# ROS 환경 점검
ros2 doctor --report
  • C++17과 경고 옵션을 명시했다.
  • 모든 ROS 의존성을 CMakeLists.txt와 package.xml에 선언했다.
  • 실행 target을 lib/${PROJECT_NAME}에 설치한다.
  • callback 인자의 const 의도를 분명히 했다.
  • 데이터 성격에 맞는 QoS를 명시했다.

마무리

rclcpp 패키지는 소스 코드뿐 아니라 CMake target, 의존성, install 규칙이 함께 완성되어야 합니다. 다음 편에서는 Topic·Service·Action을 기능별로 비교하고 인터페이스를 잘못 선택했을 때 생기는 설계 문제를 정리합니다.

반응형
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/09   »
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
글 보관함