C++에서 ROS2 노드의 구조
ROS2에서 C++로 노드를 작성할 때는 rclcpp
라이브러리를 사용하여 노드를 생성하고, ROS2의 퍼블리셔(Publisher), 서브스크라이버(Subscriber), 서비스(Service), 액션(Action) 등을 관리할 수 있다. C++ 노드 작성의 주요 단계는 아래와 같다.
- 헤더 파일 포함
노드를 구현하기 위해 필요한 헤더 파일을 포함한다.
rclcpp
라이브러리의 핵심 구성 요소는rclcpp::Node
클래스를 상속하여 구현할 수 있다.
#include "rclcpp/rclcpp.hpp"
- 노드 클래스 정의
rclcpp::Node
클래스를 상속한 노드 클래스를 정의한다. 예시로, 퍼블리셔 노드를 정의하는 기본 구조는 아래와 같다.
class SimplePublisher : public rclcpp::Node {
public:
SimplePublisher()
: Node("simple_publisher") {
publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
timer_ = this->create_wall_timer(
std::chrono::milliseconds(500),
std::bind(&SimplePublisher::publish_message, this));
}
private:
void publish_message() {
auto message = std_msgs::msg::String();
message.data = "Hello, ROS2!";
publisher_->publish(message);
}
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
rclcpp::TimerBase::SharedPtr timer_;
};
이 코드는 rclcpp::Node
에서 상속된 기본적인 노드 구조를 갖추며, 노드 이름으로 "simple_publisher"를 정의하고 있다.
노드 초기화 및 실행
ROS2 노드는 rclcpp::init
과 rclcpp::spin
함수를 사용하여 초기화되고 실행된다. 아래와 같은 코드에서 노드 실행 환경을 설정할 수 있다.
int main(int argc, char *argv[]) {
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<SimplePublisher>());
rclcpp::shutdown();
return 0;
}
rclcpp::init
는 ROS2 노드 실행 환경을 초기화한다.rclcpp::spin
은 노드를 지속적으로 실행시키는 함수로, 이벤트 루프를 유지시킨다.rclcpp::shutdown
은 노드 실행을 종료할 때 호출된다.
퍼블리셔 및 서브스크라이버 구현
퍼블리셔는 노드가 메시지를 특정 토픽으로 내보내는 역할을 하고, 서브스크라이버는 특정 토픽의 메시지를 받아 처리하는 역할을 한다.
퍼블리셔
퍼블리셔는 특정 토픽에 대해 데이터를 발행할 수 있다. 퍼블리셔를 생성할 때 create_publisher
메서드를 사용하며, 퍼블리시할 메시지 타입을 명시해야 한다.
- 퍼블리셔는 큐 크기(10)를 지정하여 생성하며, 이는 메시지 버퍼의 크기를 의미한다.
- 메시지는
std_msgs::msg::String
형식으로 발행된다.
publisher_ = this->create_publisher<std_msgs::msg::String>("topic_name", 10);
서브스크라이버
서브스크라이버는 특정 토픽의 데이터를 수신하고, 이를 처리하기 위해 콜백 함수를 정의한다. 서브스크라이버를 생성할 때 create_subscription
메서드를 사용하며, 수신할 메시지 타입을 명시해야 한다.
subscription_ = this->create_subscription<std_msgs::msg::String>(
"topic_name", 10,
std::bind(&SimpleSubscriber::handle_message, this, std::placeholders::_1));
std::bind
는 콜백 함수로handle_message
를 설정한다._1
은 토픽에서 수신한 메시지의 매개변수로, 콜백 함수로 전달된다.
퍼블리셔-서브스크라이버 관계 시각화
퍼블리셔와 서브스크라이버 간의 관계는 다음과 같이 시각화할 수 있다.
퍼블리셔는 메시지를 발행하며, 서브스크라이버는 해당 메시지를 구독하여 처리한다.
QoS 설정
퍼블리셔와 서브스크라이버의 통신 품질을 제어하기 위해 QoS(품질 서비스)를 설정할 수 있다. QoS는 네트워크 상태에 따라 메시지의 신뢰성이나 내구성을 조정하는 데 사용된다.
- 신뢰성(reliability): 메시지의 신뢰도를 설정할 수 있다.
RMW_QOS_POLICY_RELIABILITY_RELIABLE
: 신뢰성 있는 전송을 보장.-
RMW_QOS_POLICY_RELIABILITY_BEST_EFFORT
: 가능한 한 빠른 전송을 보장. -
내구성(durability): 메시지의 내구성을 설정할 수 있다.
RMW_QOS_POLICY_DURABILITY_TRANSIENT_LOCAL
: 노드가 실행 중일 때 메시지를 유지.RMW_QOS_POLICY_DURABILITY_VOLATILE
: 노드가 재시작 시 메시지를 유지하지 않음.
auto qos = rclcpp::QoS(rclcpp::KeepLast(10)).reliability(RMW_QOS_POLICY_RELIABILITY_RELIABLE);
publisher_ = this->create_publisher<std_msgs::msg::String>("topic_name", qos);
퍼블리셔 및 서브스크라이버 구현 예시
퍼블리셔와 서브스크라이버를 활용한 예시는 아래와 같다.
// 퍼블리셔 예시
class MinimalPublisher : public rclcpp::Node {
public:
MinimalPublisher() : Node("minimal_publisher") {
publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
timer_ = this->create_wall_timer(
std::chrono::milliseconds(500),
std::bind(&MinimalPublisher::publish_message, this));
}
private:
void publish_message() {
auto message = std_msgs::msg::String();
message.data = "Hello, ROS2!";
publisher_->publish(message);
}
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
rclcpp::TimerBase::SharedPtr timer_;
};
// 서브스크라이버 예시
class MinimalSubscriber : public rclcpp::Node {
public:
MinimalSubscriber() : Node("minimal_subscriber") {
subscription_ = this->create_subscription<std_msgs::msg::String>(
"topic", 10,
std::bind(&MinimalSubscriber::handle_message, this, std::placeholders::_1));
}
private:
void handle_message(const std_msgs::msg::String::SharedPtr msg) {
RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};
퍼블리셔는 0.5초마다 "Hello, ROS2!" 메시지를 발행하고, 서브스크라이버는 이 메시지를 수신하여 출력한다.
퍼블리셔-서브스크라이버 성능 최적화
퍼블리셔와 서브스크라이버의 성능을 최적화하려면 네트워크 트래픽을 줄이고 메시지 처리 시간을 최소화해야 한다. 주요 방법으로는 다음과 같은 사항이 있다.
- 메시지 주기 최적화: 불필요한 메시지 발행을 줄이고, 필요한 메시지 주기를 적절히 설정한다.
- QoS 정책 조정:
QoS
설정을 통해 네트워크 상태에 맞춰 신뢰성과 내구성을 조정한다. - 병렬 처리: 멀티스레딩을 적용하여 메시지 수신과 처리를 병렬로 처리함으로써 성능을 향상시킬 수 있다.
this->create_subscription<std_msgs::msg::String>(
"topic_name", 10, rclcpp::QoS(rclcpp::KeepLast(10)),
[this](const std_msgs::msg::String::SharedPtr msg) {
// 메시지 처리 코드
});
노드의 멀티스레딩 적용
멀티스레딩은 노드의 성능을 최적화하는 데 중요한 요소이다. ROS2는 기본적으로 싱글 스레드로 동작하지만, 멀티스레드를 지원하는 실행기를 사용할 수 있다.
rclcpp::executors::MultiThreadedExecutor executor;
executor.add_node(node);
executor.spin();
위 코드에서는 멀티스레딩 실행기를 사용하여, 여러 콜백 함수가 병렬로 실행될 수 있도록 설정한다.
서비스와 액션의 C++ 노드에서의 구현
ROS2에서 서비스와 액션은 C++로도 구현할 수 있다. 서비스는 요청-응답 형태로 동작하고, 액션은 더 복잡한 비동기 작업을 수행할 수 있도록 설계된다. 이 섹션에서는 서비스와 액션을 rclcpp
기반으로 C++에서 어떻게 구현할 수 있는지 설명한다.
서비스 구현
서비스는 클라이언트가 서버에 요청을 보내고 응답을 받는 방식으로 동작한다. C++에서 서비스 서버와 클라이언트를 각각 구현할 수 있다.
- 서비스 서버 생성
서버는 클라이언트의 요청을 처리하기 위해 create_service
메서드를 사용하여 생성된다.
service_ = this->create_service<example_interfaces::srv::AddTwoInts>(
"add_two_ints", std::bind(&SimpleService::handle_service, this, std::placeholders::_1, std::placeholders::_2));
example_interfaces::srv::AddTwoInts
는 서비스 타입으로, 두 개의 정수를 더하는 서비스의 메시지 타입을 나타낸다.-
handle_service
는 클라이언트의 요청을 처리할 콜백 함수이다. -
서비스 콜백 함수 구현
서비스 요청을 처리하는 콜백 함수는 요청과 응답을 매개변수로 받아서 구현된다.
void handle_service(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(), "Incoming request: a=%ld, b=%ld", request->a, request->b);
}
request
는 클라이언트로부터 받은 데이터를 포함하고,response
는 응답을 클라이언트에게 전송하는 역할을 한다.request->a
,request->b
는 요청에 포함된 두 개의 정수를 의미한다.response->sum
은 클라이언트에게 전송할 합계를 의미한다.
서비스 클라이언트 생성
서비스 클라이언트는 서버에 요청을 보내고 응답을 기다린다. create_client
메서드를 통해 클라이언트를 생성한다.
auto client = this->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");
-
example_interfaces::srv::AddTwoInts
는 서비스 메시지 타입이다. -
서비스 요청 생성 및 호출
서비스 클라이언트는 요청을 생성하고 서버에 전송한다.
auto request = std::make_shared<example_interfaces::srv::AddTwoInts::Request>();
request->a = 5;
request->b = 3;
auto result = client->async_send_request(request);
request->a
,request->b
는 서버로 전송할 요청 값이다.async_send_request
는 비동기 요청을 전송한다. 요청이 완료되면result
로 응답을 받는다.
액션 구현
액션은 비동기 작업을 관리하며, 장시간에 걸쳐 진행되는 작업에 적합하다. 액션은 서버와 클라이언트로 나뉘며, 서버는 작업을 수행하고 클라이언트는 작업의 상태와 결과를 모니터링한다.
- 액션 서버 생성
액션 서버는 클라이언트가 요청한 작업을 수행한다. create_action_server
메서드를 사용하여 액션 서버를 생성한다.
action_server_ = rclcpp_action::create_server<example_interfaces::action::Fibonacci>(
this,
"fibonacci",
std::bind(&FibonacciActionServer::handle_goal, this, std::placeholders::_1, std::placeholders::_2),
std::bind(&FibonacciActionServer::handle_cancel, this, std::placeholders::_1),
std::bind(&FibonacciActionServer::handle_accepted, this, std::placeholders::_1));
example_interfaces::action::Fibonacci
는 액션 타입으로, 피보나치 수열을 생성하는 액션을 나타낸다.handle_goal
은 클라이언트의 목표를 처리하는 함수이다.handle_cancel
은 클라이언트가 작업을 취소할 때 호출된다.-
handle_accepted
는 목표가 수락되었을 때 호출된다. -
액션 서버의 작업 처리
서버는 클라이언트의 요청을 처리하며, 작업을 수행하는 동안 피드백을 전송할 수 있다.
void execute_goal(const std::shared_ptr<GoalHandleFibonacci> goal_handle) {
const auto goal = goal_handle->get_goal();
auto feedback = std::make_shared<example_interfaces::action::Fibonacci::Feedback>();
auto result = std::make_shared<example_interfaces::action::Fibonacci::Result>();
feedback->sequence.push_back(0);
feedback->sequence.push_back(1);
for (int i = 1; (i < goal->order) && rclcpp::ok(); ++i) {
feedback->sequence.push_back(feedback->sequence[i] + feedback->sequence[i - 1]);
goal_handle->publish_feedback(feedback);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
result->sequence = feedback->sequence;
goal_handle->succeed(result);
}
feedback
은 피드백 메시지로, 진행 중인 작업의 상태를 클라이언트에게 전송한다.result
는 작업 완료 후 결과를 클라이언트에 전송하는 역할을 한다.
액션 클라이언트 생성
액션 클라이언트는 서버에 작업을 요청하고, 작업의 상태와 피드백을 확인할 수 있다. create_client
메서드를 사용하여 액션 클라이언트를 생성한다.
auto action_client = rclcpp_action::create_client<example_interfaces::action::Fibonacci>(node, "fibonacci");
- 액션 요청 전송
클라이언트는 서버에 액션 요청을 전송한다.
auto goal_msg = example_interfaces::action::Fibonacci::Goal();
goal_msg.order = 10;
auto goal_handle_future = action_client->async_send_goal(goal_msg);
goal_msg
는 서버에 요청할 목표값이다.-
async_send_goal
은 비동기 방식으로 서버에 목표를 전송하고, 결과를 기다린다. -
액션 피드백 처리
클라이언트는 서버로부터 피드백을 수신할 수 있다.
action_client->async_get_feedback([&](auto feedback) {
RCLCPP_INFO(node->get_logger(), "Received feedback: %d", feedback->sequence.back());
});
- 서버로부터 피드백이 수신될 때마다 콜백 함수가 호출되어 현재 상태를 모니터링할 수 있다.