액션 서버와 클라이언트의 역할
ROS2 액션 서버와 클라이언트는 비동기 작업을 수행하기 위해 사용된다. 액션은 서비스와 비슷한 개념이지만, 상태를 관리하고 중간 피드백을 제공하는 등 더 복잡한 작업을 지원한다. 액션 서버는 실제 작업을 처리하는 주체이며, 클라이언트는 액션 서버에 작업을 요청하고 결과를 받는 역할을 한다.
액션 서버 구조
액션 서버는 특정 작업을 처리하는 동안 여러 상태를 관리하고, 클라이언트에게 중간 결과를 피드백하며 최종 결과를 반환하는 구조로 이루어진다. 액션 서버는 다음과 같은 주요 구성 요소를 갖는다:
- Goal (목표): 클라이언트로부터 요청받은 작업의 목표
- Feedback (피드백): 서버가 클라이언트에 작업의 중간 상태를 전송
- Result (결과): 작업이 완료되었을 때 클라이언트에 전송되는 최종 결과
액션 클라이언트 구조
액션 클라이언트는 액션 서버와 비동기적으로 상호작용하며, 서버에 목표를 전송하고 상태, 피드백, 결과를 비동기적으로 수신한다. 클라이언트는 주로 다음과 같은 단계로 액션을 처리한다:
- Send Goal (목표 전송): 클라이언트가 서버에 목표를 전송
- Receive Feedback (피드백 수신): 작업 진행 중에 서버로부터 중간 피드백을 수신
- Receive Result (결과 수신): 작업이 완료되었을 때 서버로부터 최종 결과를 수신
액션의 상태 관리
액션의 상태는 다양한 단계로 나뉜다. ROS2 액션 서버와 클라이언트는 액션의 진행 상황을 관리하기 위해 상태 변화를 지원한다. 주로 다음과 같은 상태를 갖는다:
- Pending: 목표가 서버에 도달하여 대기 중인 상태
- Active: 서버가 목표를 받아들이고 작업을 진행 중인 상태
- Succeeded: 작업이 성공적으로 완료된 상태
- Canceled: 클라이언트가 작업을 취소한 상태
- Aborted: 작업이 실패한 상태
액션 통신 과정
위 시퀀스 다이어그램은 액션 서버와 클라이언트 간의 통신 과정을 나타낸다. 클라이언트는 목표를 서버에 전송한 후, 서버로부터 피드백과 결과를 수신한다. 이 과정은 비동기적으로 이루어지며, 클라이언트는 서버의 상태에 따라 응답을 받는다.
액션 상태 전이
액션 서버와 클라이언트 간의 통신에서 중요한 부분 중 하나는 액션의 상태 전이이다. 액션은 여러 상태로 전환될 수 있으며, 이는 액션의 수행 여부에 따라 결정된다. 각 상태 전이는 액션의 진행 상태를 클라이언트에게 알려주고, 클라이언트는 그에 따라 적절한 대응을 할 수 있다.
액션의 상태 전이는 다음과 같은 흐름을 따른다:
- Pending 상태에서 Active로 전환: 서버가 클라이언트로부터 목표를 수락하여 작업을 시작하는 순간 발생한다.
- Active 상태에서 Succeeded 또는 Aborted로 전환: 작업이 성공적으로 완료되면 Succeeded로, 작업이 실패하면 Aborted로 전환된다.
- Pending 또는 Active 상태에서 Canceled로 전환: 클라이언트가 작업을 취소했을 때 발생한다.
상태 전이 다이어그램
이 상태 다이어그램은 액션의 상태 전이를 설명한다. 각각의 상태는 작업의 진행 상황에 따라 변하며, 클라이언트는 각 상태에 대해 서버와 상호작용할 수 있다.
ROS2 액션 서버와 클라이언트 구현
액션 서버
액션 서버는 rclcpp
또는 rclpy
라이브러리를 사용하여 구현할 수 있으며, 서버는 클라이언트로부터 목표를 수신하고 처리 후 피드백과 결과를 전송한다. 액션 서버의 주요 구성 요소는 다음과 같다:
- ActionServer 클래스: 서버가 동작하는 기본 클래스
- execute_callback 함수: 목표를 처리하는 핵심 함수
- feedback_callback 함수: 클라이언트로 피드백을 보내는 함수
- result_callback 함수: 작업 완료 후 최종 결과를 반환하는 함수
예를 들어, C++로 작성된 액션 서버는 다음과 같이 동작한다:
#include <rclcpp/rclcpp.hpp>
#include <example_interfaces/action/fibonacci.hpp>
class FibonacciActionServer : public rclcpp::Node {
public:
FibonacciActionServer()
: Node("fibonacci_action_server") {
action_server_ = rclcpp_action::create_server<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));
}
private:
rclcpp_action::Server<Fibonacci>::SharedPtr action_server_;
};
액션 클라이언트
액션 클라이언트는 서버에 목표를 전송하고 피드백과 결과를 비동기적으로 수신하는 역할을 한다. 클라이언트는 주로 send_goal
함수와 콜백을 통해 서버와 상호작용한다.
예를 들어, 파이썬으로 작성된 액션 클라이언트는 다음과 같이 동작한다:
import rclpy
from rclpy.action import ActionClient
from example_interfaces.action import Fibonacci
class FibonacciActionClient(Node):
def __init__(self):
super().__init__('fibonacci_action_client')
self._action_client = ActionClient(self, Fibonacci, 'fibonacci')
def send_goal(self, order):
goal_msg = Fibonacci.Goal()
goal_msg.order = order
self._action_client.send_goal_async(goal_msg, feedback_callback=self.feedback_callback)
이와 같이, 클라이언트는 서버에 목표를 전송하고, 피드백을 수신하는 역할을 수행한다.
액션 서버의 실행 흐름
액션 서버는 클라이언트로부터 목표를 수신한 후, 해당 목표를 처리하는 동안 피드백을 제공하고 최종 결과를 반환하는 구조로 동작한다. 이 과정에서 서버는 클라이언트로부터의 요청을 비동기적으로 처리한다.
목표 수신과 처리
액션 서버가 목표를 수신하면, 해당 목표는 handle_goal
함수에 의해 처리된다. 이 함수는 클라이언트로부터 전송된 목표를 평가하여 수락 여부를 결정한다. 수락된 목표는 execute_callback
함수에서 처리되며, 서버는 목표를 실행하는 동안 지속적으로 피드백을 전송할 수 있다.
rclcpp_action::GoalResponse handle_goal(
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);
if (goal->order > 0) {
return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
} else {
return rclcpp_action::GoalResponse::REJECT;
}
}
위 코드에서 handle_goal
함수는 클라이언트로부터 받은 목표를 확인하고, 목표를 수락하거나 거부한다. 목표가 수락되면 서버는 execute_callback
함수를 통해 실제 작업을 처리한다.
피드백 전송
작업이 진행되는 동안 서버는 클라이언트에게 중간 피드백을 전송할 수 있다. 피드백은 feedback_callback
함수를 통해 클라이언트에 전송되며, 이 과정에서 서버는 작업의 진행 상황을 클라이언트에 알릴 수 있다.
void execute_callback(const std::shared_ptr<rclcpp_action::ServerGoalHandle<Fibonacci>> goal_handle) {
const auto goal = goal_handle->get_goal();
auto feedback = std::make_shared<Fibonacci::Feedback>();
auto &sequence = feedback->partial_sequence;
sequence.push_back(0);
sequence.push_back(1);
for (int i = 2; (i < goal->order) && rclcpp::ok(); ++i) {
if (goal_handle->is_canceling()) {
goal_handle->canceled(result);
return;
}
sequence.push_back(sequence[i - 1] + sequence[i - 2]);
goal_handle->publish_feedback(feedback);
rclcpp::sleep_for(std::chrono::milliseconds(500));
}
}
이 예제에서는 피드백을 통해 클라이언트에 피보나치 수열의 중간 값을 지속적으로 전송하고 있다. 서버는 publish_feedback
메소드를 사용하여 피드백을 클라이언트에 전송하며, 클라이언트는 이를 통해 작업의 진행 상황을 확인할 수 있다.
액션 클라이언트의 실행 흐름
액션 클라이언트는 서버에 목표를 전송하고, 피드백과 결과를 비동기적으로 수신한다. 클라이언트는 목표 전송과 함께 비동기적으로 피드백을 수신할 준비를 하며, 작업이 완료되면 최종 결과를 수신한다.
목표 전송
액션 클라이언트는 send_goal_async
함수를 통해 서버에 목표를 전송한다. 목표가 수락되면, 클라이언트는 서버와의 상호작용을 시작하며 피드백과 결과를 기다린다.
self._action_client.send_goal_async(
goal_msg,
feedback_callback=self.feedback_callback
).add_done_callback(self.goal_response_callback)
여기서 send_goal_async
함수는 비동기적으로 목표를 서버에 전송하며, 목표가 수락되면 goal_response_callback
함수가 호출된다.
피드백 수신
클라이언트는 feedback_callback
함수를 통해 서버로부터 중간 피드백을 수신할 수 있다. 피드백은 작업이 진행되는 동안 지속적으로 서버로부터 전송된다.
def feedback_callback(self, feedback_msg):
feedback = feedback_msg.feedback
self.get_logger().info('Received feedback: {0}'.format(feedback.partial_sequence))
위의 피드백 콜백 함수는 서버로부터 받은 피드백을 출력하는 역할을 한다. 클라이언트는 이 피드백을 통해 서버가 작업을 잘 진행하고 있는지 확인할 수 있다.
결과 수신
작업이 완료되면, 클라이언트는 서버로부터 최종 결과를 수신하게 된다. 클라이언트는 get_result_async
함수를 통해 비동기적으로 결과를 요청하며, 결과를 받은 후에는 이를 처리하는 콜백 함수가 호출된다.
def goal_response_callback(self, future):
goal_handle = future.result()
if not goal_handle.accepted:
self.get_logger().info('Goal rejected')
return
self.get_logger().info('Goal accepted')
self._get_result_future = goal_handle.get_result_async()
self._get_result_future.add_done_callback(self.get_result_callback)
여기서 goal_response_callback
함수는 서버가 목표를 수락했는지 여부를 확인하며, 목표가 수락된 경우 결과를 기다린다.