跳到正文

Wiki

Gz Sim(Gazebo Harmonic)

約 33 分鐘閱讀

本文由簡體中文內容確定性轉換,並受版本化術語表保護。

1.Gz Sim(Gazebo Harmonic 及之後的版本(ROS2 Jazzy及之後的版本))

新版Gazebo 是 ROS2 中使用的全新機器人模擬工具,它是 Gazebo 的升級版本。在Humble他叫Ignition Gazebo(也叫Gazebo Fortress),在Jazzy中叫Gazebo Harmonic(去掉了Ignition的名字)(https://community.gazebosim.org/t/a-new-era-for-gazebo/1356)。它具備更好的效能和可用性,並通過緊密整合 ROS2 來提供強大的模擬環境。

1.1.Gazebo安裝與執行

官方教程: https://docs.ros.org/en/jazzy/Tutorials/Advanced/Simulators/Gazebo/Gazebo.html

下面這個網站是官方教程(ROS2 Jazzy 的 Gazebo Harmonic):

https://gazebosim.org/docs/harmonic/getstarted/

https://gazebosim.org/docs/harmonic/library_reference_nav/

原始碼:https://github.com/gazebosim/docs/blob/master/harmonic/tutorials

安裝

Gazebo 是不依賴於ROS2的一個獨立的專案,可以獨自安裝。但是如果安裝了ROS2,在ROS2儲存庫中已經集成了對應版本的 Gazebo,可以呼叫如下指令直接安裝:


# 通用命令
sudo apt install ros-${ROS_DISTRO}-ros-gz

# Humble版本
sudo apt install ros-humble-ros-gz

# Jazzy版本
sudo apt install ros-jazzy-ros-gz

執行

Gazebo 安裝完畢之後,可以通過兩種方式啟動。

方式1,以Gazebo 的方式啟動,指令如下:


# Humble版本
ign gazebo

# Jazzy版本
gz sim

方式2,以ROS2的方式 啟動,指令如下 :

ros2 launch ros_gz_sim gz_sim.launch.py

二者執行結果一致,如下圖所示:在彈出視窗中,選擇模擬環境然後點選run按鈕即可執行。

介面介紹

接下來以Empty模擬環境為例,介紹一下Gazebo的介面組成。

注意:如果你的Gazebo不卡,但是Gazebo巨卡的話,請確認Gazebo是以獨顯開啟的,而不是核顯。

如果不會切換應用顯示卡,可以直接把核顯關閉掉,從混合輸出切換為獨立顯示卡輸出。

工具欄

  • 頂部的工具欄包含兩個按鈕,左側的檔案選單按鈕(水平條紋)和右側的外掛按鈕(垂直省略號)。
  1. 檔案選單按鈕(水平條紋)

  • 檔案選單按鈕包含將模擬環境儲存到檔案、儲存和載入介面配置以及自定義介面樣式等設定。
  1. 右側的外掛按鈕(垂直省略號)

  • 外掛按鈕列出了所有可用的外掛。點選後會彈出外掛列表,向下滾動此列表以檢視所有外掛。 當選擇一個時,其介面將出現在右側面板中。

3D視窗

  • 左上方工具欄包含多種幾何體(球體、框體、圓柱體)按鈕和變換控制元件。通過集合體按鈕可以直接將盒子、球體或圓柱體模型插入模擬環境。只需單擊要插入的形狀,然後將其放入環境中。該形狀將自動捕捉到地平面上。

  • 主檢視會顯示模擬環境,我們可以通過滑鼠以不同方式來導航場景,相關操作如下:
左键单击:选择实体
右键单击:打开带有选项的菜单:
   Move to:移动到以实体为中心的场景
   Follow:选择一个实体让视图保持居中,无论是移动还是平移
   Remove:从模拟中删除实体
   Copy:复制实体
   Past: 粘贴实体
   View:显示实体的重心(Center of Mass)、碰撞边界(Collisions)、惯性(Inertia)、
         关节(Joints)、坐标系(Frames)、透明度(Transparent)、线框(Wireframe)等属性
左键单击并拖动:在场景中平移
右键单击并拖动:放大和缩小
滚轮向前/向后:放大和缩小
滚轮单击并拖动:旋转场景
  • 想移動這個球,需要點左上角的移動模式,再左鍵單擊選中物體

  • 在視窗的底部,從左到右分別是是播放、步長按鈕和即時因子(Real-Time Factor,RTF)。點選播放按鈕將開始執行模擬環境, 再次點選可以暫停執行。步長按鈕用於設定模擬時間的離散單位,可以通過將滑鼠懸停在按鈕上來自定義步長。即時因子表示模擬執行速度相對於真即時間的比例。

右側面板

右側面板用於顯示外掛,當前模擬環境預設包含兩個外掛:Model和Entity Tree。

  • Entity Tree 中會顯示模擬環境中的實體列表;

  • 點選Entity Tree中的實體後,可以在Model中顯示該實體的相關資訊。

  • 也可以按住 Ctrl 並單擊以選擇多個實體;

  • 還可以右鍵單擊任何外掛以開啟基本設定或關閉。

在Gazebo中內建了許多外掛,可以點選工具欄的右側按鈕自行新增,比如:可以選擇 Grid Config 外掛調整世界網格的特徵,包括單元格大小、網格位置、單元格計數、或顏色等。

後期隨著應用的深入,也會陸續介紹其他一些外掛。

1.2.與ROS2整合

本節將介紹如何實現Ignition Gazebo與ROS2的整合,以實現二者之間的互動,比如,可以通過ROS2的鍵盤控制節點控制機器人運動,並且在rviz2中顯示機器人的里程計(odom)資料。其流程大致如下:

  1. 啟動 Ignition Gazebo 模擬環境;

  2. 通過 ros_gz_bridge 建立 ROS2 與 Ignition Gazebo 的連線;

  3. 啟動 ROS2 相關節點實現與 Ignition Gazebo 的資料收發。

Ignition Gazebo與ROS2的的所有整合實現,基本都遵循上述流程。

啟動模擬環境

在 Ignition Gazebo 安裝時,已經內建了一些模擬環境,直接啟動即可。在此我們可以使用名為visualize_lidar.sdf的模擬檔案,該檔案對應的模擬環境中包括了差速機器人以及雷射雷達的模擬。啟動指令如下:

ign gazebo -v 4 -r visualize_lidar.sdf
#或者
gz sim -v 4 -r visualize_lidar.sdf

或者也可以以ROS2 launch的方式啟動,指令如下:

ros2 launch ros_gz_sim gz_sim.launch.py gz_args:="-v 4 -r visualize_lidar.sdf"

兩種方式本質相同,都是啟動了Ignition Gazebo並且載入了visualize_lidar.sdf檔案。

建立連線

雖然模擬環境中的機器人已經配置了運動控制外掛,可以通過/model/vehicle_blue/cmd_vel話題訂閱速度指令並運動,但是Gazebo與ROS2中的訊息格式並不一致,所以還需要通過ros_gz_bridge這一橋接功能包,實現二者之間訊息的轉換,呼叫指令如下:

ros2 run ros_gz_bridge parameter_bridge /model/vehicle_blue/cmd_vel@geometry_msgs/msg/Twist]gz.msgs.Twist

通過該指令可以將釋出在/model/vehicle_blue/cmd_vel話題上的geometry_msgs/msg/Twist型別的ROS2訊息轉換成可以被Gzebo識別的gz.msgs.Twist型別的訊息。

啟動ROS2節點

啟動ROS2的鍵盤控制節點,並將話題重對映為/model/vehicle_blue/cmd_vel,指令如下:

ros2 run teleop_twist_keyboard teleop_twist_keyboard --ros-args -r /cmd_vel:=/model/vehicle_blue/cmd_vel

接下來就可以使用鍵盤控制機器人運動了。

1.3.ros_gz_bridge

ros_gz_bridge是連線ROS2與Gazebo的橋梁,ROS2與Gazebo使用的訊息並不相容,必須通過ros_gz_bridge進行轉換。

ros_gz_bridge使用語法

ROS2與Gazebo的橋接是通過ros_gz_bridge包中的parameter_bridge節點實現,其使用語法如下:

parameter_bridge [<topic@ROS2_type@Gz_type> ..]  [<service@ROS2_srv_type[@Gz_req_type@Gz_rep_type]> ..]

在話題Topic中, 第一個@ 符號是話題名稱和訊息型別的 分隔符

第一個@符號後面是ROS訊息型別。

ROS訊息型別後面是@、[或]符號:

  • @ 表示雙向橋接;

  • [ 表示從Gazebo到ROS的橋接;

  • ] 表示從ROS到Gazebo的橋接。

方向符號後是Gazebo Transport訊息型別。

(兩個@不是同一個含義)

在服務Service中, 第一個@ 符號是服務名稱和型別的 分隔符

第一個@符號後面是ROS服務型別。可以選擇地包括Gazebo請求和響應型別,在它們之間用@符號分隔。

支援將Gazebo服務公開為ROS服務,即ROS服務將請求轉發到Gazebo服務,然後將響應轉發回ROS客戶端。

雙向橋接示例:

parameter_bridge /chatter@std_msgs/msg/String@gz.msgs.StringMsg

從Gazebo到ROS的橋接示例:

parameter_bridge /chatter@std_msgs/msg/String[gz.msgs.StringMsg

從ROS到Gazebo的橋接示例:

parameter_bridge /chatter@std_msgs/msg/String]gz.msgs.StringMsg

服務橋接示例:

parameter_bridge /world/default/control@ros_gz_interfaces/srv/ControlWorld
或者:
parameter_bridge /world/default/control@ros_gz_interfaces/srv/ControlWorld@gz.msgs.WorldControl@gz.msgs.Boolean

ros_gz_bridge支援的訊息型別

以下列出 Jazzy 當前常用和官方文件中列出的部分對映;完整對映以 ros2 run ros_gz_bridge parameter_bridge -h 和 ROS2 對應版本的 ros_gz_bridge 文件為準。

以下是ROS2與Gazebo中話題訊息型別對應表:

ROS2訊息型別 Gazebo Transport 型別
builtin_interfaces/msg/Time gz.msgs.Time
geometry_msgs/msg/Point gz.msgs.Vector3d
geometry_msgs/msg/Pose gz.msgs.Pose
geometry_msgs/msg/PoseArray gz.msgs.Pose_V
geometry_msgs/msg/PoseStamped gz.msgs.Pose
geometry_msgs/msg/PoseWithCovariance gz.msgs.PoseWithCovariance
geometry_msgs/msg/Quaternion gz.msgs.Quaternion
geometry_msgs/msg/Transform gz.msgs.Pose
geometry_msgs/msg/TransformStamped gz.msgs.Pose
geometry_msgs/msg/Twist gz.msgs.Twist
geometry_msgs/msg/TwistWithCovariance gz.msgs.TwistWithCovariance
geometry_msgs/msg/TwistWithCovarianceStamped gz.msgs.TwistWithCovariance
geometry_msgs/msg/Vector3 gz.msgs.Vector3d
geometry_msgs/msg/Wrench gz.msgs.Wrench
geometry_msgs/msg/WrenchStamped gz.msgs.Wrench
nav_msgs/msg/Odometry gz.msgs.Odometry
nav_msgs/msg/Odometry gz.msgs.OdometryWithCovariance
rcl_interfaces/msg/ParameterValue gz.msgs.Any
ros_gz_interfaces/msg/Altimeter gz.msgs.Altimeter
ros_gz_interfaces/msg/Contact gz.msgs.Contact
ros_gz_interfaces/msg/Contacts gz.msgs.Contacts
ros_gz_interfaces/msg/Dataframe gz.msgs.Dataframe
ros_gz_interfaces/msg/Entity gz.msgs.Entity
ros_gz_interfaces/msg/Float32Array gz.msgs.Float_V
ros_gz_interfaces/msg/GuiCamera gz.msgs.GUICamera
ros_gz_interfaces/msg/JointWrench gz.msgs.JointWrench
ros_gz_interfaces/msg/Light gz.msgs.Light
ros_gz_interfaces/msg/SensorNoise gz.msgs.SensorNoise
ros_gz_interfaces/msg/StringVec gz.msgs.StringMsg_V
ros_gz_interfaces/msg/TrackVisual gz.msgs.TrackVisual
ros_gz_interfaces/msg/VideoRecord gz.msgs.VideoRecord
ros_gz_interfaces/msg/WorldControl gz.msgs.WorldControl
rosgraph_msgs/msg/Clock* gz.msgs.Clock*
sensor_msgs/msg/BatteryState gz.msgs.BatteryState
sensor_msgs/msg/CameraInfo gz.msgs.CameraInfo
sensor_msgs/msg/FluidPressure gz.msgs.FluidPressure
sensor_msgs/msg/Image gz.msgs.Image
sensor_msgs/msg/Imu gz.msgs.IMU
sensor_msgs/msg/JointState gz.msgs.Model
sensor_msgs/msg/Joy gz.msgs.Joy
sensor_msgs/msg/LaserScan gz.msgs.LaserScan
sensor_msgs/msg/MagneticField gz.msgs.Magnetometer
sensor_msgs/msg/NavSatFix gz.msgs.NavSat
sensor_msgs/msg/PointCloud2 gz.msgs.PointCloudPacked
std_msgs/msg/Bool gz.msgs.Boolean
std_msgs/msg/ColorRGBA gz.msgs.Color
std_msgs/msg/Empty gz.msgs.Empty
std_msgs/msg/Float32 gz.msgs.Float
std_msgs/msg/Float64 gz.msgs.Double
std_msgs/msg/Header gz.msgs.Header
std_msgs/msg/Int32 gz.msgs.Int32
std_msgs/msg/String gz.msgs.StringMsg
std_msgs/msg/UInt32 gz.msgs.UInt32
tf2_msgs/msg/TFMessage gz.msgs.Pose_V
trajectory_msgs/msg/JointTrajectory gz.msgs.JointTrajectory

還有一些增補的型別

ROS 2 訊息型別 Gazebo Transport 型別
actuator_msgs/msg/Actuators gz.msgs.Actuators
geometry_msgs/msg/PoseWithCovarianceStamped gz.msgs.PoseWithCovariance
geometry_msgs/msg/TwistStamped gz.msgs.Twist
gps_msgs/msg/GPSFix gz.msgs.NavSat
marine_acoustic_msgs/msg/Dvl gz.msgs.DVLVelocityTracking
ros_gz_interfaces/msg/EntityWrench gz.msgs.EntityWrench
ros_gz_interfaces/msg/LogicalCameraImage gz.msgs.LogicalCameraImage
ros_gz_interfaces/msg/LogPlaybackStatistics gz.msgs.LogPlaybackStatistics
ros_gz_interfaces/msg/ParamVec gz.msgs.Param
ros_gz_interfaces/msg/ParamVec gz.msgs.Param_V
ros_gz_interfaces/msg/WorldStatistics gz.msgs.WorldStatistics
sensor_msgs/msg/Range gz.msgs.LaserScan
vision_msgs/msg/Detection2D gz.msgs.AnnotatedAxisAligned2DBox
vision_msgs/msg/Detection2DArray gz.msgs.AnnotatedAxisAligned2DBox_V
vision_msgs/msg/Detection3D gz.msgs.AnnotatedOriented3DBox
vision_msgs/msg/Detection3DArray gz.msgs.AnnotatedOriented3DBox_V

以及服務訊息型別對應表:

ROS2訊息型別 Gazebo 請求 Gazebo 響應
ros_gz_interfaces/srv/ControlWorld gz.msgs.WorldControl gz.msgs.Boolean

1.4.與ROS2整合最佳化

Gazebo與ROS2整合 實現中需要在終端中使用不同的指令啟動不同模組,該流程實現稍顯複雜,本節將介紹如何以launch檔案的方式進行最佳化。

新建功能包

請首先呼叫如下指令建立一個功能包:

ros2 pkg create demo_gazebo_sim

新增目錄

在新建的功能包下新增目錄: launch、rviz、world。並在CmakeLists.txt中新增如下程式碼:

install(DIRECTORY rviz world launch DESTINATION share/${PROJECT_NAME})

launch目錄用於儲存launch檔案,rviz目錄由於儲存rviz2的配置檔案,而world目錄則用於儲存Gazebo模擬環境的相關檔案。

rviz目錄中生成rviz2的配置檔案

啟動 rviz2,直接將預設配置儲存至當前功能包的rviz目錄,儲存檔案命名為sim.rviz。

複製world檔案

在ros安裝路徑下的worlds目錄(/opt/ros/jazzy/opt/gz_sim_vendor/share/gz/gz-sim8/worlds/)中複製visualize_lidar.sdf檔案至world目錄。

如果以上路徑下沒有,那可能在ign(humble的)的安裝路徑下:

/usr/share/ignition/ignition-gazebo6/worlds

如果還沒有的話,手動查詢一下:

sudo find / -name "visualize_lidar.sdf"

編寫launch檔案

launch目錄下新建launch檔案gazebo_sim_demo.launch.py,並輸入如下內容:

import os

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.actions import IncludeLaunchDescription
from launch.conditions import IfCondition
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import LaunchConfiguration

from launch_ros.actions import Node

def generate_launch_description():

    this_pkg = get_package_share_directory('demo_gazebo_sim')
    pkg_ros_gz_sim = get_package_share_directory('ros_gz_sim')
    world_file = os.path.join(this_pkg,'world','visualize_lidar.sdf')

    gz_sim = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(pkg_ros_gz_sim, 'launch', 'gz_sim.launch.py')),
        launch_arguments={
            'gz_args': '-r ' + world_file
        }.items(),
    )

    # RViz
    rviz = Node(
       package='rviz2',
       executable='rviz2',
       arguments=['-d', os.path.join(this_pkg, 'rviz', 'sim.rviz')],
       condition=IfCondition(LaunchConfiguration('rviz'))
    )

    # Bridge
    bridge = Node(
        package='ros_gz_bridge',
        executable='parameter_bridge',
        arguments=['/model/vehicle_blue/cmd_vel@geometry_msgs/msg/Twist@gz.msgs.Twist',
                   '/model/vehicle_blue/odometry@nav_msgs/msg/Odometry@gz.msgs.Odometry',
                   '/model/vehicle_blue/tf@tf2_msgs/msg/TFMessage[gz.msgs.Pose_V',
                   ],
        parameters=[{'qos_overrides./model/vehicle_blue.subscriber.reliability': 'reliable'}],
        remappings=[
                ('/model/vehicle_blue/tf', '/tf'),
                ('/model/vehicle_blue/cmd_vel','cmd_vel')
            ],
        output='screen'
    )

    return LaunchDescription([
        gz_sim,
        DeclareLaunchArgument('rviz', default_value='true',
                              description='Open RViz.'),
        bridge,
        rviz
    ])

該launch檔案中,啟動了Ignition Gazebo模擬環境、通過ros_gz_bridge建立了模擬與ROS2的連線,並且啟動了rviz2節點。其中建立連線時,實現了速度指令、里程計以及座標變換等訊息的轉換。

構建

終端中進入當前工作空間,編譯功能包:

colcon build  --packages-select demo_gazebo_sim

執行

終端中進入當前工作空間,呼叫如下指令執行launch檔案:

. install/setup.bash
ros2 launch demo_gazebo_sim gazebo_sim_demo.launch.py

新開終端,啟動鍵盤控制節點:

ros2 run teleop_twist_keyboard teleop_twist_keyboard

再配置rviz2,

  1. Fixed Frame設定為vehicle_blue/odom
  2. 新增TF外掛,
  3. 新增Odometry外掛並將話題設定為/model/vehicle_blue/odometry, 當通過鍵盤控制傳送速度指令時,模擬環境的機器人開始運動,並且在rviz2中可以回顯座標變換以及里程計等訊息。

1.5.模擬環境建立 SDF檔案

前面幾節內容我們使用的是Ignition Gazebo內建的模擬環境,本節開始將介紹如何自行搭建模擬環境。本節案例將模擬一個長10m寬5m的矩形房間。該案例可以先啟動Ignition Gazebo以拖拽的方式搭建模擬環境,然後再修改模擬環境對應的檔案以調整細節。

SDF、URDF 和 Xacro 的關係:

  • URDF 和 SDF 的區別:

    • 複雜性: SDF 支援的功能更強大,能夠描述完整的模擬環境;URDF 更適合定義機器人模型。

    • 用途: URDF 是 ROS 的標準;SDF 是 Gazebo 的標準。

    • 物理引擎支援: URDF 通過外掛支援 Gazebo;SDF 原生支援 Gazebo。

    • 格式轉換: URDF 可以轉換為 SDF(通過 ROS 提供的工具gz sdf -p)。

  • Xacro 的作用:

    • Xacro 是 URDF 的生成工具,幫助使用者高效編寫 URDF 檔案,但它與 SDF 無直接關係。

實踐建議

  • 在 Gazebo 模擬中: 如果你用的是 ROS 2 和 Gazebo,可以直接使用 SDF 檔案,功能更強大。

  • 在 ROS 中: 如果主要用於機器人控制和規劃,推薦使用 URDF 或由 Xacro 生成的 URDF。

  • 兩者結合: 使用 URDF 進行控制,使用 SDF 進行模擬。例如,使用 URDF 定義機器人結構後,藉助 Gazebo 外掛將其轉換為 SDF。

示例對比

URDF 示例:

<robot name="example_robot">
  <link name="base_link">
    <inertial>
      <mass value="1.0" />
      <inertia ixx="1.0" ixy="0.0" ixz="0.0" iyy="1.0" iyz="0.0" izz="1.0" />
    </inertial>
  </link>
</robot>

Xacro 示例(生成 URDF):

<xacro:robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="example_robot">
  <xacro:macro name="base_link" params="mass">
    <link name="base_link">
      <inertial>
        <mass value="${mass}" />
        <inertia ixx="1.0" ixy="0.0" ixz="0.0" iyy="1.0" iyz="0.0" izz="1.0" />
      </inertial>
    </link>
  </xacro:macro>

  <xacro:base_link mass="1.0" />
</xacro:robot>

SDF 示例:

<sdf version="1.6">
  <model name="example_robot">
    <link name="base_link">
      <inertial>
        <mass>1.0</mass>
        <inertia>
          <ixx>1.0</ixx>
          <iyy>1.0</iyy>
          <izz>1.0</izz>
        </inertia>
      </inertial>
    </link>
  </model>
</sdf>

1.建立sdf檔案

首先請呼叫指令gz sim啟動Gazebo,選擇Empty模擬環境,然後新增立方體,每一個立方體都對應一堵牆。

上下左右立方體box、box_1、box_2、box_3對應的座標分別為(5.0,0.0,0.5)、(-5.0,0.0,0.5)、(0.0,2.5,0.5)、(0.0,-2.5,0.5)。

(以上座標是指X,Y,Z座標,沒有旋轉度)

儲存檔案到功能包的world目錄下,儲存的檔名稱需要以.sdf為字尾,此處檔名為house.sdf。

<sdf version='1.10'>
  <world name='empty'>
    <physics name='1ms' type='ignored'>
      <max_step_size>0.001</max_step_size>
      <real_time_factor>1</real_time_factor>
      <real_time_update_rate>1000</real_time_update_rate>
    </physics>
    <plugin name='gz::sim::systems::Physics' filename='gz-sim-physics-system'/>
    <plugin name='gz::sim::systems::UserCommands' filename='gz-sim-user-commands-system'/>
    <plugin name='gz::sim::systems::SceneBroadcaster' filename='gz-sim-scene-broadcaster-system'/>
    <plugin name='gz::sim::systems::Contact' filename='gz-sim-contact-system'/>
    <gravity>0 0 -9.8</gravity>
    <magnetic_field>6e-06 2.3e-05 -4.2e-05</magnetic_field>
    <atmosphere type='adiabatic'/>
    <scene>
      <ambient>0.4 0.4 0.4 1</ambient>
      <background>0.7 0.7 0.7 1</background>
      <shadows>true</shadows>
    </scene>
    <model name='ground_plane'>
      <static>true</static>
      <link name='link'>
        <collision name='collision'>
          <geometry>
            <plane>
              <normal>0 0 1</normal>
              <size>100 100</size>
            </plane>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='visual'>
          <geometry>
            <plane>
              <normal>0 0 1</normal>
              <size>100 100</size>
            </plane>
          </geometry>
          <material>
            <ambient>0.8 0.8 0.8 1</ambient>
            <diffuse>0.8 0.8 0.8 1</diffuse>
            <specular>0.8 0.8 0.8 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <inertial>
          <pose>0 0 0 0 -0 0</pose>
          <mass>100</mass>
          <inertia>
            <ixx>1</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>1</iyy>
            <iyz>0</iyz>
            <izz>1</izz>
          </inertia>
        </inertial>
        <enable_wind>false</enable_wind>
      </link>
      <pose>0 0 0 0 -0 0</pose>
      <self_collide>false</self_collide>
    </model>
    <model name='box'>
      <pose>5.0 0 0.5 -0 0 0</pose>
      <link name='box_link'>
        <inertial>
          <inertia>
            <ixx>16.666</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>16.666</iyy>
            <iyz>0</iyz>
            <izz>16.666</izz>
          </inertia>
          <mass>100</mass>
          <pose>0 0 0 0 -0 0</pose>
        </inertial>
        <collision name='box_collision'>
          <geometry>
            <box>
              <size>0.1 5 1</size>
            </box>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='box_visual'>
          <geometry>
            <box>
              <size>0.1 5 1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.3 0.3 0.3 1</ambient>
            <diffuse>0.7 0.7 0.7 1</diffuse>
            <specular>1 1 1 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <enable_wind>false</enable_wind>
      </link>
      <static>true</static>
      <self_collide>false</self_collide>
    </model>
    <model name='box_0'>
      <pose>-5.0 -0 0.50000 -0 0 0</pose>
      <link name='box_link'>
        <inertial>
          <inertia>
            <ixx>16.666</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>16.666</iyy>
            <iyz>0</iyz>
            <izz>16.666</izz>
          </inertia>
          <mass>100</mass>
          <pose>0 0 0 0 -0 0</pose>
        </inertial>
        <collision name='box_collision'>
          <geometry>
            <box>
              <size>0.1 5 1</size>
            </box>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='box_visual'>
          <geometry>
            <box>
              <size>0.1 5 1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.3 0.3 0.3 1</ambient>
            <diffuse>0.7 0.7 0.7 1</diffuse>
            <specular>1 1 1 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <enable_wind>false</enable_wind>
      </link>
      <static>true</static>
      <self_collide>false</self_collide>
    </model>
    <model name='box_1'>
      <pose>-0 -2.5 0.5 -0 -0 -0</pose>
      <link name='box_link'>
        <inertial>
          <inertia>
            <ixx>16.666</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>16.666</iyy>
            <iyz>0</iyz>
            <izz>16.666</izz>
          </inertia>
          <mass>100</mass>
          <pose>0 0 0 0 -0 0</pose>
        </inertial>
        <collision name='box_collision'>
          <geometry>
            <box>
              <size>10 0.1 1</size>
            </box>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='box_visual'>
          <geometry>
            <box>
              <size>10 0.1 1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.3 0.3 0.3 1</ambient>
            <diffuse>0.7 0.7 0.7 1</diffuse>
            <specular>1 1 1 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <enable_wind>false</enable_wind>
      </link>
      <static>true</static>
      <self_collide>false</self_collide>
    </model>
    <model name='box_2'>
      <pose>-0 2.5 0.5 0 -0 -0</pose>
      <link name='box_link'>
        <inertial>
          <inertia>
            <ixx>16.666</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>16.666</iyy>
            <iyz>0</iyz>
            <izz>16.666</izz>
          </inertia>
          <mass>100</mass>
          <pose>0 0 0 0 -0 0</pose>
        </inertial>
        <collision name='box_collision'>
          <geometry>
            <box>
              <size>10 0.1 1</size>
            </box>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='box_visual'>
          <geometry>
            <box>
              <size>10 0.1 1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.3 0.3 0.3 1</ambient>
            <diffuse>0.7 0.7 0.7 1</diffuse>
            <specular>1 1 1 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <enable_wind>false</enable_wind>
      </link>
      <static>true</static>
      <self_collide>false</self_collide>
    </model>
    <light name='sun' type='directional'>
      <pose>0 0 10 0 -0 0</pose>
      <cast_shadows>true</cast_shadows>
      <intensity>1</intensity>
      <direction>-0.5 0.1 -0.9</direction>
      <diffuse>0.8 0.8 0.8 1</diffuse>
      <specular>0.2 0.2 0.2 1</specular>
      <attenuation>
        <range>1000</range>
        <linear>0.01</linear>
        <constant>0.90000000000000002</constant>
        <quadratic>0.001</quadratic>
      </attenuation>
      <spot>
        <inner_angle>0</inner_angle>
        <outer_angle>0</outer_angle>
        <falloff>0</falloff>
      </spot>
    </light>
  </world>
</sdf>

2.修改sdf檔案

修改sdf檔案,調整立方體的尺寸,實現牆體的合圍。在sdf檔案中,四個立方體分別對應了四個<model>標籤,其name屬性分別為boxbox_1box_2box_3,將boxbox_1中的<size>1 1 1</size>修改為<size>0.1 5 1</size>,將box_2box_3中的<size>1 1 1</size>修改為<size>10 0.1 1</size>注意:每個<model>標籤下,都包含兩個<size>標籤,分別位於<collision>標籤和<visual>標籤下,兩個<size>標籤內容都需要修改)。

修改後與的house.sdf檔案內容如下:

<sdf version='1.10'>
  <world name='empty'>
    <physics name='1ms' type='ignored'>
      <max_step_size>0.001</max_step_size>
      <real_time_factor>1</real_time_factor>
      <real_time_update_rate>1000</real_time_update_rate>
    </physics>
    <plugin name='gz::sim::systems::Physics' filename='gz-sim-physics-system'/>
    <plugin name='gz::sim::systems::UserCommands' filename='gz-sim-user-commands-system'/>
    <plugin name='gz::sim::systems::SceneBroadcaster' filename='gz-sim-scene-broadcaster-system'/>
    <plugin name='gz::sim::systems::Contact' filename='gz-sim-contact-system'/>
    <gravity>0 0 -9.8</gravity>
    <magnetic_field>6e-06 2.3e-05 -4.2e-05</magnetic_field>
    <atmosphere type='adiabatic'/>
    <scene>
      <ambient>0.4 0.4 0.4 1</ambient>
      <background>0.7 0.7 0.7 1</background>
      <shadows>true</shadows>
    </scene>
    <model name='ground_plane'>
      <static>true</static>
      <link name='link'>
        <collision name='collision'>
          <geometry>
            <plane>
              <normal>0 0 1</normal>
              <size>100 100</size>
            </plane>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='visual'>
          <geometry>
            <plane>
              <normal>0 0 1</normal>
              <size>100 100</size>
            </plane>
          </geometry>
          <material>
            <ambient>0.8 0.8 0.8 1</ambient>
            <diffuse>0.8 0.8 0.8 1</diffuse>
            <specular>0.8 0.8 0.8 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <inertial>
          <pose>0 0 0 0 -0 0</pose>
          <mass>100</mass>
          <inertia>
            <ixx>1</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>1</iyy>
            <iyz>0</iyz>
            <izz>1</izz>
          </inertia>
        </inertial>
        <enable_wind>false</enable_wind>
      </link>
      <pose>0 0 0 0 -0 0</pose>
      <self_collide>false</self_collide>
    </model>
    <model name='box'>
      <pose>5.0 0 0.5 -0 0 0</pose>
      <link name='box_link'>
        <inertial>
          <inertia>
            <ixx>16.666</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>16.666</iyy>
            <iyz>0</iyz>
            <izz>16.666</izz>
          </inertia>
          <mass>100</mass>
          <pose>0 0 0 0 -0 0</pose>
        </inertial>
        <collision name='box_collision'>
          <geometry>
            <box>
              <size>0.1 5 1</size>
            </box>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='box_visual'>
          <geometry>
            <box>
              <size>0.1 5 1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.3 0.3 0.3 1</ambient>
            <diffuse>0.7 0.7 0.7 1</diffuse>
            <specular>1 1 1 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <enable_wind>false</enable_wind>
      </link>
      <static>true</static>
      <self_collide>false</self_collide>
    </model>
    <model name='box_0'>
      <pose>-5.0 -0 0.50000 -0 0 0</pose>
      <link name='box_link'>
        <inertial>
          <inertia>
            <ixx>16.666</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>16.666</iyy>
            <iyz>0</iyz>
            <izz>16.666</izz>
          </inertia>
          <mass>100</mass>
          <pose>0 0 0 0 -0 0</pose>
        </inertial>
        <collision name='box_collision'>
          <geometry>
            <box>
              <size>0.1 5 1</size>
            </box>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='box_visual'>
          <geometry>
            <box>
              <size>0.1 5 1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.3 0.3 0.3 1</ambient>
            <diffuse>0.7 0.7 0.7 1</diffuse>
            <specular>1 1 1 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <enable_wind>false</enable_wind>
      </link>
      <static>true</static>
      <self_collide>false</self_collide>
    </model>
    <model name='box_1'>
      <pose>-0 -2.5 0.5 -0 -0 -0</pose>
      <link name='box_link'>
        <inertial>
          <inertia>
            <ixx>16.666</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>16.666</iyy>
            <iyz>0</iyz>
            <izz>16.666</izz>
          </inertia>
          <mass>100</mass>
          <pose>0 0 0 0 -0 0</pose>
        </inertial>
        <collision name='box_collision'>
          <geometry>
            <box>
              <size>10 0.1 1</size>
            </box>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='box_visual'>
          <geometry>
            <box>
              <size>10 0.1 1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.3 0.3 0.3 1</ambient>
            <diffuse>0.7 0.7 0.7 1</diffuse>
            <specular>1 1 1 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <enable_wind>false</enable_wind>
      </link>
      <static>true</static>
      <self_collide>false</self_collide>
    </model>
    <model name='box_2'>
      <pose>-0 2.5 0.5 0 -0 -0</pose>
      <link name='box_link'>
        <inertial>
          <inertia>
            <ixx>16.666</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>16.666</iyy>
            <iyz>0</iyz>
            <izz>16.666</izz>
          </inertia>
          <mass>100</mass>
          <pose>0 0 0 0 -0 0</pose>
        </inertial>
        <collision name='box_collision'>
          <geometry>
            <box>
              <size>10 0.1 1</size>
            </box>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='box_visual'>
          <geometry>
            <box>
              <size>10 0.1 1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.3 0.3 0.3 1</ambient>
            <diffuse>0.7 0.7 0.7 1</diffuse>
            <specular>1 1 1 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <enable_wind>false</enable_wind>
      </link>
      <static>true</static>
      <self_collide>false</self_collide>
    </model>
    <light name='sun' type='directional'>
      <pose>0 0 10 0 -0 0</pose>
      <cast_shadows>true</cast_shadows>
      <intensity>1</intensity>
      <direction>-0.5 0.1 -0.9</direction>
      <diffuse>0.8 0.8 0.8 1</diffuse>
      <specular>0.2 0.2 0.2 1</specular>
      <attenuation>
        <range>1000</range>
        <linear>0.01</linear>
        <constant>0.90000000000000002</constant>
        <quadratic>0.001</quadratic>
      </attenuation>
      <spot>
        <inner_angle>0</inner_angle>
        <outer_angle>0</outer_angle>
        <falloff>0</falloff>
      </spot>
    </light>
  </world>
</sdf>

3.編寫launch檔案

在launch目錄下新建launch檔案gazebo_sim_world.launch.py,並輸入如下內容:

import os

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource

def generate_launch_description():

    this_pkg = get_package_share_directory('demo_gazebo_sim')
    pkg_ros_gz_sim = get_package_share_directory('ros_gz_sim')
    world_file = os.path.join(this_pkg,"world","house.sdf")

    gz_sim = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(pkg_ros_gz_sim, 'launch', 'gz_sim.launch.py')),
        launch_arguments={
            'gz_args': '-r ' + world_file
        }.items(),
    )
    return LaunchDescription([
        gz_sim
    ])

4.構建

終端中進入當前工作空間,編譯功能包:

colcon build  --packages-select demo_gazebo_sim

5.執行

終端中進入當前工作空間,呼叫如下指令執行launch檔案:

. install/setup.bash
ros2 launch demo_gazebo_sim gazebo_sim_world.launch.py

執行結果如下圖所示。

也可以根據個人喜好,繼續設計房間模型。

1.6.Gz Sim新增模型

在Gazebo官網提供了許多模擬模型,可以自行下載並使用以最佳化模擬環境,使其更多樣、美觀且真實。

資源下載

模擬Gazebo的官方模型連結:

https://app.gazebosim.org/fuel/models

自行選擇模擬模型點選進入詳情頁面,然後點選下載按鈕即可將模型資源儲存到本地。

在使用者目錄下新建ign_models目錄,將下載的資源解壓縮到該目錄以作備用。 這個目錄的名字隨便起,你想起什麼起什麼,但是前提是純英文,符合Linux命名規則,別有非法。

資源配置

為了可以讓Gazebo識別到模型資源,下一步還需要修改使用者目錄下的 .bashrc 檔案,新增如下程式碼:

# Jazzy版本的宏如下:
export GZ_SIM_RESOURCE_PATH=~/ign_models

# Humble版本一般是下面的
export IGN_GAZEBO_RESOURCE_PATH=~/ign_models

https://gazebosim.org/docs/latest/fuel_insert/

模型新增

終端下進入功能包demo_gazebo_sim的world目錄,使用指令gz sim house.sdf啟動模擬環境,點選視窗右上的摺疊按鈕,搜尋Resource Spawner並開啟,點選Local resources並選擇模型拖拽至模擬環境中。將修改後的內容儲存至house.sdf檔案。

正常下載資源後,這個local resources這裡就會顯示了

house.sdf檔案示例內容如下:

<sdf version='1.10'>
  <world name='empty'>
    <physics name='1ms' type='ignored'>
      <max_step_size>0.001</max_step_size>
      <real_time_factor>1</real_time_factor>
      <real_time_update_rate>1000</real_time_update_rate>
    </physics>
    <plugin name='gz::sim::systems::Physics' filename='gz-sim-physics-system'/>
    <plugin name='gz::sim::systems::UserCommands' filename='gz-sim-user-commands-system'/>
    <plugin name='gz::sim::systems::SceneBroadcaster' filename='gz-sim-scene-broadcaster-system'/>
    <plugin name='gz::sim::systems::Contact' filename='gz-sim-contact-system'/>
    <gravity>0 0 -9.8</gravity>
    <magnetic_field>6e-06 2.3e-05 -4.2e-05</magnetic_field>
    <atmosphere type='adiabatic'/>
    <scene>
      <ambient>0.4 0.4 0.4 1</ambient>
      <background>0.7 0.7 0.7 1</background>
      <shadows>true</shadows>
    </scene>
    <model name='ground_plane'>
      <static>true</static>
      <link name='link'>
        <collision name='collision'>
          <geometry>
            <plane>
              <normal>0 0 1</normal>
              <size>100 100</size>
            </plane>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='visual'>
          <geometry>
            <plane>
              <normal>0 0 1</normal>
              <size>100 100</size>
            </plane>
          </geometry>
          <material>
            <ambient>0.8 0.8 0.8 1</ambient>
            <diffuse>0.8 0.8 0.8 1</diffuse>
            <specular>0.8 0.8 0.8 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <inertial>
          <pose>0 0 0 0 -0 0</pose>
          <mass>100</mass>
          <inertia>
            <ixx>1</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>1</iyy>
            <iyz>0</iyz>
            <izz>1</izz>
          </inertia>
        </inertial>
        <enable_wind>false</enable_wind>
      </link>
      <pose>0 0 0 0 -0 0</pose>
      <self_collide>false</self_collide>
    </model>
    <model name='box'>
      <pose>5.05017 0 0.5 -0 0 3e-05</pose>
      <link name='box_link'>
        <inertial>
          <inertia>
            <ixx>16.666</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>16.666</iyy>
            <iyz>0</iyz>
            <izz>16.666</izz>
          </inertia>
          <mass>100</mass>
          <pose>0 0 0 0 -0 0</pose>
        </inertial>
        <collision name='box_collision'>
          <geometry>
            <box>
              <size>0.1 5 1</size>
            </box>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='box_visual'>
          <geometry>
            <box>
              <size>0.1 5 1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.3 0.3 0.3 1</ambient>
            <diffuse>0.7 0.7 0.7 1</diffuse>
            <specular>1 1 1 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <enable_wind>false</enable_wind>
      </link>
      <static>true</static>
      <self_collide>false</self_collide>
    </model>
    <model name='box_0'>
      <pose>-5.05003 -7.8e-05 0.5 -0 -0 1.8e-05</pose>
      <link name='box_link'>
        <inertial>
          <inertia>
            <ixx>16.666</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>16.666</iyy>
            <iyz>0</iyz>
            <izz>16.666</izz>
          </inertia>
          <mass>100</mass>
          <pose>0 0 0 0 -0 0</pose>
        </inertial>
        <collision name='box_collision'>
          <geometry>
            <box>
              <size>0.1 5 1</size>
            </box>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='box_visual'>
          <geometry>
            <box>
              <size>0.1 5 1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.3 0.3 0.3 1</ambient>
            <diffuse>0.7 0.7 0.7 1</diffuse>
            <specular>1 1 1 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <enable_wind>false</enable_wind>
      </link>
      <static>true</static>
      <self_collide>false</self_collide>
    </model>
    <model name='box_1'>
      <pose>0.000162 -2.53144 0.499999 0 -0 -0.001876</pose>
      <link name='box_link'>
        <inertial>
          <inertia>
            <ixx>16.666</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>16.666</iyy>
            <iyz>0</iyz>
            <izz>16.666</izz>
          </inertia>
          <mass>100</mass>
          <pose>0 0 0 0 -0 0</pose>
        </inertial>
        <collision name='box_collision'>
          <geometry>
            <box>
              <size>10 0.1 1</size>
            </box>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='box_visual'>
          <geometry>
            <box>
              <size>10 0.1 1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.3 0.3 0.3 1</ambient>
            <diffuse>0.7 0.7 0.7 1</diffuse>
            <specular>1 1 1 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <enable_wind>false</enable_wind>
      </link>
      <static>true</static>
      <self_collide>false</self_collide>
    </model>
    <model name='box_2'>
      <pose>0.00012 2.51517 0.499999 0 -0 -0.00303</pose>
      <link name='box_link'>
        <inertial>
          <inertia>
            <ixx>16.666</ixx>
            <ixy>0</ixy>
            <ixz>0</ixz>
            <iyy>16.666</iyy>
            <iyz>0</iyz>
            <izz>16.666</izz>
          </inertia>
          <mass>100</mass>
          <pose>0 0 0 0 -0 0</pose>
        </inertial>
        <collision name='box_collision'>
          <geometry>
            <box>
              <size>10 0.1 1</size>
            </box>
          </geometry>
          <surface>
            <friction>
              <ode/>
            </friction>
            <bounce/>
            <contact/>
          </surface>
        </collision>
        <visual name='box_visual'>
          <geometry>
            <box>
              <size>10 0.1 1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.3 0.3 0.3 1</ambient>
            <diffuse>0.7 0.7 0.7 1</diffuse>
            <specular>1 1 1 1</specular>
          </material>
        </visual>
        <pose>0 0 0 0 -0 0</pose>
        <enable_wind>false</enable_wind>
      </link>
      <static>true</static>
      <self_collide>false</self_collide>
    </model>
    <!-- Jazzy 迁移说明开始:下面是本机 file:// 模型目录,ign_models 只是文件夹名,不属于 Gazebo API,因此保留不改。 -->
    <include>
      <uri>file:///home/tungchiahui/UserFolder/MySource/ROS_WS/ign_models/bed</uri>
      <name>Bed</name>
      <pose>2.11564 -0.080355 0 0 -0 0</pose>
    </include>
    <include>
      <uri>file:///home/tungchiahui/UserFolder/MySource/ROS_WS/ign_models/WhiteCabinet</uri>
      <name>WhiteCabinet</name>
      <pose>-1.69505 1.99357 0 0 0 -1.59625</pose>
    </include>
    <include>
      <uri>file:///home/tungchiahui/UserFolder/MySource/ROS_WS/ign_models/WhiteCabinet</uri>
      <name>WhiteCabinet_1</name>
      <pose>-1.71706 1.10132 0 0 0 -1.59625</pose>
    </include>
    <include>
      <uri>file:///home/tungchiahui/UserFolder/MySource/ROS_WS/ign_models/WhiteCabinet</uri>
      <name>WhiteCabinet_2</name>
      <pose>-1.74096 0.206747 0 0 0 -1.59625</pose>
    </include>
    <include>
      <uri>file:///home/tungchiahui/UserFolder/MySource/ROS_WS/ign_models/WhiteCabinet</uri>
      <name>WhiteCabinet_3</name>
      <pose>-1.76908 -0.654114 0 0 0 -1.59625</pose>
    </include>
    <include>
      <uri>file:///home/tungchiahui/UserFolder/MySource/ROS_WS/ign_models/WhiteCabinet</uri>
      <name>WhiteCabinet_4</name>
      <pose>-3.54096 -1.97391 0 0 0 -1.59625</pose>
    </include>
    <include>
      <uri>file:///home/tungchiahui/UserFolder/MySource/ROS_WS/ign_models/WhiteCabinet</uri>
      <name>WhiteCabinet_4_1</name>
      <pose>-3.51784 -1.10026 0 0 0 -1.59625</pose>
    </include>
    <include>
      <uri>file:///home/tungchiahui/UserFolder/MySource/ROS_WS/ign_models/WhiteCabinet</uri>
      <name>WhiteCabinet_4_1_1</name>
      <pose>-3.44853 2.02068 0 0 0 -1.59625</pose>
    </include>
    <include>
      <uri>file:///home/tungchiahui/UserFolder/MySource/ROS_WS/ign_models/WhiteCabinet</uri>
      <name>WhiteCabinet_4_1_1_1</name>
      <pose>-3.499 -0.197449 0 0 0 -1.59625</pose>
    </include>
    <include>
      <uri>file:///home/tungchiahui/UserFolder/MySource/ROS_WS/ign_models/WhiteCabinet</uri>
      <name>WhiteCabinet_3_1</name>
      <pose>-0.465147 -0.678914 0 0 0 -1.59625</pose>
    </include>
    <include>
      <uri>file:///home/tungchiahui/UserFolder/MySource/ROS_WS/ign_models/WhiteCabinet</uri>
      <name>WhiteCabinet_3_1_1</name>
      <pose>-1.12325 -0.88551 0 0 0 -0.016899</pose>
    </include>
    <include>
      <uri>file:///home/tungchiahui/UserFolder/MySource/ROS_WS/ign_models/WhiteCabinet</uri>
      <name>WhiteCabinet_3_1_2</name>
      <pose>-0.445223 0.182127 0 0 0 -1.59625</pose>
    </include>
    <include>
      <uri>file:///home/tungchiahui/UserFolder/MySource/ROS_WS/ign_models/WhiteCabinet</uri>
      <name>WhiteCabinet_3_1_2_1</name>
      <pose>-0.422109 1.02215 0 0 0 -1.59625</pose>
    </include>
    <!-- Jazzy 迁移说明结束:如果以后重命名本机模型目录,需要同步修改这些 file:// 路径。 -->
    <light name='sun' type='directional'>
      <pose>0 0 10 0 -0 0</pose>
      <cast_shadows>true</cast_shadows>
      <intensity>1</intensity>
      <direction>-0.5 0.1 -0.9</direction>
      <diffuse>0.8 0.8 0.8 1</diffuse>
      <specular>0.2 0.2 0.2 1</specular>
      <attenuation>
        <range>1000</range>
        <linear>0.01</linear>
        <constant>0.90000000000000002</constant>
        <quadratic>0.001</quadratic>
      </attenuation>
      <spot>
        <inner_angle>0</inner_angle>
        <outer_angle>0</outer_angle>
        <falloff>0</falloff>
      </spot>
    </light>
  </world>
</sdf>

構建

終端中進入當前工作空間,編譯功能包:

colcon build  --packages-select demo_gazebo_sim

執行

終端中進入當前工作空間,呼叫如下指令執行launch檔案:

. install/setup.bash
ros2 launch demo_gazebo_sim gazebo_sim_world.launch.py

執行結果如下圖所示。

1.7.Gazebo新增機器人

Gazebo中可以直接建立機器人模型,或者也可以載入ROS2中URDF格式的機器人模型,此處我們使用後者(也可以選擇用自己的urdf小車,但是注意修改launch的路徑)。

咱們可以用之前建立的cpp06_urdf裡的模型。

準備機器人模型功能包

在工作空間中輸入以下命令從而去建立功能包mycar_description

cd ./src
ros2 pkg create mycar_description --build-type ament_cmake
cd ..

在功能包下建立以下資料夾launch,urdf,rviz,meshes

修改以下配置檔案:

1. package.xml: 在 package.xml 中需要手動新增一些執行時依賴,核心內容如下:

<exec_depend>rviz2</exec_depend>
<exec_depend>xacro</exec_depend>
<exec_depend>robot_state_publisher</exec_depend>
<exec_depend>joint_state_publisher</exec_depend>
<exec_depend>ros2launch</exec_depend>

2.CMakeLists.txt 在功能包下,新建了若干目錄,需要為這些目錄配置安裝路徑,核心內容如下:

install(
  DIRECTORY launch urdf rviz meshes
  DESTINATION share/${PROJECT_NAME}  
)

把之前cpp06_urdf功能包中的urdf資料夾裡的內容覆蓋到mycar_description功能包中的urdf資料夾中。

ign_models中新建mycar_description目錄,並將功能包mycar_description下的mesh目錄複製進ign_models中的mycar_description目錄。(此時meshes裡是空的,很正常,你後續用到其他模型的時候可能才會有.STL檔案)

新增Gazebo需要的sdf特有的標籤

先修改car_base.urdf.xacro,新增上各種慣性,碰撞,摩擦等等:

<robot xmlns:xacro="http://wiki.ros.org/xacro">
    <!-- PI 值 -->
    <xacro:property name="PI" value="3.1416"/>
    <!-- 定义车辆参数 -->
    <!-- 车体长宽高 -->
    <xacro:property name="base_link_x" value="0.2"/>
    <xacro:property name="base_link_y" value="0.12"/>
    <xacro:property name="base_link_z" value="0.07"/>
    <!-- 离地间距 -->
    <xacro:property name="distance" value="0.015"/>
    <!-- 车轮半径 宽度 -->
    <xacro:property name="wheel_radius" value="0.025"/>
    <xacro:property name="wheel_length" value="0.02"/>

    <!-- Gazebo 新增开始:Gazebo 物理参数,用于给 link 生成 inertial -->
    <xacro:property name="base_footprint_radius" value="0.001"/>
    <xacro:property name="base_footprint_mass" value="0.001"/>
    <xacro:property name="base_link_mass" value="1.0"/>
    <xacro:property name="wheel_mass" value="0.05"/>
    <xacro:property name="wheel_mu1" value="1.0"/>
    <xacro:property name="wheel_mu2" value="0.05"/>
    <!-- Gazebo 新增结束:Gazebo 物理参数 -->

    <!-- Gazebo 新增开始:球体惯性宏,给 base_footprint 使用 -->
    <xacro:macro name="sphere_inertial" params="mass radius">
        <inertial>
            <mass value="${mass}"/>
            <inertia
                ixx="${2.0 / 5.0 * mass * radius * radius}" ixy="0.0" ixz="0.0"
                iyy="${2.0 / 5.0 * mass * radius * radius}" iyz="0.0"
                izz="${2.0 / 5.0 * mass * radius * radius}"/>
        </inertial>
    </xacro:macro>
    <!-- Gazebo 新增结束:球体惯性宏 -->

    <!-- Gazebo 新增开始:长方体惯性宏,给 base_link 使用 -->
    <xacro:macro name="box_inertial" params="mass x y z">
        <inertial>
            <mass value="${mass}"/>
            <inertia
                ixx="${mass / 12.0 * (y * y + z * z)}" ixy="0.0" ixz="0.0"
                iyy="${mass / 12.0 * (x * x + z * z)}" iyz="0.0"
                izz="${mass / 12.0 * (x * x + y * y)}"/>
        </inertial>
    </xacro:macro>
    <!-- Gazebo 新增结束:长方体惯性宏 -->

    <!-- Gazebo 新增开始:圆柱车轮惯性宏,给四个轮子使用 -->
    <xacro:macro name="wheel_inertial" params="mass radius length">
        <inertial>
            <mass value="${mass}"/>
            <inertia
                ixx="${mass / 12.0 * (3.0 * radius * radius + length * length)}" ixy="0.0" ixz="0.0"
                iyy="${mass / 2.0 * radius * radius}" iyz="0.0"
                izz="${mass / 12.0 * (3.0 * radius * radius + length * length)}"/>
        </inertial>
    </xacro:macro>
    <!-- Gazebo 新增结束:圆柱车轮惯性宏 -->

    <!-- 定义颜色 -->
    <material name="yellow">
        <color rgba="0.7 0.7 0 0.8" />
    </material>
    <material name="red">
        <color rgba="0.8 0.1 0.1 0.8" />
    </material>
    <material name="gray">
        <color rgba="0.2 0.2 0.2 0.95" />
      </material>
    <!-- 定义 base_footprint -->
    <link name="base_footprint">
        <visual>
            <geometry>
                <!-- Codex 修改:把原来的固定 0.001 改成上面新增的参数 -->
                <sphere radius="${base_footprint_radius}"/>
            </geometry>
        </visual>
        <!-- Gazebo 新增:base_footprint 的惯性,避免 Gazebo 转 SDF 时丢弃根 link -->
        <xacro:sphere_inertial mass="${base_footprint_mass}" radius="${base_footprint_radius}"/>
    </link>

    <!-- 定义 base_link -->
    <link name="base_link">
        <visual>
            <!-- 形状 -->
            <geometry>
                <box size="${base_link_x} ${base_link_y} ${base_link_z}" />
            </geometry>
            <origin xyz="0 0 0" rpy="0 0 0" />
            <material name="yellow"/>
        </visual>
        <!-- Gazebo 新增开始:base_link 碰撞体,Gazebo 物理仿真需要 collision -->
        <collision>
            <geometry>
                <box size="${base_link_x} ${base_link_y} ${base_link_z}" />
            </geometry>
            <origin xyz="0 0 0" rpy="0 0 0" />
        </collision>
        <!-- Gazebo 新增结束:base_link 碰撞体 -->
        <!-- Gazebo 新增:base_link 惯性,Gazebo 物理仿真需要 inertial -->
        <xacro:box_inertial mass="${base_link_mass}" x="${base_link_x}" y="${base_link_y}" z="${base_link_z}"/>
    </link>
    <joint name="baselink2basefootprint" type="fixed">
        <parent link="base_footprint"/>
        <child link="base_link"/>
        <origin xyz="0.0 0.0 ${distance + base_link_z / 2}"/>
    </joint>
    <!-- 车轮宏定义 -->
    <xacro:macro name="wheel_func" params="wheel_name is_front is_left" >
        <link name="${wheel_name}_wheel">
            <visual>
                <geometry>
                    <cylinder radius="${wheel_radius}" length="${wheel_length}" />
                </geometry>
                <origin xyz="0 0 0" rpy="${PI / 2} 0 0" />
                <material name="gray"/>
            </visual>
            <!-- Gazebo 新增开始:车轮碰撞体,四个轮子都会通过这个宏生成 -->
            <collision>
                <geometry>
                    <cylinder radius="${wheel_radius}" length="${wheel_length}" />
                </geometry>
                <origin xyz="0 0 0" rpy="${PI / 2} 0 0" />
            </collision>
            <!-- Gazebo 新增结束:车轮碰撞体 -->
            <!-- Gazebo 新增:车轮惯性,四个轮子都会通过这个宏生成 -->
            <xacro:wheel_inertial mass="${wheel_mass}" radius="${wheel_radius}" length="${wheel_length}"/>
        </link>
        <joint name="${wheel_name}2baselink" type="continuous">
            <parent link="base_link"  />
            <child link="${wheel_name}_wheel" />
            <origin xyz="${(base_link_x / 2 - wheel_radius) * is_front} ${base_link_y / 2 * is_left} ${(base_link_z / 2 + distance - wheel_radius) * -1}" rpy="0 0 0" />
            <axis xyz="0 1 0" />
        </joint>
        <!-- Gazebo 新增:四轮差速原地转弯需要轮胎横向滑移;降低 mu2 可以减少 Gazebo 中的卡顿和抖动 -->
        <gazebo reference="${wheel_name}_wheel">
            <mu1>${wheel_mu1}</mu1>
            <mu2>${wheel_mu2}</mu2>
            <fdir1>1 0 0</fdir1>
        </gazebo>
    </xacro:macro>
    <!-- 车轮宏调用 -->
    <xacro:wheel_func wheel_name="left_front" is_front="1" is_left="1" />
    <xacro:wheel_func wheel_name="left_back" is_front="-1" is_left="1" />
    <xacro:wheel_func wheel_name="right_front" is_front="1" is_left="-1" />
    <xacro:wheel_func wheel_name="right_back" is_front="-1" is_left="-1" />
</robot>

修改car.urdf.xacro

<robot name="car" xmlns:xacro="http://wiki.ros.org/xacro">
    <xacro:include filename="car_base.urdf.xacro"/>
    <xacro:include filename="car_camera.urdf.xacro"/>
    <xacro:include filename="car_laser.urdf.xacro"/>
</robot>

機器人模型功能包下新建launch檔案

新建launch檔案mycar_desc_sim.launch.py,並輸入如下內容:

from launch import LaunchDescription
from launch_ros.actions import Node
import os
from ament_index_python.packages import get_package_share_directory
from launch_ros.parameter_descriptions import ParameterValue
from launch.substitutions import Command,LaunchConfiguration
from launch.actions import DeclareLaunchArgument

def generate_launch_description():

    mycar_description = get_package_share_directory("mycar_description")
    default_model_path = os.path.join(mycar_description,"urdf/xacro","car.urdf.xacro")
    model = DeclareLaunchArgument(name="model", default_value=default_model_path)

    # 加载机器人模型
    # 启动 robot_state_publisher 节点并以参数方式加载 urdf 文件
    robot_description = ParameterValue(Command(["xacro ",LaunchConfiguration("model")]))
    robot_state_publisher = Node(
        package="robot_state_publisher",
        executable="robot_state_publisher",
        parameters=[{"robot_description": robot_description}]
    )

    return LaunchDescription([
        model,
        robot_state_publisher,
    ])

較之於以往該檔案缺少了joint_state_publisher節點,該節點作用是釋出活動關節狀態,這一功能後續由ignition實現。

新增機器人模型

建立gazebo_sim_robot_world.launch.py檔案,包含機器人模型的釋出檔案並在Gazebo中生成機器人模型,修改後的程式碼如下:

import os

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import Node

def generate_launch_description():

    this_pkg = get_package_share_directory("demo_gazebo_sim")
    mycar_desc_pkg = get_package_share_directory("mycar_description")
    pkg_ros_gz_sim = get_package_share_directory("ros_gz_sim")
    world_file = os.path.join(this_pkg,"world","house.sdf")

    gz_sim = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(pkg_ros_gz_sim, "launch", "gz_sim.launch.py")),
        launch_arguments={
            "gz_args": "-r " + world_file
        }.items(),
    )
    mycar_desc = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(mycar_desc_pkg,"launch","mycar_desc_sim.launch.py")
        )
    )
    spawn = Node(package="ros_gz_sim", executable="create",
                arguments=[
                "-name", "mycar",
                "-x", "0",
                "-z", "0.01", #设置为0,可能会陷进地里
                "-y", "0",
                "-R", "0",
                "-P", "0",
                "-Y", "1.57", # Yaw航向角逆时针旋转90度
                "-topic", "/robot_description"],
            output="screen")

    return LaunchDescription([
        gz_sim,
        spawn,
        mycar_desc,
    ])

構建

終端中進入當前工作空間,編譯功能包:

colcon build --packages-select mycar_description demo_gazebo_sim

執行

終端中進入當前工作空間,呼叫如下指令執行launch檔案:(執行起來有問題的話,你只要學過urdf怎麼跑,應該擁有自我尋找錯誤的能力了,自己找吧)

. install/setup.bash

ros2 launch demo_gazebo_sim gazebo_sim_robot_world.launch.py

執行結果如下圖所示。

1.8.Gz Sim運動控制器

本節將介紹如何讓你的機器人動起來。

原理就是給urdf或xacro等新增標籤:

http://sdformat.org/tutorials?tut=sdformat_urdf_extensions&cat=specification&

https://gazebosim.org/api/plugin/2/index.html

安裝庫: 官方教程: 進入https://gazebosim.org/docs/harmonic/library_reference_nav/ 點選外掛進入https://gazebosim.org/api/plugin/2/installation.html

sudo apt-get update
sudo apt-get install lsb-release
sudo sh -c 'echo "deb http://packages.osrfoundation.org/gazebo/ubuntu-stable `lsb_release -cs` main" > /etc/apt/sources.list.d/gazebo-stable.list'
wget http://packages.osrfoundation.org/gazebo.key -O - | sudo apt-key add -
sudo apt-get update
sudo apt install libgz-plugin2-dev

利用外掛去讓小車動,比如有兩輪差速外掛,四輪麥輪外掛等等

同時該外掛還提供了一些可以控制輸出的選項,因為是模擬,所以還要告訴外掛輪子對應的joint名稱等資訊,這樣就有了下面這個參數列格:

配置項 含義
ros ros相關配置,包含名稱空間和話題重對映等
update_rate 資料更新速率
left_joint 左輪關節名稱
right_joint 右輪關節名稱
wheel_separation 左右輪子的間距
wheel_diameter 輪子的直徑
max_wheel_torque 輪子最大的力矩
max_wheel_acceleration 輪子最大的加速度
publish_odom 是否釋出里程計
publish_odom_tf 是否釋出里程計的tf開關
publish_wheel_tf 是否釋出輪子的tf資料開關
odometry_frame 里程計的framed ID,最終體現在話題和TF上
robot_base_frame 機器人的基礎frame的ID

修改URDF檔案

2輪差速車的話,在<robot>根標籤下新增如下程式碼:


  <gazebo>
      <plugin filename="gz-sim-diff-drive-system"
        name="gz::sim::systems::DiffDrive">
        <left_joint>left_joint</left_joint>
        <right_joint>right_joint</right_joint>
        <wheel_separation>0.2097</wheel_separation>
        <wheel_radius>0.03415</wheel_radius>
        <odom_publish_frequency>10</odom_publish_frequency>
        <frame_id>odom</frame_id>
        <child_frame_id>base_footprint</child_frame_id>
        <topic>/cmd_vel</topic>
        <max_linear_acceleration>10</max_linear_acceleration>
        <min_linear_acceleration>-10</min_linear_acceleration>
        <max_angular_acceleration>10</max_angular_acceleration>
        <min_angular_acceleration>-10</min_angular_acceleration>
        <max_linear_velocity>0.5</max_linear_velocity>
        <min_linear_velocity>-0.5</min_linear_velocity>
        <max_angular_velocity>1</max_angular_velocity>
        <min_angular_velocity>-1</min_angular_velocity>
      </plugin>

  </gazebo>

  <gazebo>
    <plugin filename="gz-sim-joint-state-publisher-system"
      name="gz::sim::systems::JointStatePublisher">
    </plugin>
  </gazebo>

4輪差速車的話,在<robot>根標籤下新增如下程式碼:


<gazebo>
    <plugin
        filename="gz-sim-diff-drive-system"
        name="gz::sim::systems::DiffDrive">
        <left_joint>left_former_joint</left_joint>
        <left_joint>left_rear_joint</left_joint>
        <right_joint>right_former_joint</right_joint>
        <right_joint>right_rear_joint</right_joint>
        <wheel_separation>0.4</wheel_separation>
        <wheel_radius>0.0415</wheel_radius>
        <odom_publish_frequency>50</odom_publish_frequency>
        <frame_id>odom</frame_id>
        <child_frame_id>base_footprint</child_frame_id>
        <topic>/cmd_vel</topic>
        <max_linear_acceleration>10</max_linear_acceleration>
        <min_linear_acceleration>-10</min_linear_acceleration>
        <max_angular_acceleration>10</max_angular_acceleration>
        <min_angular_acceleration>-10</min_angular_acceleration>
        <max_linear_velocity>0.5</max_linear_velocity>
        <min_linear_velocity>-0.5</min_linear_velocity>
        <max_angular_velocity>1</max_angular_velocity>
        <min_angular_velocity>-1</min_angular_velocity>
      </plugin>
  </gazebo>

  <gazebo>
    <plugin filename="gz-sim-joint-state-publisher-system"
      name="gz::sim::systems::JointStatePublisher">
    </plugin>
  </gazebo>

我們這裡肯定是4輪車,我建立了一個move_control.urdf.xacro專門存放運動控制類程式碼:


<robot name="car" xmlns:xacro="http://wiki.ros.org/xacro">
    <!-- 运动控制插件 -->
    <gazebo>
        <plugin
            filename="gz-sim-diff-drive-system"
            name="gz::sim::systems::DiffDrive">
            <left_joint>left_front2baselink</left_joint>
            <left_joint>left_back2baselink</left_joint>
            <right_joint>right_front2baselink</right_joint>
            <right_joint>right_back2baselink</right_joint>
            <!-- wheel_separation轮距 是左右轮中心线之间的距离:0.06 - (-0.06) = 0.12m -->
            <wheel_separation>0.12</wheel_separation>
            <wheel_radius>0.025</wheel_radius>
            <odom_publish_frequency>50</odom_publish_frequency>
            <frame_id>odom</frame_id>
            <child_frame_id>base_footprint</child_frame_id>
            <topic>/cmd_vel</topic>
            <max_linear_acceleration>10</max_linear_acceleration>
            <min_linear_acceleration>-10</min_linear_acceleration>
            <max_angular_acceleration>10</max_angular_acceleration>
            <min_angular_acceleration>-10</min_angular_acceleration>
            <max_linear_velocity>0.5</max_linear_velocity>
            <min_linear_velocity>-0.5</min_linear_velocity>
            <max_angular_velocity>1</max_angular_velocity>
            <min_angular_velocity>-1</min_angular_velocity>
        </plugin>
    </gazebo>
    <!-- 关节状态发布 -->
    <gazebo>
        <plugin filename="gz-sim-joint-state-publisher-system"
        name="gz::sim::systems::JointStatePublisher">
        </plugin>
    </gazebo>
</robot>

修改launch檔案

修改gazebo_sim_robot_world.launch.py檔案,修改後的程式碼如下:

import os

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import Node

def generate_launch_description():

    this_pkg = get_package_share_directory("demo_gazebo_sim")
    mycar_desc_pkg = get_package_share_directory("mycar_description")
    pkg_ros_gz_sim = get_package_share_directory("ros_gz_sim")
    world_file = os.path.join(this_pkg,"world","house.sdf")

    gz_sim = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(pkg_ros_gz_sim, "launch", "gz_sim.launch.py")),
        launch_arguments={
            "gz_args": "-r " + world_file
        }.items(),
    )
    mycar_desc = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(mycar_desc_pkg,"launch","mycar_desc_sim.launch.py")
        )
    )
    spawn = Node(package="ros_gz_sim", executable="create",
                arguments=[
                "-name", "mycar",
                "-x", "-4",
                "-z", "0.01", #设置为0,可能会陷进地里
                "-y", "0",
                "-R", "0",
                "-P", "0",
                "-Y", "1.57",   #逆时针旋转90度
                "-topic", "/robot_description"],
            output="screen")

    # Bridge
    bridge = Node(
        package="ros_gz_bridge",
        executable="parameter_bridge",
        arguments=["/cmd_vel@geometry_msgs/msg/Twist@gz.msgs.Twist",
                   "/model/mycar/odometry@nav_msgs/msg/Odometry@gz.msgs.Odometry",
                   "/model/mycar/tf@tf2_msgs/msg/TFMessage[gz.msgs.Pose_V",
                   "/clock@rosgraph_msgs/msg/Clock[gz.msgs.Clock",
                   "/world/empty/model/mycar/joint_state@sensor_msgs/msg/JointState[gz.msgs.Model",
                   ],
        parameters=[{"qos_overrides./model/mycar.subscriber.reliability": "reliable"}],
        remappings=[
                ("/model/mycar/tf", "/tf"),
                ("/world/empty/model/mycar/joint_state","joint_states"),
                ("/model/mycar/odometry","/odom")
            ],
        output="screen"
    )

    return LaunchDescription([
        gz_sim,
        spawn,
        mycar_desc,
        bridge
    ])

構建

終端中進入當前工作空間,編譯功能包:

colcon build --packages-select mycar_description demo_gazebo_sim

執行

終端中進入當前工作空間,呼叫如下指令執行launch檔案:

. install/setup.bash
ros2 launch demo_gazebo_sim gazebo_sim_robot_world.launch.py

再啟動鍵盤控制節點,就可以控制機器人運動了。

ros2 run teleop_twist_keyboard teleop_twist_keyboard

還可以啟動rviz2,以檢視里程計訊息以及座標變換。終端中進入當前工作空間,呼叫如下指令執行launch檔案:

啟動rviz2

. install/setup.bash
rviz2

RVIZ2軟體配置如下圖所示:

1.9.Gz Sim模擬之感測器

本節將介紹如何為模擬機器人新增雷達、相機等感測器。 新增感測器外掛

在進行感測器模擬之前,需要先新增一個名為gz-sim-sensors-system的外掛,開啟urdf檔案,在<robot>根標籤內新增如下程式碼: (建議建立一個gazebo_sensor.urdf.xacro專門存放)


<gazebo>
    <plugin
      filename="gz-sim-sensors-system"
      name="gz::sim::systems::Sensors">
      <render_engine>ogre2</render_engine>
    </plugin>
  </gazebo>

gz-sim-sensors-system是Gazebo模擬環境的外掛,提供感測器模型和相關功能,用於建立、模擬和測試各種感測器裝置。它包含常見感測器模型,如攝像頭、雷射雷達等。

新增各種感測器

(注意,你的模型一定要有以下幾個感測器的模型)

雷達的模型不需要collision,請刪掉,否則會擋雷射射出。


  <gazebo reference="laser">
    <sensor name='gpu_lidar' type='gpu_lidar'>
      <topic>scan</topic>
      <update_rate>30</update_rate>
      <lidar>
        <scan>
          <horizontal>
            <samples>640</samples>
            <resolution>1</resolution>
            <min_angle>-3.1415926</min_angle>
            <max_angle>3.1415926</max_angle>
          </horizontal>
          <vertical>
            <samples>16</samples>
            <resolution>1</resolution>
            <min_angle>-0.261799</min_angle>
            <max_angle>0.261799</max_angle>
          </vertical>
        </scan>
        <range>
          <min>0.08</min>
          <max>10.0</max>
          <resolution>0.01</resolution>
        </range>
      </lidar>
      <visualize>true</visualize>
      <gz_frame_id>laser<gz_frame_id>
      <pose relative_to="laser">0 0 0 0 0 0</pose>
    </sensor>
  </gazebo>

  <gazebo reference="camera" >
    <sensor name="cam_link" type="camera">
      <update_rate>10.0</update_rate>
      <always_on>true</always_on>
      <gz_frame_id>camera</gz_frame_id>
      <pose relative_to="camera">0 0 0 0 0 0</pose>
      <pose>0 0 0 0 0 0</pose>
      <topic>/image_raw</topic>
      <camera name="my_camera">
        <horizontal_fov>1.3962634</horizontal_fov>
        <image>
           <width>600</width>
           <height>600</height>
           <format>R8G8B8</format>
        </image>
        <clip>
          <near>0.02</near>
          <far>300</far>
        </clip>
      </camera>
    </sensor>
  </gazebo>

  <gazebo reference="camera">
    <sensor name="depth_camera" type="depth_camera">
          <update_rate>10</update_rate>
          <topic>depth_camera</topic>
          <camera>
            <horizontal_fov>1.05</horizontal_fov>
            <image>
              <width>256</width>
              <height>256</height>
              <format>R_FLOAT32</format>
            </image>
            <clip>
              <near>0.1</near>
              <far>10.0</far>
            </clip>
          </camera>
          <always_on>1</always_on>
          <gz_frame_id>camera</gz_frame_id>
          <pose relative_to="camera">0 0 0 0 0 0</pose>
      </sensor>
  </gazebo>

從官網找到的imu感測器的


    <gazebo>
        <plugin filename="gz-sim-imu-system"
                name="gz::sim::systems::Imu">
        </plugin>
    </gazebo>

    <gazebo reference="base_link">
        <sensor name="imu_sensor" type="imu">
            <always_on>1</always_on>
            <update_rate>30</update_rate>
            <visualize>true</visualize>
            <topic>imu</topic>
        </sensor>
    </gazebo>

可以用ign topic -e -t /imu測試gazebo是否釋出了話題,後面再用gazebo_bridge把話題給ROS2就行了。

預設情況下,rviz2沒有顯示imu訊息的外掛,需要自行安裝相關外掛,具體安裝指令如下:

sudo apt install ros-${ROS_DISTRO}-imu-tools

sudo apt install ros-jazzy-imu-tools

SolidWorks自動生成的模型可能翻轉了laser_joint,請你修改回正,這樣可能rivz2就有激光了,然後修改一下視覺化的模型,讓模型正常,不要給碰撞,不然可能會遮擋雷射。

修改gazebo_sim_robot_world.launch.py檔案,修改後的程式碼如下:

import os

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import Node

def generate_launch_description():

    this_pkg = get_package_share_directory("demo_gazebo_sim")
    mycar_desc_pkg = get_package_share_directory("mycar_description")
    pkg_ros_gz_sim = get_package_share_directory("ros_gz_sim")
    world_file = os.path.join(this_pkg,"world","house.sdf")

    gz_sim = IncludeLaunchDescription(
    PythonLaunchDescriptionSource(
        os.path.join(pkg_ros_gz_sim, "launch", "gz_sim.launch.py")),
        launch_arguments={
        "gz_args": "-r " + world_file
        }.items(),
    )
    mycar_desc = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(mycar_desc_pkg,"launch","mycar_desc_sim.launch.py")
        )
    )
    spawn = Node(package="ros_gz_sim", executable="create",
        arguments=[
            "-name", "mycar",
            "-x", "-4",
            "-z", "0.01", #设置为0,可能会陷进地里
            "-y", "0",
            "-R", "0",
            "-P", "0",
            "-Y", "1.57",   #逆时针旋转90度
            "-topic", "/robot_description"],
        output="screen")

    # Bridge
    bridge = Node(
        package="ros_gz_bridge",
        executable="parameter_bridge",
        arguments=["/cmd_vel@geometry_msgs/msg/Twist@gz.msgs.Twist",
            "/model/mycar/odometry@nav_msgs/msg/Odometry@gz.msgs.Odometry",
            "/model/mycar/tf@tf2_msgs/msg/TFMessage[gz.msgs.Pose_V",
            "/clock@rosgraph_msgs/msg/Clock[gz.msgs.Clock",
            "/world/empty/model/mycar/joint_state@sensor_msgs/msg/JointState[gz.msgs.Model",
            "/scan@sensor_msgs/msg/LaserScan@gz.msgs.LaserScan",
            "/scan/points@sensor_msgs/msg/PointCloud2@gz.msgs.PointCloudPacked",
            "/image_raw@sensor_msgs/msg/Image@gz.msgs.Image",
            "/camera_info@sensor_msgs/msg/CameraInfo@gz.msgs.CameraInfo",
            "/depth_camera@sensor_msgs/msg/Image@gz.msgs.Image",
            "/imu@sensor_msgs/msg/Imu[gz.msgs.IMU",
            "/imu/angular_velocity@geometry_msgs/msg/Vector3[gz.msgs.Vector3d"
        ],
        parameters=[{"qos_overrides./model/mycar.subscriber.reliability": "reliable"}],
        remappings=[
            ("/model/mycar/tf", "/tf"),
            ("/world/empty/model/mycar/joint_state","joint_states"),
            ("/model/mycar/odometry","/odom")
        ],
        output="screen"
    )

    return LaunchDescription([
        gz_sim,
        spawn,
        mycar_desc,
        bridge
    ])

構建

終端中進入當前工作空間,編譯功能包:

colcon build --packages-select mycar_description demo_gazebo_sim

執行

終端中進入當前工作空間,呼叫如下指令執行launch檔案:

. install/setup.bash
ros2 launch demo_gazebo_sim gazebo_sim_robot_world.launch.py

這裡gz_frame_id可能會被sdf1.10規範誤報警告,不用管這個警告。實際上,這個版本的Gazebo會讀取這個自定義標籤gz_frame_id。 在sdf1.12上貌似被改成了frame_id

你可以通過ros2 topic echo /scan --once | grep frame_id來檢視scan的frame_id是否被修改成功了,修改成功會變成laser,否則會變成gazebo預設的mycar/base_footprint/gpu_lidar

問題解決參考:https://github.com/gazebosim/gz-sensors/issues/306

再啟動鍵盤控制節點,就可以控制機器人運動了。

還可以啟動rviz2,以檢視機器人釋出的諸多資料。終端中進入當前工作空間,呼叫如下指令執行launch檔案:

. install/setup.bash
rviz2

(把上面的全部復現,才能夠進行下一章導航,下一章導航依然基於模擬)