퍼블리셔 (Publisher) 구현

퍼블리셔는 데이터를 다른 노드로 전송하는 역할을 한다. ROS2에서는 퍼블리셔를 사용하여 특정 토픽에 데이터를 발행할 수 있다. C++에서 퍼블리셔를 구현하는 방법은 rclcpp 라이브러리를 사용하여 이루어진다. 아래는 기본적인 퍼블리셔 구현의 절차이다.

1. 퍼블리셔 클래스 정의

C++에서는 퍼블리셔를 정의하기 위해 rclcpp::Node 클래스를 상속받는 노드를 생성해야 한다.

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

class PublisherNode : public rclcpp::Node
{
public:
  PublisherNode() : Node("publisher_node")
  {
    publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
    timer_ = this->create_wall_timer(
      std::chrono::milliseconds(500), 
      std::bind(&PublisherNode::publishMessage, this));
  }

private:
  void publishMessage()
  {
    auto message = std_msgs::msg::String();
    message.data = "Hello, ROS2!";
    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_;
};

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

2. 주요 부분 설명

3. 수식 관련

만약 퍼블리싱 데이터가 단순한 텍스트가 아닌 벡터 데이터를 포함할 경우, 벡터 형태의 데이터를 다룰 수 있다. 예를 들어, \mathbf{v} \in \mathbb{R}^3인 3차원 벡터 데이터를 퍼블리싱할 수 있다.

\mathbf{v} = \begin{bmatrix} x \\ y \\ z \end{bmatrix}

여기서 x, y, z는 각각 퍼블리싱할 실수 값이다.

서브스크라이버 (Subscriber) 구현

서브스크라이버는 퍼블리셔가 발행한 데이터를 받아 처리하는 역할을 한다. 퍼블리셔와 마찬가지로 rclcpp 라이브러리를 사용하여 서브스크라이버를 구현할 수 있다.

1. 서브스크라이버 클래스 정의

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

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

private:
  void receiveMessage(const std_msgs::msg::String::SharedPtr msg) const
  {
    RCLCPP_INFO(this->get_logger(), "Received: '%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<SubscriberNode>());
  rclcpp::shutdown();
  return 0;
}

2. 주요 부분 설명

3. 수식 관련

만약 수신된 데이터가 벡터 데이터인 경우, 이를 처리할 수 있다. 예를 들어, 수신된 벡터 데이터를 처리할 수 있으며, 데이터는 다음과 같은 3차원 벡터일 수 있다.

\mathbf{v} = \begin{bmatrix} x \\ y \\ z \end{bmatrix}

데이터는 각 축의 좌표로 해석되며, 수신 후 처리 로직에서 사용될 수 있다.

서비스 (Service) 구현

서비스는 요청(request)과 응답(response)으로 이루어진 통신 방식이다. ROS2에서 서비스는 서버와 클라이언트 구조로 동작하며, 서버는 클라이언트의 요청을 받아 처리하고, 클라이언트는 서버에 요청을 보내는 역할을 한다. 이를 구현하기 위해 rclcpp를 사용하여 C++로 서비스 서버와 클라이언트를 구현할 수 있다.

1. 서비스 서버 구현

먼저 서비스 서버를 구현하여 클라이언트로부터 요청을 받아 처리하는 노드를 작성한다.

#include <rclcpp/rclcpp.hpp>
#include <example_interfaces/srv/add_two_ints.hpp>

class ServiceServerNode : public rclcpp::Node
{
public:
  ServiceServerNode() : Node("service_server_node")
  {
    service_ = this->create_service<example_interfaces::srv::AddTwoInts>(
      "add_two_ints", 
      std::bind(&ServiceServerNode::handleService, this, std::placeholders::_1, std::placeholders::_2));
  }

private:
  void handleService(const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,
                     std::shared_ptr<example_interfaces::srv::AddTwoInts::Response> response)
  {
    response->sum = request->a + request->b;
    RCLCPP_INFO(this->get_logger(), "Request: a=%ld, b=%ld", request->a, request->b);
    RCLCPP_INFO(this->get_logger(), "Sending back response: [%ld]", response->sum);
  }

  rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr service_;
};

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

2. 주요 부분 설명

3. 수식 관련

요청에서 받은 값 ab는 각각 정수이며, 서버는 이 두 값을 더하여 응답으로 돌려준다.

\text{sum} = a + b

여기서 a, b \in \mathbb{Z}이며, 결과는 요청된 값의 합계이다.

4. 서비스 클라이언트 구현

클라이언트는 서버에 요청을 보내고 응답을 받는 역할을 한다.

#include <rclcpp/rclcpp.hpp>
#include <example_interfaces/srv/add_two_ints.hpp>

class ServiceClientNode : public rclcpp::Node
{
public:
  ServiceClientNode() : Node("service_client_node")
  {
    client_ = this->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");

    auto request = std::make_shared<example_interfaces::srv::AddTwoInts::Request>();
    request->a = 5;
    request->b = 3;

    while (!client_->wait_for_service(std::chrono::seconds(1))) {
      RCLCPP_INFO(this->get_logger(), "Waiting for service...");
    }

    auto result_future = client_->async_send_request(request);
    auto result = result_future.get();

    RCLCPP_INFO(this->get_logger(), "Result of add_two_ints: %ld", result->sum);
  }

private:
  rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client_;
};

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

5. 주요 부분 설명

6. 수식 관련

클라이언트에서 서버에 보낸 요청은 다음과 같다.

a = 5, \quad b = 3

서버는 이 값을 받아 더한 결과 \text{sum} = 8을 응답으로 반환한다.

액션 (Action) 구현

액션은 장시간 수행되는 작업을 비동기적으로 처리할 수 있도록 도와주는 통신 방식이다. 액션은 액션 서버(Action Server)와 액션 클라이언트(Action Client)로 구성되며, 상태(State)와 피드백(Feedback)을 통해 작업의 진행 상황을 모니터링할 수 있다. 이를 통해 작업이 완료될 때까지 기다리거나 중간에 피드백을 받을 수 있다.

1. 액션 서버 구현

ROS2에서 액션 서버를 C++로 구현하기 위해서는 rclcpp_action 라이브러리를 사용한다. 액션 서버는 작업을 받아 비동기적으로 처리한 후 결과를 클라이언트에게 전달한다.

#include <rclcpp/rclcpp.hpp>
#include <rclcpp_action/rclcpp_action.hpp>
#include <example_interfaces/action/fibonacci.hpp>

class ActionServerNode : public rclcpp::Node
{
public:
  using Fibonacci = example_interfaces::action::Fibonacci;
  using GoalHandleFibonacci = rclcpp_action::ServerGoalHandle<Fibonacci>;

  ActionServerNode() : Node("action_server_node")
  {
    this->action_server_ = rclcpp_action::create_server<Fibonacci>(
      this, "fibonacci",
      std::bind(&ActionServerNode::handleGoal, this, std::placeholders::_1, std::placeholders::_2),
      std::bind(&ActionServerNode::handleCancel, this, std::placeholders::_1),
      std::bind(&ActionServerNode::handleAccepted, this, std::placeholders::_1));
  }

private:
  rclcpp_action::Server<Fibonacci>::SharedPtr action_server_;

  rclcpp_action::GoalResponse handleGoal(
    const rclcpp_action::GoalUUID & uuid,
    std::shared_ptr<const Fibonacci::Goal> goal)
  {
    RCLCPP_INFO(this->get_logger(), "Received goal request with order %d", goal->order);
    (void)uuid;
    return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
  }

  rclcpp_action::CancelResponse handleCancel(
    const std::shared_ptr<GoalHandleFibonacci> goal_handle)
  {
    RCLCPP_INFO(this->get_logger(), "Received request to cancel goal");
    (void)goal_handle;
    return rclcpp_action::CancelResponse::ACCEPT;
  }

  void handleAccepted(const std::shared_ptr<GoalHandleFibonacci> goal_handle)
  {
    std::thread{std::bind(&ActionServerNode::execute, this, goal_handle)}.detach();
  }

  void execute(const std::shared_ptr<GoalHandleFibonacci> goal_handle)
  {
    RCLCPP_INFO(this->get_logger(), "Executing goal...");
    const auto goal = goal_handle->get_goal();
    auto feedback = std::make_shared<Fibonacci::Feedback>();
    auto & sequence = feedback->sequence;
    sequence.push_back(0);
    sequence.push_back(1);

    auto result = std::make_shared<Fibonacci::Result>();

    for (int i = 1; i < goal->order; ++i) {
      if (goal_handle->is_canceling()) {
        result->sequence = sequence;
        goal_handle->canceled(result);
        RCLCPP_INFO(this->get_logger(), "Goal canceled");
        return;
      }

      sequence.push_back(sequence[i] + sequence[i - 1]);
      goal_handle->publish_feedback(feedback);
      RCLCPP_INFO(this->get_logger(), "Publish feedback");
      std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }

    if (rclcpp::ok()) {
      result->sequence = sequence;
      goal_handle->succeed(result);
      RCLCPP_INFO(this->get_logger(), "Goal succeeded");
    }
  }
};

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

2. 주요 부분 설명

3. 수식 관련

피보나치 수열은 다음과 같은 재귀적인 수식으로 정의된다.

F(0) = 0, \quad F(1) = 1, \quad F(n) = F(n-1) + F(n-2) \quad \text{for } n \geq 2

액션 서버는 이러한 피보나치 수열을 계산하여 클라이언트에게 피드백을 제공한다.

4. 액션 클라이언트 구현

클라이언트는 서버에 작업을 요청하고, 그 작업의 진행 상황을 실시간으로 모니터링할 수 있다.

#include <rclcpp/rclcpp.hpp>
#include <rclcpp_action/rclcpp_action.hpp>
#include <example_interfaces/action/fibonacci.hpp>

class ActionClientNode : public rclcpp::Node
{
public:
  using Fibonacci = example_interfaces::action::Fibonacci;
  using GoalHandleFibonacci = rclcpp_action::ClientGoalHandle<Fibonacci>;

  ActionClientNode() : Node("action_client_node")
  {
    this->client_ = rclcpp_action::create_client<Fibonacci>(this, "fibonacci");

    if (!this->client_->wait_for_action_server()) {
      RCLCPP_ERROR(this->get_logger(), "Action server not available");
      return;
    }

    auto goal_msg = Fibonacci::Goal();
    goal_msg.order = 10;

    RCLCPP_INFO(this->get_logger(), "Sending goal");
    auto send_goal_options = rclcpp_action::Client<Fibonacci>::SendGoalOptions();
    send_goal_options.feedback_callback =
      std::bind(&ActionClientNode::feedbackCallback, this, std::placeholders::_1, std::placeholders::_2);
    send_goal_options.result_callback =
      std::bind(&ActionClientNode::resultCallback, this, std::placeholders::_1);

    this->client_->async_send_goal(goal_msg, send_goal_options);
  }

private:
  rclcpp_action::Client<Fibonacci>::SharedPtr client_;

  void feedbackCallback(
    GoalHandleFibonacci::SharedPtr, const std::shared_ptr<const Fibonacci::Feedback> feedback)
  {
    RCLCPP_INFO(this->get_logger(), "Received feedback: %ld", feedback->sequence.back());
  }

  void resultCallback(const GoalHandleFibonacci::WrappedResult & result)
  {
    switch (result.code) {
      case rclcpp_action::ResultCode::SUCCEEDED:
        RCLCPP_INFO(this->get_logger(), "Goal succeeded");
        break;
      case rclcpp_action::ResultCode::CANCELED:
        RCLCPP_INFO(this->get_logger(), "Goal canceled");
        break;
      default:
        RCLCPP_INFO(this->get_logger(), "Goal failed");
        break;
    }
    RCLCPP_INFO(this->get_logger(), "Result: %ld", result.result->sequence.back());
  }
};

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

5. 주요 부분 설명

6. 수식 관련

피드백으로 받는 수열의 값은 피보나치 수열의 각 항이다. F(n)은 서버에서 계산되어 클라이언트로 전달된다.