# rviz2 플러그인 개발 및 디버깅

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

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

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

**1.1 플러그인의 구조**

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

* **Visualization Panel**: 사용자가 인터페이스에서 볼 수 있는 GUI 요소
* **Rendering Logic**: 데이터가 어떻게 화면에 그려질지 정의하는 부분

**1.2 플러그인 구성 파일**

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

```xml
<!-- 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>
```

```cmake
# 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의 퍼블리셔, 서브스크라이버, 서비스, 액션을 통해 데이터를 수신하거나 전송할 수 있다. 기본적인 플러그인에서는 주로 토픽을 구독하고 그 데이터를 화면에 표현하는 작업이 이루어진다.

```cpp
#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에서 사용자 정의 메시지를 생성한 후, 이를 플러그인에서 사용하기 위해 직렬화 및 역직렬화 과정을 거친다. 사용자 정의 메시지의 정의는 다음과 같다.

```msg
float64 x
float64 y
float64 z
```

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

**3.2 데이터 렌더링**

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

```cpp
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 위젯을 쉽게 추가할 수 있다. 아래는 플러그인에서 슬라이더 위젯을 추가하는 간단한 예이다.

```cpp
#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 퍼블리셔를 통해 값을 퍼블리싱하거나 특정 동작을 트리거할 수 있다.

```cpp
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` 등의 로그 수준을 설정하여 다양한 정보를 출력할 수 있다.

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

**5.2 rviz2의 디버깅 도구**

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

```bash
ros2 run rqt_console rqt_console
ros2 run rqt_logger_level rqt_logger_level
```

#### 6. 플러그인 성능 최적화

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

**6.1 OpenGL 성능 최적화**

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

```cpp
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 설정을 통해 필요한 데이터만 수신하도록 조정할 수 있으며, 콜백 함수의 처리 시간을 줄이는 것이 중요하다.

```cpp
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의 표준을 준수하는지 확인할 수 있다.

```bash
ament_lint_auto_find_test_dependencies()
```

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

```cpp
#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
   ```
2. **배포 전략**: 패키지를 다른 시스템에서 사용할 수 있도록 `.deb` 패키지로 배포하거나, Docker 이미지를 사용하여 배포할 수 있다. 이를 통해 다양한 환경에서 동일한 플러그인을 쉽게 실행할 수 있다.

```bash
docker build -t my_rviz_plugin_image .
```

**7.3 CI/CD 설정**

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

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

```yaml
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의 로그 시스템을 통해 메시지의 형식 및 내용에 대한 오류를 확인하는 것이 중요하다.

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

**8.2 렌더링 오류 확인**

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

**8.3 실시간 성능 문제**

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

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

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

**9.1 호환성 유지**

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

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

**9.2 문서화**

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

````md
# My Rviz Plugin

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

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