# ROS2 Humble - Gazebo/시뮬레이터 상의 오류 해결 법

Gazebo는 ROS2 환경에서 로봇을 시뮬레이션하는 핵심 도구 중 하나다. 하지만 버전 차이, 다양한 플러그인, 하드웨어 및 드라이버 문제 등으로 인해 시뮬레이터 환경에서 오류가 자주 발생한다. 이 문서에서는 Gazebo의 전형적인 문제 상황을 파악하고, 실제로 어떠한 방식으로 해결할 수 있는지 구체적인 사례와 함께 살펴본다.

#### Gazebo 버전 호환성 문제

ROS2 Humble 버전에서 기본적으로 지원하는 Gazebo(또는 Ignition Gazebo) 버전과 달리, 시스템에 설치된 Gazebo 버전이 상이할 때 다양한 오류가 발생할 수 있다. 보통 다음과 같은 상황이 대표적이다.

**라이브러리 링크 에러**:

* Gazebo 플러그인이나 ROS2 패키지 빌드 시, 해당 라이브러리를 찾지 못해 발생한다.
* `$CMAKE_PREFIX_PATH$` 등에 Gazebo 라이브러리가 설치된 경로가 누락된 경우가 많다.

**ROS2 메시지 타입 불일치**:

* Gazebo가 구버전의 ROS 메시지를 참조하거나, ROS2 Humble에서 사용하는 메시지 타입과 호환되지 않는 경우가 있다.

**SDF(또는 URDF) 파싱 실패**:

* 로봇 모델을 SDF(또는 URDF)로 불러올 때, Gazebo 버전에 따라 지원하지 않는 태그가 있거나 문법 차이가 발생할 수 있다.
* 예:

```xml
<joint name="my_joint" type="continuous">
  <!-- 구버전에서 해석 불가한 태그 예시 -->
  <axis>
    <limit effort="10" velocity="5"/>
  </axis>
</joint>
```

* 새 버전에서만 추가된 태그를 구버전에서 파싱하려고 하면 에러 로그에 “Tag not recognized”와 같은 오류가 찍힌다.

**해결 방법**

* ROS2 Humble에 맞는 Gazebo 버전을 재설치하거나, ROS2 공식 문서에서 권장하는 버전을 사용한다.
* 패키지 빌드 시 필요한 환경 변수($CMAKE\_PREFIX\_PATH$, `$LD_LIBRARY_PATH$` 등)을 명시적으로 지정한다.
* SDF나 URDF를 구성할 때, 불필요하거나 미지원되는 태그가 없는지 사전에 확인하고 수정한다.

#### 플러그인 로드 오류

Gazebo에서 가장 흔히 볼 수 있는 에러 메시지 중 하나는 “Failed to load plugin” 형태의 문자열이다. 이 문제는 두 가지 원인으로 나눌 수 있다.

**플러그인 파일 경로가 잘못되었거나 누락**:

* 예:

  ```bash
  [Err] [PluginLoader.cc:333] Failed to load plugin libmy_custom_plugin.so: file not found
  ```
* 컴파일은 성공했지만 실행 파일이 있는 폴더나 라이브러리 폴더에 .so 파일이 존재하지 않는 경우 발생한다.

**플러그인 심볼(함수) 링크 실패**:

* 로드하려는 플러그인 내부의 함수 시그니처가 Gazebo 또는 ROS2 버전에 맞지 않을 때 발생한다.
* 플러그인 C++ API가 변경되었는데, 오래된 헤더를 기반으로 빌드하거나 반대로 최신 헤더를 사용했음에도 Gazebo가 구버전일 때 생긴다.

**해결 방법**

* 빌드 완료 후 실제로 생성된 플러그인 바이너리(.so)가 올바른 경로에 있는지 확인한다.
* CMakeLists.txt에서 타겟 라이브러리 경로를 지정할 때

  ```cmake
  set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/lib)
  ```

  와 같은 형태로 빌드 산출물 위치를 명시하고,

  ```
  $LD_LIBRARY_PATH$
  ```

  에 추가한다.
* Gazebo 플러그인 C++ API 버전을 정확히 맞추도록, ROS2 Humble 문서나 Gazebo 릴리즈 노트를 통해 함수 사용법을 확인한다.

#### TF 변환 및 좌표계 충돌 문제

Gazebo 내에서 로봇의 좌표계를 명시하고 센서 데이터를 ROS2 토픽으로 퍼블리시할 때, TF 트리 구조 충돌이 발생할 수 있다. 이는 특히 멀티 조인트 로봇 모델에서 흔하다.

1. TF 트리 상 부모 링크 설정 오류
   * 모델 파일(SDF 혹은 URDF)에서 $\mathbf{T}\_{\text{linkA}\rightarrow\text{linkB}}$ 변환이 중복 정의되거나, 잘못된 링크 이름을 참조할 때 생긴다.
2. ROS2와 Gazebo 간의 TF 브로드캐스트 충돌
   * Gazebo 플러그인에서 이미 TF를 퍼블리시하고 있는데, 별도로 ROS2 노드가 같은 프레임에 대한 TF를 퍼블리시하면 프레임 충돌이 발생한다.

**해결 방법**

* 모든 링크 사이의 관계를 명시적으로 정의하고, 불필요한 변환 정보가 없는지 점검한다.
* TF를 중복 브로드캐스트하지 않도록, Gazebo 플러그인 혹은 ROS2 노드 중 하나만 활성화한다.
* TF 트리가 정상적으로 구성되었는지 확인하기 위해 아래와 같은 명령어를 활용한다.

  ```bash
  ros2 run tf2_tools view_frames
  ```
* TF 트리 구조를 시각화하고, 문제가 되는 프레임이 있는지 확인한다.

#### 시뮬레이션 성능 저하 및 지연

Gazebo 상에서 복잡한 로봇 모델을 구동하거나, 여러 개의 센서 데이터를 동시에 퍼블리시하면 시뮬레이션이 급격히 느려지거나 정지하는 상황이 발생할 수 있다.

1. 물리 엔진 과부하
   * GPU 가속을 활용하거나, CPU 코어가 충분하지 않은 시스템에서 과도하게 많은 모델을 시뮬레이션할 때 주로 발생한다.
2. ROS2 토픽 과부하
   * 카메라나 LiDAR 등 대용량 센서 데이터를 짧은 주기로 퍼블리시하면서, 네트워크 트래픽 및 프로세스 부하가 커진다.
3. World 파일 내 과도한 오브젝트
   * Gazebo 내에 지나치게 많은 오브젝트나 높은 폴리곤 수를 가진 3D 모델이 배치되어 렌더링 리소스가 고갈된다.

**해결 방법**

* Gazebo 플러그인의 물리 엔진 파라미터(예: `max_update_rate`, `real_time_update_rate` 등)를 조절한다.
* 센서 데이터의 퍼블리시 주기를 높이거나 해상도를 낮춰서 트래픽 부담을 줄인다.
* 월드 파일에서 불필요한 오브젝트나 높은 폴리곤 수의 3D 모델을 제거하거나 단순화한다.

#### 디버깅 절차 예시

아래는 Gazebo 플러그인 관련 문제를 디버깅하는 일반적인 절차를 간략히 도식화한 예시다.

{% @mermaid/diagram content="flowchart TB
A\[에러 로그 확인] --> B\[에러 유형 분류]
B --> C{플러그인\n이슈?}
C -- Yes --> D\[플러그인 so 파일 위치 확인<br>심볼 링크 에러 확인]
C -- No --> E\[ROS2 메시지 호환성<br>또는 SDF 파싱 이슈 점검]
D --> F\[리빌드 후<br>설치 경로 점검]
E --> F\[메시지/URDF 수정<br>버전 맞춤]
F --> G\[테스트 재실행]
G --> A" %}

#### GDB를 통한 디버깅

Gazebo에서 동작하는 플러그인(예: .so)이나 시뮬레이터 자체의 충돌 문제를 조사할 때, GDB( GNU 디버거 )를 사용하는 것이 유용하다.

**GDB 실행 방식**:

Gazebo를 다음과 같이 GDB와 함께 실행할 수 있다.

```bash
gdb --args gzserver --verbose my_world.sdf
```

Ignition Gazebo( Fortress, Garden 등 ) 환경이라면 아래와 같이 실행한다.

```bash
gdb --args ign gazebo my_world.sdf
```

디버깅 모드 진입 후, `$run$` 명령어로 시뮬레이션을 시작한다.

**브레이크포인트 설정**:

작성한 플러그인 코드(.cpp) 내 특정 함수를 타겟으로 하려면 `break` 명령어를 사용한다.

```gdb
(gdb) break MyGazeboPlugin::OnUpdate
```

라이브러리(.so) 심볼 테이블이 로드된 후에만 브레이크포인트가 유효하다.

**스택 트레이스와 변수 확인**:

* 충돌이 발생하거나 예외가 던져지면 GDB가 해당 시점에서 정지한다.
* `$bt$`(backtrace)를 통해 호출 스택을 확인하고, `$print 변수명$`으로 해당 시점의 변수 값을 확인한다.

**직접적인 원인 파악**:

* 메시지 타입 불일치, 포인터 잘못 참조 등 C++ 코딩 오류로 인한 충돌을 빠르게 잡아낼 수 있다.

#### 환경 변수 및 경로 이슈

Gazebo와 ROS2를 함께 사용하다 보면, 서로 다른 설치 위치와 버전 충돌로 인해 환경 변수가 꼬이는 사례가 많다.

**GAZEBO\_PLUGIN\_PATH**:

Gazebo가 플러그인을 검색할 때 참조하는 경로다. 플러그인 빌드 후 공유 라이브러리(.so)가 위치한 경로를 추가해야 한다.

```bash
export GAZEBO_PLUGIN_PATH=$GAZEBO_PLUGIN_PATH:~/ros2_ws/install/my_gazebo_plugins/lib
```

**GAZEBO\_MODEL\_PATH**:

SDF/URDF 및 모델 관련 리소스를 검색할 때 사용된다.

```bash
export GAZEBO_MODEL_PATH=$GAZEBO_MODEL_PATH:~/ros2_ws/src/my_robot_models
```

**LD\_LIBRARY\_PATH**:

리눅스 환경에서 공유 라이브러리를 찾을 때 사용한다. ROS2의 ament 빌드 시스템을 통해 자동 세팅되기도 하지만, 별도 설치된 Gazebo 플러그인의 경로는 수동으로 지정해야 할 수 있다.

**일관성 검증**:

`$env | grep GAZEBO$`, `$env | grep LD_LIBRARY_PATH$` 등을 통해 실제 시스템 환경 변수를 확인한다.

ROS2 패키지에서 Gazebo를 실행할 때는 `source ~/ros2_ws/install/setup.bash`로 작업공간을 올바르게 세팅했는지 항상 확인한다.

#### 시뮬레이션 타이밍과 시간 동기화 문제

ROS2에서는 노드들이 `ROS2_time` 을 사용하지만, Gazebo는 자체 시뮬레이션 시간(`sim_time`)을 가진다. 따라서 시간 동기화 설정이 잘못되면 로봇 동작이 예상과 다를 수 있다.

/clock 토픽 사용:

* 시뮬레이터가 $\text{/clock}$ 토픽을 퍼블리시하고, ROS2 노드들이 이를 Subscribe 하여 시뮬레이션 시간을 인식한다.
* `$use_sim_time$` 파라미터가 `true`로 설정되어 있어야 한다.

ROS2 파라미터 확인:

노드 실행 시

```bash
ros2 param set /my_node use_sim_time true
```

와 같이 파라미터를 설정해야 Gazebo와 시간 동기화가 이뤄진다.

time warp 및 실시간 비율:

* Gazebo의 $\text{real\_time\_factor}$ 값이 1 이하일 경우, 실제 시간 대비 시뮬레이션 시간이 느리게 흐른다.
* 이를 잘못 인지하면 로봇 동작 타이밍이 뒤엉켜 오작동이 발생할 수 있다.

#### 윈도우에서의 시뮬레이션(WSL)

ROS2 Humble은 리눅스를 주 플랫폼으로 하지만, 윈도우 환경(WSL2 포함)에서 시뮬레이터를 구동하려는 시도가 많다. 이 경우 다음 문제들이 자주 보고된다.

1. 그래픽 드라이버 이슈
   * WSL2에서 하드웨어 가속을 완전히 지원하지 않을 수 있어, Gazebo 렌더링 속도가 극도로 느려지거나 충돌한다.
2. 네트워크 브리지 문제
   * Gazebo와 ROS2 간의 내부 통신에 필요한 포트가 올바르게 열려 있지 않아 토픽이 제대로 교환되지 않는다.
3. X11 포워딩
   * GUI 모드( gzclient )를 실행하려면 X11 서버 설정이 필요하다. 설정이 불완전하면 GUI가 뜨지 않거나 마우스 컨트롤이 제한된다.

**대응 방안**

* 가상머신이나 듀얼부트 형태로 네이티브 리눅스 환경에서 실행하는 것이 안정적이다.
* 어쩔 수 없이 WSL2를 사용해야 한다면, 가능한 최신 버전의 Windows와 GPU 드라이버를 설치하고, X 서버 설정(예: VcXsrv, Xming)을 면밀히 확인한다.
* Headless 모드( `gzserver` )로만 실행하고, GUI는 별도의 REMOTE X 세팅이나 가상화 환경에서 돌리는 전략도 있다.

#### 센서 플러그인 오류 및 보정 문제

Gazebo에서 센서를 시뮬레이션하는 과정에서, 실제 하드웨어 대비 오차나 토픽 퍼블리시 불량 등이 자주 발생한다. 이는 센서 플러그인 설정(매개변수) 또는 ROS2의 QoS( Quality of Service ) 설정과 관련이 깊다.

**센서 모델 파라미터 설정**:

Gazebo에서 제공하는 기본 센서(카메라, LiDAR, IMU 등)는 SDF/URDF 파일에 특정 태그를 통해 노이즈, 주파수, 범위 등의 파라미터를 기술한다.

예:

```xml
<sensor name="lidar" type="gpu_lidar">
  <update_rate>30</update_rate>
  <range>
    <min>0.2</min>
    <max>10.0</max>
    <resolution>0.01</resolution>
  </range>
  <noise>
    <stddev>0.01</stddev>
  </noise>
</sensor>
```

파라미터가 실제 센서 사양과 크게 다르거나, 불필요하게 높은 주파수로 설정되어 있으면 성능 문제를 일으키고, 낮은 주파수로 설정되어 있으면 로봇 제어에 필요한 데이터가 제때 들어오지 않는다.

**ROS2 QoS 불일치**:

센서 노드가 퍼블리시하는 토픽과 이를 구독하는 노드의 QoS 설정이 다르면 메시지가 제대로 수신되지 않을 수 있다.

예를 들어, 센서 플러그인은 `Reliability = Best Effort`로 퍼블리시하지만, 구독 노드는 `Reliability = Reliable`를 요구할 경우 연결 실패가 발생한다.

**플러그인 버그 및 부정확한 시뮬레이션**:

* 직접 커스텀 센서 플러그인을 작성한 경우, 자료형 변환 에러나 잘못된 좌표계 변환 등으로 인해 값이 왜곡될 수 있다.
* 특히 IMU 센서에서 가속도, 각속도 등을 변환할 때 로ーカ르 프레임과 월드 프레임을 혼동하여 오차가 누적되는 사례가 있다.
* $\mathbf{R}\_{\text{world}\rightarrow\text{sensor}}$ 변환이 정확한지, 오일러 각도나 쿼터니언을 옳게 계산했는지 꼼꼼히 확인해야 한다.

**해결 방법**

* 센서 플러그인의 매개변수를 실제 센서 사양 또는 프로젝트 요구사항에 맞게 적절히 수정한다.
* ROS2 QoS 프로파일을 확인하여 퍼블리셔와 서브스크라이버가 서로 호환되는지 점검한다.
* 센서 좌표계 변환 시, $\mathbf{R}\_{\text{world}\rightarrow\text{sensor}}$ 등을 단계별로 출력(logging)하여 실제 값이 의도와 일치하는지 비교한다.

#### Gazebo GUI 관련 이슈

Gazebo에는 두 가지 주요 실행 프로세스(`gzserver`, `gzclient`)가 있다. 시뮬레이션 로직은 `gzserver`가 담당하고, GUI는 `gzclient`가 담당한다. GUI가 정상적으로 뜨지 않거나, 마우스-키보드 조작이 되지 않는 문제는 다음 요인에서 기인한다.

gzclient 실행 충돌:

* 그래픽 라이브러리(OpenGL 등)와 호환되지 않는 드라이버를 사용하면 GUI 창이 뜨자마자 크래시( Crash )가 날 수 있다.

실행 권한 및 디스플레이 설정:

* 원격 SSH 환경에서 X11 포워딩이 제대로 되지 않으면 GUI가 열리지 않는다.
* `$DISPLAY$` 환경 변수가 적절히 설정돼 있는지 확인이 필요하다.

옵션 파라미터 설정:

* `gzclient` 실행 시 각종 파라미터(해상도, 풀스크린 여부 등)를 잘못 세팅하면 화면이 비정상적으로 보일 수 있다.

**대처 방법**

* `$gzclient --verbose$` 옵션으로 실행해 로그를 살펴보고, 그래픽 충돌 관련 메시지를 확인한다.
* 원격 환경에서라면, `$xhost +local:$`, `$ssh -X$` 등 X11 포워딩 설정을 꼼꼼히 점검한다.
* GUI 없이 시뮬레이션만 필요하다면, `gzserver`만 실행하는 Headless 모드를 고려한다.

#### Gazebo Log 및 설정 파일 활용

Gazebo는 기본적으로 로그 파일을 남겨 디버깅에 활용할 수 있다. 또한, 설정 파일(`~/.gazebo/` 등)에 일부 파라미터를 기록해둔다.

**로그 저장**:

* `$gz log --help$` 명령으로 로깅 기능을 확인할 수 있다.
* 시뮬레이션 중 발생한 충돌이나 토픽 교환 이력을 확인하기 위해 로그를 활용한다.

**World SDF 파일 기록**:

* 실제 실행 시점에서 로딩된 모델, 오브젝트, 조인트 상태 등이 SDF 형식으로 기록될 수 있다.
* 이를 재로드해 문제 상황을 재현하거나, 다른 환경(PC)으로 이동해 문제를 분석하는 데 쓸 수 있다.

**설정 파일**:

* `~/.gazebo/gui.ini` 등 GUI나 Physics 엔진 설정이 저장되는 파일이 있다.
* 불필요한 설정이 남아 있는 경우, 해당 파일을 삭제(혹은 백업 후 초기화)하여 문제를 해결하기도 한다.

#### Docker 환경에서의 Gazebo 시뮬레이션 문제

ROS2 Humble 및 Gazebo 환경을 Docker로 구성하면, 개발 환경을 이식성 있게 유지할 수 있다. 하지만 가상화 계층 때문에 그래픽 가속, 네트워크, 공유 메모리 등의 문제로 시뮬레이션이 원활히 동작하지 않는 상황이 발생할 수 있다.

**그래픽 가속 지원**:

* Gazebo는 3D 렌더링을 위해 OpenGL 등의 그래픽 API를 사용한다. Docker 컨테이너 내부에서는 기본적으로 GPU 접근이 제한적이므로, 별도의 설정(예: `nvidia-docker2` 또는 `--gpus all` 옵션)이 필요하다.
* 호스트 머신의 GPU 자원을 컨테이너에서 활용하기 위해서는 NVIDIA 드라이버 및 CUDA 버전에 맞는 베이스 이미지를 사용해야 한다.

**네트워크 브리지**:

* Docker는 기본적으로 호스트와 격리된 내부 네트워크를 쓴다. ROS2 노드 간의 통신(또는 Gazebo의 다양한 포트 통신)이 컨테이너 경계를 넘나들어야 할 경우, 포트 매핑 혹은 host 네트워크 모드 등을 고려해야 한다.
* 예:

  ```bash
  docker run --net=host -it ros2-humble-gazebo
  ```
* 단, host 네트워크 모드 사용 시, 보안 이슈 등에 유의한다.

**공유 메모리 및 IPC**:

* Gazebo는 프로세스 간 통신(IPC)을 활용하는 경우가 있다. Docker 컨테이너 내에서 `/dev/shm` 등 공유 메모리 영역 크기가 제한적이거나, 설정이 호환되지 않으면 퍼포먼스 저하가 심해질 수 있다.
* 컨테이너 실행 시 `--shm-size` 옵션을 조절하여 충분한 공유 메모리 영역을 확보한다.

**X11 포워딩**:

* Docker 컨테이너 안에서 GUI(

  ```
  gzclient
  ```

  )를 띄우려면, 호스트의 X 서버로 화면을 전송해야 한다. 일반적으로 다음과 같은 설정이 필요하다.

  ```bash
  xhost +local:root
  docker run -e DISPLAY=$DISPLAY \
             -v /tmp/.X11-unix:/tmp/.X11-unix \
             --gpus all \
             -it ros2-humble-gazebo
  ```
* WSL2 환경에서 Docker를 사용하는 경우라면, 추가로 VcXsrv 등 X 서버 설정이 필요할 수 있다.

#### GPU 가속 시뮬레이션에서 발생하는 이슈

Gazebo 시뮬레이션에서 센서(특히 LiDAR, Depth 카메라 등)는 GPU 기반 가속을 지원하기도 한다. 다음과 같은 문제가 보고된다.

CUDA 버전 호환성:

* 호스트 환경의 CUDA 드라이버 버전과 컨테이너 혹은 설치된 Gazebo 플러그인에서 요구하는 CUDA 버전이 맞지 않을 때, “CUDA driver not found” 또는 “invalid device function” 등의 오류가 발생한다.

OpenGL/GLX 충돌:

* NVIDIA 드라이버가 설치된 시스템에서, X11 포워딩 시 OpenGL 라이브러리가 올바르게 링크되지 않으면 화면이 까맣게 뜨거나 렌더링이 깨질 수 있다.

Headless 모드에서 GPU 사용:

* GUI를 띄우지 않는 Headless 모드(`gzserver`)에서 GPU 상의 센서를 활용하려면, 가상 GPU 환경을 구성해야 하는데, 이때 제대로 지원되지 않는 기능이 있을 수 있다.

**대응 방안**

* 호스트와 동일한 NVIDIA 드라이버(또는 호환 버전)를 Docker 컨테이너에서 사용하도록 설정한다.
* OpenGL/GLX 관련 라이브러리가 충돌하는 경우, 우선 CPU 렌더링으로 테스트한 뒤 점진적으로 GPU 가속을 적용해 문제 지점을 찾는다.
* Headless 환경에서는 반드시 GPU 플러그인이 필요한지 여부를 검토하고, CPU 렌더링만으로도 충분한지 판단한다.

#### 멀티 로봇 시뮬레이션 시의 충돌 이슈

Gazebo 상에서 여러 대의 로봇을 띄워놓고 운영할 때, 좌표계 충돌, 이름공간(namespace) 충돌 등이 자주 발생한다.

네임스페이스 관리:

* ROS2에서는 노드, 토픽, 서비스 등을 네임스페이스로 구분하여 충돌을 피한다. 로봇 각각에 고유한 네임스페이스를 부여해야 한다.
* 예: 로봇1의 LiDAR 토픽: `/robot1/scan`, 로봇2의 LiDAR 토픽: `/robot2/scan`.

TF 트리 겹침:

* 다수 로봇의 TF 트리가 각기 독립적으로 구성돼야 하는데, 잘못 설계된 경우 동일한 프레임 이름(예: `base_link`)을 여러 로봇이 공유하여 충돌한다.
* “Authority ” 경고 메시지가 뜨면, 해당 프레임에 대해 서로 다른 노드에서 동시 브로드캐스트하고 있을 가능성이 높다.

물리 엔진 리소스 부족:

* Gazebo 물리 엔진이 여러 대의 로봇과 센서를 동시에 시뮬레이션할 때, CPU나 GPU가 과부하되어 성능이 급락한다.
* 로봇 수를 줄이거나, 센서 업데이트 주기를 낮춰보면서 원인을 찾는다.

**해결 방법**

* 각 로봇에 고유한 이름, 네임스페이스를 부여하고 TF 프레임 명도 충돌하지 않도록 설계한다.
* 물리 엔진 파라미터(`max_step_size`,`real_time_update_rate` 등)을 조절하거나, 하드웨어 자원을 확충한다.
* 시뮬레이션 환경을 분할하여 병렬적으로 구동하는 방법(예: 별도의 Gazebo 인스턴스에서 다른 로봇을 시뮬레이션)도 고려해 볼 수 있다.

#### World 플러그인 관련 문제

World 플러그인은 Gazebo 월드 전체에 영향을 주는 로직(환경 변화, 오브젝트 생성/삭제, 물리 엔진 전역 설정 등)을 제어한다. 여기서 주의할 점은 다음과 같다.

1. 월드 플러그인의 라이프사이클
   * Gazebo가 실행되면서 월드 플러그인이 초기화( `Load()` ), 업데이트( `OnUpdate()` ), 종료( `Fini()` ) 과정을 순차적으로 거친다. 각 단계에서 사용할 API를 잘 구분해야 한다.
2. 메모리 접근 오류
   * 월드 플러그인이 다른 플러그인(예: 로봇 플러그인) 내부 데이터를 직접 읽으려 할 때, 적절한 인터페이스(서비스 호출, 노드 간 통신 등)를 거치지 않으면 세그멘테이션 폴트가 발생할 수 있다.
3. 다이나믹 모델 생성/삭제
   * 월드 플러그인을 통해 런타임에 새로운 모델(SDF)을 스폰하거나 삭제할 때, ROS2 토픽이나 TF 등이 즉시 반영되지 않을 수 있다.
   * 동적으로 추가된 모델에 대해 ROS2 노드가 인식하도록 별도의 초기화 루틴을 구성해야 한다.

**해결 방법**

* World 플러그인에서 다른 플러그인이나 노드와 통신할 때는 ROS2의 클라이언트/서비스 또는 메시지 교환을 활용해 확실한 인터페이스를 마련한다.
* 동적 모델 생성/삭제 시, 모델이 스폰된 뒤 필요한 ROS2 노드를 기동하거나, TF 트리 갱신을 요청하는 절차를 구현한다.

#### 포맷 변환 및 SDF/URDF 호환성 체크

Gazebo는 기본적으로 SDF를 사용하지만, ROS2에서 주로 활용되는 로봇 모델은 URDF 형태가 많다. URDF를 Gazebo에서 사용하려면 변환 혹은 호환성 체크를 수행한다.

1. URDF -> SDF 자동 변환
   * ROS2에서는 `robot_state_publisher`를 통해 URDF를 파싱하고, Gazebo 플러그인이 URDF를 자동으로 변환하는 경우가 있다.
   * 변환 시 URDF에 없는 태그(예: inertia 표현, 마테리얼 등)가 SDF에선 필수일 수 있으므로 로깅으로 확인한다.
2. Xacro -> URDF -> SDF 파이프라인
   * Xacro 파일에서 매크로로 로봇 모델을 정의하고, 이를 URDF로 확장한 뒤, 최종적으로 Gazebo가 SDF로 변환하는 복잡한 구조가 가능하다.
   * 매 변환 단계마다 태그 누락이나 문법 오류가 없는지 주의 깊게 살펴본다.
3. SDF 버전 차이
   * `$<sdf version="1.6">$` 등 버전 태그에 따라 지원되는 엘리먼트가 상이하다. 구버전 Gazebo를 사용하면 최신 SDF 기능이 인식되지 않아 에러가 발생한다.

**대응 방안**

* URDF <-> SDF 변환 로직에서 발생하는 경고와 오류 로그를 꼼꼼히 확인하고, 누락된 태그를 보완한다.
* `$ign sdf -k my_robot.sdf$` 와 같이 SDF 파일의 유효성을 체크하는 도구( Ignition 사용 시 )를 활용해 전처리한다.
* Xacro 매크로를 반복적으로 테스트하며 각 단계별로 변환된 결과(URDF, SDF)를 별도 파일로 저장해 디버깅한다.

#### Ignition Gazebo vs. Gazebo Classic 충돌 이슈

ROS2 Humble 환경에서는 기존의 “Gazebo Classic”과 “Ignition Gazebo(이하 Ign)” 두 가지 계열을 동시에 사용할 수도 있다. 이 두 시뮬레이터가 동일 환경에 함께 설치되어 있을 경우 버전이나 라이브러리, 명령어(툴체인) 충돌이 발생하기도 한다.

1. **패키지 의존성 중복**
   * 예: apt 패키지로 `gazebo11`, `libignition-gazebo4-dev` 등을 모두 설치했을 경우, `$CMAKE_PREFIX_PATH$`에 Gazebo Classic용 경로와 Ignition용 경로가 동시에 잡혀 혼란스러워진다.
   * 빌드 시 “multiple definitions of symbol”이나 “missing symbol” 형태의 에러가 날 수 있다.
2. **명령어 충돌**
   * “gazebo”, “gzserver” 등 Gazebo Classic 실행 파일과, “ign”, “ign gazebo” 등 Ign에서 쓰는 실행 파일이 혼재할 수 있다.
   * Shell 상에서 PATH 우선순위가 잘못되면, 원치 않는 버전의 실행 파일이 먼저 실행된다.
3. **공유 라이브러리(so) 이름 충돌**
   * 플러그인 이름이 동일하거나, 내부적으로 사용되는 라이브러리가 동일한 이름으로 설치되었을 경우, “symbol lookup error”가 빈번히 나타난다.
   * 특히 Ignition Gazebo에 포함된 물리 엔진(ign-physics), 센서 라이브러리(ign-sensors)와 Gazebo Classic의 물리 엔진(Ode, Bullet) 등 버전을 혼동하지 않도록 주의해야 한다.

**대응 방안**

* 특정 프로젝트에서 Gazebo Classic만 사용할지, Ignition Gazebo만 사용할지를 결정하고, 불필요한 패키지는 제거한다.
* 동시에 유지해야 한다면, 각각을 다른 워크스페이스나 Docker 컨테이너로 분리하여 의존성을 격리한다.
* CMakeLists.txt에서 포함할 헤더 및 링크할 라이브러리를 정확히 지정하고, `$find_package(ignition-gazeboX REQUIRED)` vs. `$ find_package(gazebo REQUIRED)` 등으로 구분한다.

#### ROS2 Launch 시스템 통합 문제

Gazebo 시뮬레이션을 ROS2 Launch 파일과 통합할 때, 시뮬레이터 실행 순서나 노드 파라미터 전달이 엉키는 문제가 자주 발생한다.

**Launch 파일에서 시뮬레이터 실행 순서**:

* ROS2 Launch 시스템에서 `IncludeLaunchDescription` 또는 `ExecuteProcess`를 통해 Gazebo를 실행할 때, 다른 노드(예: 로봇 제어 노드, 센서 노드)보다 먼저 또는 늦게 실행되어야 할 경우가 있다.
* 순서가 어긋나면 URDF/SDF 로딩, TF 브로드캐스트 등이 제대로 이뤄지지 않아 “Model not found” 또는 “Failed to spawn” 문제가 생길 수 있다.

**Launch에서 파라미터 전달**:

* `$use_sim_time$`나 로봇 모델 경로( `robot_description` ) 등을 Launch 파일로부터 동적으로 전달해야 할 때, 노드 파라미터 혹은 Launch argument가 제대로 반영되는지 확인이 필요하다.
* 예를 들어, Python Launch 파일에서

```python
sim_time_param = LaunchConfiguration('use_sim_time', default='true')
node = Node(
  package='my_robot_bringup',
  executable='robot_state_publisher',
  parameters=[{'use_sim_time': sim_time_param}]
)
```

과 같은 방법으로 설정할 수 있다.

**환경 변수 세팅 누락**:

* Launch 파일 내에서 Gazebo를 실행하기 전에 `$GAZEBO_MODEL_PATH$`, `$GAZEBO_PLUGIN_PATH$` 등 필요한 환경 변수를 세팅하지 않으면 모델이나 플러그인이 로드되지 않는다.
* Launch 상에서 `SetEnvironmentVariable` 액션을 통해 명시적으로 설정하거나, 노드 실행 전에 Shell 스크립트로 사전 세팅한다.

**해결 방법:**

* Launch 파일을 단계적으로 분리해, 먼저 시뮬레이터만 구동한 뒤 로봇 노드/센서 노드를 로드하는 식으로 순서를 명확히 조정한다.
* 파라미터와 환경 변수가 올바르게 전달되었는지 `print`(또는 `log_info`)를 통해 Launch 실행 시점에 확인한다.
* 여러 설정 파일(URDF, SDF, Rviz 설정 등)을 한번에 로딩하는 복잡한 Launch 구성일수록, 각 노드가 필요로 하는 파라미터가 즉시 유효한지 세밀히 체크한다.

#### Gazebo 통합 테스트(Integration Test)에서의 에러

CI(Continuous Integration) 환경이나 자동화된 테스트 스크립트에서 Gazebo 시뮬레이션을 활용하는 경우, 로컬에서는 잘 동작하지만 CI 서버에서는 실패하는 상황이 생길 수 있다.

1. **헤드리스 모드 vs. GUI 모드**
   * CI 환경에서는 보통 GUI가 없는 헤드리스 모드로 실행한다. GUI 관련 종속 라이브러리가 누락되었거나, 테스트 시 불필요한 GUI 프로세스를 띄우려 하여 에러가 날 수 있다.
   * `$ gzserver my_world.sdf --record` 등으로 로그나 기록만 남기고 GUI는 비활성화하는 방법을 쓴다.
2. **서버 자원 부족**
   * CI 서버가 CPU/GPU 스펙이 낮거나, 컨테이너 리소스 제한이 걸려 있으면 시뮬레이션 로딩부터 실패하거나 실제와 다른 타이밍으로 로봇이 동작해 테스트가 깨질 수 있다.
3. **비결정론적 물리 엔진 상태**
   * 물리 시뮬레이션은 종종 비결정론적인 결과(부동소수점 연산 순서, 쓰레드 동기화 등)를 낳아, 테스트가 같은 입력임에도 서로 다른 결과를 줄 때가 있다.
   * 특정 수치적 오차 범위를 허용하는 식으로 테스트를 구성해야 한다.

**대응 방안**

* CI 환경에서는 “Headless + record/log” 모드로 시뮬레이션을 돌리고, GUI 관련 라이브러리를 아예 설치하지 않는 방법을 고려한다.
* 테스트 조건을 최대한 단순화하고, 반복 테스트 시 발생할 수 있는 오차 범위를 설정한다.
* 물리엔진 파라미터(랜덤 시드, real\_time\_update\_rate 등)를 고정해 결과의 재현성을 높인다.

#### 물리 엔진 및 충돌(Collision) 문제 디버깅

Gazebo에서 가장 빈번하고도 난해한 문제 중 하나는 불안정한 물리 시뮬레이션이다. 예상치 못한 충돌, 조인트(Joint) 떨림, 로봇이 바닥을 관통(Penetration)하는 등의 현상은 물리 엔진 파라미터가 잘못 설정되어 있거나, 모델 정의에 오류가 있을 때 발생한다.

**물리 엔진 선택**:

Gazebo Classic 환경에서 대표적으로 ODE, Bullet, DART, Simbody 등의 물리 엔진을 선택할 수 있다. Ignition Gazebo 역시 Ignition Physics를 통해 다양한 엔진을 지원한다.

엔진별로 충돌 검사(Collision detection) 알고리즘, 조인트 제약 방식 등이 다르므로, 특정 엔진에서만 발생하는 문제가 있을 수 있다.

**Joint 불안정 문제**:

로봇의 조인트(회전, 프리스매틱 등) 설정에서 마찰 계수, 댐핑 계수, 한계각(Limit) 등이 적절히 설정되지 않으면 진동이 생기거나 모델이 폭주할 수 있다.

URDF나 SDF에서

```xml
<limit effort="100.0" velocity="1.0" lower="-1.57" upper="1.57"/>
<dynamics damping="0.1" friction="0.2"/>
```

와 같은 세부 파라미터를 올바르게 기입해야 한다.

**Collision Geometry 충돌**:

Collision geometry가 실제 로봇 형상과 맞지 않거나, 지나치게 복잡한 메시에 의해 계산량이 폭증하면 시뮬레이션이 불안정해진다.

충돌 지오메트리는 단순 도형(Box, Cylinder, Sphere 등) 또는 적은 폴리곤 메쉬로 구성하는 것이 좋다.

SDF 예:

```xml
<collision name="base_collision">
  <geometry>
    <box>
      <size>0.5 0.5 0.2</size>
    </box>
  </geometry>
</collision>
```

**땅 관통(Penetration) 문제**:

관통이 발생하면, 모델이 지면을 뚫고 내려가거나 충돌 계산이 꼬여서 “NaN” 에러 등을 일으킬 수 있다.

Physics 파라미터 중 `contact_surface_layer` 또는 `min_depth`와 같은 옵션이 너무 작게 설정되어 있을 때 발생하기도 한다.

해결 방법:

충돌 지오메트리가 지면과 확실히 닿도록 모델 높이를 약간 띄운 상태로 스폰

지면(plane) 모델의 두께나 마찰력, $\text{contact\_surface\_layer}$ 값을 적절히 조정

**마찰(Friction) 계수 불일치**:

* 실제 로봇 주행 상황을 시뮬레이션하려면, 바퀴와 지면 사이 마찰 계수를 조정해야 한다.
* 마찰 계수가 과도하게 높으면 로봇이 회전 시 튀거나, 반대로 너무 낮으면 바퀴가 헛도는 등 비현실적 거동을 보인다.

ODE 기준 파라미터 예시:

```xml
<surface>
  <friction>
    <ode>
      <mu>0.8</mu>
      <mu2>0.8</mu2>
    </ode>
  </friction>
</surface>
```

#### 고급 디버깅 도구: Valgrind, AddressSanitizer

Gazebo를 비롯한 복잡한 C++ 애플리케이션에서는 메모리 누수나 초기화되지 않은 변수로 인해 예측 불가한 오류가 일어나기도 한다. C++ 기반 플러그인이나 월드 플러그인을 작성했다면 다음 도구를 활용해보자.

**Valgrind**:

* 메모리 누수(Leak), 잘못된 메모리 접근을 식별하기에 유용하다.
* Gazebo를 Valgrind로 실행:

  ```bash
  valgrind --tool=memcheck gzserver my_world.sdf
  ```
* 성능이 크게 저하되므로, 필요한 시점에만 사용한다.

**AddressSanitizer(ASan)**:

* Clang이나 GCC에서 `-fsanitize=address` 옵션을 사용해 빌드하면, 실시간으로 잘못된 메모리 접근을 감지할 수 있다.
* 예:

  ```bash
  colcon build --cmake-args -DCMAKE_CXX_FLAGS="-fsanitize=address" ...
  ```
* 러닝타임에 메모리 오류가 발생하면 즉시 프로그램을 중단시키고 스택 트레이스를 출력한다.

**ThreadSanitizer(TSan)**:

* 멀티쓰레드 환경에서의 데드락, 데이터 레이스 등을 찾는 용도.
* Gazebo 플러그인이 여러 쓰레드를 생성하거나, ROS2 콜백이 병렬로 수행되는 상황을 테스트할 때 유용하다.

#### 커스터마이징된 물리 엔진 빌드

표준 물리 엔진(ODE, Bullet 등)을 넘어, 특정 기능을 위해 엔진을 직접 빌드하거나 포크(Fork)해서 사용할 수도 있다.

**엔진 소스 코드 수정**:

* 마찰 모델(Friction model)을 커스터마이징하거나, Joint 제약 방정식을 직접 수정하려는 경우, 엔진 레벨의 코드를 변경해야 한다.
* 이때 Gazebo(또는 Ignition)와의 연동을 고려해, 버전에 맞는 엔진 저장소를 클론한다.

**빌드 시스템 호환성**:

* ODE나 Bullet의 빌드 설정(CMake, Autotools 등)과 Gazebo(또는 Ignition) 빌드 설정을 맞춰야 한다.
* “libode.so” 버전이 달라 충돌하거나, Bullet의 C++ 표준 버전이 다른 경우 빌드가 깨질 수 있다.

**플러그인/월드 설정에서의 사용자 정의 파라미터**:

* 수정된 물리 엔진에 새 파라미터를 추가했다면, SDF나 URDF 파일에도 해당 파라미터를 반영해야 한다.
* “Unrecognized parameter” 같은 에러가 나오지 않도록 주의한다.

#### 고급 로그(Verbose) 활용

Gazebo 및 Ignition에서는 상세 로그를 남길 수 있는 Verbose 옵션이나 Debug 레벨 설정을 지원한다.

Gazebo Classic:

* `gzserver --verbose` 실행 시, 물리 엔진 초기화 과정, 플러그인 로딩 과정 등에 대한 상세 로그가 출력된다.
* `$GZ_LOG_LEVEL=DEBUG gzserver my_world.sdf$` 와 같이 환경 변수를 통해 디버그 레벨을 높일 수도 있다.

Ignition Gazebo:

* `ign gazebo my_world.sdf -v 4` 처럼 `-v` 옵션 뒤에 숫자를 붙여 로그 레벨을 설정한다(기본 1, 최대 4).
* Ignition 라이브러리별 로그 레벨을 다르게 세팅할 수도 있다. 예:

  ```bash
  export IGN_TRANSPORT_CONFIG_PATH=...
  export IGN_LOGGER_LEVEL=4
  ```

플러그인 로깅:

* C++ 코드 내에서 ROS2의 RCLCPP logging 혹은 Gazebo/Ignition의 자체 로그 함수를 호출해 디버그 메시지를 남길 수 있다.
* 예:

  ```cpp
  gzdbg << "Debug message for Gazebo plugin" << std::endl;
  ```
