rviz2는 ROS2에서 데이터를 시각적으로 표현할 수 있는 강력한 도구로, 플러그인을 통해 기능을 확장할 수 있다. 플러그인 개발은 ROS2의 시각적 워크플로우에 맞추어 데이터를 보다 효과적으로 처리하고 표현할 수 있게 해준다. 여기서는 rviz2 플러그인을 개발하고 디버깅하는 방법을 단계별로 설명하겠다.

1. rviz2 플러그인 개발의 개요

rviz2의 플러그인은 사용자 정의 기능을 추가하여 특정 시각화 요구를 충족시키기 위해 개발된다. 플러그인은 ROS 메시지, 서비스, 또는 액션을 시각적으로 표현하거나, 새로운 데이터 형식의 시각화를 가능하게 한다.

1.1 플러그인의 구조

rviz2 플러그인은 ROS2 패키지 안에서 작성되며, 플러그인은 보통 두 가지 주요 구성 요소로 나누어진다.

1.2 플러그인 구성 파일

플러그인을 로드하고 빌드하기 위해서는 ROS2의 package.xmlCMakeLists.txt 파일이 필요하다. 이 파일들에는 플러그인의 의존성, 빌드 과정 및 설치 정보가 포함되어 있다.

<!-- package.xml -->
<package format="3">
  <name>my_rviz_plugin</name>
  <version>0.0.1</version>
  <description>Custom rviz2 plugin</description>
  <maintainer email="user@example.com">User</maintainer>
  <license>Apache-2.0</license>

  <buildtool_depend>ament_cmake</buildtool_depend>
  <build_depend>rviz_common</build_depend>
  <build_depend>rviz_rendering</build_depend>

  <exec_depend>rviz2</exec_depend>
</package>
# CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(my_rviz_plugin)

find_package(ament_cmake REQUIRED)
find_package(rviz_common REQUIRED)
find_package(rviz_rendering REQUIRED)

add_library(${PROJECT_NAME} SHARED src/my_plugin.cpp)
target_link_libraries(${PROJECT_NAME} rviz_common::rviz_common rviz_rendering::rviz_rendering)

ament_package()

2. 플러그인 초기화 및 노드 연결

rviz2 플러그인은 ROS2 노드와 상호작용해야 하므로, ROS2에서 제공하는 통신 메커니즘(토픽, 서비스 등)을 사용하여 데이터를 시각화한다. 이 과정에서 rclcpp와 같은 노드 인터페이스가 사용된다.

2.1 노드와의 연결

플러그인은 rclcpp::Node 객체를 사용하여 ROS2의 퍼블리셔, 서브스크라이버, 서비스, 액션을 통해 데이터를 수신하거나 전송할 수 있다. 기본적인 플러그인에서는 주로 토픽을 구독하고 그 데이터를 화면에 표현하는 작업이 이루어진다.

#include <rclcpp/rclcpp.hpp>
#include <rviz_common/panel.hpp>

class MyPlugin : public rviz_common::Panel
{
public:
  MyPlugin(QWidget* parent = 0) : rviz_common::Panel(parent)
  {
    // ROS2 노드 초기화
    node_ = std::make_shared<rclcpp::Node>("my_plugin_node");

    // 토픽 구독
    sub_ = node_->create_subscription<std_msgs::msg::String>(
      "/my_topic", 10, std::bind(&MyPlugin::callback, this, std::placeholders::_1));
  }

private:
  rclcpp::Node::SharedPtr node_;
  rclcpp::Subscription<std_msgs::msg::String>::SharedPtr sub_;

  void callback(const std_msgs::msg::String::SharedPtr msg)
  {
    // 수신된 데이터를 이용한 화면 업데이트 로직
    RCLCPP_INFO(node_->get_logger(), "Received: %s", msg->data.c_str());
  }
};

3. 데이터 렌더링과 시각화

플러그인의 핵심은 데이터를 시각적으로 표현하는 것이다. 이를 위해 rviz2에서는 다양한 데이터 타입을 지원하는 렌더링 툴킷을 제공한다. OpenGL을 기반으로 한 렌더링을 사용하여 데이터를 그릴 수 있으며, 사용자 정의 메시지 타입도 지원된다.

3.1 메시지 타입 정의

ROS2에서 사용자 정의 메시지를 생성한 후, 이를 플러그인에서 사용하기 위해 직렬화 및 역직렬화 과정을 거친다. 사용자 정의 메시지의 정의는 다음과 같다.

float64 x
float64 y
float64 z

이 데이터를 수신한 후, 3D 좌표계에서의 점을 시각화하려면 메시지를 좌표 데이터로 변환하여 OpenGL로 렌더링할 수 있다.

3.2 데이터 렌더링

OpenGL 또는 Ogre와 같은 그래픽 라이브러리를 사용하여 데이터를 화면에 그리는 과정을 설명한다. 예를 들어, 좌표 데이터를 기반으로 3D 공간에 점을 그리는 방법은 다음과 같다.

void renderPoint(double x, double y, double z)
{
  // OpenGL 명령어를 사용하여 3D 점 그리기
  glBegin(GL_POINTS);
  glVertex3d(x, y, z);
  glEnd();
}

3.3 시각화 데이터 관리

시각화할 데이터가 지속적으로 갱신되므로, 효율적인 데이터 관리를 위해 버퍼링 및 데이터 구조를 고려해야 한다. 이는 실시간 데이터 시각화에서 매우 중요하다.

4. rviz2 플러그인 UI 요소 개발

rviz2 플러그인에서는 GUI 요소를 포함할 수 있다. 예를 들어, 사용자가 직접 플러그인의 동작을 제어하거나 데이터를 설정할 수 있는 인터페이스를 제공할 수 있다. 이러한 UI 요소는 주로 Qt 위젯을 사용하여 개발된다.

4.1 Qt 위젯과 rviz2의 통합

rviz2는 Qt 기반의 GUI 프레임워크를 사용하므로, 사용자 정의 플러그인에서 Qt 위젯을 쉽게 추가할 수 있다. 아래는 플러그인에서 슬라이더 위젯을 추가하는 간단한 예이다.

#include <QSlider>
#include <QVBoxLayout>

class MyPlugin : public rviz_common::Panel
{
public:
  MyPlugin(QWidget* parent = 0) : rviz_common::Panel(parent)
  {
    QVBoxLayout* layout = new QVBoxLayout;

    // 슬라이더 추가
    QSlider* slider = new QSlider(Qt::Horizontal);
    layout->addWidget(slider);

    setLayout(layout);
  }
};

이처럼 Qt 위젯을 활용하여 사용자가 실시간으로 플러그인의 매개변수를 조정할 수 있게 설계할 수 있다.

4.2 상호작용 요소

플러그인의 UI 요소는 단순히 화면에 보여지는 것만이 아니라, ROS2 노드와 상호작용하여 데이터를 실시간으로 반영할 수 있다. 예를 들어, 슬라이더 값이 변경되면 ROS2 퍼블리셔를 통해 값을 퍼블리싱하거나 특정 동작을 트리거할 수 있다.

class MyPlugin : public rviz_common::Panel
{
public:
  MyPlugin(QWidget* parent = 0) : rviz_common::Panel(parent)
  {
    QVBoxLayout* layout = new QVBoxLayout;
    slider_ = new QSlider(Qt::Horizontal);
    layout->addWidget(slider_);

    setLayout(layout);

    // 슬라이더 값이 변경될 때 콜백 함수 호출
    connect(slider_, &QSlider::valueChanged, this, &MyPlugin::onSliderValueChanged);
  }

private:
  QSlider* slider_;

  void onSliderValueChanged(int value)
  {
    // 슬라이더 값이 변경될 때 실행되는 로직
    RCLCPP_INFO(node_->get_logger(), "Slider Value: %d", value);
  }
};

5. rviz2 플러그인 디버깅

플러그인을 개발하는 과정에서 발생할 수 있는 오류를 디버깅하는 것은 매우 중요하다. ROS2와 rviz2는 강력한 로그 시스템과 디버깅 툴을 제공한다.

5.1 ROS2 로그 시스템 활용

rclcpp 라이브러리의 로그 기능을 활용하여 플러그인 내부의 상태를 실시간으로 출력하고, 이를 통해 플러그인 동작을 모니터링할 수 있다. RCLCPP_INFO, RCLCPP_WARN, RCLCPP_ERROR 등의 로그 수준을 설정하여 다양한 정보를 출력할 수 있다.

RCLCPP_INFO(node_->get_logger(), "Plugin initialized successfully");
RCLCPP_ERROR(node_->get_logger(), "Failed to load data");

5.2 rviz2의 디버깅 도구

rviz2는 자체적으로 플러그인을 디버깅할 수 있는 툴을 제공한다. rqt_consolerqt_logger_level 도구는 특히 유용하다. rqt_console은 ROS2 애플리케이션의 로그를 실시간으로 모니터링할 수 있게 해주며, rqt_logger_level은 로그 수준을 동적으로 조정할 수 있게 한다.

ros2 run rqt_console rqt_console
ros2 run rqt_logger_level rqt_logger_level

6. 플러그인 성능 최적화

플러그인의 성능은 실시간 시스템에서 매우 중요하다. 플러그인의 성능을 최적화하려면 데이터 처리와 렌더링이 효율적으로 이루어져야 하며, 불필요한 연산을 최소화해야 한다.

6.1 OpenGL 성능 최적화

OpenGL을 사용할 때 성능을 최적화하는 중요한 방법은 필요한 최소한의 그리기 작업만 수행하는 것이다. 많은 데이터가 화면에 그려지는 경우, 버퍼링을 통해 성능을 향상시킬 수 있다. 예를 들어, glDrawArrays를 사용하는 방법을 고려해볼 수 있다.

void renderPoints(std::vector<Eigen::Vector3d> points)
{
  glBegin(GL_POINTS);
  for (const auto& point : points)
  {
    glVertex3d(point.x(), point.y(), point.z());
  }
  glEnd();
}

6.2 메시지 처리 최적화

플러그인이 수신하는 메시지가 매우 빈번하게 발생하는 경우, 불필요한 연산을 피하기 위해 메시지 처리 루틴을 최적화해야 한다. QoS 설정을 통해 필요한 데이터만 수신하도록 조정할 수 있으며, 콜백 함수의 처리 시간을 줄이는 것이 중요하다.

rclcpp::SubscriptionOptions options;
options.qos_profile.depth = 1;  // 최소한의 메시지 버퍼 크기 설정
sub_ = node_->create_subscription<std_msgs::msg::String>(
  "/my_topic", rclcpp::QoS(10), std::bind(&MyPlugin::callback, this, std::placeholders::_1), options);

7. 플러그인의 테스트 및 배포

플러그인이 정상적으로 작동하는지 확인하기 위해서는 다양한 테스트와 배포 전략을 고려해야 한다. ROS2에서는 패키지 내에서 테스트를 쉽게 실행할 수 있는 도구를 제공하며, 이를 통해 플러그인의 기능을 자동으로 검증할 수 있다.

7.1 플러그인 테스트

ROS2에서는 ament_lint 패키지를 사용하여 코드의 스타일 및 구조를 검사할 수 있다. 이를 통해 개발된 플러그인의 코드가 ROS2의 표준을 준수하는지 확인할 수 있다.

ament_lint_auto_find_test_dependencies()

테스트 파일은 GTest 또는 pytest와 같은 도구를 사용하여 작성할 수 있다. 예를 들어, 플러그인의 일부 기능을 테스트하는 GTest 코드는 아래와 같다.

#include <gtest/gtest.h>

TEST(MyPluginTest, TestInitialization)
{
  auto plugin = std::make_shared<MyPlugin>();
  EXPECT_TRUE(plugin->isInitialized());
}

이 코드는 플러그인이 올바르게 초기화되었는지를 검증한다.

7.2 플러그인 배포

플러그인이 완성되면, 이를 다른 사용자나 시스템에 배포할 수 있어야 한다. ROS2에서는 플러그인을 쉽게 배포할 수 있도록 패키징과 배포 도구를 지원한다.

  1. 패키징: 패키지를 배포하기 전에 모든 종속성과 빌드 설정이 올바르게 설정되었는지 확인해야 한다.

bash colcon build --packages-select my_rviz_plugin

  1. 배포 전략: 패키지를 다른 시스템에서 사용할 수 있도록 .deb 패키지로 배포하거나, Docker 이미지를 사용하여 배포할 수 있다. 이를 통해 다양한 환경에서 동일한 플러그인을 쉽게 실행할 수 있다.
docker build -t my_rviz_plugin_image .

7.3 CI/CD 설정

플러그인의 지속적인 통합 및 배포(CI/CD)를 위해 GitHub Actions 또는 Jenkins와 같은 CI 도구를 활용할 수 있다. 이를 통해 코드 변경 시마다 자동으로 빌드 및 테스트를 실행하고, 새로운 릴리스를 배포할 수 있다.

예를 들어, GitHub Actions에서는 다음과 같은 yaml 파일을 통해 CI 설정을 할 수 있다.

name: Build and Test Plugin

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Set up ROS2
        run: sudo apt update && sudo apt install ros-humble-desktop
      - name: Build plugin
        run: colcon build
      - name: Run tests
        run: colcon test

8. 디버깅 시 유의사항

플러그인 개발 과정에서 디버깅 시 주의해야 할 몇 가지 요소가 있다.

8.1 메시지 충돌 방지

플러그인이 잘못된 메시지 형식을 수신하거나, 메시지 구독이 중단되는 경우, 통신 충돌이나 성능 저하가 발생할 수 있다. 이때 ROS2의 로그 시스템을 통해 메시지의 형식 및 내용에 대한 오류를 확인하는 것이 중요하다.

RCLCPP_ERROR(node_->get_logger(), "Unexpected message type received");

8.2 렌더링 오류 확인

OpenGL 또는 Ogre 기반의 렌더링 오류는 화면의 왜곡이나 비정상적인 그래픽 출력으로 나타날 수 있다. 이러한 오류는 주로 좌표 변환 오류, 잘못된 메시지 처리, 또는 잘못된 버퍼 설정 등으로 인해 발생한다. 문제를 발견하면, 좌표 변환 수식이나 렌더링 로직을 점검하는 것이 필요하다.

8.3 실시간 성능 문제

실시간으로 데이터를 시각화하는 과정에서 CPU나 GPU 자원을 과도하게 소모하는 경우가 많다. 이를 방지하려면 플러그인의 연산량을 줄이고, 데이터 버퍼링이나 스레드 풀을 활용하여 성능을 최적화해야 한다.

// 스레드 풀을 활용한 비동기 작업 처리
std::thread render_thread([&]() {
  while(running) {
    renderData();
  }
});
render_thread.detach();

9. 플러그인 배포 시 고려사항

9.1 호환성 유지

rviz2 플러그인을 배포할 때는 다양한 환경과의 호환성을 유지하는 것이 중요하다. 특히 ROS2 버전 간 차이점이나 플랫폼 간의 호환성 문제를 고려하여 패키지를 빌드해야 한다. 예를 들어, ROS2 Foxy에서 동작하던 플러그인이 Humble에서 문제가 발생하지 않도록 테스트하고, 플랫폼에 따라 필요한 의존성을 명확히 설정해야 한다.

rosdep install --from-paths src --ignore-src -r -y

9.2 문서화

플러그인을 배포할 때는 사용자가 쉽게 사용할 수 있도록 문서화를 철저히 해야 한다. 문서에는 플러그인의 설치 방법, 사용법, 그리고 예제 코드가 포함되어야 한다. 또한, 자주 발생할 수 있는 문제에 대한 해결책을 미리 제시하는 것도 좋은 방법이다.

# My Rviz Plugin

## 설치
```bash
colcon build
source install/setup.bash

## 사용법
플러그인을 rviz2에서 실행하려면 다음 명령어를 사용하라:
``
rviz2 -d my_plugin_config.rviz
``