퍼블리셔(Publisher) 구현

ROS2에서 퍼블리셔는 데이터를 생성하고, 해당 데이터를 주어진 토픽을 통해 송신하는 역할을 한다. 퍼블리셔 노드를 작성하기 위해 rclpy 패키지를 사용하며, Python에서 퍼블리셔를 설정하는 일반적인 구조는 다음과 같다.

import rclpy
from rclpy.node import Node
from std_msgs.msg import String

class SimplePublisher(Node):

    def __init__(self):
        super().__init__('simple_publisher')
        self.publisher_ = self.create_publisher(String, 'topic_name', 10)
        self.timer = self.create_timer(1.0, self.publish_message)

    def publish_message(self):
        msg = String()
        msg.data = 'Hello, ROS2'
        self.publisher_.publish(msg)
        self.get_logger().info(f"Publishing: {msg.data}")

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

if __name__ == '__main__':
    main()

퍼블리셔의 기본 원리

퍼블리셔는 데이터를 주기적으로 송신할 수 있도록 Timer를 사용한다. 이는 주기적으로 호출되며, 특정 주기마다 메시지를 퍼블리셔를 통해 발행한다. 이때 발행하는 메시지의 타입은 std_msgs.msg.String과 같이 미리 정의된 메시지 타입을 사용한다.

퍼블리셔는 다음과 같은 수식을 기반으로 주기적으로 메시지를 발행할 수 있다. 퍼블리셔 노드는 메시지를 \mathbf{x}_p와 같은 벡터로 표현할 수 있으며, 각 메시지의 상태는 시간 \mathbf{t}에 따라 변화한다.

\mathbf{x}_p(t) = \text{message}(t)

여기서 \mathbf{x}_p는 퍼블리셔가 발행하는 메시지 벡터를 나타내며, 이는 각 시점 t에서 메시지의 내용에 따라 변할 수 있다.

서브스크라이버(Subscriber) 구현

서브스크라이버는 퍼블리셔가 발행하는 데이터를 구독하여 수신하는 역할을 한다. 서브스크라이버 노드를 작성하기 위해서도 rclpy를 사용하며, 다음과 같이 구현할 수 있다.

import rclpy
from rclpy.node import Node
from std_msgs.msg import String

class SimpleSubscriber(Node):

    def __init__(self):
        super().__init__('simple_subscriber')
        self.subscription = self.create_subscription(
            String, 'topic_name', self.listener_callback, 10)
        self.subscription

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

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

if __name__ == '__main__':
    main()

서브스크라이버의 기본 원리

서브스크라이버는 특정 토픽을 구독하여 해당 토픽에서 발행된 메시지를 수신한다. 수신된 메시지는 콜백 함수로 전달되며, 이를 통해 데이터 처리가 가능한다. 수신된 메시지의 벡터 \mathbf{x}_s는 시간 t에 따라 변화하는 메시지 데이터를 수신한다.

\mathbf{x}_s(t) = \mathbf{x}_p(t)

서브스크라이버가 수신하는 데이터 \mathbf{x}_s(t)는 퍼블리셔가 발행한 메시지 \mathbf{x}_p(t)와 동일한 내용을 갖는다.

서비스 구현

ROS2에서 서비스는 클라이언트-서버 모델을 기반으로 동작한다. 클라이언트가 서버에게 요청(request)을 보내면, 서버는 이에 응답(response)하는 구조로 이루어져 있다. Python에서 서비스를 구현하는 예제는 다음과 같다.

from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.node import Node

class SimpleService(Node):

    def __init__(self):
        super().__init__('simple_service')
        self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback)

    def add_two_ints_callback(self, request, response):
        response.sum = request.a + request.b
        self.get_logger().info(f"Request: {request.a} + {request.b} = {response.sum}")
        return response

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

if __name__ == '__main__':
    main()

서비스의 기본 원리

서비스는 클라이언트가 요청을 보낸 후, 서버가 응답을 반환하는 구조로 작동한다. 서비스 서버에서의 계산은 특정 요청 \mathbf{r}에 대해 결과 \mathbf{y}를 반환하는 함수로 나타낼 수 있다.

\mathbf{y} = f(\mathbf{r})

여기서 f는 서비스에서 처리되는 연산이며, 요청 \mathbf{r}은 두 개의 정수로 표현될 수 있다. 예를 들어, 위 코드에서는 두 정수 ab의 합을 구하는 함수 f가 수행된다.

액션 구현

ROS2의 액션(Action)은 비동기적으로 실행되는 작업을 처리할 수 있는 기능을 제공한다. 액션 서버와 클라이언트는 긴 시간 동안 진행되는 작업을 처리하는 데 유용하다.

import rclpy
from rclpy.action import ActionServer
from rclpy.node import Node
from example_interfaces.action import Fibonacci

class SimpleActionServer(Node):

    def __init__(self):
        super().__init__('simple_action_server')
        self._action_server = ActionServer(
            self,
            Fibonacci,
            'fibonacci',
            self.execute_callback)

    def execute_callback(self, goal_handle):
        self.get_logger().info('Executing goal...')
        feedback_msg = Fibonacci.Feedback()
        feedback_msg.sequence = [0, 1]
        for i in range(2, goal_handle.request.order):
            feedback_msg.sequence.append(
                feedback_msg.sequence[i - 1] + feedback_msg.sequence[i - 2])
            goal_handle.publish_feedback(feedback_msg)
        goal_handle.succeed()

        result = Fibonacci.Result()
        result.sequence = feedback_msg.sequence
        return result

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

if __name__ == '__main__':
    main()

액션의 기본 원리

액션은 목표(Goal)와 피드백(Feedback), 그리고 결과(Result)를 기반으로 작업을 비동기적으로 수행한다. 액션 서버는 목표 \mathbf{g}에 대해 피드백 \mathbf{f}를 주고, 최종적으로 결과 \mathbf{r}를 반환하는 프로세스로 작동한다.

\mathbf{r} = \mathbf{f}(\mathbf{g})

액션 서버는 주어진 목표 \mathbf{g}에 대해 피드백 \mathbf{f}를 통해 중간 과정을 전달하고, 결과 \mathbf{r}을 통해 작업의 완료 상태를 반환한다.

퍼블리셔, 서브스크라이버, 서비스 및 액션의 상호작용

퍼블리셔, 서브스크라이버, 서비스, 그리고 액션은 각기 다른 목적을 가지고 있지만, 함께 결합하여 복잡한 시스템을 구현할 수 있다. 퍼블리셔와 서브스크라이버는 메시지 기반 통신을 수행하고, 서비스는 동기적 요청-응답 패턴을 처리하며, 액션은 비동기적 장시간 작업에 적합한다. 이들의 상호작용은 다양한 상황에서 시스템의 유연성을 높인다.

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

퍼블리셔는 특정 토픽을 통해 데이터를 발행하고, 서브스크라이버는 이를 받아 처리한다. 퍼블리셔의 노드에서 데이터가 발행될 때, 해당 토픽을 구독하고 있는 서브스크라이버는 이를 실시간으로 수신하게 된다. 이 과정을 벡터 \mathbf{x}_p(t)\mathbf{x}_s(t)로 설명하면, 퍼블리셔가 발행한 메시지 \mathbf{x}_p(t)는 서브스크라이버가 수신하는 메시지 \mathbf{x}_s(t)와 동일하게 동작한다.

서비스와 액션의 상호작용

서비스는 클라이언트-서버 구조에서 동작하며, 클라이언트가 요청을 보내면 서버가 이를 처리하고 응답한다. 서비스는 짧은 작업을 처리하는 데 적합한다. 반면, 액션은 비동기적이며, 긴 시간 동안 실행되는 작업을 처리하는 데 사용된다. 서비스는 즉각적인 응답이 필요한 작업에 적합하고, 액션은 중간 피드백을 제공하며 장시간 작업을 관리할 수 있다.

퍼블리셔, 서브스크라이버, 서비스 및 액션의 ROS2 통합 구조

이제 퍼블리셔, 서브스크라이버, 서비스, 그리고 액션이 함께 사용되는 간단한 구조를 살펴보겠다. 퍼블리셔가 데이터를 발행하고, 서브스크라이버가 이를 수신하여 처리한다. 서비스는 서브스크라이버가 수신한 데이터를 기반으로 클라이언트의 요청에 응답한다. 액션은 장기적인 작업을 수행하며, 퍼블리셔와 서브스크라이버의 데이터 교환과 연계될 수 있다.

graph TD; A[퍼블리셔] -->|데이터 발행| B[서브스크라이버]; B -->|데이터 처리| C[서비스 서버]; D[서비스 클라이언트] -->|요청| C; C -->|응답| D; E[액션 서버] -->|작업 요청| F[액션 클라이언트]; F -->|작업 수행| E; E -->|피드백 제공| F;

위의 다이어그램은 퍼블리셔, 서브스크라이버, 서비스, 액션이 통합된 구조를 간단히 나타낸 것이다. 퍼블리셔는 데이터를 발행하고, 서브스크라이버가 이를 받아 처리한 후, 서비스가 그 데이터를 기반으로 요청에 응답할 수 있다. 또한, 액션은 장기적인 작업을 비동기적으로 처리하면서 퍼블리셔와 서브스크라이버의 데이터를 활용할 수 있다.