생명주기 노드의 기본 개념

ROS2의 생명주기 노드는 노드의 상태를 관리하며, 특정 상태에서만 동작을 수행할 수 있도록 한다. 생명주기 노드에는 다양한 상태가 있으며, 각 상태에서의 전환을 제어할 수 있다. 상태는 크게 아래와 같은 단계로 나눌 수 있다.

이러한 상태 전환은 서비스 콜을 통해 이루어지며, 각 상태에서 노드의 행동이 어떻게 변화하는지를 정의할 수 있다.

생명주기 노드의 상태 전이

생명주기 노드는 상태 전이를 통해 상태를 변경한다. 주요 상태 전이 방법은 다음과 같다.

각 상태 전이는 특정한 서비스 콜을 통해 이루어지며, 사용자는 이 상태 전이 로직을 직접 구현해야 한다.

생명주기 노드의 구현 예제

생명주기 노드를 구현할 때, ROS2에서 제공하는 rclcpp_lifecycle::LifecycleNode 클래스를 상속받아 노드를 정의한다. 아래는 간단한 예제 코드이다.

#include "rclcpp/rclcpp.hpp"
#include "rclcpp_lifecycle/lifecycle_node.hpp"

using rclcpp_lifecycle::LifecycleNode;
using CallbackReturn = rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn;

class MyLifecycleNode : public LifecycleNode
{
public:
  MyLifecycleNode(const std::string & node_name)
  : LifecycleNode(node_name)
  {}

  CallbackReturn on_configure(const rclcpp_lifecycle::State & state)
  {
    RCLCPP_INFO(get_logger(), "Configuring...");
    // 노드가 'Inactive' 상태로 전환되기 전 초기 설정 작업을 수행
    return CallbackReturn::SUCCESS;
  }

  CallbackReturn on_activate(const rclcpp_lifecycle::State & state)
  {
    RCLCPP_INFO(get_logger(), "Activating...");
    // 노드가 'Active' 상태로 전환되기 전에 활성화 작업을 수행
    return CallbackReturn::SUCCESS;
  }

  CallbackReturn on_deactivate(const rclcpp_lifecycle::State & state)
  {
    RCLCPP_INFO(get_logger(), "Deactivating...");
    // 노드가 'Inactive' 상태로 전환되기 전에 비활성화 작업을 수행
    return CallbackReturn::SUCCESS;
  }

  CallbackReturn on_cleanup(const rclcpp_lifecycle::State & state)
  {
    RCLCPP_INFO(get_logger(), "Cleaning up...");
    // 노드가 'Unconfigured' 상태로 전환되기 전 정리 작업을 수행
    return CallbackReturn::SUCCESS;
  }

  CallbackReturn on_shutdown(const rclcpp_lifecycle::State & state)
  {
    RCLCPP_INFO(get_logger(), "Shutting down...");
    // 노드가 'Finalized' 상태로 전환되기 전에 종료 작업을 수행
    return CallbackReturn::SUCCESS;
  }
};

이 예제에서 MyLifecycleNode 클래스는 rclcpp_lifecycle::LifecycleNode를 상속받으며, 주요 상태 전이 콜백을 정의한다. 상태 전이가 발생할 때마다 해당 콜백 함수가 호출되며, 여기서 필요한 초기화나 정리 작업을 수행할 수 있다.

노드의 상태 전이 흐름도

아래는 생명주기 노드의 상태 전이를 mermaid로 표현한 다이어그램이다.

stateDiagram [*] --> Unconfigured Unconfigured --> Inactive : configure() Inactive --> Active : activate() Active --> Inactive : deactivate() Inactive --> Unconfigured : cleanup() Unconfigured --> Finalized : shutdown() Active --> Finalized : shutdown()

이 다이어그램은 생명주기 노드의 상태 전이를 시각적으로 보여준다. 각 상태는 특정 서비스 콜을 통해 전이되며, 상태 전이에 따라 노드의 동작이 달라진다.

생명주기 노드 상태 전이 시의 퍼블리셔/서브스크라이버 동작

생명주기 노드는 노드의 상태에 따라 퍼블리셔와 서브스크라이버의 동작이 달라진다. 일반적으로 퍼블리셔는 노드가 Active 상태일 때만 데이터를 퍼블리싱하며, 그 외의 상태에서는 퍼블리싱이 중단된다. 예를 들어, 퍼블리셔가 Inactive 상태로 전환되면 데이터를 더 이상 송신하지 않게 된다.

CallbackReturn on_activate(const rclcpp_lifecycle::State & state)
{
  RCLCPP_INFO(get_logger(), "Activating...");
  // 퍼블리셔 활성화
  my_publisher_->on_activate();
  return CallbackReturn::SUCCESS;
}

CallbackReturn on_deactivate(const rclcpp_lifecycle::State & state)
{
  RCLCPP_INFO(get_logger(), "Deactivating...");
  // 퍼블리셔 비활성화
  my_publisher_->on_deactivate();
  return CallbackReturn::SUCCESS;
}

퍼블리셔는 on_activate() 콜백에서 활성화되며, on_deactivate() 콜백에서 비활성화된다. 이를 통해 노드가 Active 상태일 때만 퍼블리셔가 동작하도록 제어할 수 있다.

생명주기 노드와 서비스

서비스는 노드의 생명주기 상태와 무관하게 항상 호출될 수 있도록 설정하는 것이 일반적이다. 즉, 노드가 Inactive 상태일 때도 서비스 호출을 처리할 수 있으며, 상태 전환과 상관없이 노드가 서비스 요청에 응답할 수 있도록 구현된다.

CallbackReturn on_configure(const rclcpp_lifecycle::State & state)
{
  RCLCPP_INFO(get_logger(), "Configuring...");

  // 서비스 생성
  my_service_ = create_service<std_srvs::srv::Trigger>(
    "my_service", 
    [this](const std::shared_ptr<std_srvs::srv::Trigger::Request> request,
           std::shared_ptr<std_srvs::srv::Trigger::Response> response)
    {
      RCLCPP_INFO(this->get_logger(), "Service called.");
      response->success = true;
      response->message = "Service executed.";
    });

  return CallbackReturn::SUCCESS;
}

이 예제에서는 노드가 configure() 상태로 전이될 때 서비스를 생성하며, 서비스가 호출될 때마다 지정된 콜백 함수가 실행된다. 이처럼 생명주기 노드에서는 상태와 무관하게 서비스가 동작할 수 있도록 설계할 수 있다.

QoS 정책을 적용한 생명주기 노드

생명주기 노드에서는 QoS (Quality of Service) 설정을 통해 퍼블리셔와 서브스크라이버의 통신 성능을 제어할 수 있다. ROS2에서는 다양한 QoS 설정을 지원하며, 생명주기 노드에서 이를 적절히 적용하는 것이 중요하다.

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

my_publisher_ = this->create_publisher<std_msgs::msg::String>("my_topic", qos);

위 예제에서 qos 객체는 QoS 설정을 정의하며, 이 설정을 퍼블리셔에 적용하여 특정 메시지의 신뢰성을 높인다. 생명주기 노드에서는 이러한 QoS 설정을 통해 데이터 손실을 방지하거나 통신 지연을 최소화할 수 있다.

생명주기 노드의 타이머 활용

생명주기 노드는 주기적으로 작업을 수행해야 할 때 타이머를 활용할 수 있다. 타이머는 Active 상태에서만 활성화되며, 타이머가 활성화되면 주기적으로 특정 작업을 수행하도록 설정할 수 있다.

CallbackReturn on_activate(const rclcpp_lifecycle::State & state)
{
  RCLCPP_INFO(get_logger(), "Activating...");

  // 타이머 설정
  timer_ = this->create_wall_timer(
    std::chrono::seconds(1),
    [this]() {
      RCLCPP_INFO(this->get_logger(), "Timer triggered.");
    });

  return CallbackReturn::SUCCESS;
}

CallbackReturn on_deactivate(const rclcpp_lifecycle::State & state)
{
  RCLCPP_INFO(get_logger(), "Deactivating...");

  // 타이머 해제
  timer_->cancel();

  return CallbackReturn::SUCCESS;
}

이 예제에서는 노드가 Active 상태일 때 타이머를 생성하고, 타이머는 1초마다 콜백 함수를 호출한다. 노드가 Inactive 상태로 전환되면 타이머는 cancel()을 통해 비활성화된다.

상태 전이 중 오류 처리

상태 전이 중에 발생하는 오류를 처리하기 위해서는 상태 전이 콜백 함수에서 적절한 오류 처리를 구현해야 한다. 예를 들어, 노드가 Active 상태로 전환될 때 특정 조건이 충족되지 않으면 오류를 반환하고 상태 전이를 중단할 수 있다.

CallbackReturn on_activate(const rclcpp_lifecycle::State & state)
{
  RCLCPP_INFO(get_logger(), "Activating...");

  // 오류 조건 확인
  if (!is_ready_to_activate())
  {
    RCLCPP_ERROR(get_logger(), "Activation failed. Node not ready.");
    return CallbackReturn::FAILURE;
  }

  // 퍼블리셔 활성화
  my_publisher_->on_activate();
  return CallbackReturn::SUCCESS;
}

이 코드는 is_ready_to_activate() 함수에서 특정 조건을 확인한 후, 조건이 충족되지 않으면 FAILURE를 반환하여 상태 전이를 중단한다. 이렇게 함으로써 상태 전이 중에 발생할 수 있는 오류를 방지하고, 노드의 안정성을 높일 수 있다.

생명주기 노드와 실시간 시스템

실시간 시스템에서는 노드의 상태 전환과 타이밍이 중요한 요소로 작용한다. 특히, 생명주기 노드에서의 상태 전이는 시스템의 응답성과 관련이 깊으며, 실시간 요구 사항을 충족하기 위해서는 상태 전이 시간을 최소화해야 한다.

실시간 시스템에서 생명주기 노드를 사용할 때 고려해야 할 요소는 다음과 같다.

실시간 시스템에서의 생명주기 노드 구현 시, 실시간 타이머를 설정하고 상태 전이 중 데이터 일관성을 유지하는 방법은 다음과 같다.

CallbackReturn on_activate(const rclcpp_lifecycle::State & state)
{
  RCLCPP_INFO(get_logger(), "Activating...");

  // 실시간 타이머 설정
  timer_ = this->create_wall_timer(
    std::chrono::milliseconds(10),
    [this]() {
      RCLCPP_INFO(this->get_logger(), "Real-time task triggered.");
    });

  // 퍼블리셔 활성화
  my_publisher_->on_activate();

  // 실시간 시스템에서 상태 전이 중 데이터 일관성 보장
  ensure_data_consistency();

  return CallbackReturn::SUCCESS;
}

위 코드는 실시간 타이머를 10ms 간격으로 설정하여 주기적인 작업을 수행하며, 상태 전이 중 데이터 일관성을 보장하는 로직을 포함하고 있다. 실시간 시스템에서 이러한 구현은 지연 시간 최소화와 데이터 손실 방지에 중요한 역할을 한다.

생명주기 노드의 테스트와 디버깅

생명주기 노드의 상태 전이와 동작을 테스트할 때는 각 상태에서의 노드 동작을 검증해야 한다. 이를 위해 ROS2에서는 다양한 테스트 도구를 제공하며, 각 상태에서의 동작이 예상대로 이루어지는지 확인할 수 있다.

ros2 lifecycle 명령어

ros2 lifecycle 명령어는 생명주기 노드의 상태를 제어하고, 상태 전이를 직접 수행할 수 있는 도구이다. 이 명령어를 통해 노드를 다양한 상태로 전이시키며, 상태 전이가 올바르게 이루어졌는지 확인할 수 있다.

# 노드의 상태 조회
ros2 lifecycle get /my_lifecycle_node

# 노드를 'configure' 상태로 전이
ros2 lifecycle set /my_lifecycle_node configure

# 노드를 'activate' 상태로 전이
ros2 lifecycle set /my_lifecycle_node activate

위 명령어를 사용하면 노드의 상태를 조회하거나 특정 상태로 전이시킬 수 있으며, 각 상태 전이 시점에서 노드의 동작을 확인할 수 있다.

로깅을 활용한 디버깅

노드의 상태 전이 중 문제가 발생할 경우, 로깅을 통해 오류를 추적할 수 있다. ROS2의 RCLCPP_INFO, RCLCPP_ERROR 등의 로깅 매크로를 활용하여 노드의 상태 및 상태 전이 중 발생하는 문제를 실시간으로 기록할 수 있다.

CallbackReturn on_activate(const rclcpp_lifecycle::State & state)
{
  RCLCPP_INFO(get_logger(), "Activating...");

  // 오류 발생 시 로그 출력
  if (!is_ready_to_activate())
  {
    RCLCPP_ERROR(get_logger(), "Activation failed. Node not ready.");
    return CallbackReturn::FAILURE;
  }

  // 퍼블리셔 활성화
  my_publisher_->on_activate();
  return CallbackReturn::SUCCESS;
}

위 코드에서는 상태 전이 중 오류가 발생할 경우, RCLCPP_ERROR를 통해 로그를 출력하여 문제를 추적할 수 있다. 이렇게 로깅을 활용하면 디버깅 시 중요한 정보를 기록할 수 있어 문제 해결에 도움이 된다.

생명주기 노드의 실제 응용 사례

생명주기 노드는 다양한 응용 분야에서 활용될 수 있으며, 특히 복잡한 시스템에서의 노드 관리와 상태 전이를 효과적으로 처리할 수 있다. 예를 들어, 자율 주행 차량에서는 특정 노드가 정상적으로 작동하기 위해서는 여러 상태 전이를 거쳐야 하며, 이러한 상태 관리가 시스템의 안전성과 신뢰성을 높이는 데 기여한다.

자율 주행 차량에서의 생명주기 노드

자율 주행 차량 시스템에서 생명주기 노드는 다음과 같은 방식으로 응용될 수 있다.

class AutonomousVehicleNode : public rclcpp_lifecycle::LifecycleNode
{
public:
  AutonomousVehicleNode(const std::string & node_name)
  : LifecycleNode(node_name)
  {}

  CallbackReturn on_activate(const rclcpp_lifecycle::State & state)
  {
    RCLCPP_INFO(get_logger(), "Activating sensors and control...");

    // 센서 퍼블리셔 활성화
    sensor_publisher_->on_activate();

    // 제어 명령 퍼블리셔 활성화
    control_publisher_->on_activate();

    return CallbackReturn::SUCCESS;
  }

  CallbackReturn on_deactivate(const rclcpp_lifecycle::State & state)
  {
    RCLCPP_INFO(get_logger(), "Deactivating sensors and control...");

    // 센서 퍼블리셔 비활성화
    sensor_publisher_->on_deactivate();

    // 제어 명령 퍼블리셔 비활성화
    control_publisher_->on_deactivate();

    return CallbackReturn::SUCCESS;
  }
};

이 예제에서는 자율 주행 차량에서 사용되는 생명주기 노드를 정의하고, Active 상태에서만 센서 데이터와 제어 명령을 퍼블리싱하도록 설정하였다. 이를 통해 차량의 안전성과 안정성을 보장할 수 있다.