티스토리 친구하기

본문 바로가기

Robotics/ROS2

ROS2 humble Writing a simple publisher and subscriber (C++)

728x90

Background

Node는 ROS 그래프 상에서 통신하는 executable 프로세스들입니다. 이 튜토리얼에서는 노드들이 문자열 메시지 형태로 서로에게 정보를 전달할 것입니다. 여기서 사용되는 예제는 간단한 'talker'와 'listener' 시스템입니다. 하나의 node는 데이터를 publish하고, 다른 하나의 node는 해당 topic을 subscribe해서 그 데이터를 받아볼 수 있습니다.

 

Task

1 Create a package

터미널을 열고 ros2의 command가 동작할 수 있도록 아래와 같이 source를 합니다.

 

source /opt/ros/humble/setup.bash

 

이전에 만들었던 ros2_ws 디렉토리로 이동하세요.

package는 src 디렉토리 안에 만들어야 합니다. 그래서 ros2_ws/src 로 이동하고 아래와 같이 package를 만들기 위한 command를 입력하세요.

 

ros2 pkg create --build-type ament_cmake cpp_pubsub

 

터미널에는 cpp_pubsub 패키지가 만들어졌다는 것을 확인해주는 메시지와 필요한 파일들, 폴더들이 출력됩니다. 

 

ros2_ws/src/cpp_pubsub/src 디렉토리로 이동하세요. 앞서 언급한 것처럼, 이는 CMake 패키지 내의 디렉토리로, 실행 파일을 포함하는 소스 파일들이 위치하는 곳을 가리킵니다.

 

2 Write the publisher node

아래의 command를 터미널에 입력해서 talker 예제 코드를 다운로드하세요.

 

wget -O publisher_member_function.cpp https://raw.githubusercontent.com/ros2/examples/humble/rclcpp/topics/minimal_publisher/member_function.cpp

 

지금 여기에는 publisher_member_function.cpp 라는 이름의 새로운 파일이 있을겁니다. vscode나 다른 text editor로 파일을 열어보세요.

 

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

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

using namespace std::chrono_literals;

/* This example creates a subclass of Node and uses std::bind() to register a
* member function as a callback from the timer. */

class MinimalPublisher : public rclcpp::Node
{
  public:
    MinimalPublisher()
    : Node("minimal_publisher"), count_(0)
    {
      publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
      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, world! " + std::to_string(count_++);
      RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
      publisher_->publish(message);
    }
    rclcpp::TimerBase::SharedPtr timer_;
    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
    size_t count_;
};

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

 

2.1 Examine the code

코드의 윗부분은 사용할 standard C++ header를 include한다. 표준 C++ 헤더 다음에는 rclcpp/rclcpp.hpp 헤더가 포함되어 있는데, ROS 2 시스템의 가장 일반적인 부분을 사용할 수 있습니다. 마지막으로 std_msgs/msg/string.hpp가 포함되어 있는데, 이는 데이터를 publish 하는데 built-in message type을 포함하고 있습니다.

 

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

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

using namespace std::chrono_literals;

 

이러한 코드들은 node의 dependencies(의존성)를 나타냅니다. Dependencies는 다음 섹션에서 수행할 package.xml 및 CMakeLists.txt에 추가되어야 합니다.

아래의 코드는 rclcpp::Node를 상속하여 MinimalPublisher 노드 클래스를 생성합니다. 코드 내의 "this"는 모두 해당 노드를 가리킵니다.

 

class MinimalPublisher : public rclcpp::Node

 

아래의 public 생성자는 node를 minimal_publisher로 명명하고 count_를 0으로 초기화합니다. 생성자 내부에서는 publisher가 String 메시지 유형, 토픽 이름 "topic", 그리고 백업 시 메시지를 제한하는 데 필요한 큐 크기와 함께 초기화됩니다. 그 다음으로 timer_가 초기화되며, 이로 인해 timer_callback 함수가 1초에 두 번(500ms) 실행됩니다.

 

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

 

timer_callback 함수는 메시지 데이터를 설정하고 메시지를 실제로 publish하는 곳입니다. RCLCPP_INFO 매크로는 각 publish된 메시지가 콘솔에 출력합니다.

 

private:
  void timer_callback()
  {
    auto message = std_msgs::msg::String();
    message.data = "Hello, world! " + std::to_string(count_++);
    RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
    publisher_->publish(message);
  }

 

마지막으로 timer, publisher, 그리고 count_ 가 선언되어 있습니다.

 

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

 

MinimalPublisher 클래스 다음에는 실제로 node가 실행되는 main이 있습니다. rclcpp::init는 ROS2를 초기화하고, rclcpp::spin은 노드에서의 데이터 처리를 시작하며 timer에서의 callback을 포함합니다.

 

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

 

2.2 Add dependencies

이전 튜토리얼에서 언급했듯이, ros2_ws/src/cpp_pubsub 디렉토리로 한 단계 뒤로 이동하면, CMakeLists.txt와 package.xml 파일이 생성되어 있는 것을 볼 수 있습니다.

text editor로 package.xml 파일을 엽니다.

<description>, <maintainer> 및 <license> 태그를 채워 넣는 것을 잊지 마세요.

 

<description>Examples of minimal publisher/subscriber using rclcpp</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache License 2.0</license>

 

ament_cmake 빌드 툴(build tool) dependency 바로 다음 줄에 새로운 줄을 추가하고, node의 include 문에 해당하는 다음 dependencies를 붙여넣으세요

 

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

 

이는 코드가 build되고 실행될 때, 패키지가 rclcpp와 std_msgs를 필요로 한다는 것을 선언합니다.

파일을 저장하는 것을 잊지 마세요.

 

2.3 CMakeLists.txt

이제 CMakeLists.txt 파일을 엽니다. 기존의 dependency find_package(ament_cmake REQUIRED) 아래에 다음 줄을 추가하세요

 

find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

 

이후, 실행 가능한 파일을 추가하고 talker라는 이름을 지정하여 ros2 run을 사용하여 노드를 실행할 수 있도록 만드세요.

 

add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)

 

마지막으로 install(TARGETS...) 섹션을 추가하여 ros2 run이 실행 가능한 파일을 찾을 수 있도록 해주세요.

 

install(TARGETS
  talker
  DESTINATION lib/${PROJECT_NAME})

 

일부 불필요한 섹션 및 주석을 제거하여 CMakeLists.txt를 정리할 수 있습니다. 다음과 같이 보이도록 해보세요.

 

cmake_minimum_required(VERSION 3.5)
project(cpp_pubsub)

# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 14)
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(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)

install(TARGETS
  talker
  DESTINATION lib/${PROJECT_NAME})

ament_package()

 

지금 패키지를 build하고 로컬 설정 파일을 소스(source)하고 실행(execute)할 수 있지만, 먼저 subscriber node를 생성하여 전체 시스템이 작동하는 것을 볼 수 있도록 하겠습니다.

 

3 Write the subscriber node

다음 노드를 생성하려면 ros2_ws/src/cpp_pubsub/src로 돌아가세요. 터미널에 다음 코드를 입력하세요.

 

wget -O subscriber_member_function.cpp https://raw.githubusercontent.com/ros2/examples/humble/rclcpp/topics/minimal_subscriber/member_function.cpp

 

콘솔에 ls를 입력하면 이제 다음과 같이 나타날 것입니다.

 

publisher_member_function.cpp  subscriber_member_function.cpp

 

텍스트 편집기로 subscriber_member_function.cpp 파일을 열어주세요.

 

#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using std::placeholders::_1;

class MinimalSubscriber : public rclcpp::Node
{
  public:
    MinimalSubscriber()
    : Node("minimal_subscriber")
    {
      subscription_ = this->create_subscription<std_msgs::msg::String>(
      "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
    }

  private:
    void topic_callback(const std_msgs::msg::String & 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);
  rclcpp::spin(std::make_shared<MinimalSubscriber>());
  rclcpp::shutdown();
  return 0;
}

 

3.1 Examine the code

Subscriber node의 코드는 거의 Publisher node와 동일합니다. 노드의 이름은 minimal_subscriber로 되어 있으며, 생성자는 노드의 create_subscription 클래스를 사용하여 콜백(callback 함수)을 실행합니다.

여기에는 타이머가 없습니다. Subscriber는 간단히 "topic" 토픽에 데이터가 publish 될 때마다 응답합니다.

 

public:
  MinimalSubscriber()
  : Node("minimal_subscriber")
  {
    subscription_ = this->create_subscription<std_msgs::msg::String>(
    "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
  }

 

토픽 튜토리얼에서 기억하듯, publisher와 subscriber가 통신하기 위해서는 사용되는 토픽 이름과 메시지 유형이 일치해야 합니다.

topic_callback 함수는 토픽을 통해 publish된 문자열 메시지 데이터를 수신하고, 해당 데이터를 RCLCPP_INFO 매크로를 사용하여 콘솔에 출력합니다.

이 클래스에서 유일한 필드 선언은 subscription입니다.

 

private:
  void topic_callback(const std_msgs::msg::String & msg) const
  {
    RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg.data.c_str());
  }
  rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;

 

main 함수는 완전히 동일합니다. 다만 이제 MinimalSubscriber 노드를 spin하게 됩니다. Publisher node에서 spin은 타이머를 시작하는 것을 의미했지만, subscriber의 경우 메시지가 도착할 때마다 메시지를 받을 준비를 하는 것을 의미합니다.

이 노드는 publisher node와 동일한 dependecy를 가지므로 package.xml에 추가할 새로운 내용은 없습니다.

 

3.2 CMakeLists.txt

CMakeLists.txt 파일을 다시 열고, publisher node의 항목 아래에 subscriber node의 executable 및 대상(target)을 추가하세요.

 

add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp std_msgs)

install(TARGETS
  talker
  listener
  DESTINATION lib/${PROJECT_NAME})

 

파일을 저장한 후, 이제  pub/sub 시스템이 준비되어 있어야 합니다.

 

4 Build and run

아마도 이미 ROS2 시스템의 일부로 rclcpp와 std_msgs 패키지가 설치되어 있을 것입니다. 빌드하기 전에 작업 공간(workspace)의 루트(rosw2_ws)에서 rosdep를 실행하여 누락된 종속성을 확인하는 것이 좋습니다.

 

rosdep install -i --from-path src --rosdistro humble -y

 

작업 공간의 루트인 ros2_ws에서 새 패키지를 빌드하세요.

 

colcon build --packages-select cpp_pubsub

 

새 터미널을 열고 ros2_ws로 이동한 다음, 설정 파일을 소스하세요.

 

. install/setup.bash

 

터미널에서는 매 0.5초마다 정보 메시지를 publish하게 될 것입니다. 다음과 같이 보일 것입니다.

 

[INFO] [minimal_publisher]: Publishing: "Hello World: 0"
[INFO] [minimal_publisher]: Publishing: "Hello World: 1"
[INFO] [minimal_publisher]: Publishing: "Hello World: 2"
[INFO] [minimal_publisher]: Publishing: "Hello World: 3"
[INFO] [minimal_publisher]: Publishing: "Hello World: 4"

 

다른 터미널을 열고, 다시 ros2_ws 안에서 설정 파일을 소스한 다음, listener node를 실행하세요.

 

 

. install/setup.bash
ros2 run cpp_pubsub listener

 

listener는 메시지를 콘솔에 출력하기 시작할 것이며, 그 시간에 publisher가 있는 메시지 카운트부터 시작합니다. 다음과 같이 보일 것입니다.

 

[INFO] [minimal_subscriber]: I heard: "Hello World: 10"
[INFO] [minimal_subscriber]: I heard: "Hello World: 11"
[INFO] [minimal_subscriber]: I heard: "Hello World: 12"
[INFO] [minimal_subscriber]: I heard: "Hello World: 13"
[INFO] [minimal_subscriber]: I heard: "Hello World: 14"

 

각 터미널에서 노드의 실행을 중지하려면 Ctrl+C를 입력하세요.

 

Summary

두 개의 노드를 생성하여 데이터를 토픽을 통해 publish하고 subscribe 했습니다. 컴파일하고 실행하기 전에, 이들의 dependencies와 executables를 패키지 설정 파일에 추가했습니다.

 

 

반응형

'Robotics > ROS2' 카테고리의 다른 글

ROS2 service [Understanding services]  (0) 2023.10.08
ROS2 humble Writing a simple publisher and subscriber (Python)  (0) 2023.08.19
ROS2 topic 2 [ROS2 토픽 2]  (0) 2023.05.21
ROS2 topic 1 [ROS2 토픽 1]  (0) 2023.05.14
ROS2 Node [ROS2 노드]  (2) 2023.04.27