# 액션 서버와 클라이언트 구조

#### 액션 서버와 클라이언트의 역할

ROS2 액션 서버와 클라이언트는 비동기 작업을 수행하기 위해 사용된다. 액션은 서비스와 비슷한 개념이지만, 상태를 관리하고 중간 피드백을 제공하는 등 더 복잡한 작업을 지원한다. 액션 서버는 실제 작업을 처리하는 주체이며, 클라이언트는 액션 서버에 작업을 요청하고 결과를 받는 역할을 한다.

**액션 서버 구조**

액션 서버는 특정 작업을 처리하는 동안 여러 상태를 관리하고, 클라이언트에게 중간 결과를 피드백하며 최종 결과를 반환하는 구조로 이루어진다. 액션 서버는 다음과 같은 주요 구성 요소를 갖는다:

* **Goal (목표)**: 클라이언트로부터 요청받은 작업의 목표
* **Feedback (피드백)**: 서버가 클라이언트에 작업의 중간 상태를 전송
* **Result (결과)**: 작업이 완료되었을 때 클라이언트에 전송되는 최종 결과

**액션 클라이언트 구조**

액션 클라이언트는 액션 서버와 비동기적으로 상호작용하며, 서버에 목표를 전송하고 상태, 피드백, 결과를 비동기적으로 수신한다. 클라이언트는 주로 다음과 같은 단계로 액션을 처리한다:

* **Send Goal (목표 전송)**: 클라이언트가 서버에 목표를 전송
* **Receive Feedback (피드백 수신)**: 작업 진행 중에 서버로부터 중간 피드백을 수신
* **Receive Result (결과 수신)**: 작업이 완료되었을 때 서버로부터 최종 결과를 수신

#### 액션의 상태 관리

액션의 상태는 다양한 단계로 나뉜다. ROS2 액션 서버와 클라이언트는 액션의 진행 상황을 관리하기 위해 상태 변화를 지원한다. 주로 다음과 같은 상태를 갖는다:

* **Pending**: 목표가 서버에 도달하여 대기 중인 상태
* **Active**: 서버가 목표를 받아들이고 작업을 진행 중인 상태
* **Succeeded**: 작업이 성공적으로 완료된 상태
* **Canceled**: 클라이언트가 작업을 취소한 상태
* **Aborted**: 작업이 실패한 상태

#### 액션 통신 과정

{% @mermaid/diagram content="sequenceDiagram
participant Client
participant Server
Client->>Server: 목표 전송 (Send Goal)
Server-->>Client: 목표 수락 확인 (Goal Accepted)
Server-->>Client: 피드백 전송 (Feedback)
Server-->>Client: 피드백 전송 (Feedback)
Server-->>Client: 최종 결과 전송 (Result)" %}

위 시퀀스 다이어그램은 액션 서버와 클라이언트 간의 통신 과정을 나타낸다. 클라이언트는 목표를 서버에 전송한 후, 서버로부터 피드백과 결과를 수신한다. 이 과정은 비동기적으로 이루어지며, 클라이언트는 서버의 상태에 따라 응답을 받는다.

#### 액션 상태 전이

액션 서버와 클라이언트 간의 통신에서 중요한 부분 중 하나는 액션의 상태 전이이다. 액션은 여러 상태로 전환될 수 있으며, 이는 액션의 수행 여부에 따라 결정된다. 각 상태 전이는 액션의 진행 상태를 클라이언트에게 알려주고, 클라이언트는 그에 따라 적절한 대응을 할 수 있다.

액션의 상태 전이는 다음과 같은 흐름을 따른다:

* **Pending** 상태에서 **Active**로 전환: 서버가 클라이언트로부터 목표를 수락하여 작업을 시작하는 순간 발생한다.
* **Active** 상태에서 **Succeeded** 또는 **Aborted**로 전환: 작업이 성공적으로 완료되면 **Succeeded**로, 작업이 실패하면 **Aborted**로 전환된다.
* **Pending** 또는 **Active** 상태에서 **Canceled**로 전환: 클라이언트가 작업을 취소했을 때 발생한다.

**상태 전이 다이어그램**

{% @mermaid/diagram content="stateDiagram
\[*] --> Pending
Pending --> Active: 목표 수락
Pending --> Canceled: 목표 취소
Active --> Succeeded: 작업 완료
Active --> Aborted: 작업 실패
Active --> Canceled: 작업 중 취소
Canceled --> \[*]
Succeeded --> \[*]
Aborted --> \[*]" %}

이 상태 다이어그램은 액션의 상태 전이를 설명한다. 각각의 상태는 작업의 진행 상황에 따라 변하며, 클라이언트는 각 상태에 대해 서버와 상호작용할 수 있다.

#### ROS2 액션 서버와 클라이언트 구현

**액션 서버**

액션 서버는 `rclcpp` 또는 `rclpy` 라이브러리를 사용하여 구현할 수 있으며, 서버는 클라이언트로부터 목표를 수신하고 처리 후 피드백과 결과를 전송한다. 액션 서버의 주요 구성 요소는 다음과 같다:

* **ActionServer 클래스**: 서버가 동작하는 기본 클래스
* **execute\_callback 함수**: 목표를 처리하는 핵심 함수
* **feedback\_callback 함수**: 클라이언트로 피드백을 보내는 함수
* **result\_callback 함수**: 작업 완료 후 최종 결과를 반환하는 함수

예를 들어, C++로 작성된 액션 서버는 다음과 같이 동작한다:

```cpp
#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` 함수와 콜백을 통해 서버와 상호작용한다.

예를 들어, 파이썬으로 작성된 액션 클라이언트는 다음과 같이 동작한다:

```python
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` 함수에서 처리되며, 서버는 목표를 실행하는 동안 지속적으로 피드백을 전송할 수 있다.

```cpp
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` 함수를 통해 클라이언트에 전송되며, 이 과정에서 서버는 작업의 진행 상황을 클라이언트에 알릴 수 있다.

```cpp
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` 함수를 통해 서버에 목표를 전송한다. 목표가 수락되면, 클라이언트는 서버와의 상호작용을 시작하며 피드백과 결과를 기다린다.

```python
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` 함수를 통해 서버로부터 중간 피드백을 수신할 수 있다. 피드백은 작업이 진행되는 동안 지속적으로 서버로부터 전송된다.

```python
def feedback_callback(self, feedback_msg):
    feedback = feedback_msg.feedback
    self.get_logger().info('Received feedback: {0}'.format(feedback.partial_sequence))
```

위의 피드백 콜백 함수는 서버로부터 받은 피드백을 출력하는 역할을 한다. 클라이언트는 이 피드백을 통해 서버가 작업을 잘 진행하고 있는지 확인할 수 있다.

#### 결과 수신

작업이 완료되면, 클라이언트는 서버로부터 최종 결과를 수신하게 된다. 클라이언트는 `get_result_async` 함수를 통해 비동기적으로 결과를 요청하며, 결과를 받은 후에는 이를 처리하는 콜백 함수가 호출된다.

```python
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` 함수는 서버가 목표를 수락했는지 여부를 확인하며, 목표가 수락된 경우 결과를 기다린다.
