# 토픽 퍼블리싱 및 구독

#### 토픽이란?

ROS2에서 **토픽**(Topic)은 노드 간의 데이터 통신을 위한 주요 메커니즘 중 하나이다. 퍼블리셔(Publisher)가 특정 주제에 대해 데이터를 보내면, 서브스크라이버(Subscriber)가 해당 주제를 구독하고 데이터를 수신하는 구조로 동작한다. **토픽**은 노드 간의 비동기적 통신을 가능하게 하며, 분산된 시스템 환경에서 유용하게 활용된다.

#### 퍼블리셔와 서브스크라이버의 역할

**퍼블리셔**는 특정 주제에 대해 데이터를 발행하는 노드의 구성 요소이다. 이 데이터는 주로 센서 데이터, 로봇 상태 정보, 명령 등이 될 수 있다. 반면, **서브스크라이버**는 해당 주제를 구독하고 데이터를 수신하는 노드이다. 서브스크라이버는 퍼블리셔가 발행한 데이터를 실시간으로 받으며, 이를 통해 노드 간의 정보 교환이 이루어진다.

#### 퍼블리셔의 구현

ROS2에서 퍼블리셔를 구현하는 과정은 간단하다. 퍼블리셔 객체를 생성하고, 특정 토픽에 대해 데이터를 발행하면 된다. 일반적으로 퍼블리셔는 `Publisher` 클래스를 통해 생성되며, 생성자에서 토픽 이름과 메시지 타입을 지정한다. 퍼블리셔는 주기적으로 데이터를 발행하거나 특정 이벤트에 의해 데이터를 발행할 수 있다.

퍼블리셔의 기본 구조는 다음과 같다:

```cpp
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher;
publisher = this->create_publisher<std_msgs::msg::String>("topic_name", 10);
```

위 코드는 "topic\_name"이라는 이름의 주제를 구독하는 퍼블리셔를 생성하며, 큐 크기는 10으로 설정된다.

#### 서브스크라이버의 구현

ROS2에서 **서브스크라이버**는 퍼블리셔와 비슷하게 구현된다. 서브스크라이버는 특정 토픽을 구독하고 해당 데이터를 처리하는 콜백 함수를 설정한다. 서브스크라이버는 `Subscription` 클래스를 통해 생성되며, 생성자에서 토픽 이름과 메시지 타입을 지정하고, 콜백 함수를 설정한다.

서브스크라이버의 기본 구조는 다음과 같다:

```cpp
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription;
subscription = this->create_subscription<std_msgs::msg::String>(
  "topic_name", 10, std::bind(&NodeClass::topic_callback, this, _1));
```

위 코드는 "topic\_name"이라는 주제를 구독하는 서브스크라이버를 생성하며, 콜백 함수는 `topic_callback`으로 설정된다.

#### 퍼블리싱과 구독의 메시지 구조

ROS2의 메시지 구조는 매우 유연하다. 기본적으로 제공되는 메시지 타입 외에도 사용자가 원하는 데이터 구조를 가진 메시지를 직접 정의할 수 있다. 사용자 정의 메시지는 `.msg` 파일을 통해 정의되며, ROS2의 빌드 시스템에서 자동으로 처리된다. 예를 들어, `std_msgs::msg::String`과 같은 기본 메시지 타입은 문자열 데이터를 처리하는 메시지이며, 이는 ROS2에서 기본적으로 제공된다.

사용자 정의 메시지는 아래와 같이 정의된다:

```plaintext
# CustomMessage.msg
int32 id
string data
```

이 메시지는 `id`와 `data`라는 두 필드를 가지는 구조체 형식으로, ROS2 퍼블리셔와 서브스크라이버 간에 이 데이터를 주고받을 수 있다.

#### 퍼블리셔와 서브스크라이버의 상호작용

퍼블리셔와 서브스크라이버가 상호작용하는 과정에서 중요한 요소는 **QoS(품질 서비스, Quality of Service)** 정책이다. QoS는 퍼블리셔와 서브스크라이버 간의 통신 품질을 보장하기 위한 설정이며, 네트워크 상황에 따라 데이터를 유실하거나 지연되는 상황을 제어한다.

예를 들어, 퍼블리셔는 QoS 정책을 설정하여 데이터를 손실 없이 보장된 방식으로 전달할 수 있으며, 서브스크라이버는 수신한 데이터의 신뢰성을 높일 수 있다. ROS2에서 제공하는 QoS 정책에는 다음과 같은 항목이 포함된다:

* **Reliability**: 신뢰성을 보장하는지 여부
* **Durability**: 퍼블리싱된 데이터가 구독 전후로도 유지되는지 여부
* **History**: 퍼블리싱된 데이터의 기록 방식

```cpp
rclcpp::QoS qos(rclcpp::KeepLast(10));
qos.reliability(RMW_QOS_POLICY_RELIABILITY_RELIABLE);
```

위 코드에서 **QoS**는 **RMW\_QOS\_POLICY\_RELIABILITY\_RELIABLE**을 설정하여 신뢰성 높은 데이터를 보장한다.

#### 메시지의 직렬화 및 역직렬화 과정

메시지의 **직렬화**(Serialization)는 퍼블리셔가 발행한 데이터를 바이트 스트림으로 변환하여 네트워크를 통해 전송하는 과정이며, **역직렬화**(Deserialization)는 서브스크라이버가 수신한 바이트 스트림을 원래의 메시지 구조로 복원하는 과정이다.

이 과정을 수식으로 표현하면, 메시지 $\mathbf{m}$는 직렬화 함수 $f\_s(\mathbf{m})$를 통해 바이트 스트림 $\mathbf{b}$로 변환되며,

$$
f\_s(\mathbf{m}) = \mathbf{b}
$$

역직렬화 함수 $f\_d(\mathbf{b})$는 바이트 스트림 $\mathbf{b}$를 다시 메시지 $\mathbf{m}$로 복원한다.

$$
f\_d(\mathbf{b}) = \mathbf{m}
$$

#### 퍼블리싱 주기와 실시간성

퍼블리셔는 특정 주기에 따라 데이터를 발행할 수 있다. 이는 주기적인 센서 데이터 송신이나, 특정 이벤트가 발생할 때마다 데이터를 송신하는 경우에 활용된다. 퍼블리싱 주기는 타이머를 사용하여 설정할 수 있으며, 실시간성이 요구되는 환경에서는 주기적인 퍼블리싱이 중요한 역할을 한다.

타이머를 이용하여 퍼블리셔의 주기를 설정하는 기본 구조는 다음과 같다:

```cpp
auto timer_callback = [this]() -> void {
  auto message = std_msgs::msg::String();
  message.data = "Hello World";
  publisher_->publish(message);
};
timer_ = this->create_wall_timer(500ms, timer_callback);
```

위 코드는 500ms 주기로 **"Hello World"** 메시지를 퍼블리싱하는 예제이다. 타이머를 사용하여 퍼블리셔의 주기를 설정하는 방식은, 특히 실시간 시스템에서 유용하게 쓰인다.

#### 서브스크라이버의 콜백 함수

서브스크라이버는 데이터를 수신하면 설정된 콜백 함수를 호출하여 해당 데이터를 처리한다. 콜백 함수는 **람다 함수**로 설정되거나 별도의 함수로 정의될 수 있다. 콜백 함수는 수신한 메시지를 처리하는 로직을 포함해야 하며, 퍼포먼스나 실시간성 요구사항에 맞게 설계되어야 한다.

서브스크라이버 콜백 함수 예제는 다음과 같다:

```cpp
void topic_callback(const std_msgs::msg::String::SharedPtr msg) const {
  RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
}
```

이 코드는 `msg` 객체로 수신한 데이터를 콘솔에 출력하는 간단한 예제이다. 콜백 함수는 네트워크 지연이나 데이터 손실이 발생할 수 있는 상황을 고려하여 설계되어야 하며, 데이터 처리 시간이 길어지지 않도록 최적화가 필요하다.

#### 토픽의 데이터 흐름

토픽의 퍼블리셔와 서브스크라이버 간의 데이터 흐름은 일반적으로 다음과 같은 과정으로 이루어진다:

1. 퍼블리셔는 **토픽**을 통해 데이터를 발행한다.
2. 서브스크라이버는 해당 토픽을 구독하고, 발행된 데이터를 수신한다.
3. 서브스크라이버는 수신된 데이터를 **콜백 함수**를 통해 처리한다.
4. 데이터는 실시간으로 처리될 수도 있고, 특정 로직에 따라 저장되거나 다른 노드로 전송될 수도 있다.

이 데이터 흐름은 분산된 시스템에서 매우 중요한 역할을 하며, 노드 간의 독립성을 보장한다.

#### 메시지 타입과 데이터 타입

ROS2에서는 다양한 **메시지 타입**을 제공한다. 기본적인 메시지 타입으로는 `std_msgs::msg::String`, `sensor_msgs::msg::Image`, `geometry_msgs::msg::Pose` 등이 있으며, 각각의 메시지는 특정 데이터 구조를 가진다. 예를 들어, `geometry_msgs::msg::Pose`는 로봇의 위치와 방향을 나타내는 메시지로, **위치 벡터**와 **회전 행렬**로 구성된다.

위치 벡터 $\mathbf{p}$와 회전 행렬 $\mathbf{R}$는 다음과 같이 표현된다:

$$
\mathbf{p} = \begin{bmatrix} x \ y \ z \end{bmatrix}, \quad \mathbf{R} = \begin{bmatrix} r\_{11} & r\_{12} & r\_{13} \ r\_{21} & r\_{22} & r\_{23} \ r\_{31} & r\_{32} & r\_{33} \end{bmatrix}
$$

메시지 타입에 따라 다양한 데이터 타입을 지원하며, 사용자 정의 메시지를 통해 로봇의 특정 요구사항에 맞는 데이터를 효율적으로 주고받을 수 있다.

#### 메시지 전달의 신뢰성

퍼블리셔와 서브스크라이버 간의 통신에서 중요한 요소 중 하나는 **메시지 전달의 신뢰성**이다. ROS2에서는 DDS(Data Distribution Service) 프로토콜을 사용하여 메시지 전달의 신뢰성을 보장한다. DDS는 퍼블리셔가 발행한 메시지를 서브스크라이버가 누락 없이 수신할 수 있도록 보장하며, 네트워크 지연이나 데이터 손실을 최소화하는 다양한 QoS 정책을 제공한다.

#### 메시지 큐와 데이터 손실 방지

서브스크라이버는 퍼블리셔로부터 발행된 데이터를 큐(queue)에 저장하여 처리한다. 큐의 크기는 서브스크라이버 생성 시 지정할 수 있으며, 너무 작은 큐 크기를 설정하면 데이터가 손실될 수 있고, 너무 큰 큐는 메모리 자원을 과도하게 사용하게 된다. 큐 크기는 시스템의 자원과 성능을 고려하여 적절히 설정해야 한다.

큐 크기 설정 예시는 다음과 같다:

```cpp
auto qos = rclcpp::QoS(rclcpp::KeepLast(10));
subscription = this->create_subscription<std_msgs::msg::String>(
  "topic_name", qos, std::bind(&NodeClass::topic_callback, this, _1));
```

위 예시에서 `KeepLast(10)`은 최근 10개의 메시지를 큐에 저장하도록 설정하는 QoS 정책이다. 이를 통해 데이터 손실을 방지하고, 서브스크라이버가 처리할 수 있는 만큼의 데이터를 수신할 수 있다.

#### 토픽 퍼포먼스 최적화

토픽 퍼포먼스를 최적화하려면 다음 요소들을 고려해야 한다:

* **큐 크기**: 시스템 성능에 맞게 적절한 큐 크기를 설정하여 데이터 손실을 방지한다.
* **QoS 정책**: 신뢰성, 지속성, 히스토리 등의 QoS 정책을 상황에 맞게 설정하여 데이터 통신의 품질을 보장한다.
* **콜백 함수 최적화**: 콜백 함수에서 불필요한 연산을 줄이고, 비동기 처리를 통해 실시간성을 높인다.
* **타이머 주기 조절**: 퍼블리싱 주기를 시스템의 처리 능력에 맞게 조절하여 과부하를 방지한다.

ROS2의 퍼블리셔와 서브스크라이버는 위와 같은 다양한 최적화 기법을 통해 성능을 극대화할 수 있다.

####
