[ROS2 응용] Day 4: Gazebo와 ROS2 시뮬레이션 - 하드웨어 없이 개발하기
[ROS2 응용] Day 4: Gazebo와 ROS2 시뮬레이션 - 하드웨어 없이 개발하기
서론: 시뮬레이션은 개발 속도를 높이고 비용을 낮춘다
실제 로봇은 부수고 수리하는 데 시간과 비용이 든다. Gazebo 시뮬레이션에서 검증한 코드를 실제 로봇에 올리면 위험을 크게 줄일 수 있다. ROS2 Jazzy는 Gazebo Harmonic과 공식 호환된다.
1. Gazebo Harmonic 설치
1
2
3
4
5
6
# Gazebo Harmonic 설치
sudo apt install ros-jazzy-ros-gz
# 브리지 패키지 (ROS2 ↔ Gazebo 통신)
sudo apt install ros-jazzy-ros-gz-bridge
sudo apt install ros-jazzy-gz-ros2-control
2. ROS1 Gazebo vs Gazebo Harmonic
1
2
3
4
5
6
7
과거: gazebo_ros_pkgs (classic Gazebo, gazebo11)
현재: ros_gz (Gazebo Harmonic, gz-sim8)
차이점:
- 시뮬레이터 이름: gazebo → gz sim
- 플러그인 형식: 공유 라이브러리 → SDF 시스템 플러그인
- ROS2 통신: 브리지(ros_gz_bridge)를 통해 연결
3. SDF 월드 파일 구조
Gazebo는 URDF 대신 SDF(Simulation Description Format)를 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!-- worlds/empty.sdf -->
<?xml version="1.0" ?>
<sdf version="1.8">
<world name="default">
<physics name="1ms" type="ignored">
<max_step_size>0.001</max_step_size>
<real_time_factor>1.0</real_time_factor>
</physics>
<plugin filename="gz-sim-physics-system"
name="gz::sim::systems::Physics"/>
<plugin filename="gz-sim-sensors-system"
name="gz::sim::systems::Sensors"/>
<plugin filename="gz-sim-ros2-control-system"
name="gz_ros2_control::GazeboSimROS2ControlPlugin"/>
<light type="directional" name="sun">
<direction>-0.5 0.1 -0.9</direction>
</light>
<model name="ground_plane">
<static>true</static>
<link name="link">
<collision name="surface">
<geometry><plane><normal>0 0 1</normal></plane></geometry>
</collision>
</link>
</model>
</world>
</sdf>
4. URDF에 Gazebo 플러그인 추가
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<robot name="my_robot">
<!-- 기존 링크·조인트 정의 -->
<!-- ros2_control 하드웨어를 Gazebo로 교체 -->
<ros2_control name="GazeboSystem" type="system">
<hardware>
<plugin>gz_ros2_control/GazeboSimSystem</plugin>
</hardware>
<joint name="left_wheel_joint">
<command_interface name="velocity"/>
<state_interface name="position"/>
<state_interface name="velocity"/>
</joint>
<joint name="right_wheel_joint">
<command_interface name="velocity"/>
<state_interface name="position"/>
<state_interface name="velocity"/>
</joint>
</ros2_control>
<!-- LiDAR 센서 -->
<gazebo reference="lidar_link">
<sensor name="lidar" type="gpu_lidar">
<update_rate>10</update_rate>
<ray>
<scan>
<horizontal>
<samples>360</samples>
<min_angle>-3.14159</min_angle>
<max_angle>3.14159</max_angle>
</horizontal>
</scan>
<range>
<min>0.1</min>
<max>10.0</max>
</range>
</ray>
<plugin filename="gz-sim-sensors-system"
name="gz::sim::systems::Sensors"/>
</sensor>
</gazebo>
</robot>
5. ros_gz_bridge로 토픽 연결
Gazebo의 토픽은 ROS2와 분리된 버스에 있다. 브리지를 통해 연결한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# launch 파일에 브리지 추가
from launch_ros.actions import Node
bridge = Node(
package='ros_gz_bridge',
executable='parameter_bridge',
arguments=[
# Gazebo → ROS2
'/lidar@sensor_msgs/msg/LaserScan[gz.msgs.LaserScan',
'/camera/image@sensor_msgs/msg/Image[gz.msgs.Image',
'/odom@nav_msgs/msg/Odometry[gz.msgs.Odometry',
# ROS2 → Gazebo
'/cmd_vel@geometry_msgs/msg/Twist]gz.msgs.Twist',
],
output='screen',
)
브리지 형식: topic@ros_type[gz_type (Gz→ROS), topic@ros_type]gz_type (ROS→Gz)
6. Gazebo 실행 런치 파일
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription, DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
from ros_gz_sim.actions import GzServer
import os
def generate_launch_description():
pkg_dir = get_package_share_directory('my_robot')
world_file = os.path.join(pkg_dir, 'worlds', 'empty.sdf')
urdf_file = os.path.join(pkg_dir, 'urdf', 'my_robot.urdf')
return LaunchDescription([
# Gazebo 서버 실행
IncludeLaunchDescription(
PythonLaunchDescriptionSource([
get_package_share_directory('ros_gz_sim'),
'/launch/gz_sim.launch.py'
]),
launch_arguments={'gz_args': world_file}.items(),
),
# URDF를 Gazebo에 스폰
Node(
package='ros_gz_sim',
executable='create',
arguments=['-file', urdf_file, '-name', 'my_robot'],
),
# 브리지
Node(
package='ros_gz_bridge',
executable='parameter_bridge',
arguments=[
'/lidar@sensor_msgs/msg/LaserScan[gz.msgs.LaserScan',
'/cmd_vel@geometry_msgs/msg/Twist]gz.msgs.Twist',
],
),
# ros2_control 컨트롤러
Node(
package='controller_manager',
executable='spawner',
arguments=['diff_drive_controller'],
),
])
7. 시뮬레이션 시간 동기화
시뮬레이션에서는 /clock 토픽이 시뮬레이션 시간을 제공한다. 모든 노드에서 use_sim_time: true를 설정해야 TF 타임스탬프가 맞는다.
1
2
3
4
# 모든 노드에 공통 적용
/**:
ros__parameters:
use_sim_time: true
1
2
3
4
5
# 시뮬레이션 일시정지 / 재개
gz service -s /world/default/control \
--reqtype gz.msgs.WorldControl \
--reptype gz.msgs.Boolean \
--req 'pause: true'
8. Day 4 체크리스트
- Gazebo Harmonic과
ros_gz브리지를 설치했다. - URDF의
ros2_control하드웨어 플러그인을GazeboSimSystem으로 설정했다. parameter_bridge로 Gazebo 토픽을 ROS2 토픽에 연결했다.- 모든 노드에
use_sim_time: true를 적용해 시간 동기화를 확인했다. - 시뮬레이션에서 diff_drive_controller로
/cmd_vel을 수신해 로봇을 이동시켰다.
다음 글 예고
Day 5에서는 ROS2 테스트와 품질 관리를 다룬다. 노드 단위 테스트(ament_cmake_gtest/pytest), 런치 통합 테스트, 그리고 CI 파이프라인에서 ROS2 테스트를 실행하는 방법을 정리한다.
This post is licensed under CC BY 4.0 by the author.