제10부 시뮬레이션
10.4 gz_ros2_control과 시뮬 하드웨어
10.4.1 이 장에서 배우는 것 / 선수 지식
6.1.7에서 하드웨어 플러그인만 바꾸면 시뮬과 실기가 같은 코드로 실행된다고 했다. 이 장이 그 시뮬 쪽 플러그인이다.
이것이 ros2_control 아키텍처의 가장 큰 실무적 이득이다. 컨트롤러·MoveIt 설정·Nav2 설정이 그대로 재사용되고, 시뮬에서 검증한 것이 실기로 넘어간다. 5.6.6의 "단일 xacro 분기"가 여기서 완성된다.
다만 완전히 같지는 않다. 시뮬 관절은 이상적이고, PID가 다르게 동작하며, 마찰·유격이 없다. 이 장은 그 차이도 함께 다룬다.
배우는 것:
- 구조 — 시뮬 하드웨어 플러그인의 위치.
- 설정 — 같은 URDF, 다른 플러그인.
- 시뮬 관절의 성질 — 이상적 서보와 그 함의.
- PID와 동역학 — 시뮬과 실기의 차이.
- 무엇이 이전되고 무엇이 안 되는가 — 게인·타이밍.
- mimic 관절 — 그리퍼의 흔한 문제.
- 진단 — 시뮬에서 제어가 안 될 때.
선수 지식: 6.1~6.5(ros2_control 전반), 6.2.8(mock 하드웨어), 5.6.6(xacro 분기), 10.2(Gazebo), 5.3.5(관성).
정직:
gz_ros2_control의 패키지명·플러그인명·파라미터는 Gazebo·ROS 배포판 조합에 따라 다르다. Classic용(gazebo_ros2_control)과 신 Gazebo용이 별개다(10.2.2).실행 확인(2026-08-16):
ros-jazzy-gz-ros2-control1.2.19를 설치해 이 장이 대는 이름 셋을 실물과 대조했다 — 전부 일치한다.
이 장의 표기 실물 SDF filename="gz_ros2_control-system"libgz_ros2_control-system.so✅SDF name="gz_ros2_control::GazeboSimROS2ControlPlugin"라이브러리 심볼로 확인 ✅ 하드웨어 <plugin>gz_ros2_control/GazeboSimSystem</plugin>플러그인 XML에 name="gz_ros2_control/GazeboSimSystem"슬래시 조회 이름 그대로 등록 ✅덤으로 하나 더 있다 — 같은 XML에
name="ign_ros2_control/IgnitionSystem"이 하위 호환용으로 함께 등록돼 있다("For backward compatibility with old ign* plugin names"). Ignition 시절 설정을 물려받았다면 그 이름도 여전히 동작한다는 뜻이다.다만 파라미터·실제 제어 동작은 검증하지 않았다
(확인 필요)— 헤드리스 컨테이너에서 시뮬레이터를 구동하지 않았다(목차 3).
10.4.2 구조
핵심: gz_ros2_control은 하드웨어 컴포넌트다(6.2). 실기의 시리얼·CAN 드라이버 자리에 들어가며, read()·write()가 Gazebo 관절 API를 호출한다.
| 층 | 실기 | 시뮬 |
|---|---|---|
| 컨트롤러 | 동일 | 동일 |
| Resource Manager | 동일 | 동일 |
| 하드웨어 컴포넌트 | 자체 드라이버(6.2) | gz_ros2_control |
| 아래 | 시리얼·CAN(6.7) | Gazebo 물리 |
견해: 이 구조가 제6부·제9부·제10부를 잇는다. 컨트롤러 설정(6.5)·MoveIt 설정(9.1)·Nav2 설정(8.1)이 시뮬과 실기에서 동일하므로, 시뮬에서 튜닝한 대부분이 실기로 넘어간다. 예외는 10.4.5에서 다룬다.
Gazebo 시스템 플러그인으로 로드된다(10.2.6):
<!-- URDF의 <gazebo> 태그 안 -->
<gazebo>
<plugin filename="gz_ros2_control-system"
name="gz_ros2_control::GazeboSimROS2ControlPlugin">
<parameters>$(find my_robot_bringup)/config/controllers.yaml</parameters>
<controller_manager_name>controller_manager</controller_manager_name>
</plugin>
</gazebo>
정직: 플러그인 파일명·클래스명은 버전 의존이다
(확인 필요).
이 플러그인이 controller_manager를 시뮬 안에서 실행한다 — 별도 ros2_control_node를 실행하지 않는다.
함정: 실기용 launch에서
ros2_control_node를 실행하고, 시뮬에서는 실행하지 않아야 한다. 둘 다 기동되면 컨트롤러가 중복되어 충돌한다. launch를 조건부로 구성해야 한다.
10.4.3 설정
5.6.6·6.1.7의 xacro 분기가 여기서 실체를 갖는다.
<ros2_control name="MyRobotSystem" type="system">
<hardware>
<xacro:if value="${use_mock}">
<plugin>mock_components/GenericSystem</plugin>
</xacro:if>
<xacro:if value="${use_sim}">
<plugin>gz_ros2_control/GazeboSimSystem</plugin>
</xacro:if>
<xacro:unless value="${use_mock or use_sim}">
<plugin>my_robot_driver/MyRobotHardware</plugin>
<param name="device">/dev/robot_arm</param>
</xacro:unless>
</hardware>
<!-- ★ 관절 정의는 완전히 공통 -->
<joint name="joint1">
<command_interface name="position">
<param name="min">-3.14</param>
<param name="max">3.14</param>
</command_interface>
<state_interface name="position"/>
<state_interface name="velocity"/>
<state_interface name="effort"/>
</joint>
<!-- ... -->
</ros2_control>
세 모드가 하나의 파일에서 나온다(6.2.8):
| 모드 | 하드웨어 | 물리 | 용도 |
|---|---|---|---|
| mock | 없음 | 없음 | 로직·CI(빠름) |
| sim | 없음 | Gazebo | 동역학 검증 |
| real | 실제 | 실제 | 최종 |
견해: 세 모드의 관절·인터페이스 정의가 동일한 것이 핵심 가치다. 컨트롤러 YAML(6.5.9)이 그대로 재사용되고, 5.7.9에서 검증한 "시뮬·실기 관절 이름 일치" 가 구조적으로 보장된다.
초기 관절 위치:
<joint name="joint1">
<command_interface name="position"/>
<state_interface name="position">
<param name="initial_value">0.0</param> <!-- 시뮬 시작 자세 -->
</state_interface>
</joint>
함정: 초기 위치를 설정하지 않으면 로봇이 0 자세로 시작한다. 팔이 접힌 자세가 자연스러운데 0이 뻗은 자세면, 시작 순간 중력으로 떨어지거나 바닥과 충돌한다.
initial_value로 안전한 자세를 지정하라.
10.4.4 시뮬 관절의 성질
Gazebo 관절은 실제 액추에이터와 다르다.
| 항목 | 실제 | 시뮬 |
|---|---|---|
| 위치 제어 | 서보가 내부 제어 | 엔진이 직접 위치 설정 또는 PID |
| 백래시·유격 | 있음 | 없음 |
| 마찰 | 복잡·비선형 | 단순 모델 |
| 탄성·유연성 | 있음 | 대개 강체 |
| 토크 한계 | 물리적 | 설정값 |
| 엔코더 분해능 | 유한 | 무한(연속값) |
| 지연 | 통신·제어 | 거의 없음 |
| 온도 영향 | 있음 | 없음 |
위치 제어 방식이 특히 중요하다:
| 방식 | 동작 |
|---|---|
| 직접 설정(kinematic) | 명령한 위치로 즉시 이동 — 물리 무시 |
| PID 제어 | 힘·토크로 목표 추종 — 물리 반영 |
정직:
gz_ros2_control이 어느 방식을 쓰는지, 설정 가능한지는 버전 의존이다(확인 필요). 이것이 시뮬 결과 해석에 결정적이므로 반드시 확인해야 한다.
견해: "직접 설정" 방식이면 동역학 검증이 무의미하다. 관절이 명령대로 완벽히 움직이므로 관성·중력·토크 한계가 반영되지 않는다. 로직 검증에는 충분하지만(10.1.4의 ①·② 사이), 제어 튜닝이나 물리 검증에는 PID 방식이 필요하다.
PID 설정:
# 시뮬 관절 PID (확인 필요: 설정 위치·형식)
gazebo_ros2_control:
ros__parameters:
joint1:
p: 100.0
i: 0.01
d: 10.0
함정: 시뮬 PID 게인은 실기와 무관하다. 시뮬 게인은 Gazebo 물리 엔진과 관성 모델에 맞춘 값이고, 실기 게인은 실제 액추에이터에 맞춘 값이다. 둘을 혼동하면 안 된다 — 시뮬에서 튜닝한 게인을 실기에 그대로 넣으면 대개 불리하다.
10.4.5 무엇이 이전되고 무엇이 안 되는가
이것이 이 장의 핵심 판단이다.
| 항목 | 이전 |
|---|---|
| 컨트롤러 설정 구조 | ✅ 그대로 |
| 관절 이름·인터페이스 | ✅ 그대로 |
| 궤적 생성 로직(MoveIt·Nav2) | ✅ 그대로 |
| 상위 상태 기계·BT | ✅ 그대로 |
| 허용오차 구조 | ✅ 구조는, 값은 조정 |
| 속도·가속 한계 | ⚠️ 실제 사양 기준이어야(9.1.4) |
| 제어 게인 | ❌ 다시 튜닝 |
| 궤적 추종 오차 | ❌ 시뮬이 더 좋다 |
| 사이클 타임 | ❌ 근사만 |
| 진동·공진 | ❌ 시뮬에 없음 |
| 실시간 타이밍 | ❌ 시뮬은 다르다(6.6) |
| 통신 오류 | ❌ 없음(6.7) |
견해: "구조는 이전되고 수치는 안 된다"가 요약이다. 컨트롤러 종류·관절 매핑·인터페이스 구성은 그대로 쓰지만, 게인·허용오차·타이밍은 실기에서 다시 정해야 한다.
견해: 그럼에도 이전되는 것이 많다는 점이 큰 가치다. 컨트롤러 설정이 틀렸거나, 관절 이름이 안 맞거나, MoveIt 궤적이 비정상이거나, BT 로직이 잘못된 것은 시뮬에서 전부 검출된다. 실기에서 검출하면 위험하고 느리다.
시뮬에서 검증할 것 / 실기에서 할 것:
| 시뮬에서 | 실기에서 |
|---|---|
| 컨트롤러 로드·활성화(6.3) | 게인 튜닝 |
| 관절 매핑 정확성 | 추종 오차 측정 |
| 궤적 생성·실행 흐름 | 허용오차 결정(6.5.5) |
| 충돌·도달성(9.3) | 진동·공진 확인 |
| 상태 전이·복구 | 실시간 주기(6.6.6) |
| 안전 로직 흐름 | 통신 안정성(6.7.9) |
10.4.6 mimic 관절
그리퍼에서 흔한 문제다(5.3.3·5.5.8).
<joint name="right_finger" type="prismatic">
<mimic joint="left_finger" multiplier="-1.0" offset="0.0"/>
</joint>
| 소비자 | mimic 처리 |
|---|---|
robot_state_publisher | ✅ 처리(5.5.8) |
| RViz2 | ✅ (RSP를 통해) |
| ros2_control | ⚠️ 지원 여부·방식 확인 필요 |
| Gazebo 물리 | ⚠️ 별도 처리 필요(확인 필요) |
| MoveIt | ⚠️(확인 필요) |
정직: mimic 관절의 시뮬 처리는 이 장에서 확신이 낮은 부분이다. 물리 엔진은 mimic 개념이 없으므로, 플러그인이나 추가 관절 제약으로 구현해야 한다. 버전에 따라 지원 방식이 다르며
(확인 필요), 지원하지 않으면 손가락 하나만 움직인다.
대안:
| 방법 | 설명 |
|---|---|
| 두 관절 모두 제어 | mimic 없이 컨트롤러가 둘 다 명령 |
| 시뮬 전용 제약 | Gazebo 관절 제약 플러그인 |
| 단순화 | 시뮬에서는 그리퍼를 한 관절로 |
견해: 파지 자체를 시뮬로 검증하기는 어렵다. 접촉 물리(마찰·변형·미끄러짐)가 시뮬에서 부정확해, 물체가 손에서 빠지거나 반대로 붙어 버린다. 파지 성공 여부보다 "파지 전후의 로직 흐름"을 검증하는 데 시뮬을 쓰는 것이 현실적이다(9.5.2의 단계 흐름).
흔한 우회: 시뮬에서 파지 시 물체를 그리퍼에 부착(attach) 시키는 플러그인을 쓴다(확인 필요: 제공 여부). 물리적으로 파지하는 대신 논리적으로 붙여, 이송·배치 로직을 검증한다.
10.4.7 관성과 물리 안정성
5.3.5에서 "관성을 부정확하게 넣으면 시뮬이 발산한다"고 했다. 여기가 그 무대다.
| 증상 | 원인 |
|---|---|
| 로봇이 튀어 날아감 | 관성 0·질량 0(5.3.5) |
| 관절이 진동 | 관성 대비 게인 과대·스텝 과대 |
| 팔이 중력에 처짐 | 토크 부족·게인 부족 |
| 바닥을 뚫음 | 물리 스텝 과대(10.2.8) |
| 로봇이 미끄러짐 | 마찰 계수 |
| 제자리에서 떨림 | 접촉 불안정·관성 부적절 |
진단 순서:
견해: "시뮬에서 로봇이 가만히 있는가"가 물리 모델의 통합 검사다(5.7.11의 반복). 아무 명령도 안 줬는데 떨리거나 움직이면 관성·마찰·게인 중 무언가가 잘못됐다. 이것을 먼저 해결하지 않고 제어를 시험하면 원인이 뒤섞인다.
함정: 관절 마찰·감쇠를 0으로 두면 진동하기 쉽다.
<joint name="joint1" type="revolute">
<dynamics damping="0.1" friction="0.05"/> <!-- 5.3.3 -->
</joint>
실제 로봇에는 항상 마찰이 있으므로, 약간의 감쇠를 주는 것이 안정성에 도움이 된다.
10.4.8 진단
# 컨트롤러가 로드됐는가 (6.3.8)
ros2 control list_hardware_components
ros2 control list_hardware_interfaces
ros2 control list_controllers
# 관절 상태가 나오는가 (5.5)
ros2 topic hz /joint_states
ros2 topic echo /joint_states --once
# 명령이 전달되는가
ros2 topic echo /joint_trajectory_controller/controller_state --once
# Gazebo 쪽 확인 (10.2.10)
gz topic -l | grep joint
gz model --list
증상별 원인:
| 증상 | 원인 |
|---|---|
| 하드웨어 컴포넌트 없음 | 플러그인 로드 실패·이름 오류 |
/joint_states 없음 | joint_state_broadcaster 미활성(6.5.3) |
| 컨트롤러 활성화 실패 | 인터페이스 불일치(6.3.2) |
| 명령해도 안 움직임 | PID 게인·토크 한계·초기 자세 |
| 관절이 처짐 | 중력 대비 게인·토크 |
| 한 손가락만 움직임 | mimic 미지원(10.4.6) |
| 로봇이 불안정 | 관성·스텝(10.4.7) |
| 컨트롤러 중복 | ros2_control_node도 실행 중 |
견해: 시뮬 제어 문제의 절반은 "실기와 같은 문제"다(6.3.8의 진단표가 그대로 적용된다). 나머지 절반이 시뮬 특유(PID·물리·mimic)다. 먼저 ros2_control 일반 진단을 하고, 그래도 안 되면 시뮬 특유를 본다.
10.4.9 launch 구성
def generate_launch_description():
use_sim = LaunchConfiguration('use_sim')
robot_desc = ParameterValue(
Command(['xacro ', xacro_file, ' use_sim:=', use_sim]), value_type=str)
rsp = Node(package='robot_state_publisher', executable='robot_state_publisher',
parameters=[{'robot_description': robot_desc, 'use_sim_time': use_sim}])
# ★ 실기에서만 ros2_control_node (시뮬은 플러그인이 실행)
control_node = Node(
package='controller_manager', executable='ros2_control_node',
parameters=[robot_desc, controllers_yaml],
condition=UnlessCondition(use_sim), # 조건부
)
# 스폰 (시뮬만)
spawn = Node(
package='ros_gz_sim', executable='create',
arguments=['-topic', 'robot_description', '-name', 'my_robot', '-z', '0.15'],
condition=IfCondition(use_sim),
)
# 컨트롤러 spawner — 양쪽 공통 (6.3.6)
jsb = Node(package='controller_manager', executable='spawner',
arguments=['joint_state_broadcaster'])
jtc = Node(package='controller_manager', executable='spawner',
arguments=['joint_trajectory_controller'])
return LaunchDescription([
DeclareLaunchArgument('use_sim', default_value='false'),
rsp, control_node, spawn,
RegisterEventHandler(OnProcessExit(target_action=spawn, on_exit=[jsb])),
RegisterEventHandler(OnProcessExit(target_action=jsb, on_exit=[jtc])),
])
조건부 노드가 핵심이다:
| 노드 | 시뮬 | 실기 |
|---|---|---|
robot_state_publisher | ✅ | ✅ |
ros2_control_node | ❌ 플러그인이 실행 | ✅ |
create(스폰) | ✅ | ❌ |
| 컨트롤러 spawner | ✅ | ✅ |
| 브리지 | ✅ | ❌ |
견해: 하나의 launch에서 조건부로 구성하는 것과, 시뮬·실기 launch를 나누는 것 중 후자가 읽기 쉬울 수 있다. 조건이 많아지면 launch가 복잡해진다. 공통 부분을 별도 launch로 빼고 시뮬·실기가 각각 include하는 구조가 절충안이다(14.4).
함정: 시뮬에서 컨트롤러 spawner 타이밍이 중요하다. 로봇이 스폰되고 플러그인이
controller_manager를 실행한 뒤에 spawner가 연결돼야 한다.OnProcessExit연쇄나 지연이 필요하다(6.3.6).
10.4.10 함정 카탈로그
| # | 함정 | 증상 | 대처 |
|---|---|---|---|
| 1 | 시뮬에서 ros2_control_node도 실행 | 컨트롤러 중복·충돌 | 조건부 launch |
| 2 | 초기 관절 위치 미설정 | 시작 시 낙하·충돌 | initial_value |
| 3 | 시뮬 PID 게인을 실기에 | 낮은 성능 | 별도 튜닝 |
| 4 | 시뮬 추종 오차로 허용오차 결정 | 실기에서 중단 빈발 | 실기 실측(6.5.5) |
| 5 | 위치 제어 방식 미확인 | 동역학 검증이 무의미 | 방식 확인 |
| 6 | mimic 시뮬 미지원 | 손가락 하나만 | 두 관절 제어·대안 |
| 7 | 파지를 물리로 검증 시도 | 불안정·비현실 | 부착 플러그인·로직 검증 |
| 8 | 관성 부실 | 시뮬 발산(5.3.5) | 5.7.4 검증 |
| 9 | 감쇠·마찰 0 | 진동 | 약간의 값 부여 |
| 10 | 물리 스텝 과대 | 관통·불안정 | 1~5 ms(10.2.8) |
| 11 | spawner 타이밍 오류 | 컨트롤러 로드 실패 | 연쇄 실행 |
| 12 | 시뮬 사이클 타임을 실기 예측으로 | 계획 오류 | 근사만 |
| 13 | 시뮬 특유 문제부터 의심 | 진단 지연 | 일반 진단 먼저(6.3.8) |
10.4.11 체크포인트
gz_ros2_control이 ros2_control 구조에서 어느 자리인가. 실기와 무엇이 같고 무엇이 다른가.- 세 모드(mock·sim·real)를 하나의 xacro에서 만드는 구조와, 그 핵심 가치는?
- 초기 관절 위치를 설정하지 않으면 무슨 일이 생기는가.
- 시뮬 관절이 실제와 다른 점 여덟 가지를 말할 수 있는가.
- 위치 제어의 두 방식과, "직접 설정"이면 무엇이 무의미해지는가.
- 시뮬 PID 게인을 실기에 쓰면 안 되는 이유는?
- "구조는 이전되고 수치는 안 된다"를 구체적 항목으로 설명할 수 있는가.
- 시뮬에서 검증할 것과 실기에서 할 것을 각각 세 가지 들 수 있는가.
- mimic 관절의 소비자별 지원 차이와, 시뮬에서의 대안 세 가지는?
- 파지를 시뮬로 검증하기 어려운 이유와, 현실적 활용법은?
- 시뮬 물리 불안정의 원인 여섯 가지와 진단 순서를 말할 수 있는가.
- 시뮬·실기 launch 구성에서 조건부로 다뤄야 할 노드는?
- 시뮬 제어 문제 진단에서 먼저 해야 할 것은?
다음 장 10.5은 Gazebo 외의 선택지 — Isaac Sim 등 대안 시뮬레이터를 다룬다. 무엇이 있고, 어떤 축으로 비교하며, 언제 다른 것을 검토하는가.