Gazebo 내부 통신 구조와 ROS2 연동

Gazebo Transport의 개념

Gazebo(이하 클래식 Gazebo) 또는 Ignition Gazebo(이하 통칭 Gazebo)는 시뮬레이션 환경 내부에서 컴포넌트 간 통신을 하기 위해 고유한 ‘Transport’ 라이브러리를 사용한다. 이는 ROS2의 pub/sub 모델과 유사한 구조를 가지지만, Gazebo는 별도의 메시지 형식과 상호 연결 방식을 통해 자체적인 통신 메커니즘을 형성한다.

Gazebo Transport 계층을 살펴보면 크게 다음과 같은 특징이 있다.

프로토콜 메시지 정의: Gazebo 내부에서는 Google Protocol Buffers(이하 Protobuf)로 메시지를 정의한다. 예를 들어, 센서 데이터나 로봇 모델 상태, 시뮬레이션 이벤트 등이 Protobuf 메시지로 표현된다.

트랜스포트 레이어: 각 메시지는 ZeroMQ(또는 Ignition Transport에서는 소켓 기반 라이브러리) 등을 통해 발행(Publish)되고, 구독(Subscribe)되는 구조를 띤다. Gazebo 자체가 하나의 거대한 분산 시스템처럼 동작하며, 서로 다른 프로세스나 플러그인이 동일한 토픽에 접근함으로써 메시지를 교환한다.

노드 개념이 존재하지 않음: ROS2에서 노드는 매우 중요한 개념이지만, (클래식) Gazebo의 Transport 계층에는 ‘노드’라는 추상화가 직접적으로 존재하지 않는다. 대신 토픽 이름과 메시지 타입만 맞다면 서로 다른 프로세스, 플러그인 간에도 메시지를 직접 주고받는다.

다시 말해, Gazebo는 시뮬레이션 엔진 내부에서의 독자적인 통신 모델(Transport)을 활용하여 안정적이고 빠른 데이터 교환이 가능하도록 설계되었다. 이 특성을 이해하는 것은 Gazebo와 ROS2를 연결(Bridge)할 때 매우 중요하다.

Gazebo Plugins와 SDF

Gazebo 시뮬레이션 환경을 구성하는 핵심 요소 중 하나가 바로 플러그인(Plugin)이다. 플러그인은 Gazebo가 실행되는 동안 특정 시뮬레이션 기능을 확장하거나, 물리 엔진이나 시각화 엔진의 동작을 세부 조정할 수 있도록 하는 동적 라이브러리다.

World Plugin: 시뮬레이션 세계(World) 전체에 영향을 주는 로직을 담는다. 예: 시뮬레이션 시나리오를 제어하거나, 자동 제어 루프를 구성하는 경우.

Model Plugin: 특정 모델(로봇, 물체, 센서 등)에 연결되어 해당 오브젝트의 거동을 제어한다. 예: 로봇 조인트를 제어하거나 센서 데이터를 가공하는 경우.

Sensor Plugin: 센서의 동작을 구현하고, Gazebo Transport를 사용하여 측정값을 발행한다. 예: 카메라, LIDAR 센서에 대한 인터페이스.

각 플러그인은 SDF(Simulation Description Format) 파일 내에서 설정할 수 있으며, 이 SDF 파일에서 토픽 이름, 업데이트 주기, 메시지 타입 등을 지정해 준다. 예를 들어, 센서 플러그인에서 카메라 이미지가 발행되는 토픽 이름을 “~/camera”라고 명시할 수 있다.

Gazebo Transport와 ROS2 간 브릿지 개념

ROS2 Humble 환경에서 Gazebo와 통신을 하기 위해서는 ROS2가 제공하는 $rclcpp$ 인터페이스와 Gazebo Transport 간의 매개체가 필요하다. 이를 흔히 “bridge”라고 부르는데, bridge의 작동 방식은 크게 다음과 같이 요약할 수 있다.

Gazebo 토픽 구독: bridge 노드는 Gazebo Transport API를 통해 특정 토픽을 구독(Subscribe)한다. 예: “~/cmd_vel” 토픽 구독.

ROS2 토픽 발행: bridge 노드는 구독한 데이터를 ROS2 메시지로 변환한 후, ROS2의 $rclcpp$를 통해 ROS2 토픽으로 발행(Publish)한다. 예: “/robot/cmd_vel” 토픽 발행.

ROS2 토픽 구독: 반대로 bridge 노드는 ROS2 토픽(예: “/odom”)을 구독하고, 해당 메시지를 Protobuf 형식으로 변환한 뒤 Gazebo 토픽으로 발행한다(예: “~/odom”).

이렇게 함으로써 Gazebo 환경 내부의 플러그인(예: Model Plugin이 “~/cmd_vel”을 구독)에 ROS2 노드(예: Nav2 stack에서 “/robot/cmd_vel”을 발행)가 연결되어, 실제 로봇이 아닌 시뮬레이션 상의 로봇을 제어하거나 데이터를 모니터링할 수 있다.

ROS2 통신 구조와 Gazebo Transport의 상호 비교

ROS2는 DDS(Data Distribution Service)를 기반으로 각 노드 사이의 통신을 지원한다. Gazebo Transport는 ZeroMQ(또는 Ignition Transport 버전별 차이 존재) 위에서 Protobuf 메시지를 교환한다. 이 차이로 인해 다음과 같은 특징이 있다.

토픽 이름 스페이스:

  • ROS2: 노드를 구분하고, 토픽별 네임스페이스가 존재. ex) “/robot/camera/image_raw”

  • Gazebo: 기본적으로 “/”라는 루트를 사용하거나, 모델, 센서 별로 토픽을 구분. ex) “/model/robot1/joint_state”

메시지 타입:

  • ROS2: .msg 파일(C++) 또는 .idl 파일을 통해 정의된 타입, DDS IDL에 맞춰 컴파일됨.

  • Gazebo: .proto 파일을 통해 정의된 Protobuf 메시지. ex) msgs/Vector3d.proto, msgs/Pose.proto

노드 개념:

  • ROS2: 엄격히 노드의 개념 존재, 라이프사이클, QoS 설정 등 노드 단위로 설정 가능.

  • Gazebo: Transport 레벨에서 노드 없이, 토픽별 발행/구독.

품질(QoS) 설정:

  • ROS2: DDS QoS 정책(신뢰성, 지속성, 우선순위 등)을 비교적 세밀하게 다룰 수 있음.

  • Gazebo: ZeroMQ의 내부 설정을 통해 QoS-like 설정을 구성하지만, DDS보다 옵션이 적음.

이처럼 Gazebo와 ROS2는 근본적인 통신 구조가 다르므로, 이를 연결해 주는 bridge 레이어가 필요하다. ROS2에서 발행된 메시지를 그대로 Gazebo에서 이해하기 위해서는 Protobuf 형식 변환이 필요하고, Gazebo의 Protobuf 메시지를 ROS2에서 이해하기 위해서는 ROS2 메시지 타입(.msg/.idl)으로 변환해야 한다.

Gazebo <-> ROS2 Bridge 구현 방식

Gazebo Transport와 ROS2를 서로 연결하기 위해서는 메시지 포맷 및 통신 체계를 중간에서 변환해 주는 모듈, 즉 ‘브리지(bridge)’가 필요하다. ROS2 Humble 버전에서 사용되는 대표적인 브리지로는 다음과 같은 것들이 존재한다.

ros_gz_bridge (이전 명칭: ros_ign_bridge):

  • Ignition Gazebo와 ROS2 사이에서 메시지를 상호 변환하고, 토픽을 양방향으로 연결해 주는 패키지다.

  • ros_ign_bridge 패키지가 ros_gz_bridge 로 명칭이 변경되었으나, 기능 면에서 큰 차이는 없으며 Gazebo의 버전과 ROS2의 버전에 맞추어 브랜치가 나뉜다.

self-made plugin-based bridge:

  • 특정 용도에 맞춰 사용자 스스로 플러그인을 작성해, Gazebo Transport를 통해 메시지를 구독/발행하고, 이 메시지를 C++ 코드로 ROS2의 rclcpp API를 통해 다시 발행/구독하는 방식을 의미한다.

  • 이러한 자체 구현 방식은 최대한의 유연성을 제공하지만, 모든 메시지 타입에 대해 수작업으로 변환 로직을 구현해야 하므로 유지보수 측면에서 부담이 될 수 있다.

ros_gz_bridge 동작 개념

ros_gz_bridge 를 예로 들어 살펴보자. 이는 ROS2 노드처럼 구동되면서, Gazebo 토픽과 ROS2 토픽 간에 메시지 변환 로직을 수행한다. 내부 흐름을 간단히 표현하면 다음과 같다:

ROS2 구독 → Gazebo 발행:

  1. ROS2 토픽에서 C++(또는 Python) 노드가 메시지를 발행한다.

  2. ros_gz_bridge는 이 토픽을 구독한다.

  3. 해당 메시지를 Protobuf 형식으로 변환한다.

  4. 변환된 메시지를 Gazebo Transport 토픽으로 발행한다.

Gazebo 구독 → ROS2 발행:

  1. Gazebo 플러그인(혹은 Gazebo Transport API)에서 특정 토픽에 메시지를 발행한다.

  2. ros_gz_bridge는 해당 Gazebo 토픽을 구독한다.

  3. Protobuf 메시지를 ROS2 메시지 타입으로 변환한다.

  4. 변환된 메시지를 ROS2 토픽으로 발행한다.

이 과정을 통해 ROS2 노드들은 Gazebo 내부 상태를 센서 데이터나 TF(Transform) 정보 등으로 실시간 확인할 수 있고, Gazebo는 ROS2 노드에서 보낸 명령(CmdVel, JointTrajectory 등)을 받아 시뮬레이션 내부 로봇이나 모델을 제어하게 된다.

아래는 ros_gz_bridge 동작을 단순화하여 나타낸 시퀀스 다이어그램 예시이다:

spinner

메시지 타입 매핑

ROS2와 Gazebo 사이에서 데이터를 주고받으려면, 양쪽에서 동일한 의미를 가지는 메시지 타입끼리 일대일로 매핑되어야 한다. 예를 들어:

  • geometry_msgs/Twistignition::msgs::Twist

  • sensor_msgs/Imageignition::msgs::Image

  • nav_msgs/Odometryignition::msgs::Odometry

단, 일부 복잡한 메시지(예: sensor_msgs/PointCloud2ignition::msgs::PointCloudPacked) 등은 구조가 조금씩 달라 변환 로직이 추가로 필요하다. ros_gz_bridge는 기본적인 ROS2 <-> Ignition/Gazebo 메시지 매핑 테이블을 내장하고 있으며, 사용자가 정의한 커스텀 메시지에 대해서는 별도의 변환 코드를 작성해 준 뒤 빌드해야 한다.

예시 코드 (ROS2 → Gazebo)

ROS2에서 geometry_msgs/Twist 타입으로 로봇에 명령 속도를 보내고, Gazebo 플러그인이 ignition::msgs::Twist를 구독한다고 가정해 보자. ros_gz_bridge 설정은 다음과 같이 할 수 있다.

위 명령을 실행하면,

  • ROS2 토픽 /cmd_vel(geometry_msgs/Twist)이 Gazebo 토픽 /cmd_vel(ignition::msgs::Twist)로 양방향 연결되고,

  • ROS2 토픽 /odom(nav_msgs/Odometry)이 Gazebo 토픽 /odom(ignition::msgs::Odometry)로 양방향 연결된다.

브리지 동작 시 고려 사항

브리지를 쓸 때 주의해야 할 몇 가지 중요한 포인트는 다음과 같다.

타이밍 및 주파수:

  • Gazebo 시뮬레이션에서는 물리 시뮬레이션 루프(Physics Update)마다 센서 데이터를 생성할 수 있다. 반면 ROS2 노드는 메시지 구독/발행 시콜로 일정한 주파수(예: $r = 30,\mathrm{Hz}$)로 동작할 수도 있다. 각 토픽 간 주파수가 너무 높거나 낮으면, 브리지에서 병목 혹은 메시지 누락이 발생할 수 있다.

QoS(품질 설정) 문제:

  • ROS2 측에서 QoS를 변경하면, 예컨대 ‘신뢰 모드’를 Best Effort → Reliable 로 바꾸거나, History 설정을 바꾸는 경우, Gazebo 쪽 ZeroMQ/Protobuf 통신과 성능 차이 혹은 호환성 이슈가 생길 수 있다.

토픽 네임스페이스 충돌:

  • ROS2와 Gazebo 각각에서 토픽 이름을 일관되게 설정해야 혼동을 피할 수 있다. ex) Gazebo 플러그인이 ‘cmd_vel’을 발행하는데, ROS2에서 이를 ‘/robot/cmd_vel’로 구독한다면 브리지 쪽 매핑 테이블에서 정확히 일치해야 한다.

메시지 구조 불일치:

  • ROS2가 가진 메시지 구조와 Gazebo가 가진 Protobuf 메시지 구조가 완전히 일치하지 않는 경우가 존재한다. 예를 들어, IMU(관성 측정 장치) 데이터에서 Covariance 행렬이 추가되어 있는가 없는가 등. 이러한 경우 브리지 구현에서 별도의 변환 로직이나 Approximation(근사치) 처리가 필요하다.

이런 점들을 고려해 가며 브리지를 구성하면, ROS2 쪽에서 시뮬레이션을 제어하고 모니터링하는 체계를 손쉽게 구축할 수 있다.

TF(Transform) 브리지

ROS2에서 로봇 및 센서 좌표계를 체계적으로 다루기 위해서는 tf2를 사용하며, 보통 tf 또는 tf_static 토픽으로 좌표 변환 정보를 주고받는다. Gazebo(특히 Ignition 버전)에서는 ignition::msgs::Pose 또는 ignition::msgs::Pose_V 등을 통해 모델의 위치와 자세를 표현한다. 따라서 Gazebo <-> ROS2 브리지를 구성할 때, 다음과 같은 포인트를 고려해야 한다.

시간(timestamp) 동기화:

  • ROS2 메시지에서 중요한 요소 중 하나가 ‘헤더’의 시간 정보(예: builtin_interfaces/Time)다. Gazebo 쪽 Protobuf 메시지에도 시뮬레이션 시간(sim time)이 담긴다. 이 시간을 올바르게 매핑해야 tf 트리에서 올바른 변환 시점을 추적할 수 있다.

좌표계 변환:

  • Gazebo 월드 좌표계와 ROS2 좌표계가 동일하다고 전제할 수도 있으나, 특정 축(예: Z축이 위를 향하는지, Y축이 위를 향하는지)이 다를 수 있다면 변환 로직에서 별도의 회전(예: 90°, 180° 등)이 필요할 수 있다.

브리지 토픽 설정:

  • tf 또는 tf_static 브리지를 설정하기 위해서는 ros_gz_bridge에 /tf@tf2_msgs/msg/TFMessage@ignition.msgs.Pose_V와 같은 매핑을 명시해 줄 수도 있지만, 이는 단순 일대일 매핑으로는 부족할 때가 많다. 이유는 tf2_msgs/TFMessageignition::msgs::Pose_V 구조가 다소 다르기 때문이다.

  • 일반적으로는 Gazebo 플러그인(예: World Plugin)이 주기적으로 모델들의 Pose를 읽고, 이를 ROS2 쪽에서 tf2_ros::TransformBroadcaster를 사용해 /tf 토픽으로 내보내는 방식을 구현하기도 한다.

World Plugin 예시

Gazebo World Plugin 내부에서 모든 모델의 Pose를 가져와서 ROS2의 tf 토픽에 발행한다고 가정하면, 대략 다음과 같은 로직을 포함한다.

이렇게 하면 Gazebo에서 매 시뮬레이션 업데이트마다 모델의 Pose를 ROS2의 tf로 발행하여, RViz 같은 도구에서 시뮬레이션 내 로봇과 센서의 위치관계를 시각화할 수 있다. 물론 위 방식은 매우 단순화된 예시로, 실제 환경에서는 퍼블리시 주기를 조절하거나 특정 모델만 골라서 발행하도록 구현할 수 있다.

센서 데이터 브리지

센서 데이터(카메라, LIDAR, IMU 등)를 Gazebo에서 ROS2로 전달할 때는 다음 과정을 거친다.

  1. Gazebo 센서 플러그인에서 센서 데이터를 Protobuf 메시지(예: msgs::Image, msgs::LaserScan)로 발행.

  2. ros_gz_bridge(혹은 자체 작성 브리지)가 이 Protobuf를 수신 → ROS2 메시지(e.g. sensor_msgs/Image$,$sensor_msgs/LaserScan)로 변환 → ROS2 토픽 발행.

카메라 예시

  • Gazebo (Ignition)에서 카메라 센서 플러그인이 /camera/image(Protobuf: msgs::Image)로 발행.

  • 브리지가 /camera/image를 구독하고, ROS2의 /camera/image_raw(sensor_msgs/Image) 토픽으로 변환 발행.

이를 위해 ros_gz_bridge는 다음과 같은 매핑을 설정할 수 있다:

사용자는 RViz에서 /camera/image_raw를 구독해 실시간 시뮬레이션 영상을 볼 수 있다.

주행/제어 커맨드 브리지

ROS2로 작성된 제어 노드나 네비게이션 노드가 Gazebo의 로봇 모델을 제어하려면, 속도 명령(geometry_msgs/Twist)이나 trajectory 명령(trajectory_msgs/JointTrajectory) 등이 Gazebo 로봇 모델 플러그인에서 구독하는 Protobuf 메시지 형식(예: msgs::Twist, msgs::JointTrajectory)으로 변환되어야 한다.

2D 이동 로봇 예시

  • ROS2 토픽: /cmd_vel (geometry_msgs/Twist)

  • Gazebo 토픽: /model/cmd_vel (Protobuf: ignition::msgs::Twist)

  • 브리지 매핑:

Gazebo 로봇 모델이 /model/cmd_vel을 구독하는 플러그인(Model Plugin)이 있으면, ROS2에서 퍼블리시되는 이동 명령을 그대로 적용할 수 있다.

대용량 데이터 브리지 최적화

카메라 영상이나 3D LIDAR(PointCloud2 등)처럼 메시지 크기가 큰 토픽을 브리지할 때는 성능 이슈가 발생하기 쉽다. 이를 방지하기 위한 방법은 다음과 같다.

  1. 전송 주파수 제한

    • Gazebo 센서 플러그인에서 최대 프레임 레이트를 낮춰서, 예: 30Hz → 10Hz로 제한.

    • ROS2 쪽에서 필요한 만큼만 메시지를 전송하도록 정책을 정한다.

  2. QoS 설정

    • ROS2 측에서 Best Effort(신뢰도보다 성능 우선) 모드로 설정해, 재전송 오버헤드를 줄일 수 있다.

  3. 압축 전송

    • 카메라 이미지의 경우, compressed_image_transport 등을 통해 압축 토픽을 사용하면 전송량을 줄일 수 있다. 다만 Gazebo Protobuf 메시지도 자체 포맷이 있으므로, 브리지 구현에서 추가 로직이 필요할 수 있다.

멀티 로봇 시나리오

여러 대의 로봇을 동시에 시뮬레이션하는 경우, 다음과 같이 네임스페이스를 구분해서 브리지를 설정해야 충돌을 방지할 수 있다.

  • 로봇A: /robot1/cmd_vel/model/robot1/cmd_vel

  • 로봇B: /robot2/cmd_vel/model/robot2/cmd_vel

브리지 설정 시에도 각각 따로 매핑을 작성하거나, 매핑 문자열을 쉼표로 구분해 한 번에 여러 매핑을 선언할 수도 있다. 예:

Gazebo Service 호출과 ROS2 Service 연동

Gazebo(또는 Ignition Gazebo)에서는 토픽(pub/sub) 기반의 메시지 교환 외에도, 특정한 기능을 요청하고 결과를 반환받는 ‘Service’ 개념이 존재한다. 예를 들어 모델 스폰(Spawn), 모델 제거(Remove), 물리 엔진 정보 조회(Get Physics Properties) 등이 서비스 형태로 구현되어 있다.

ROS2에서도 마찬가지로 rclcpp의 Service 서버/클라이언트를 통해 request-response 방식 통신을 할 수 있다. Gazebo와 ROS2 사이에서도 이와 유사한 연동이 가능하며, 주로 아래와 같은 케이스에서 활용된다.

  1. 모델 스폰/제거

    • Gazebo: /world/default/create (Ignition Transport Service) 혹은 /gazebo/spawn_sdf_model (클래식 Gazebo ROS1 브리지) 등의 서비스를 통해 로봇 모델을 시뮬레이션에 추가할 수 있음.

    • ROS2: $ros2 service call /spawn_entity <요청 메세지 타입> 으로 로봇 모델 스폰 가능.

    • ros_gz_bridge를 이용하면, 특정 Gazebo Service와 ROS2 Service를 매핑해, ROS2에서 서비스 호출 → Gazebo에서 모델 스폰 → 응답 반환 흐름을 구성할 수 있다.

  2. 물리 엔진 파라미터 변경

    • 예: 시뮬레이션 중에 friction coefficient, ODE/Simbody/DART 파라미터 등을 동적으로 바꾸고 싶을 때 Gazebo Service를 호출할 수 있다. 이를 ROS2 Service 형태로 연결하면, ROS2 노드에서 명령해 Gazebo 물리 파라미터를 수정 가능.

  3. 월드 상태 저장/로드

    • Gazebo World를 특정 시점에 스냅샷 찍어 저장하거나, 불러오는 기능도 일부 버전에서는 서비스 형태로 제공된다. 이를 ROS2 상에서 제어해, 실험 시나리오 자동화(예: “특정 시점으로 되돌리기”)를 구현할 수 있다.

Gazebo Service → ROS2 Service 매핑

ros_gz_bridge에서도 토픽과 마찬가지로 서비스에 대한 매핑을 지정할 수 있다. 다만, 서비스 매핑은 토픽 매핑보다 설정이 복잡해질 수 있고, 사용 사례가 상대적으로 적으므로 필요한 경우에 한해 별도로 구현하는 편이다.

예시 명령(단순 가정):

위와 같은 호출 형태는 실제로 사용할 때, 각 메시지/서비스 타입 정의에 맞추어 달라질 수 있다.

Gazebo <-> ROS2 Actions

ROS2에서는 목표(Action Goal)을 전송하고, 목표 달성 중간 과정(Feedback)과 최종 결과(Result)를 비동기적으로 받는 Action 구조가 있다. Gazebo에는 ROS2 Action과 정확히 일치하는 추상화 계층은 없으나, 다음과 같은 시도를 할 수 있다.

  • Action Goal: Gazebo가 특정 작업(예: 로봇을 특정 지점까지 이동)을 수행하도록 요청

  • Feedback: Gazebo 시뮬레이션 상태를 주기적으로 확인(토픽 구독)

  • Result: 목표 지점 도달 여부 판단

이를 구현하려면 별도의 Action Server(ROS2 측에서 구현)를 두고, Gazebo는 일반 토픽과 서비스로부터 명령을 받아 실행하거나, 시뮬레이션 결과를 피드백으로 발행하도록 구성한다. 브리지 레벨에서 Action 자체를 직접 변환하기보다는, Action 내부에서 사용하는 토픽/서비스를 연결하는 식으로 간접적으로 처리하게 된다.

디버깅과 로깅

Gazebo와 ROS2 간 브리지가 제대로 동작하지 않을 때, 다음과 같은 디버깅 방법을 적용할 수 있다.

  1. Gazebo Transport Topic List

    • Gazebo 클래식: $gz topic -l, Ignition: $ign topic -l

    • 현재 활성화된 토픽 이름과 메시지 타입을 확인한다. 브리지하려는 토픽이 실제로 발행되고 있는지, 이름이 일치하는지 점검한다.

  2. ROS2 Topic Echo

    • $ros2 topic list, $ros2 topic echo /토픽이름

    • 브리지로 연결된 ROS2 토픽 쪽에 메시지가 들어오고 있는지 확인한다.

  3. ros_gz_bridge 로그 레벨 설정

    • $RCLCPP_DEBUG, $RCLCPP_INFO, $RCLCPP_WARN, $RCLCPP_ERROR 등을 활용해 브리지 내부에서 메시지가 변환되는 과정을 로그로 남길 수 있다.

  4. 메시지 타입 불일치 확인

    • 동일한 목적이라도 Ignition/Gazebo Protobuf 메시지 필드와 ROS2 메시지 필드가 다를 수 있으므로, $ros_gz_bridge$가 지원하는 매핑인지 체크한다. 지원하지 않는 메시지 타입이라면, 직접 변환 코드를 추가하거나 별도 플러그인 방식으로 작성해야 한다.

시뮬레이션 성능 최적화

Gazebo와 ROS2를 브리지하여 복잡한 시뮬레이션을 돌릴 때 성능이 저하될 수 있다. 개선 방법은 크게 다음과 같다.

  1. 센서 업데이트 주기 제한

    • 카메라, LIDAR 등 대용량 센서 메시지를 너무 자주(예: 60~100Hz 이상) 발행하면 네트워크 병목이 일어날 수 있다. 10Hz나 5Hz처럼 낮추면 크게 개선된다.

  2. 여러 브리지 인스턴스 분산

    • 하나의 노드에서 모든 토픽을 브리지하지 않고, 센서별로 나눠서 병렬로 띄울 수도 있다. 예: /camera_bridge, /lidar_bridge 등.

  3. ROS2 QoS 조정

    • BestEffort 또는 KeepLast(버퍼 제한) 등으로 설정하면 메모리 사용을 줄이고 전송 속도를 높일 수 있다.

  4. 머신 리소스 확충

    • 시뮬레이션은 CPU, GPU, 메모리를 많이 소모한다. 고성능 하드웨어 환경에서 병렬 처리 스레드를 늘리면 체감 성능이 좋아진다.

로컬/원격 시뮬레이션 브리징

실제 로봇 운영 환경을 고려해, Gazebo 시뮬레이터를 원격 머신(예: 클라우드 서버)에서 띄우고, 로봇의 ROS2 노드는 로컬 머신에서 동작하도록 구성하는 경우가 있다. 이 때도 브리지를 통해 통신이 가능하지만, 네트워크 지연(latency)이 발생할 수 있으므로 다음을 고려한다.

  1. DDS 설정(TCP/UDP, 보안 등)

    • ROS2 디스커버리(Discovery) 문제가 있을 수 있으므로, Fast DDS, Cyclone DDS 등에서 멀티캐스트, unicast 옵션을 설정한다.

  2. Gazebo Transport 포트 열기

    • 원격 머신에서 ZeroMQ(혹은 Ignition Transport) 통신 포트를 열어 주고 방화벽 설정을 수정해야 한다.

  3. 머신 간 시간 동기화

    • 시뮬레이션 시간(sim time)과 ROS2 시간(clock)이 일치하지 않으면 $tf$ 및 센서 데이터 타임스탬프가 꼬일 수 있다. NTP 등을 활용해 시스템 시간을 동기화한다.

이렇게 로컬-원격 간 환경을 구축하면, 클라우드에서 대규모 시뮬레이션(멀티 로봇, 물리 모델 등)을 돌리고, 로컬 개발 PC에서 RViz, TF, 제어 노드를 이용해 제어하는 식으로 작업할 수 있다.

Gazebo Classic vs Ignition(Gazebo) 차이

Gazebo 시뮬레이터는 과거 ‘Gazebo Classic’(버전 11까지)과 새 브랜드이자 차세대 구조인 ‘Ignition Gazebo’(Fortress, Garden 등 여러 코드명)로 나뉘어 발전해 왔다. 양자는 내부 통신 메커니즘(Transport), 플러그인 구조, 메시지 타입에 유사점과 차이점이 존재한다.

  1. Transport 레이어 차이

    • Gazebo Classic: ZeroMQ 기반 transport (이하 “Gazebo Transport”)

    • Ignition Gazebo: ‘Ignition Transport’(라이브러리 이름은 ignition-transport)로 이름이 바뀌면서, 메시지 구조와 API가 개선되었다.

    • 두 시스템 모두 Protobuf를 사용하지만, Ignition 쪽이 더욱 최신 버전의 Protobuf와 ZeroMQ를 활용하며, 메시지 타입 이름(예: ignition::msgs::...)도 달라졌다.

  2. 플러그인 구조

    • Gazebo Classic: WorldPlugin, ModelPlugin, SensorPlugin 3가지가 대표적이며, CMakeLists 설정이나 SDF(.sdf, .world)에서 플러그인을 직접 로드한다.

    • Ignition Gazebo: System 플러그인(예: ignition::gazebo::System) 기반으로, ECM(Entity-Component-Manager) 구조를 활용한다. 기존 Classic 방식보다 확장성이 높고, 여러 기능이 System 플러그인 형태로 모듈화되어 있다.

  3. SDF vs SDFormat (거의 동일)

    • 파일 확장자나 구조는 큰 틀에서 동일하지만, Ignition에서는 조금 더 최신 버전의 SDF를 우선 적용하기도 한다.

    • Classic에서 사용하던 .world 파일과 Ignition의 .sdf/.world 파일 간에는 대부분 호환되지만, 일부 태그(예: <plugin>, <gui> 등) 차이가 존재한다.

  4. ros_gz_bridge(구 ros_ign_bridge)와 ros_gazebo_pkgs

    • Ignition 쪽 브리지는 ros_gz_bridge(이전 명칭 ros_ign_bridge)로 불린다.

    • Gazebo Classic은 gazebo_ros_pkgs(ROS1/ROS2 지원)를 통해 Classic Gazebo <-> ROS2 연동을 제공하며, 플러그인 구조로 연동된다.

    • 최종적으로 ROS2 Humble 이후 버전에서 주로 권장되는 것은 Ignition 기반 시뮬레이션 + ros_gz_bridge.

System Plugin과 Entity-Component-Manager

Ignition Gazebo의 System Plugin은 Gazebo Classic의 World Plugin과 역할이 유사하지만, 내부적으로는 아래와 같은 개념 차이가 있다.

  1. Entity-Component-Manager(ECM)

    • Ignition은 로봇, 센서, 환경 객체 등을 모두 ‘Entity’로 보고, 각 Entity에 다양한 ‘Component’를 붙이는 구조다.

    • 예: 로봇 모델은 “Model” 엔티티, 링크는 “Link” 컴포넌트, 센서는 “Sensor” 컴포넌트, 좌표계는 “Pose” 컴포넌트 등.

    • 이러한 ECM 구조는 데이터 중심 아키텍처(ECS) 철학을 반영, 유연한 확장과 상태 관리가 용이하다.

  2. System Plugin 흐름

    • Classic Gazebo: OnUpdate() 콜백 내부에서 World, Model, Sensor 객체에 직접 접근.

    • Ignition Gazebo: PreUpdate(), PostUpdate() 등 특정 시점에 ECM에 접근하여, 필요한 컴포넌트를 읽거나 쓴다.

  3. ROS2 연동 예시

    • Ignition System Plugin 안에서 $rclcpp 노드를 초기화하고, /cmd_vel 등 특정 토픽을 구독한 뒤, ECM을 통해 로봇 모델의 Joint Velocity를 세팅할 수 있다.

    • 반대로 센서 데이터를 ECM에서 읽은 후, ROS2 메시지로 변환해 퍼블리시한다.

아래는 Ignition Gazebo System Plugin이 ROS2 노드를 사용하는 가상의 예시를 개략화한 코드다.

위 예시처럼 Ignition Gazebo 안에서 ROS2 노드를 직접 구동해도 되고, 외부에서 ros_gz_bridge를 사용해도 된다. 다만, 하나의 프로세스에 Ignition과 ROS2가 동시에 구동되므로, 스레드 관리나 리소스 충돌에 주의가 필요하다.

Gazebo GUI(프런트엔드)와 ROS2 연동

Gazebo 또는 Ignition Gazebo의 GUI는 시뮬레이션 세계를 시각적으로 관찰하고, 간단한 인터랙션(예: 모델 클릭, 이동, 조작) 등을 수행하도록 한다. 이 GUI와 ROS2를 연동해 다양한 기능을 구현할 수도 있다.

  1. GUI 플러그인

    • Ignition: Qt 기반 GUI 플러그인을 작성하여, 버튼 클릭 시 ROS2 Service를 호출하거나, ROS2 Action을 트리거할 수 있다. 예: “시뮬레이션 일시정지” 버튼 → ROS2 토픽 발행 → 브리지 통해 Gazebo Physics 멈춤 명령.

  2. rviz2와 병행 사용

    • 실제 로봇 시스템에서는 시뮬레이터 GUI 대신 rviz2를 통해 로봇 상태를 모니터링한다.

    • Gazebo GUI와 rviz2를 동시에 켜 놓으면 시각적 중복이 있을 수 있으나, 각각의 장점(시뮬레이터 상의 물리적 인터랙션 vs ROS TF/Map 시각화)을 살려서 사용할 수 있다.

  3. 모델 편집, 파라미터 조정

    • Ignition의 GUI에는 ‘Component Inspector’ 등이 있어, 모델의 위치, 질량, 관성 모멘트 등을 직접 편집할 수 있다.

    • ROS2 파라미터(예: PID gains)도 동적으로 바꿔가며 시뮬레이션 효과를 확인하고자 할 경우, GUI 플러그인으로 연동이 가능하다.

Custom Message 변환

사용자가 정의한 ROS2 메시지(.msg 또는 .idl)와 Gazebo 쪽의 Protobuf 메시지가 표준 매핑에 없는 경우, 직접 변환 로직을 작성해야 한다.

  1. 사용자 정의 Protobuf 생성

    • msgs/Custom.proto 등의 파일을 만들어, Gazebo가 이해할 수 있는 Protobuf 메시지 구조를 정의.

    • 빌드시 Protobuf 컴파일러를 통해 .pb.cc, .pb.h 생성.

  2. ROS2 Custom Message 빌드

    • my_msgs/msg/CustomMsg.msg 등을 정의하고, ament_cmake 기반 빌드로 .hpp 파일 생성.

  3. 브리지 변환 노드 작성

    • C++ 또는 Python에서 ROS2 메시를 구독 → CustomMsg 파싱 → Protobuf 구조에 맞게 변환 → Gazebo Transport로 발행.

    • 반대 방향도 동일한 방식으로 구현.

아래는 개략적 예시(단순화)이다:

이러한 과정을 수동으로 구현해야 하므로 유지보수가 까다롭다. 따라서, 표준으로 지원되는 메시지 타입을 최대한 활용하고, 가능하면 ros_gz_bridge가 제공하는 매핑을 우선 고려하는 것이 일반적이다.

Docker(컨테이너) 환경에서의 Gazebo-ROS2 연동

Gazebo와 ROS2를 동시에 사용하는 프로젝트를 컨테이너(Docker 등)로 배포·운영하려면, 시뮬레이션과 ROS2 환경 모두를 컨테이너 이미지에 포함해야 한다. 이때 다음 사항을 고려한다.

  1. 그래픽(3D 가속) 지원

    • Gazebo(GPU 가속)와 RViz 등이 3D 렌더링을 사용하기 때문에, 호스트 시스템의 GPU를 컨테이너에 전달하는 설정이 필요할 수 있다. 예: NVIDIA GPU를 사용할 때 --gpus all 옵션을 주거나, Intel/AMD GPU의 경우 OpenGL/X11 세팅을 추가한다.

    • X11 포워딩(혹은 VNC, VirtualGL) 등을 통해 시뮬레이션 GUI를 띄우거나, Headless 모드(렌더링 생략)로 Gazebo를 구동할 수도 있다.

  2. 네트워크 설정

    • ROS2는 DDS 기반으로 노드 간 통신에 멀티캐스트를 사용할 수 있다. Docker 컨테이너를 브리지 네트워크(bridge network) 모드로 띄운 경우, 멀티캐스트 패킷이 정상적으로 전달되지 않을 수 있다.

    • 필요하다면 host network 모드(--net=host)나, 컨테이너 간 별도 multicast/UDP 설정을 통해 ROS2 Discovery 문제를 해결해야 한다.

    • Gazebo Transport(ZeroMQ/Ignition Transport) 역시 포트 충돌이나 방화벽 설정에 유의해야 하며, 컨테이너 내에서 브리지 실행 시 호스트와의 포트 매핑이 올바른지 확인한다.

  3. Volumes(데이터 공유)

    • 시뮬레이션 world 파일(.sdf, .world 등)이나 로봇 모델(.urdf, .xacro)을 로컬에서 수정 후 컨테이너에서 곧바로 반영하려면, 볼륨 마운트를 사용해 파일을 공유한다.

    • 센서 로그(예: 카메라 이미지 저장)나 bag 레코딩(rosbag2)을 수행할 때도 적절히 마운트된 디렉토리를 사용하면 편리하다.

  4. CI/CD 파이프라인 통합

    • Docker 이미지를 자동 빌드해, 시뮬레이션 테스트(예: 단위 테스트, 통합 테스트)를 정기적으로 수행하는 환경을 구축하면, Gazebo+ROS2 호환성을 안정적으로 유지할 수 있다.

    • 예: GitHub Actions, GitLab CI 등의 파이프라인에서 컨테이너 기반 시뮬레이션을 띄우고, ROS2 노드가 시뮬레이션 환경에 대해 기대한대로 동작하는지 검증.

  5. Headless 모드 활용

    • GUI 없이 물리 엔진만 구동하려면 Ignition에서 gz sim -s(server only) 또는 Classic Gazebo에서 gzserver 등을 사용할 수 있다.

    • 컨테이너 내부에서 Headless 시뮬레이션을 실행하고, 호스트(또는 다른 컨테이너)에서 ROS2 노드와 RViz2를 돌려 모니터링 및 제어를 수행하면, X11 설정 부담이 줄어든다.

고성능 컴퓨팅(HPC) 환경에서의 시뮬레이션

멀티 로봇, 고해상도 센서, 복잡한 물리 시나리오 등 대규모 시뮬레이션이 필요한 경우에는 HPC(High Performance Computing) 또는 클라우드 서버를 활용하기도 한다.

  1. 분산 시뮬레이션

    • 단일 서버에서 처리하기 힘든 복잡한 시나리오는 여러 시뮬레이션 인스턴스를 병렬로 실행하고, 각각의 결과를 취합하는 방식을 취한다. 예: 100대 로봇을 동시에 시뮬레이션하기보다는, 10대씩 10개 노드에서 돌린다.

    • ROS2/Gazebo 간 통신을 네트워크로 연결할 때, 앞서 언급된 DDS 멀티캐스트 설정이나 포트 충돌 문제, 시간 동기화 문제를 해결해야 한다.

  2. GPU 컴퓨팅 및 물리 엔진 가속

    • Ignition Gazebo의 일부 물리 엔진은 GPU 가속(예: nvidia PhysX, CUDA 지원)을 부분적으로 지원한다. 고성능 하드웨어를 갖춘 HPC 클러스터에서 이를 활성화해 대규모 시뮬레이션 속도를 높일 수 있다.

    • 센서 시뮬레이션(예: 카메라, LIDAR)에서도 GPU를 활용한 레이 트레이싱이나 딥러닝 기반 합성 데이터 생성이 가능하다.

  3. 시간 가속(Time Acceleration)

    • 실제 시간보다 빠른 속도로 시뮬레이션을 진행하려면, 물리 엔진과 센서 업데이트 루프를 가능한 높게 설정해야 한다. 다만, 복잡한 물리 계산이나 고해상도 센서가 많으면 실시간보다 느리게 동작할 수도 있다.

    • HPC 환경에선 병렬 처리를 통해 어느 정도 시간 단축이 가능하지만, Gazebo 구조상 완전한 병렬화에는 한계가 있으므로 시나리오 설계를 잘 해야 한다.

가상-실기 하이브리드(HIL, SIL) 테스트

Gazebo 시뮬레이션은 순수 소프트웨어 환경(SIL: Software-In-the-Loop) 외에, 실제 하드웨어 제어기나 센서 장치를 연결(HIL: Hardware-In-the-Loop)해 테스트할 수도 있다.

  1. HIL 시나리오

    • 예: 실제 로봇의 모터 컨트롤러나 센서 보드를 시뮬레이터와 연결. Gazebo에서 가상 물리 환경을 계산해, 모터 입력/출력을 에뮬레이션.

    • 이를 위해서는 해당 하드웨어가 ROS2 인터페이스나 별도 드라이버를 통해 통신 가능해야 하며, 시뮬레이터와의 타이밍/주파수 동기화를 맞춰야 한다.

  2. Sensor Fusion 테스트

    • 일부 센서는 실제 장치를 이용하고, 다른 센서는 가상 센서(예: Gazebo 카메라, LiDAR)로 대체하는 혼합 환경을 구성해, 알고리즘(예: SLAM, Navigation)을 검증.

    • 시나리오: “실제 IMU + 가상 카메라 + 가상 라이다 + 실제 바퀴 엔코더” 등을 조합한다.

  3. 에뮬레이션 vs 실제 센서 비교

    • 실제 센서 데이터와 시뮬레이션 센서 데이터를 동시에 로깅하여, 알고리즘의 성능 차이(노이즈 모델, 지연 시간, 해상도 등)를 분석할 수 있다.

시뮬레이션 테스트 자동화

ROS2와 Gazebo를 연동할 때, 테스트를 자동화함으로써 배포 안정성을 높일 수 있다.

  1. launch 파일 활용

    • ROS2 Launch 시스템(파이썬 기반)으로 Gazebo 노드 + 브리지 노드 + 로봇 제어 노드를 동시에 띄우고, 테스트 스크립트를 실행.

    • 예: $ros2 launch my_package sim_test.launch.py 명령만으로 전체 환경이 준비되도록 구성한다.

  2. pytest, gtest 연동

    • Python으로 작성된 노드의 경우 pytest 프레임워크를 사용, 시뮬레이션 환경에서 일정 시간 대기 후 특정 토픽이 기대한 메시지를 발행했는지 검사한다.

    • C++ 노드의 경우 gtest 기반 테스트 코드를 작성해, 시뮬레이터 상에서 동작 검증을 자동화한다.

  3. Continuous Integration (CI)

    • CI 서버(예: GitHub Actions)에서 Headless 모드 Gazebo를 구동해, ROS2 노드 테스트를 수행하고, 결과(로그, rosbag2)를 아티팩트로 저장한다.

    • 시뮬레이션 시간(예: 10초) 안에 로봇이 특정 지점에 도달해야 한다거나, 충돌이 없어야 한다거나, 특정 Topic 메시지(에러 코드 등)가 발생하지 않아야 한다는 조건을 스크립트화한다.

추가적인 참고 사항

  • ROS2 Distributions: Humble, Iron, Rolling 등 버전에 따라 $ros_gz_bridge$ 호환 버전이 다르므로, 설치 전 Compatibility Matrix를 꼭 확인한다.

  • Gazebo 버전: Ignition Fortress, Garden, Classic Gazebo 11 등, 사용 버전에 맞는 문서와 패키지를 선택해야 한다.

  • Physics 엔진: ODE, Bullet, DART, Simbody, PhysX 등 엔진별 특징이 있으므로, 시뮬레이션 목적(로봇 조인트 역학, 소프트 바디 시뮬레이션 등)에 부합하는 엔진을 선정한다.

Last updated