# URDF/명령어를 이용한 로봇 스폰(Spawn)

#### 로봇 스폰의 개념과 필요성

ROS2 환경에서 로봇 모델을 시뮬레이션하기 위해서는 Gazebo 상에 로봇을 배치(Spawn)해야 한다. 로봇을 직접 Gazebo GUI에서 불러오는 방식도 있지만, ROS2 패키지를 통해 자동화된 명령으로 로봇을 배치하면 훨씬 효율적이다. 특히 URDF 파일을 통해 로봇 모델을 정의하고, 정의된 내용을 명령어를 통해 Gazebo에 적절히 전달함으로써 다음과 같은 이점이 있다.

* **일관성**: 실제 로봇 모델과 시뮬레이션 모델을 동일한 URDF 파일로 관리할 수 있다.
* **유연성**: ROS2의 다양한 툴(launch 파일, spawn 스크립트 등)을 활용하여 여러 로봇이나 상황별 구성을 자동으로 로드할 수 있다.
* **재사용성**: 동일한 URDF 파일을 다른 프로젝트나 다른 시뮬레이션 환경(예: RViz)에도 그대로 적용할 수 있다.

#### Gazebo에서의 로봇 스폰 절차

Gazebo에서 URDF로 정의된 로봇을 스폰하기 위해서는 대체로 다음과 같은 단계를 밟는다.

1. **URDF 모델 확보** 로봇의 링크(link)와 조인트(joint), 조인트 제한, 충돌 모델, 관성 정보, 센서 플러그인 등이 기술된 URDF(.urdf) 혹은 Xacro(.xacro) 파일을 준비한다.
2. **URDF 로드 및 파싱** ROS2 패키지 내에서 URDF 파일의 경로를 찾고, 필요한 경우 Xacro를 통해 매크로를 확장하여 최종 URDF 형태로 파싱한다.
3. **로봇 스폰 명령 실행** Gazebo가 실행 중인 상태에서, ROS2의 노드 혹은 명령어를 사용하여 URDF 모델을 Gazebo 월드에 배치한다. 보통 `spawn_entity.py` 또는 Launch 파일에서 해당 스크립트를 호출한다.
4. **좌표계 조정 및 초기 포즈 설정** 로봇이 월드 좌표계(world frame)에서 어떤 위치와 자세(orientation)로 놓여야 할지를 지정한다.
5. **확인 및 디버깅** Gazebo UI 혹은 rviz2에서 로봇이 정상적으로 배치되었는지 확인하고, 원하는 동작을 수행하는지 점검한다.

#### URDF로 정의된 로봇 모델 확인

URDF는 XML 구조로 되어 있으며, 링크, 조인트, 센서 등을 태그로 기술한다. 예를 들어, 다음과 같은 간단한 링크 정의가 있을 수 있다.

```xml
<link name="base_link">
  <visual>
    <geometry>
      <box size="0.2 0.2 0.1"/>
    </geometry>
    <material name="Cyan">
      <color rgba="0 1.0 1.0 1.0"/>
    </material>
  </visual>
  <collision>
    <geometry>
      <box size="0.2 0.2 0.1"/>
    </geometry>
  </collision>
  <inertial>
    <mass value="1.0"/>
    <origin xyz="0 0 0"/>
    <inertia ixx="0.001" iyy="0.001" izz="0.001" ixy="0.0" ixz="0.0" iyz="0.0"/>
  </inertial>
</link>
```

* `<visual>` 태그: 시각적으로 어떻게 보일 것인가를 정의
* `<collision>` 태그: 충돌 검출 및 물리 연산에 사용될 형상을 정의
* `<inertial>` 태그: 질량 및 관성 모멘트를 정의

이러한 정의가 로봇 전체 링크와 조인트에 대해 충분히 이루어져 있어야 Gazebo에서 현실적인 물리 시뮬레이션이 가능하다.

#### ROS2에서 spawn\_entity.py 사용하기

ROS2 Humble 버전 이상에서는 `gazebo_ros` 패키지에서 제공하는 `spawn_entity.py` 스크립트를 통해 로봇을 Gazebo에 스폰할 수 있다. 기본적인 명령어는 다음과 같다.

```bash
ros2 run gazebo_ros spawn_entity.py \
  --entity <EntityName> \
  --file <path_to_urdf_or_sdf> \
  --x <X_좌표> \
  --y <Y_좌표> \
  --z <Z_좌표> \
  --roll <ROLL값> \
  --pitch <PITCH값> \
  --yaw <YAW값>
```

* `--entity`: 스폰할 로봇(또는 객체)의 이름
* `--file`: 로드할 파일(URDF 혹은 SDF)의 경로
* `--x, --y, --z`: 초기 위치
* `--roll, --pitch, --yaw`: 초기 자세(orientation)

만약 Xacro 파일을 사용하는 경우에는 먼저 Xacro를 URDF로 확장한 뒤 스폰해야 한다.

#### URDF를 통한 자동 스폰 예제

만약 ROS2 패키지 내부에 `robot.urdf`라는 파일이 있고, 이를 Gazebo에 스폰하려 한다고 하자. Gazebo를 아래와 같이 우선 실행한다.

```bash
ros2 launch gazebo_ros gazebo.launch.py
```

이후 별도의 터미널에서 다음 명령으로 로봇을 스폰할 수 있다.

```bash
ros2 run gazebo_ros spawn_entity.py \
  --entity my_robot \
  --file /home/user/ros2_ws/src/my_robot_description/urdf/robot.urdf \
  --x 0.0 --y 0.0 --z 0.5 \
  --roll 0.0 --pitch 0.0 --yaw 0.0
```

실행 후 Gazebo UI에서 `my_robot`이 월드 좌표계 `(0.0, 0.0, 0.5)` 위치에 놓여 있는 것을 확인할 수 있다.

#### 변환 관계와 좌표계 지정

로봇 모델을 URDF로 정의할 때, 링크와 조인트는 기본적으로 부모-자식 관계를 통해 트리 구조로 연결된다. 이때, 각 링크마다 국소 좌표계가 정의되며, URDF 상에서 `<origin>` 태그 등을 통해 상대 변환을 기술한다. 이를 수학적으로 표현하면 다음과 같은 행렬 형태를 생각할 수 있다.

$$
^{\text{parent}}\mathbf{T}\_{\text{child}} =  \begin{bmatrix} \mathbf{R} & \mathbf{p} \ 0 & 1 \end{bmatrix}
$$

* $\mathbf{R}$: 3×3 회전행렬
* $\mathbf{p}$: 3×1 병진(translation) 벡터

ROS2에서 이러한 변환 정보는 TF2 패키지를 통해 시각화/관리되며, Gazebo 시뮬레이터는 이를 기반으로 물리 엔진 상의 연결을 해석한다.

#### Xacro를 활용한 동적 파라미터 처리

URDF는 XML 형식이므로 중복되는 텍스트가 많을 경우 유지보수나 가독성이 떨어질 수 있다. 이를 보완하기 위해 ROS2에서는 Xacro(XML Macros)를 활용하여 템플릿화, 파라미터화된 URDF 생성을 지원한다.

* **Xacro 매크로** `<xacro:macro>` 태그로 반복적으로 사용될 구조를 정의하고, `<xacro:call_macro>` 혹은 `<xacro:macro_name>` 형태로 재사용 가능하다. 예를 들어 바퀴와 같은 부품을 여러 개 생성해야 할 때 유용하다.
* **파라미터(변수) 처리** `<xacro:property>` 또는 매크로 인자를 통해 로봇의 물리적 크기, 링크 개수, 센서 종류 등을 변수화할 수 있다. 동일한 로봇 모델이지만 회전 조인트 범위, 지름, 색상 등이 다른 경우 효율적으로 대응 가능하다.

**Xacro를 URDF로 변환하기**

Gazebo에 로봇 모델을 스폰하기 위해서는 Xacro 파일을 먼저 URDF 형태로 변환해야 한다. 보통 아래와 같은 명령어로 변환한다.

```bash
xacro my_robot.xacro > my_robot.urdf
```

변환된 `my_robot.urdf` 파일을 `spawn_entity.py` 또는 Launch 파일에서 사용하면 된다.

#### Launch 파일에서의 로봇 스폰 자동화

ROS2 Humble 이후부터 Launch 파일 구조가 확장되고 있으며, 이를 통해 여러 프로세스를 간단히 병렬적으로 실행하고, 필요한 매개변수를 전달할 수 있다. 다음과 같은 방식으로 Launch 파일에서 스폰 과정을 자동화할 수 있다.

예시: `spawn_robot.launch.py`

```python
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import ExecuteProcess
from launch.substitutions import LaunchConfiguration, Command
from launch_ros.actions import Node

def generate_launch_description():
    urdf_file_path = os.path.join(
        get_package_share_directory('my_robot_description'),
        'urdf',
        'my_robot.urdf'
    )

    return LaunchDescription([
        ExecuteProcess(
            cmd=[
                'gazebo', '--verbose', '-s', 'libgazebo_ros_factory.so'
            ],
            output='screen'
        ),
        ExecuteProcess(
            cmd=[
                'ros2', 'run', 'gazebo_ros', 'spawn_entity.py',
                '--entity', 'my_robot',
                '--file', urdf_file_path,
                '--x', '0.0',
                '--y', '0.0',
                '--z', '0.5'
            ],
            output='screen'
        )
    ])
```

* `ExecuteProcess`를 통해 Gazebo를 실행하고, `spawn_entity.py`를 자동으로 호출한다.
* Launch 파일 하나만 실행해도 Gazebo와 로봇 스폰이 동시에 이루어진다.

#### SDF로의 확장

Gazebo는 URDF 이외에도 SDF(Simulation Description Format)을 지원한다. 경우에 따라서는 URDF로 시작했다가 시뮬레이션 전용 기능(예: 특정 물리 엔진 파라미터, 고급 센서 설정 등)을 위해 SDF 포맷으로 변환하거나 직접 작성하기도 한다.

* **URDF to SDF 변환** Gazebo 구버전에서는 URDF를 SDF로 자동 변환하는 `gz sdf` 또는 `gz urdf` 명령을 제공했으나, ROS2 Humble에서는 이를 Launch나 Python 스크립트 형태로 구현하는 사례가 많다.
* **SDF 스폰** `spawn_entity.py` 스크립트는 입력 파일이 SDF이든 URDF이든 크게 구분하지 않는다. 단지 `--file` 인자에 맞춰 해석할 뿐이다.

#### 여러 로봇 동시 스폰

멀티 로봇 환경을 구성할 때, 같은 URDF 혹은 Xacro를 여러 번 호출하여 여러 개의 로봇을 다른 좌표에 배치할 수 있다. 예를 들어 다음과 같이 같은 로봇을 두 개 스폰할 수 있다.

```bash
ros2 run gazebo_ros spawn_entity.py \
  --entity robot1 \
  --file /home/user/ros2_ws/src/my_robot/urdf/my_robot.urdf \
  --x 0.0 --y 0.0 --z 0.0

ros2 run gazebo_ros spawn_entity.py \
  --entity robot2 \
  --file /home/user/ros2_ws/src/my_robot/urdf/my_robot.urdf \
  --x 1.0 --y 1.0 --z 0.0
```

* `--entity` 이름이 겹치지 않도록 주의한다.
* 서로 다른 초기 위치를 지정하여 충돌을 피한다.

#### URDF 내 물리 파라미터와 Gazebo 플러그인

Gazebo에서는 로봇 객체의 물리적 특성을 정확히 정의해야 실제 로봇에 가까운 시뮬레이션 결과를 얻을 수 있다. URDF에는 물리 엔진에서 사용되는 관성, 질량, 마찰 계수, 댐핑 계수 등 여러 요소가 반영될 수 있지만, 좀 더 고급 기능이 필요한 경우 Gazebo 고유의 플러그인을 활용할 수도 있다.

* **마찰 계수(friction coefficient)** URDF 자체에는 마찰 계수를 직접 명시하는 태그가 없다. 대신 Gazebo에서 지원하는 XML 확장을 URDF 안에 포함시키거나, SDF를 직접 사용해 `<surface><friction>`와 같은 태그로 명시한다. 바퀴가 미끄러지는 정도를 조정하거나, 지면과의 접촉 특성을 다루기 위해서는 필수적인 요소이다.
* **댐핑(damping)과 스프링(stiffness)** URDF의 `<joint>` 태그에서 `<dynamics damping="..." friction="..."/>`처럼 표현할 수 있다. 이는 조인트 레벨에서의 댐핑과 조인트마찰(friction)을 기술한다.
* **Gazebo 플러그인 사용** URDF 안에 `<gazebo>` 블록을 추가해 센서, 컨트롤러, 환경 상호 작용 등을 담당하는 플러그인을 선언할 수 있다. 예를 들어 카메라 센서를 모델링하거나, ROS 컨트롤 인터페이스를 사용하기 위해서는 `<plugin>` 태그를 사용한다.

예시:

```xml
<gazebo>
  <plugin name="camera_plugin" filename="libgazebo_ros_camera.so">
    <update_rate>30.0</update_rate>
    <camera_name>my_robot_camera</camera_name>
    ...
  </plugin>
</gazebo>
```

이렇게 URDF 내에 포함된 Gazebo 플러그인 정보는 `spawn_entity.py`를 통해 로드될 때 함께 적용되어, 시뮬레이션 환경에서 센서나 컨트롤러가 정상 동작하도록 만든다.

#### TF2와 Gazebo 좌표계 연동

ROS 환경에서 좌표계 관리를 담당하는 TF2(transform library)는 URDF와 밀접하게 연동된다. URDF를 통해 정의된 링크 간 관계와, 실제 로봇에서 측정된 조인트 값(joint state)이 TF2를 통해 실시간으로 브로드캐스팅되어 시각화나 제어에서 활용된다. Gazebo에서 로봇이 움직이면, 이를 ROS 토픽(주로 `/joint_states`)에 반영해 TF 트리를 자동 갱신한다.

* **TF 트리 예시** 로봇 모델이 base\_link → link1 → link2 → link3 구조로 되어 있을 경우, Gazebo 시뮬레이션에서 각 조인트의 변위가 업데이트되면 `/tf` 토픽으로 다음과 같은 변환이 실시간 방송된다.
  * ${}^{\text{base\_link}}\mathbf{T}\_{\text{link1}}$
  * ${}^{\text{link1}}\mathbf{T}\_{\text{link2}}$
  * ${}^{\text{link2}}\mathbf{T}\_{\text{link3}}$
* **RViz와의 연동** Gazebo를 사용하지 않고 RViz에서만 로봇 모델을 확인할 때도 URDF와 TF2가 동일하게 작동한다. 따라서 URDF를 한 번 잘 정의해 놓으면, Gazebo 시뮬레이터든 실제 로봇이든 일관성 있게 좌표계를 관리할 수 있다.

#### 모델 파라미터 변경 시 주의 사항

URDF나 Xacro에서 모델의 형상, 질량, 조인트 범위 등을 수정할 때에는 시뮬레이션 안정성을 위해 다음 사항을 유의해야 한다.

1. **관성(inertia) 값의 일관성** 관성 행렬이 물리적으로 가능한 값인지 점검해야 한다. 예를 들어, 너무 작은 Ixx, Iyy, Izz나 비현실적인 비대칭 값은 시뮬레이터가 불안정해지는 원인이 된다.
2. **질량(mass)와 치수(dimension)의 균형** 로봇의 치수에 비해 질량이 지나치게 크거나 작으면 시뮬레이션 상에서 움직임이 비현실적일 수 있다.
3. **조인트 제한범위(joint limit)** 실제 하드웨어와 동일한 값으로 맞춰야 한다. 이상치(예: 3600°까지 허용 등)를 넣으면 Gazebo가 정상 동작하지 않을 가능성이 있다.
4. **플러그인 파라미터** Gazebo 플러그인에서 정의하는 ROS 토픽 이름, 센서 주파수, 카메라 해상도 등을 수정할 때 서로 충돌되지 않는지 확인해야 한다.

#### spawn\_entity.py 대신 서비스 호출로 스폰하기

ROS2에서 Gazebo 로봇을 스폰하기 위한 또 다른 방법으로, `/spawn_entity`라는 서비스를 직접 호출하는 방식을 사용할 수도 있다. 이는 `spawn_entity.py`가 내부적으로 사용하고 있는 Gazebo-ROS 서비스이며, 다음과 같이 서비스 요청 메시지를 구성하면 된다.

1. **서비스 메시지** `gazebo_msgs/srv/SpawnEntity.srv` 형태이며, EntityName, XML(URDF 또는 SDF) 문자열, initial pose 등이 필드에 포함된다.
2. **예시** Python 노드에서 `rclpy`를 통해 해당 서비스를 호출해 로봇을 배치할 수 있다.

```python
import rclpy
from rclpy.node import Node
from gazebo_msgs.srv import SpawnEntity

class MySpawner(Node):
    def __init__(self):
        super().__init__('my_spawner')
        self.cli = self.create_client(SpawnEntity, 'spawn_entity')
        while not self.cli.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('spawn_entity service not available, waiting...')
        self.req = SpawnEntity.Request()

    def spawn(self, name, xml, x=0.0, y=0.0, z=0.0):
        self.req.name = name
        self.req.xml = xml
        self.req.initial_pose.position.x = x
        self.req.initial_pose.position.y = y
        self.req.initial_pose.position.z = z
        self.future = self.cli.call_async(self.req)
        rclpy.spin_until_future_complete(self, self.future)
        return self.future.result()

def main(args=None):
    rclpy.init(args=args)
    node = MySpawner()
    urdf_string = """<robot name="my_robot">...</robot>"""  # URDF 문자열
    response = node.spawn('my_robot', urdf_string, 0.0, 0.0, 0.5)
    node.get_logger().info('Spawned entity: {}'.format(response.status_message))
    node.destroy_node()
    rclpy.shutdown()
```

* `SpawnEntity.Request()`에 직접 URDF 문자열을 넣어 호출할 수 있다.
* `spawn_entity.py` 스크립트가 내부에서 이와 같은 과정을 자동화해주므로, 일반 사용자라면 스크립트 사용이 편리하다.

#### URDF와 Launch 파일 구조 예시(mermaid)

로봇 설명 패키지 구조와 Gazebo 스폰을 포함하는 Launch 파일 간의 관계를 다이어그램으로 표현하면 다음과 같다.

{% @mermaid/diagram content="flowchart TB
A\[my\_robot\_description package] --> B\[urdf/robot.xacro]
A --> C\[meshes/...]
A --> D\[launch/spawn\_robot.launch.py]
B --> |xacro to urdf| E\[robot.urdf]
D --> |Load URDF & spawn| F\[Gazebo Simulator]" %}

* `my_robot_description` 패키지에서 Xacro, 메쉬 파일을 관리하고, Launch 파일에서 가공 후 Gazebo에 스폰한다.
* URDF 파일은 Xacro를 통해 생성될 수도 있고, 이미 완성된 URDF일 수도 있다.

#### 디버깅 및 문제 해결 팁

Gazebo 환경에서 로봇 스폰이 실패하거나 모델이 정상적으로 나타나지 않을 때, 다음과 같은 디버깅 절차를 거치는 것이 좋다.

1. **URDF/Xacro 문법 오류 확인**
   * Xacro 파일에서 `<xacro:property>` 혹은 `<xacro:macro>` 등을 잘못 사용했거나, 태그가 올바르게 닫히지 않은 경우 변환 단계에서 에러가 발생한다.
   * `$ xacro my_robot.xacro` 명령을 통해 에러 메시지를 확인하고, 문제가 있는 부분을 수정한다.
2. **패키지 경로 확인**
   * `ros2 pkg prefix my_robot_description` 등을 통해 실제 설치된 패키지 위치를 확인한다.
   * Launch 파일이나 spawn 명령에서 `--file` 경로가 실제로 존재하는지 다시 점검한다.
3. **중복된 엔티티 이름**
   * Gazebo 내에서 이미 같은 이름의 엔티티가 존재하면 스폰이 실패하거나 덮어쓰기 위험이 있다.
   * `--entity` 옵션으로 로봇 이름을 변경해 가며 테스트한다.
4. **관성/질량 오류**
   * 시뮬레이션을 시작하자마자 로봇이 하늘로 날아가거나 바닥을 뚫고 떨어지는 경우, 관성(inertia) 값이 비정상일 수 있다.
   * URDF의 `<inertial>` 태그에서 $I\_{xx}, I\_{yy}, I\_{zz}$ 등이 물리적으로 말이 되는 값인지 다시 계산해 본다.
5. **Gazebo 플러그인 충돌**
   * 동일한 플러그인이 여러 곳에서 중복 로드되어 충돌을 일으킬 수 있다.
   * ROS 토픽 이름이 충돌하는 사례(예: 카메라 플러그인에서 서로 다른 카메라인데 `/camera/image_raw`를 같이 쓰는 등)도 주의한다.
6. **플러그인 소스 로그 확인**
   * Gazebo를 실행할 때 콘솔(log)이 뜨는 터미널을 통해 경고나 에러 메시지를 확인한다.
   * ROS2와 Gazebo가 통신하는 과정에서 버전 차이로 인한 warning이 표시되기도 하므로, 이를 통해 원인을 추적할 수 있다.

#### URDF vs. Xacro: 효율적 관리 전략

로봇 모델의 복잡도가 높아질수록 반복적으로 나타나는 요소(예: 링크의 기본 형상, 조인트 구조, 동일한 형상의 바퀴 여러 개 등)를 Xacro 매크로로 관리하는 것이 훨씬 효율적이다.

* **장점**
  * 유지보수성 향상: 작은 부분만 수정해도 모든 인스턴스에 적용 가능
  * 가독성 향상: 중복 코드가 줄어들어 모델 구조가 한눈에 들어옴
  * 매개변수화: 로봇 크기(길이, 지름 등)를 통일된 파라미터로 관리 가능
* **단점**
  * 추가적인 전처리 단계 필요: Xacro 파일을 직접 Gazebo가 해석할 수 없으므로, URDF 변환 단계가 필수
  * 매크로 구조가 복잡해지면 오히려 이해하기 어려울 수 있음

따라서 어느 정도 규모 이상의 로봇 프로젝트에서는 Xacro를 적극 사용하는 것이 일반적이며, 간단한 로봇은 곧바로 URDF를 작성해도 무방하다.

#### Spawn 시나리오별 예시

1. **로봇 팔(Robot Arm)**
   * 조인트가 많고, 각 조인트마다 관성/댐핑/마찰이 설정되어야 한다.
   * Gripper 또는 End-effector가 교체 가능하도록 매크로 구조를 만든 뒤, 스폰 시 다른 매개변수를 주어 손쉽게 교체 가능하게 설계한다.
2. **모바일 로봇(차륜형, Omni-wheel 등)**
   * 바퀴가 2개, 4개, 혹은 Mecanum 휠 같은 복잡한 구성일 경우 Xacro로 바퀴 매크로를 생성한다.
   * 바퀴 수에 따라 URDF를 동적으로 생성한 뒤 스폰한다.
3. **드론(쿼드콥터 등)**
   * 프로펠러(프로펠러 수만큼 반복되는 모델링)를 매크로로 관리한다.
   * 실제 물리 엔진 세팅이나 플러그인을 통해 양력, 항력, 관성을 세밀하게 설정해야 하므로, 커스텀 플러그인을 URDF/Gazebo 블록에 추가한다.

#### Gazebo GUI를 통한 직접 스폰

명령줄이 아닌 Gazebo GUI 상에서 `.sdf` 또는 `.urdf` 파일을 모델로 불러오는 방법도 가능하다.

* Gazebo 메뉴에서 ‘Insert’ → ‘Models’ → ‘Add’ 로 진입하여 로컬 모델을 등록한다.
* 이 방법은 URDF/Xacro 매개변수를 동적으로 조정할 수 없으므로, 미리 확정된 모델을 확인하거나 단순 시연할 때 주로 사용한다.

#### 스폰된 로봇 상태 확인

로봇이 성공적으로 스폰된 뒤, 다음 명령 등을 통해 상태를 확인할 수 있다.

```bash
ros2 topic list
```

* Gazebo 상에서 새롭게 생긴 센서 토픽(예: `/my_robot/camera1/image_raw`)이나 조인트 상태 토픽(예: `/joint_states`)이 표시되는지 확인

```bash
ros2 topic echo /joint_states
```

* 각 조인트의 현재 값, 속도, 토크 등을 실시간 확인 가능

```bash
ros2 param list
```

* ROS2 노드 파라미터에 로봇 관련 설정이 반영되어 있는지 점검 (일부 플러그인은 파라미터 서버를 활용)
