메시지 타입 정의

ROS2에서 데이터는 기본적으로 메시지 형태로 노드 간에 전송되며, 이러한 메시지 타입은 미리 정의되어 있다. ROS2는 다양한 기본 메시지 타입을 제공하며, 이들은 각 노드 간의 데이터를 주고받는 형식으로 사용된다. 기본적으로 제공되는 메시지 타입으로는 다음과 같은 것들이 있다:

각 메시지 타입은 하나 이상의 필드를 가지고 있으며, 필드는 다양한 데이터 타입으로 구성된다. 필드의 데이터 타입으로는 기본 자료형(정수, 실수, 문자열 등)과 복합 자료형(배열, 구조체 등)이 있으며, 이러한 필드를 결합하여 특정 메시지 타입을 정의할 수 있다.

사용자 정의 메시지 생성

기본적으로 제공되는 메시지 타입만으로는 모든 요구를 만족시키지 못할 수 있다. 이 경우, ROS2는 사용자 정의 메시지를 생성할 수 있는 기능을 제공한다. 사용자 정의 메시지는 특정 애플리케이션의 필요에 따라 데이터를 표현하기 위해 새롭게 정의된 메시지 타입이다.

메시지 정의 파일 (.msg)

사용자 정의 메시지를 생성하려면 .msg 파일을 작성해야 한다. .msg 파일은 메시지 타입의 구조를 정의하는 파일로, 메시지 내 필드와 그 타입을 선언한다. .msg 파일의 구조는 매우 간단한다:

# 예시 .msg 파일
int32 id
string name
float64[3] position

위의 예시는 id, name, 그리고 position이라는 세 가지 필드를 가진 메시지를 정의한 것이다. 이 중 idint32 타입, namestring 타입, 그리고 positionfloat64 배열(크기 3)이다.

이러한 메시지를 정의한 후, 빌드 시스템을 통해 해당 메시지 타입을 생성하게 된다.

CMakeLists.txt와 package.xml 설정

사용자 정의 메시지를 사용하려면 ROS2 패키지의 CMakeLists.txtpackage.xml 파일을 적절히 수정해야 한다.

CMakeLists.txt 파일에서, 사용자 정의 메시지를 추가하려면 rosidl_generate_interfaces() 함수를 사용하여 메시지를 컴파일하도록 설정한다. 예를 들어:

rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/CustomMessage.msg"
)

package.xml 파일에서도 메시지 관련 의존성을 추가해야 한다. 주로 build_depend, exec_depend에 메시지 타입을 추가하는 것이 일반적이다:

<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>

이 설정을 완료한 후, 패키지를 빌드하면 ROS2는 정의된 메시지 타입에 따라 인터페이스 코드를 생성하게 된다.

메시지 타입 컴파일

사용자 정의 메시지를 생성하기 위해서는 ROS2의 빌드 도구인 colcon을 사용하여 패키지를 빌드해야 한다. ROS2는 colcon을 통해 메시지 타입에 대한 인터페이스 파일을 자동으로 생성한다. 다음과 같은 명령어를 사용하여 패키지를 빌드한다:

colcon build

이 빌드 과정에서 .msg 파일을 기반으로 하여 C++와 Python 코드가 생성된다. 이러한 코드는 노드에서 사용자 정의 메시지를 주고받는 데 사용된다.

메시지 타입의 사용 예시 (Python)

사용자 정의 메시지 타입을 Python에서 사용하는 예제를 살펴보겠다. 예를 들어, 다음과 같은 메시지를 정의했다고 가정한다:

int32 id
string name
float64[3] position

이 메시지를 ROS2 노드에서 사용하려면 다음과 같이 메시지를 퍼블리싱하고 서브스크라이브할 수 있다.

퍼블리셔 코드:

import rclpy
from rclpy.node import Node
from my_package.msg import CustomMessage

class PublisherNode(Node):
    def __init__(self):
        super().__init__('publisher_node')
        self.publisher_ = self.create_publisher(CustomMessage, 'topic', 10)
        timer_period = 0.5  # 0.5초마다 메시지를 퍼블리싱
        self.timer = self.create_timer(timer_period, self.timer_callback)

    def timer_callback(self):
        msg = CustomMessage()
        msg.id = 1
        msg.name = "ROS2"
        msg.position = [1.0, 2.0, 3.0]
        self.publisher_.publish(msg)
        self.get_logger().info(f"Publishing: {msg.name}, Position: {msg.position}")

def main(args=None):
    rclpy.init(args=args)
    node = PublisherNode()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

위 코드에서 CustomMessage는 사용자 정의 메시지 타입으로, 노드가 주기적으로 데이터를 퍼블리싱한다. 퍼블리셔는 topic이라는 이름의 토픽을 생성하고, 0.5초마다 데이터를 전송한다.

서브스크라이버 코드:

import rclpy
from rclpy.node import Node
from my_package.msg import CustomMessage

class SubscriberNode(Node):
    def __init__(self):
        super().__init__('subscriber_node')
        self.subscription = self.create_subscription(
            CustomMessage,
            'topic',
            self.listener_callback,
            10)
        self.subscription  # 방지: 생성되지 않은 변수를 경고하는 문제

    def listener_callback(self, msg):
        self.get_logger().info(f"Received: {msg.name}, Position: {msg.position}")

def main(args=None):
    rclpy.init(args=args)
    node = SubscriberNode()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

서브스크라이버는 같은 CustomMessage 타입의 메시지를 topic에서 받아 처리한다. 이때 수신된 메시지의 필드를 활용해 로그를 출력한다.

C++에서의 사용자 정의 메시지 사용 예시

C++에서도 비슷한 방식으로 사용자 정의 메시지를 퍼블리싱하고 서브스크라이브할 수 있다.

퍼블리셔 코드:

#include "rclcpp/rclcpp.hpp"
#include "my_package/msg/custom_message.hpp"

class PublisherNode : public rclcpp::Node
{
public:
    PublisherNode() : Node("publisher_node")
    {
        publisher_ = this->create_publisher<my_package::msg::CustomMessage>("topic", 10);
        timer_ = this->create_wall_timer(
            std::chrono::milliseconds(500),
            std::bind(&PublisherNode::timer_callback, this));
    }

private:
    void timer_callback()
    {
        auto message = my_package::msg::CustomMessage();
        message.id = 1;
        message.name = "ROS2";
        message.position = {{1.0, 2.0, 3.0}};
        publisher_->publish(message);
        RCLCPP_INFO(this->get_logger(), "Publishing: '%s', Position: [%.1f, %.1f, %.1f]",
                    message.name.c_str(), message.position[0], message.position[1], message.position[2]);
    }
    rclcpp::Publisher<my_package::msg::CustomMessage>::SharedPtr publisher_;
    rclcpp::TimerBase::SharedPtr timer_;
};

int main(int argc, char *argv[])
{
    rclcpp::init(argc, argv);
    auto node = std::make_shared<PublisherNode>();
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

서브스크라이버 코드:

#include "rclcpp/rclcpp.hpp"
#include "my_package/msg/custom_message.hpp"

class SubscriberNode : public rclcpp::Node
{
public:
    SubscriberNode() : Node("subscriber_node")
    {
        subscription_ = this->create_subscription<my_package::msg::CustomMessage>(
            "topic", 10,
            std::bind(&SubscriberNode::topic_callback, this, std::placeholders::_1));
    }

private:
    void topic_callback(const my_package::msg::CustomMessage::SharedPtr msg)
    {
        RCLCPP_INFO(this->get_logger(), "Received: '%s', Position: [%.1f, %.1f, %.1f]",
                    msg->name.c_str(), msg->position[0], msg->position[1], msg->position[2]);
    }
    rclcpp::Subscription<my_package::msg::CustomMessage>::SharedPtr subscription_;
};

int main(int argc, char *argv[])
{
    rclcpp::init(argc, argv);
    auto node = std::make_shared<SubscriberNode>();
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

이 코드들은 ROS2에서 사용자 정의 메시지를 활용하여 노드 간 통신을 구현하는 방법을 보여준다. Python과 C++ 모두에서 메시지를 퍼블리시하고 구독하는 방식은 비슷하지만, 사용하는 언어에 따라 문법 차이가 있을 뿐이다.