토픽이란?

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

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

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

퍼블리셔의 구현

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

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

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

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

서브스크라이버의 구현

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

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

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에서 기본적으로 제공된다.

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

# CustomMessage.msg
int32 id
string data

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

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

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

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

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

위 코드에서 QoSRMW_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}

퍼블리싱 주기와 실시간성

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

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

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" 메시지를 퍼블리싱하는 예제이다. 타이머를 사용하여 퍼블리셔의 주기를 설정하는 방식은, 특히 실시간 시스템에서 유용하게 쓰인다.

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

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

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

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)에 저장하여 처리한다. 큐의 크기는 서브스크라이버 생성 시 지정할 수 있으며, 너무 작은 큐 크기를 설정하면 데이터가 손실될 수 있고, 너무 큰 큐는 메모리 자원을 과도하게 사용하게 된다. 큐 크기는 시스템의 자원과 성능을 고려하여 적절히 설정해야 한다.

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

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 정책이다. 이를 통해 데이터 손실을 방지하고, 서브스크라이버가 처리할 수 있는 만큼의 데이터를 수신할 수 있다.

토픽 퍼포먼스 최적화

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

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