본문으로 바로가기

이번 실습에서는 C++로 ROS2에서 제공하는 인터페이스를 이용하여 토픽 통신하는 예제를 만들어 보겠습니다.

 

예제 내용은 간단합니다.

 

temperature_publisher 노드는 1초마다 임의의 온도 값을 만들어 /temperature 토픽으로 보냅니다.

 

temperature_subscriber 노드는 /temperature 토픽을 구독하고, 받은 온도 값을 터미널에 출력합니다.

 

전체 구조는 다음과 같습니다.

temperature_publisher
        |
        | std_msgs/msg/Float32
        v
/temperature
        |
        v
temperature_subscriber
 

 

이번 예제에서는 ROS 2에서 기본 제공하는 std_msgs/msg/Float32 메시지를 사용합니다.

 

 

1. 퍼블리셔 노드 만들기

먼저 온도 데이터를 보내는 퍼블리셔 노드를 작성하겠습니다.

 
cd ~/ros2_study/src/my-cpp_package/src
touch temperature_publisher.cpp
 
 

 

code를 실행하여 temperature_publisher.cpp 파일에 아래 코드를 작성합니다.

 
#include <chrono>
#include <memory>
#include <random>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/float32.hpp"

using namespace std::chrono_literals;

class TemperaturePublisher : public rclcpp::Node
{
public:
  TemperaturePublisher()
  : Node("temperature_publisher")
  {
    publisher_ = this->create_publisher<std_msgs::msg::Float32>(
      "temperature",
      rclcpp::QoS(10)
    );

    timer_ = this->create_wall_timer(
      1s,
      std::bind(&TemperaturePublisher::publish_temperature, this)
    );

    RCLCPP_INFO(this->get_logger(), "Temperature publisher node has started.");
  }

private:
  void publish_temperature()
  {
    std_msgs::msg::Float32 msg;
    msg.data = generate_temperature();

    publisher_->publish(msg);

    RCLCPP_INFO(
      this->get_logger(),
      "Published temperature: %.2f C",
      msg.data
    );
  }

  float generate_temperature()
  {
    static std::random_device rd;
    static std::mt19937 generator(rd());
    static std::uniform_real_distribution<float> distribution(20.0, 35.0);

    return distribution(generator);
  }

  rclcpp::Publisher<std_msgs::msg::Float32>::SharedPtr publisher_;
  rclcpp::TimerBase::SharedPtr timer_;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);

  auto node = std::make_shared<TemperaturePublisher>();
  rclcpp::spin(node);

  rclcpp::shutdown();
  return 0;
}
 
 
 
 

2. 퍼블리셔 코드 분석

먼저 필요한 헤더를 포함합니다.

 
#include <chrono>
#include <memory>
#include <random>
 

 

각 헤더의 역할은 다음과 같습니다.

chrono : 시간 처리를 위해 사용
memory : std::shared_ptr 같은 스마트 포인터 사용
random : 임의의 온도 값 생성
 

 

다음은 ROS 2 관련 헤더입니다.

 
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/float32.hpp"
 

 

rclcpp는 ROS 2 C++ 프로그래밍에서 가장 기본이 되는 라이브러리입니다.

std_msgs/msg/float32.hpp는 32비트 실수 하나를 담는 메시지 타입입니다.
온도, 거리, 속도, 전압처럼 숫자 하나를 보낼 때 사용하기 좋습니다.

 

 

3. Node 클래스 상속하기

 
class TemperaturePublisher : public rclcpp::Node
 

 

ROS 2 C++에서는 보통 rclcpp::Node를 상속해서 노드 클래스를 만듭니다.

이렇게 작성하면 퍼블리셔, 서브스크라이버, 타이머 같은 ROS 2 기능을 클래스 안에서 깔끔하게 관리할 수 있습니다.

 

생성자에서는 노드 이름을 지정합니다.

 
TemperaturePublisher()
: Node("temperature_publisher")
 

 

이 노드는 실행되면 ROS 2 시스템 안에서 temperature_publisher라는 이름으로 등록됩니다.

 

 

 

4. 퍼블리셔 생성하기

 
publisher_ = this->create_publisher<std_msgs::msg::Float32>(
  "temperature",
  rclcpp::QoS(10)
);
 

 

이 코드는 temperature 토픽으로 std_msgs::msg::Float32 타입 메시지를 발행하는 퍼블리셔를 만듭니다.

 

여기서 중요한 값은 두 가지입니다.

"temperature"             : 토픽 이름
rclcpp::QoS(10)           : 메시지 큐 크기
 

 

코드에서는 "temperature"라고 작성했지만, ROS 2 명령어로 확인하면 보통 /temperature처럼 보입니다.

 

rclcpp::QoS(10)은 메시지를 최대 10개까지 큐에 저장할 수 있다는 의미입니다.
퍼블리셔가 데이터를 보내는 속도와 서브스크라이버가 받는 속도가 항상 같지는 않기 때문에, 어느 정도 메시지를 보관할지 정하는 설정이 필요합니다. 처음 공부할 때는 일단 rclcpp::QoS(10)으로 시작하면 충분합니다.

 

 

 

5. 타이머 생성하기

 
timer_ = this->create_wall_timer(
  1s,
  std::bind(&TemperaturePublisher::publish_temperature, this)
);
 

 

이 코드는 1초마다 publish_temperature() 함수를 실행합니다.

 

더보기

create_wall_timer()는 ROS 2 노드에서 일정 시간마다 콜백 함수를 반복 실행하는 타이머를 생성하는 함수입니다.

예:

 
timer_ = this->create_wall_timer(
  1s,
  std::bind(&TemperaturePublisher::publish_temperature, this)
);
 

 

의미:

 
create_wall_timer(주기, 실행할_함수)
 

 

즉,

1초마다 publish_temperature() 함수를 실행한다
 

 

여기서 wall timer는 시스템의 실제 시간, 즉 벽시계 시간을 기준으로 동작하는 타이머입니다.

 

 

 

6. 메시지 발행 함수

 
void publish_temperature()
{
  std_msgs::msg::Float32 msg;
  msg.data = generate_temperature();

  publisher_->publish(msg);

  RCLCPP_INFO(
    this->get_logger(),
    "Published temperature: %.2f C",
    msg.data
  );
}
 

 

중요한 소스는 아래입니다.

 
std_msgs::msg::Float32 msg;
msg.data = generate_temperature();
publisher_->publish(msg);
 

 

먼저 std_msgs::msg::Float32 타입의 메시지 객체를 만듭니다.

 
std_msgs::msg::Float32 msg;
 

 

그리고 메시지의 data 필드에 온도 값을 넣습니다.

 
msg.data = generate_temperature();
 

 

마지막으로 publish() 함수를 호출해서 토픽으로 보냅니다.

 
publisher_->publish(msg);
 

 

퍼블리셔는 특정 노드에게 직접 데이터를 보내는 것이 아닙니다.

정해진 토픽으로 데이터를 발행하고, 그 토픽을 구독하는 노드가 데이터를 받습니다.

 

 

 

7. 임의의 온도 값 만들기

 
float generate_temperature()
{
  static std::random_device rd;
  static std::mt19937 generator(rd());
  static std::uniform_real_distribution<float> distribution(20.0, 35.0);

  return distribution(generator);
}
 

 

이 함수는 20.0도에서 35.0도 사이의 임의 온도 값을 생성합니다.

 
static std::random_device rd;
 

 

난수 생성기의 초기값을 만들기 위한 객체입니다.

 
static std::mt19937 generator(rd());
 

 

실제 난수를 생성하는 객체입니다.

 
static std::uniform_real_distribution<float> distribution(20.0, 35.0);
 

 

20.0부터 35.0 사이의 실수를 비슷한 확률로 생성하도록 설정합니다.

 
return distribution(generator);
 

 

설정된 범위 안에서 온도 값 하나를 만들어 반환합니다.

 

여기서는 실제 온도 센서가 없기 때문에 랜덤 값으로 센서 데이터를 흉내 내는 것입니다.

 

 

 

8. 서브스크라이버 노드 만들기

이제 온도 데이터를 받는 서브스크라이버 노드를 작성하겠습니다.

 
cd ~/ros2_study/src/my_cpp_package
touch temperature_subscriber.cpp
 

 

 

 

temperature_subscriber.cpp 파일에 아래 코드를 작성합니다.

 
#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/float32.hpp"

class TemperatureSubscriber : public rclcpp::Node
{
public:
  TemperatureSubscriber()
  : Node("temperature_subscriber")
  {
    subscription_ = this->create_subscription<std_msgs::msg::Float32>(
      "temperature",
      rclcpp::QoS(10),
      std::bind(
        &TemperatureSubscriber::temperature_callback,
        this,
        std::placeholders::_1
      )
    );

    RCLCPP_INFO(this->get_logger(), "Temperature subscriber node has started.");
  }

private:
  void temperature_callback(const std_msgs::msg::Float32::SharedPtr msg)
  {
    RCLCPP_INFO(
      this->get_logger(),
      "Received temperature: %.2f C",
      msg->data
    );

    if (msg->data >= 30.0) {
      RCLCPP_WARN(
        this->get_logger(),
        "Temperature is high. Cooling check is recommended."
      );
    }
  }

  rclcpp::Subscription<std_msgs::msg::Float32>::SharedPtr subscription_;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);

  auto node = std::make_shared<TemperatureSubscriber>();
  rclcpp::spin(node);

  rclcpp::shutdown();
  return 0;
}
 
 
 
 
 

9. 서브스크라이버 코드 이해하기

1) 사용한 헤더

 
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/float32.hpp"
 

 

memory는 std::shared_ptr를 사용하기 위한 헤더입니다.

rclcpp/rclcpp.hpp는 ROS 2 C++ 기능을 사용하기 위한 기본 헤더입니다. 노드 생성, Subscriber 생성, 로그 출력, spin() 실행 등에 사용됩니다.

std_msgs/msg/float32.hpp는 Float32 메시지 타입을 사용하기 위한 헤더입니다. 이 메시지는 float data 하나를 가지고 있으며, 여기서는 온도 값을 저장하는 데 사용됩니다.

 

 

 

2) 노드 클래스

 
class TemperatureSubscriber : public rclcpp::Node
 

 

TemperatureSubscriber 클래스는 rclcpp::Node를 상속받습니다. 따라서 이 클래스는 ROS 2 노드로 동작합니다.

 

 

 

3) 생성자

 
TemperatureSubscriber()
: Node("temperature_subscriber")
 

 

노드 이름을 temperature_subscriber로 설정합니다.

즉, 실행 후 노드 목록을 확인하면 다음과 같이 보입니다.

 
ros2 node list
 
/temperature_subscriber
 
 
 
 

4) Subscriber 생성

 
subscription_ = this->create_subscription<std_msgs::msg::Float32>(
  "temperature",
  rclcpp::QoS(10),
  std::bind(
    &TemperatureSubscriber::temperature_callback,
    this,
    std::placeholders::_1
  )
);
 

 

temperature라는 토픽을 구독합니다.

메시지 타입은 std_msgs::msg::Float32입니다.

 
rclcpp::QoS(10)
 

 

은 메시지를 최대 10개까지 큐에 저장할 수 있다는 의미입니다.

 
std::bind(...)
 

 

은 메시지가 들어왔을 때 실행할 콜백 함수를 연결합니다. 즉, temperature 토픽으로 메시지가 들어오면 아래 함수가 자동 실행됩니다.

 
temperature_callback()
 
 
 
 

5) 콜백 함수

 
void temperature_callback(const std_msgs::msg::Float32::SharedPtr msg)
 

 

이 함수는 온도 메시지를 받을 때마다 실행됩니다. 수신한 온도 값은 다음처럼 읽습니다.

 
msg->data
 

 

그리고 로그로 출력합니다.

 
RCLCPP_INFO(
  this->get_logger(),
  "Received temperature: %.2f C",
  msg->data
);
 

 

예를 들어 온도 값이 25.3이면 다음처럼 출력됩니다.

 

Received temperature: 25.30 C
 
 
 
 
 

6) 온도 경고 처리

 
if (msg->data >= 30.0) {
  RCLCPP_WARN(
    this->get_logger(),
    "Temperature is high. Cooling check is recommended."
  );
}
 

 

온도 값이 30.0 이상이면 경고 로그를 출력합니다.

 

즉,

30도 미만 → 온도만 출력
30도 이상 → 온도 출력 + 경고 출력
 

 

 

 

7) Subscriber 멤버 변수

 
rclcpp::Subscription<std_msgs::msg::Float32>::SharedPtr subscription_;
 

 

생성한 Subscriber를 저장하는 변수입니다.

이 변수를 클래스 멤버로 가지고 있어야 구독 기능이 계속 유지됩니다.

 

 

 

8) main 함수

 
rclcpp::init(argc, argv);

auto node = std::make_shared<TemperatureSubscriber>();
rclcpp::spin(node);

rclcpp::shutdown();
 

 

동작 순서는 다음과 같습니다.

ROS 2 초기화
→ TemperatureSubscriber 노드 생성
→ spin()으로 노드 실행 유지
→ 메시지가 들어오면 콜백 함수 실행
→ 종료 시 ROS 2 shutdown
 

 

rclcpp::spin(node)가 실행 중이어야 Subscriber가 메시지를 받을 수 있습니다.

 

 

 

10. CMakeLists.txt 수정하기

이제 두 C++ 파일을 실행 파일로 등록해야 합니다.

아래 파일을 엽니다.

 
cd ~/ros2_study/src/my_cpp_package
code .
 

 

find_package 아래쪽, ament_package() 위쪽에 다음 내용을 추가합니다.

 
add_executable(temperature_publisher src/temperature_publisher.cpp)
ament_target_dependencies(temperature_publisher rclcpp std_msgs)

add_executable(temperature_subscriber src/temperature_subscriber.cpp)
ament_target_dependencies(temperature_subscriber rclcpp std_msgs)

install(TARGETS
  temperature_publisher
  temperature_subscriber
  DESTINATION lib/${PROJECT_NAME}
)
 

 

 

 

 

중요한 부분은 세 가지입니다.

 
add_executable(temperature_publisher src/temperature_publisher.cpp)
 

 

C++ 소스 파일을 실행 가능한 프로그램으로 만듭니다.

 
ament_target_dependencies(temperature_publisher rclcpp std_msgs)
 

 

해당 실행 파일이 사용하는 ROS 2 패키지 의존성을 연결합니다.

 
install(TARGETS
  temperature_publisher
  temperature_subscriber
  DESTINATION lib/${PROJECT_NAME}
)
 

 

ros2 run 명령어로 실행할 수 있도록 설치 경로를 지정합니다.

이 install() 설정을 빼먹으면 빌드가 성공해도 ros2 run에서 실행 파일을 찾지 못할 수 있습니다.

 

 

 

11. package.xml 확인하기

패키지를 만들 때 아래 옵션을 사용했습니다.

 
--dependencies rclcpp std_msgs
 

 

그래서 package.xml에는 보통 다음 내용이 자동으로 들어갑니다.

 
<depend>rclcpp</depend>
<depend>std_msgs</depend>
 

 

그래도 한 번 확인하는 습관을 들이는 것이 좋습니다.

 
 

package.xml를 열러 아래 항목이 있는지 확인합니다.

 
<depend>rclcpp</depend>
<depend>std_msgs</depend>
 

 

 

 

ROS 2에서 package.xml은 이 패키지가 어떤 라이브러리와 메시지 패키지에 의존하는지 알려주는 파일입니다.

 

빌드 오류가 날 때는 보통 아래 두 파일을 먼저 확인합니다.

CMakeLists.txt
package.xml
 

 

이건 기본 중의 기본입니다.

 

 

 

12. 빌드하기

워크스페이스 최상위 폴더로 이동합니다.

 
cd ~/ros2_study
 

 

빌드합니다.

 
colcon build --packages-select temperature_topic_cpp
 

 

빌드가 끝나면 환경 설정을 적용합니다.

 
source install/setup.bash
 

 

 

 

 

13. 퍼블리셔 실행하기

터미널을 하나 열고 아래 명령어를 실행합니다.

 
cd ~/ros2_study
source install/setup.bash
ros2 run temperature_topic_cpp temperature_publisher
 

 

정상적으로 실행되면 다음과 비슷한 출력이 나옵니다.

[INFO] [temperature_publisher]: Temperature publisher node has started.
[INFO] [temperature_publisher]: Published temperature: 24.73 C
[INFO] [temperature_publisher]: Published temperature: 31.42 C
[INFO] [temperature_publisher]: Published temperature: 28.15 C
 

 

이제 퍼블리셔 노드는 1초마다 온도 값을 발행하고 있습니다.

 

 

 

 

 

14. 서브스크라이버 실행하기

 

새 터미널을 하나 더 엽니다.

 
cd ~/ros2_study
source install/setup.bash
ros2 run temperature_topic_cpp temperature_subscriber
 

 

정상적으로 실행되면 다음과 비슷한 출력이 나옵니다.

[INFO] [temperature_subscriber]: Temperature subscriber node has started.
[INFO] [temperature_subscriber]: Received temperature: 24.73 C
[INFO] [temperature_subscriber]: Received temperature: 31.42 C
[WARN] [temperature_subscriber]: Temperature is high. Cooling check is recommended.
[INFO] [temperature_subscriber]: Received temperature: 28.15 C
 

 

온도가 30도 이상이면 경고 로그도 함께 출력됩니다.

[WARN] [temperature_subscriber]: Temperature is high. Cooling check is recommended.
 

 

이렇게 보이면 퍼블리셔와 서브스크라이버가 정상적으로 통신하고 있는 것입니다.

 

 

 

 

 

15. 토픽 목록 확인하기

ROS 2에서는 CLI 명령어로 현재 사용 중인 토픽을 확인할 수 있습니다.

 

새 터미널을 열고 아래 명령어를 실행합니다.

 
cd ~/ros2_study
source install/setup.bash
ros2 topic list
 

 

예상 출력은 다음과 같습니다.

/parameter_events
/rosout
/temperature
 

 

 

여기서 /temperature가 보이면 토픽이 정상적으로 생성된 것입니다.

 

 

 

 

16. 토픽 타입 확인하기

/temperature 토픽의 메시지 타입을 확인해 보겠습니다.

 
ros2 topic type /temperature
 

 

예상 출력은 다음과 같습니다.

std_msgs/msg/Float32
 

 

퍼블리셔와 서브스크라이버는 토픽 이름만 같아서는 안 됩니다.
메시지 타입도 같아야 합니다.

 

이번 예제에서는 둘 다 std_msgs/msg/Float32를 사용합니다.

 

 

 

 

 

17. 토픽 데이터 직접 확인하기

서브스크라이버를 실행하지 않아도 CLI 명령어로 토픽 데이터를 직접 볼 수 있습니다.

 
ros2 topic echo /temperature
 

 

예상 출력은 다음과 같습니다.

data: 26.374
---
data: 30.812
---
data: 22.691
---
 

 

이 명령어는 디버깅할 때 매우 자주 사용합니다.

노드는 실행 중인데 데이터가 제대로 오는지 모르겠다면, 바로 ros2 topic echo로 확인하면 됩니다.

 

 

 

 

18. 노드 목록 확인하기

현재 실행 중인 노드 목록도 확인할 수 있습니다.

 
ros2 node list
 

퍼블리셔와 서브스크라이버가 모두 실행 중이라면 다음과 비슷하게 나옵니다.

/temperature_publisher
/temperature_subscriber
 

노드가 보이지 않는다면 실행이 안 되었거나, 터미널에서 source install/setup.bash를 하지 않았을 가능성이 있습니다.

 

 

 

 

 

19. 실습 정리

이번 글에서는 ROS 2 C++에서 가장 기본적인 토픽 통신 예제를 만들었습니다.

 

만든 노드는 두 개입니다.

temperature_publisher
temperature_subscriber
 

 

사용한 토픽은 다음과 같습니다.

/temperature
 

 

사용한 메시지 타입은 다음과 같습니다.

std_msgs/msg/Float32
 

 

전체 흐름은 다음과 같습니다.

1. temperature_publisher 노드 실행
2. 1초마다 랜덤 온도 값 생성
3. /temperature 토픽으로 Float32 메시지 발행
4. temperature_subscriber 노드가 /temperature 구독
5. 수신한 온도 값을 터미널에 출력
6. 온도가 30도 이상이면 경고 출력
 

 

 

728x90
728x90