Skip to content

Create simple pub/sun node

ROS / ros cpp

Basic ros2 node using cpp, split the code into header file in implementation

Publisher

Separate node file into header and implementation file, use main file as entry point

1
2
3
4
5
6
7
8
9
cpp_demo
├── CMakeLists.txt
├── include
│   └── cpp_demo
│       └── string_publisher.hpp
├── package.xml
└── src
    ├── pub_demo_main.cpp
    └── string_publisher.cpp
  1. Code Reusability
    • The class (e.g., your publisher node) can be reused in other projects or tests without modification.
    • Only the main file is responsible for launching the node, while the logic is encapsulated in the class.
  2. Separation of Concerns
    • The main function handles application startup and shutdown.
    • The class handles the node’s logic (publishing, subscribing, etc.).
  3. Easier Testing
    • You can write unit tests for your class without running the whole ROS node.
    • The class can be instantiated and tested independently from the ROS2 runtime.
  4. Cleaner Build Structure
    • Header and implementation files (.hpp/.cpp) keep interfaces and logic organized.
    • The main.cpp file is small and only responsible for node instantiation and spinning.
include/string_publisher.hpp
#pragma once

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

class StringPublisher : public rclcpp::Node
{
public:
    StringPublisher();

private:
    void timer_callback();
    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
    rclcpp::TimerBase::SharedPtr timer_;
    size_t count_;
};
pub_demo_main.cpp
#include "cpp_demo/string_publisher.hpp"
#include <rclcpp/rclcpp.hpp>

int main(int argc, char * argv[])
{
    rclcpp::init(argc, argv);
    rclcpp::spin(std::make_shared<StringPublisher>());
    rclcpp::shutdown();
    return 0;
}
string_publisher.cpp
#include "cpp_demo/string_publisher.hpp"
#include <fmt/core.h>

using namespace std::chrono_literals;

StringPublisher::StringPublisher()
: Node("string_publisher"), count_(0)
{
    publisher_ = this->create_publisher<std_msgs::msg::String>("chatter", 10);
    timer_ = this->create_wall_timer(
        500ms, std::bind(&StringPublisher::timer_callback, this));
}

void StringPublisher::timer_callback()
{
    auto message = std_msgs::msg::String();
    message.data = fmt::format("Hello, ROS 2! {}", count_++);
    RCLCPP_INFO(this->get_logger(), "%s", fmt::format("Publishing: {}", message.data).c_str());
    publisher_->publish(message);
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(cpp_demo)

set(CMAKE_C_COMPILER clang)
set(CMAKE_CXX_COMPILER clang++)

# Set C++ standard to C++17
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)  # Disable compiler-specific extensions

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

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
find_package(fmt REQUIRED)


# Add include directory
include_directories(include)

# Build the split StringPublisher node
add_executable(pub_demo src/pub_demo_main.cpp src/string_publisher.cpp)
target_link_libraries(pub_demo fmt::fmt)
ament_target_dependencies(pub_demo rclcpp std_msgs)

install(TARGETS pub_demo
  DESTINATION lib/${PROJECT_NAME}
)
ament_package()