Wiki
Gezebo Classic
Existing English translations were reused. 8 current source blocks remain in Simplified Chinese, without triggering paid translation.
1.Gazebo(Gazebo Classic)
This is the old version of Gazebo, available only on ROS1 and ROS2 Humble. (However, ROS2 does not recommend using the old version of Gazebo; the new version of Gazebo is recommended instead.)
ROS2The old Gazebo is only available in the Humble release and has been removed in Jazzy and later releases.
(If you don't want to learn the old version of Gazebo, just skip directly to Ignition Gazebo in the next section.)
The difference between these two isn't significant for us; it's just that one has more tutorials and the other has fewer. You can directly learn the old version of Gazebo and use it the same way.
(Beginners can also learn only Gazebo Classic, which has more tutorials, to avoid some detours.)
Gezebo Official Website:

If you want to migrate from the old version of Gazebo to the new version of Gazebo, then please see the official tutorial below:
Migrating from Gazebo Classic to Ign Gazebo (Gazebo Fortress) in ROS2 Humble:
https://gazebosim.org/docs/fortress/gazebo_classic_migration/
Migrating from Gazebo Classic to Gazebo Sim (Gazebo Harmonic) in ROS2 Jazzy:
Versions after Jazzy should not change much, and you can temporarily refer to the tutorial below (or go to the official website to find the tutorial for the corresponding version):
https://gazebosim.org/docs/harmonic/gazebo_classic_migration/
1.1.Gazebo Classic Installation and Running
ROS2Only humble has the old version of Gazebo
sudo apt install ros-humble-gazebo-ros ros-humble-gazebo-ros-pkgs
安装完成后,我们就可以通过下面的命令行来启动gazebo并加载ros2插件。
gazebo --verbose -s libgazebo_ros_init.so -s libgazebo_ros_factory.so
If you see the following log and Gazebo interface and there are no major issues, it means it succeeded. (Some warnings say that Gazebo Classic has been deprecated and encourage using the new Ignition Gazebo; just ignore these warnings.)
root@Dell-G15-5511:/home/tungchiahui/UserFolder/MySource/ROS_WS/ROS2_WS/6.ws_simulations$ gazebo --verbose -s libgazebo_ros_init.so -s libgazebo_ros_factory.so
Gazebo multi-robot simulator, version 11.10.2
Copyright (C) 2012 Open Source Robotics Foundation.
Released under the Apache 2 License.
http://gazebosim.org
[Msg] Waiting for master.
Gazebo multi-robot simulator, version 11.10.2
Copyright (C) 2012 Open Source Robotics Foundation.
Released under the Apache 2 License.
http://gazebosim.org
[Wrn] [gazebo_ros_init.cpp:178]
# # ####### ####### ### ##### #######
## # # # # # # # #
# # # # # # # # #
# # # # # # # # #####
# # # # # # # # #
# ## # # # # # # #
# # ####### # ### ##### #######
This version of Gazebo, now called Gazebo classic, reaches end-of-life
in January 2025. Users are highly encouraged to migrate to the new Gazebo
using our migration guides (https://gazebosim.org/docs/latest/gazebo_classic_migration?utm_source=gazebo_ros_pkgs&utm_medium=cli)
[Msg] Waiting for master.
[Msg] Connected to gazebo master @ http://127.0.0.1:11345
[Msg] Publicized address: 192.168.31.60
[Msg] Loading world file [/usr/share/gazebo-11/worlds/empty.world]
XDG_RUNTIME_DIR (/run/user/1000) is not owned by us (uid 0), but by uid 1000! (This could e.g. happen if you try to connect to a non-root PulseAudio as a root user, over the native protocol. Don't do that.)
ALSA lib pcm_dmix.c:1032:(snd_pcm_dmix_open) unable to open slave
AL lib: (EE) ALCplaybackAlsa_open: Could not open playback device 'default': No such file or directory
[Err] [OpenAL.cc:84] Unable to open audio device[default]
Audio will be disabled.
[Msg] Connected to gazebo master @ http://127.0.0.1:11345
[Msg] Publicized address: 192.168.31.60
[Wrn] [GuiIface.cc:298] Couldn't locate specified .ini. Creating file at "/root/.gazebo/gui.ini"
[Wrn] [GuiIface.cc:120] QStandardPaths: runtime directory '/run/user/1000' is not owned by UID 0, but a directory permissions 0700 owned by UID 1000 GID 1000
[Wrn] [Event.cc:61] Warning: Deleting a connection right after creation. Make sure to save the ConnectionPtr from a Connect call
libcurl: (35) error:0A000126:SSL routines::unexpected eof while reading
[Wrn] [ModelDatabase.cc:212] Unable to connect to model database using [http://models.gazebosim.org//database.config]. Only locally installed models will be available.
[Wrn] [Event.cc:61] Warning: Deleting a connection right after creation. Make sure to save the ConnectionPtr from a Connect call

1.2.Introduction to Plugins and Node Services
Use the previous command to start Gazebo and load the gazebo_ros plugin. We use the following command to view the plugin's node and what services this node provides for us?
Node List
ros2 node list

Then let us see what services this node provides externally?
ros2 service list

Excluding the few services related to parameters, we can see three other special services:
-
/spawn_entity, used to load models into Gazebo
-
/get_model_list, used to get the model list
-
/delete_entity, used to delete models already loaded in Gazebo
We want Gazebo to display our configured robot model; simply use /spawn_entity to load it.
Next, we can request the service to load the model. Let's first show you the service's interface types.
ros2 service type /spawn_entity

ros2 interface show gazebo_msgs/srv/SpawnEntity

It can be seen that the service request content includes:
-
string name, the name of the entity to load (optional).
-
string xml, the XML description string of the entity, URDF or SDF.
-
string robot_namespace, the namespace for the generated robot and all ROS interfaces, useful for multi-robot simulation.
-
geometry_msgs/Pose initial_pose, the initial position of the robot
-
string reference_frame, the initial pose is defined relative to the frame of this entity. If kept as "empty" or "world" or "map", the gazebo world is used as the frame. If a non-existent entity is specified, an error will be returned.
1.3.Call the service to load the model
Our tutorial here uses the fishbot model from FishROS:https://github.com/fishros/fishbot/blob/navgation2/src/fishbot_description/urdf/fishbot_gazebo.urdf
<?xml version="1.0"?>
<robot name="fishbot">
<link name="base_footprint"/>
<joint name="base_joint" type="fixed">
<parent link="base_footprint"/>
<child link="base_link"/>
<origin xyz="0.0 0.0 0.076" rpy="0 0 0"/>
</joint>
<link name="base_link">
<visual>
<origin xyz="0 0 0.0" rpy="0 0 0"/>
<geometry>
<cylinder length="0.12" radius="0.10"/>
</geometry>
<material name="blue">
<color rgba="0.1 0.1 1.0 0.5" />
</material>
</visual>
<collision>
<origin xyz="0 0 0.0" rpy="0 0 0"/>
<geometry>
<cylinder length="0.12" radius="0.10"/>
</geometry>
<material name="blue">
<color rgba="0.1 0.1 1.0 0.5" />
</material>
</collision>
<inertial>
<mass value="0.2"/>
<inertia ixx="0.0122666" ixy="0" ixz="0" iyy="0.0122666" iyz="0" izz="0.02"/>
</inertial>
</link>
<link name="laser_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<cylinder length="0.02" radius="0.02"/>
</geometry>
<material name="black">
<color rgba="0.0 0.0 0.0 0.5" />
</material>
</visual>
<collision>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<cylinder length="0.02" radius="0.02"/>
</geometry>
<material name="black">
<color rgba="0.0 0.0 0.0 0.5" />
</material>
</collision>
<inertial>
<mass value="0.1"/>
<inertia ixx="0.000190416666667" ixy="0" ixz="0" iyy="0.0001904" iyz="0" izz="0.00036"/>
</inertial>
</link>
<joint name="laser_joint" type="fixed">
<parent link="base_link" />
<child link="laser_link" />
<origin xyz="0 0 0.075" />
</joint>
<link name="imu_link">
<visual>
<origin xyz="0 0 0.0" rpy="0 0 0"/>
<geometry>
<box size="0.02 0.02 0.02"/>
</geometry>
</visual>
<collision>
<origin xyz="0 0 0.0" rpy="0 0 0"/>
<geometry>
<box size="0.02 0.02 0.02"/>
</geometry>
</collision>
<inertial>
<mass value="0.1"/>
<inertia ixx="0.000190416666667" ixy="0" ixz="0" iyy="0.0001904" iyz="0" izz="0.00036"/>
</inertial>
</link>
<joint name="imu_joint" type="fixed">
<parent link="base_link" />
<child link="imu_link" />
<origin xyz="0 0 0.02" />
</joint>
<link name="left_wheel_link">
<visual>
<origin xyz="0 0 0" rpy="1.57079 0 0"/>
<geometry>
<cylinder length="0.04" radius="0.032"/>
</geometry>
<material name="black">
<color rgba="0.0 0.0 0.0 0.5" />
</material>
</visual>
<collision>
<origin xyz="0 0 0" rpy="1.57079 0 0"/>
<geometry>
<cylinder length="0.04" radius="0.032"/>
</geometry>
<material name="black">
<color rgba="0.0 0.0 0.0 0.5" />
</material>
</collision>
<inertial>
<mass value="0.2"/>
<inertia ixx="0.000190416666667" ixy="0" ixz="0" iyy="0.0001904" iyz="0" izz="0.00036"/>
</inertial>
</link>
<link name="right_wheel_link">
<visual>
<origin xyz="0 0 0" rpy="1.57079 0 0"/>
<geometry>
<cylinder length="0.04" radius="0.032"/>
</geometry>
<material name="black">
<color rgba="0.0 0.0 0.0 0.5" />
</material>
</visual>
<collision>
<origin xyz="0 0 0" rpy="1.57079 0 0"/>
<geometry>
<cylinder length="0.04" radius="0.032"/>
</geometry>
<material name="black">
<color rgba="0.0 0.0 0.0 0.5" />
</material>
</collision>
<inertial>
<mass value="0.2"/>
<inertia ixx="0.000190416666667" ixy="0" ixz="0" iyy="0.0001904" iyz="0" izz="0.00036"/>
</inertial>
</link>
<joint name="left_wheel_joint" type="continuous">
<parent link="base_link" />
<child link="left_wheel_link" />
<origin xyz="-0.02 0.10 -0.06" />
<axis xyz="0 1 0" />
</joint>
<joint name="right_wheel_joint" type="continuous">
<parent link="base_link" />
<child link="right_wheel_link" />
<origin xyz="-0.02 -0.10 -0.06" />
<axis xyz="0 1 0" />
</joint>
<link name="caster_link">
<visual>
<origin xyz="0 0 0" rpy="1.57079 0 0"/>
<geometry>
<sphere radius="0.016"/>
</geometry>
<material name="black">
<color rgba="0.0 0.0 0.0 0.5" />
</material>
</visual>
<collision>
<origin xyz="0 0 0" rpy="1.57079 0 0"/>
<geometry>
<sphere radius="0.016"/>
</geometry>
<material name="black">
<color rgba="0.0 0.0 0.0 0.5" />
</material>
</collision>
<inertial>
<mass value="0.02"/>
<inertia ixx="0.000190416666667" ixy="0" ixz="0" iyy="0.0001904" iyz="0" izz="0.00036"/>
</inertial>
</link>
<joint name="caster_joint" type="fixed">
<parent link="base_link" />
<child link="caster_link" />
<origin xyz="0.06 0.0 -0.076" />
<axis xyz="0 1 0" />
</joint>
<gazebo reference="caster_link">
<material>Gazebo/Black</material>
</gazebo>
<gazebo reference="caster_link">
<mu1 value="0.0"/>
<mu2 value="0.0"/>
<kp value="1000000.0" />
<kd value="10.0" />
</gazebo>
<gazebo>
<plugin name='diff_drive' filename='libgazebo_ros_diff_drive.so'>
<ros>
<namespace>/</namespace>
<remapping>cmd_vel:=cmd_vel</remapping>
<remapping>odom:=odom</remapping>
</ros>
<update_rate>30</update_rate>
<left_joint>left_wheel_joint</left_joint>
<right_joint>right_wheel_joint</right_joint>
<wheel_separation>0.2</wheel_separation>
<wheel_diameter>0.065</wheel_diameter>
<max_wheel_torque>20</max_wheel_torque>
<max_wheel_acceleration>1.0</max_wheel_acceleration>
<publish_odom>true</publish_odom>
<publish_odom_tf>true</publish_odom_tf>
<publish_wheel_tf>false</publish_wheel_tf>
<odometry_frame>odom</odometry_frame>
<robot_base_frame>base_footprint</robot_base_frame>
</plugin>
<plugin name="fishbot_joint_state" filename="libgazebo_ros_joint_state_publisher.so">
<ros>
<remapping>~/out:=joint_states</remapping>
</ros>
<update_rate>30</update_rate>
<joint_name>right_wheel_joint</joint_name>
<joint_name>left_wheel_joint</joint_name>
</plugin>
</gazebo>
<gazebo reference="laser_link">
<material>Gazebo/Black</material>
</gazebo>
<gazebo reference="imu_link">
<sensor name="imu_sensor" type="imu">
<plugin filename="libgazebo_ros_imu_sensor.so" name="imu_plugin">
<ros>
<namespace>/</namespace>
<remapping>~/out:=imu</remapping>
</ros>
<initial_orientation_as_reference>false</initial_orientation_as_reference>
</plugin>
<always_on>true</always_on>
<update_rate>100</update_rate>
<visualize>true</visualize>
<imu>
<angular_velocity>
<x>
<noise type="gaussian">
<mean>0.0</mean>
<stddev>2e-4</stddev>
<bias_mean>0.0000075</bias_mean>
<bias_stddev>0.0000008</bias_stddev>
</noise>
</x>
<y>
<noise type="gaussian">
<mean>0.0</mean>
<stddev>2e-4</stddev>
<bias_mean>0.0000075</bias_mean>
<bias_stddev>0.0000008</bias_stddev>
</noise>
</y>
<z>
<noise type="gaussian">
<mean>0.0</mean>
<stddev>2e-4</stddev>
<bias_mean>0.0000075</bias_mean>
<bias_stddev>0.0000008</bias_stddev>
</noise>
</z>
</angular_velocity>
<linear_acceleration>
<x>
<noise type="gaussian">
<mean>0.0</mean>
<stddev>1.7e-2</stddev>
<bias_mean>0.1</bias_mean>
<bias_stddev>0.001</bias_stddev>
</noise>
</x>
<y>
<noise type="gaussian">
<mean>0.0</mean>
<stddev>1.7e-2</stddev>
<bias_mean>0.1</bias_mean>
<bias_stddev>0.001</bias_stddev>
</noise>
</y>
<z>
<noise type="gaussian">
<mean>0.0</mean>
<stddev>1.7e-2</stddev>
<bias_mean>0.1</bias_mean>
<bias_stddev>0.001</bias_stddev>
</noise>
</z>
</linear_acceleration>
</imu>
</sensor>
</gazebo>
<gazebo reference="laser_link">
<sensor name="laser_sensor" type="ray">
<always_on>true</always_on>
<visualize>true</visualize>
<update_rate>5</update_rate>
<pose>0 0 0.075 0 0 0</pose>
<ray>
<scan>
<horizontal>
<samples>360</samples>
<resolution>1.000000</resolution>
<min_angle>0.000000</min_angle>
<max_angle>6.280000</max_angle>
</horizontal>
</scan>
<range>
<min>0.120000</min>
<max>3.5</max>
<resolution>0.015000</resolution>
</range>
<noise>
<type>gaussian</type>
<mean>0.0</mean>
<stddev>0.01</stddev>
</noise>
</ray>
<plugin name="laserscan" filename="libgazebo_ros_ray_sensor.so">
<ros>
<remapping>~/out:=scan</remapping>
</ros>
<output_type>sensor_msgs/LaserScan</output_type>
<frame_name>laser_link</frame_name>
</plugin>
</sensor>
</gazebo>
</robot>
Seeing this, are you eager to type commands to load our robot into Gazebo? Don't rush—Xiaoyu will recommend a visualization service request tool. In fact, in Chapter 6, Xiaoyu introduced a tool in the rqt toolkit called the Service Request tool.
Enter rqt in the command line, select Services->Service Caller from the plugin options, then select the /spawn_entity service from the dropdown box, and the interface below will be displayed.

Next, we copy and paste our FishBot URDF model into the XML (note that you need to delete the original ''!)

Then click Call in the upper-right corner; if successful, the screen below will be displayed.

Then you can see the factory return that the robot has been successfully manufactured and sent into Gazebo.
Now look at our Gazebo again, and a small, white robot has appeared.

Shift + left mouse button, or directly clicking the middle mouse button, both allow you to drag the view. Many juniors who have played third-person games are certainly no strangers to this kind of control, right?
1.4.Load multiple robots at different positions
You can create another fishbot (for those who will need multi-robot simulation later).
Modify the parameters in rqt, add a namespace, then modify a position so that the second robot is spawned 1 m away from the first, and then click Call.

Return success. At this point, drag and drop into Gazebo to observe. It is found that an extra robot has appeared, and its distance is exactly 1 meter along the X-axis (red), where one small grid equals one meter.

1.5.Query and delete bot
Using the rqt tool, we will make requests to the other two service interfaces.
First, query how many models are in the simulation environment.

I found three models: one is Dadi, one is fishbot, and one is fishbot_0.
Next, let's try deleting fishbot_0, select Delete Entity, enter the name fishbot_0, and use the small phone to notify the factory to recycle our fishbot No. 0.

The call succeeded. Upon observing Gazebo, the robot was found, but the person was gone.

1.6.Create workspace
This section's code refers to Fishros:https://github.com/fishros/fishbot/tree/navgation2
# 创建工作空间
mkdir -p ws_simulations/src
cd ws_simulations/src
Create a feature package
ros2 pkg create fishbot_description --build-type ament_cmake
cd fishbot_description
Then configure 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>

Then modify cmakelists.txt
install(
DIRECTORY launch urdf rviz meshes
DESTINATION share/${PROJECT_NAME}
)

Clone the fishros repository, and copy the files inside it to our directory. This is all from the previous section and unrelated to this section's learning, so just copy it directly.

Open the workspace and add asrc/fishbot_description/launchfile ingazebo.launch.pyto begin writing a launch file to load the robot model in Gazebo.
We mainly need to do two things:
- To start Gazebo, we can write the command line as a launch node.
ExecuteProcess(
cmd=['gazebo', '--verbose','-s', 'libgazebo_ros_init.so', '-s', 'libgazebo_ros_factory.so', gazebo_world_path],
output='screen')
- Above, when we loaded the robot, we directly copied the URDF in XML format for loading, which is very inconvenient. We can use gazebo_ros to provide us with a node called
spawn_entity.pynode, which supports generating the robot directly from a file path into Gazebo.
This node requires two parameters: the robot's model name and the file path to the URDF. This is simple; earlier we used package_share to concatenate the URDF path.
spawn_entity_cmd = Node(
package='gazebo_ros',
executable='spawn_entity.py',
arguments=['-entity', robot_name_in_model, '-file', urdf_model_path ], output='screen')
First load Teacher Zhao Xuzuo's template.

We first need ExecuteProcess to input terminal commands, so we need to first uncomment from launch.actions import ExecuteProcess.
To facilitate modification of the loaded robot model and URDF, we also need to use the share directory, so we must also uncomment from ament_index_python.packages import get_package_share_directory and import os.
The completed launch file is as follows:
from launch import LaunchDescription
from launch_ros.actions import Node
# 封装终端指令相关类
from launch.actions import ExecuteProcess
# from launch.substitutions import FindExecutable
# 参数声明与获取
# from launch.actions import DeclareLaunchArgument
# from launch.substitutions import LaunchConfiguration
# 文件包含相关
# from launch.actions import IncludeLaunchDescription
# from launch.launch_description_sources import PythonLaunchDescriptionSource
# 分组相关
# from launch_ros.actions import PushRosNamespace
# from launch.actions import GroupAction
# 事件相关
# from launch.event_handlers import OnProcessStart,OnProcessExit
# from launch.actions import ExecuteProcess,RegisterEventHandler,LogInfo
# 获取功能包下share目录或路径
from ament_index_python.packages import get_package_share_directory
import os
def generate_launch_description():
robot_name_in_model = 'fishbot'
package_name = 'fishbot_description'
urdf_name = "fishbot_gazebo.urdf"
pkg_share = get_package_share_directory(f"{package_name}")
urdf_model_path = os.path.join(pkg_share, f'urdf/urdf/{urdf_name}')
# Start Gazebo server
start_gazebo_cmd = ExecuteProcess(
cmd=['gazebo', '--verbose','-s', 'libgazebo_ros_init.so', '-s', 'libgazebo_ros_factory.so'],
output='screen')
# Launch the robot
spawn_entity_cmd = Node(
package='gazebo_ros',
executable='spawn_entity.py',
arguments=['-entity', robot_name_in_model, '-file', urdf_model_path ], output='screen')
return LaunchDescription([start_gazebo_cmd,spawn_entity_cmd])
Compile and run
colcon build --packages-select fishbot_description
source install/setup.bash
ros2 launch fishbot_description gazebo.launch.py

1.7.Plugin
Use the following command to view all dynamic link libraries:
ls /opt/ros/humble/lib/libgazebo_ros*

/opt/ros/humble/lib/libgazebo_ros2_control.so
/opt/ros/humble/lib/libgazebo_ros_ackermann_drive.so
/opt/ros/humble/lib/libgazebo_ros_bumper.so
/opt/ros/humble/lib/libgazebo_ros_camera.so
/opt/ros/humble/lib/libgazebo_ros_diff_drive.so
/opt/ros/humble/lib/libgazebo_ros_elevator.so
/opt/ros/humble/lib/libgazebo_ros_factory.so
/opt/ros/humble/lib/libgazebo_ros_force.so
/opt/ros/humble/lib/libgazebo_ros_force_system.so
/opt/ros/humble/lib/libgazebo_ros_ft_sensor.so
/opt/ros/humble/lib/libgazebo_ros_gps_sensor.so
/opt/ros/humble/lib/libgazebo_ros_hand_of_god.so
/opt/ros/humble/lib/libgazebo_ros_harness.so
/opt/ros/humble/lib/libgazebo_ros_imu_sensor.so
/opt/ros/humble/lib/libgazebo_ros_init.so
/opt/ros/humble/lib/libgazebo_ros_joint_pose_trajectory.so
/opt/ros/humble/lib/libgazebo_ros_joint_state_publisher.so
/opt/ros/humble/lib/libgazebo_ros_node.so
/opt/ros/humble/lib/libgazebo_ros_p3d.so
/opt/ros/humble/lib/libgazebo_ros_planar_move.so
/opt/ros/humble/lib/libgazebo_ros_projector.so
/opt/ros/humble/lib/libgazebo_ros_properties.so
/opt/ros/humble/lib/libgazebo_ros_ray_sensor.so
/opt/ros/humble/lib/libgazebo_ros_state.so
/opt/ros/humble/lib/libgazebo_ros_template.so
/opt/ros/humble/lib/libgazebo_ros_tricycle_drive.so
/opt/ros/humble/lib/libgazebo_ros_utils.so
/opt/ros/humble/lib/libgazebo_ros_vacuum_gripper.so
/opt/ros/humble/lib/libgazebo_ros_video.so
/opt/ros/humble/lib/libgazebo_ros_wheel_slip.so
1.7.1.Motion control plugin + odometry odom
In this lesson, we configure the two-wheel differential control plugin to get our robot moving.

Plugin introduction:
Gazebo is software independent of ROS that provides a rich set of APIs for external use. Gazebo plugins can be roughly divided into two types based on their purpose:
-
A plugin used for control. Through the plugin, the robot's joint motion can be controlled, including position, velocity, and force control, such as the two-wheel differential controller in this lesson.
-
A plugin used for data acquisition, such as an IMU sensor used to acquire the robot's inertia, and a LiDAR used to acquire point cloud information around the robot.
Of course, the functions of the above two types of plugins can also be written into a single plugin. The two-wheel differential drive plugin (gazebo_ros_diff_drive) is an enhanced two-in-one version. (Differential control + odom)
The two-wheel differential plugin is used to control the position changes of the robot's wheel joints. At the same time, this plugin also obtains feedback on the wheel position and velocity information. Based on the feedback position information combined with the kinematic model, the current pose of the robot (odometry) can be calculated.
The two-wheel differential controller can send the target rotational speeds of the wheels to Gazebo and obtain the actual speeds and positions from Gazebo. (Note: what is sent to Gazebo is the target speed, and what is fed back is the actual speed. Target != actual; for example, if a wheel is stuck, no matter what target speed you send, the actual speed will be 0.)
To quickly understand the functions of a system, the most direct way is to look at what the system's external inputs and outputs are. Without further ado, look at the figure below:

The figure above is a summary of the input and output information of gazebo_ros_diff_drive. It can be seen intuitively that the plugin mainly inputs control commands and mainly outputs odometry information. Next, Xiaoyu will guide you through the input and output parts respectively.
This plugin requires configuring a series of parameters as shown in the figure below:
I wonder if you still remember the introduction by Xiaoyu in Chapter 7 about the forward kinematics of a two-wheel differential drive chassis. To complete the forward and inverse solutions of the chassis and the odometry estimation, it is necessary to know the wheel diameter and spacing.
At the same time, this plugin also provides some options that can control the output. Because it is a simulation, it is also necessary to tell the plugin information such as the names of the joints corresponding to the wheels, so the following parameter table is obtained:
| Configuration Item | Meaning |
|---|---|
| ros | ROS-related configuration, including namespace and topic remapping, etc. |
| update_rate | Data update rate |
| left_joint | Left wheel joint name |
| right_joint | Right wheel joint name |
| wheel_separation | Distance between left and right wheels |
| wheel_diameter | Wheel diameter |
| max_wheel_torque | Maximum wheel torque |
| max_wheel_acceleration | Maximum wheel acceleration |
| publish_odom | Whether to publish odometry |
| publish_odom_tf | Whether to publish the odometry TF switch |
| publish_wheel_tf | Whether to publish the wheel TF data switch |
| odometry_frame | The frame ID of the odometry, which is ultimately reflected in the topic and TF |
| robot_base_frame | The ID of the robot's base frame |
控制指令:两轮差速控制器默认通过订阅话题cmd_vel来获取目标线速度和角速度。该话题的类型为:geometry_msgs/msg/Twist
This interface mainly contains linear velocity and angular velocity, both including the corresponding velocities along the x, y, and z directions of the coordinate system. (For detailed interface contents, please refer to the hardware platform chapter)
After receiving this topic data, the two-wheel differential controller converts the angular velocity and linear velocity therein into the rotational speeds of the two wheels and sends them to Gazebo.
Output information:
The default output topic for odometry information is odom, and its message type is:nav_msgs/msg/Odometry
Its data mainly consists of three parts: (For detailed interface content, please refer to the hardware platform chapter.)
-
header, indicates the time when the message was published
-
pose, indicates the current robot position and orientation
-
twist, indicates the current robot linear velocity and angular velocity
-
The data also contains a covariance, which represents the covariance matrix. I will write an article later to introduce it. Here, you only need to understand its meaning.
Odometry TF information can also be output: set to true, and by subscribing to the tf topic you can see a msg like the following. It is recommended that after configuration is complete, you manually modify it and compare the differences.
- header:
stamp:
sec: 6157
nanosec: 907000000
frame_id: odom
child_frame_id: base_footprint
transform:
translation:
x: 0.0005557960241049835
y: -0.0007350446303238693
z: 0.01599968753145574
rotation:
x: 4.691143395208505e-07
y: 7.115496626557812e-06
z: -0.018531475772549166
w: 0.9998282774331005
Wheel TF information can also be output: set it to true, and when subscribing to the tf topic, you will see messages like the following. It is recommended to manually modify it after configuration later to compare the differences.
- header:
stamp:
sec: 6157
nanosec: 941000000
frame_id: base_link
child_frame_id: left_wheel_link
transform:
translation:
x: -0.02
y: 0.1
z: -0.06
rotation:
x: 0.0
y: 0.049519025127821005
z: 0.0
w: 0.9987731805321918
- header:
stamp:
sec: 6157
nanosec: 941000000
frame_id: base_link
child_frame_id: right_wheel_link
transform:
translation:
x: -0.02
y: -0.1
z: -0.06
rotation:
x: 0.0
y: -0.0663387077034509
z: 0.0
w: 0.9977971616817898
The fishbot URDF from FishRos that we downloaded earlier already includes the differential drive plugin content, as shown below:
Because it is a plugin for Gazebo, inURDFwe need to use<gazebo>for configuration. Since we are configuring a plugin forgazeboall additions must be made under thegazebotag to addpluginsub-plugins.
<gazebo>
<plugin name='diff_drive' filename='libgazebo_ros_diff_drive.so'>
<ros>
<namespace>/</namespace>
<remapping>cmd_vel:=cmd_vel</remapping>
<remapping>odom:=odom</remapping>
</ros>
<update_rate>30</update_rate>
<left_joint>left_wheel_joint</left_joint>
<right_joint>right_wheel_joint</right_joint>
<wheel_separation>0.2</wheel_separation>
<wheel_diameter>0.065</wheel_diameter>
<max_wheel_torque>20</max_wheel_torque>
<max_wheel_acceleration>1.0</max_wheel_acceleration>
<publish_odom>true</publish_odom>
<publish_odom_tf>true</publish_odom_tf>
<publish_wheel_tf>true</publish_wheel_tf>
<odometry_frame>odom</odometry_frame>
<robot_base_frame>base_footprint</robot_base_frame>
</plugin>
</gazebo>
Compile this:
colcon build
source install/setup.bash
ros2 launch fishbot_description gazebo.launch.py
Then you can take a look at these two commands:
ros2 node list
ros2 topic list

We can now see the /cmd_vel subscribed by our plugin and the published /odom.
然后我们可以通过teleop-twist-keyboard节点发布cmd_vel来控制fishbot。
sudo apt install ros-humble-teleop-twist-keyboard
Use the node below to control
ros2 run teleop_twist_keyboard teleop_twist_keyboard
Next, try using it to control the robot's movement.
U I O
J K L
M < >
Click once, and you will see fishbot moving rapidly in Gazebo. Then open a terminal, print the odom topic and tf topic, and move the robot to observe changes in the data.
You can also use rqt to display the velocity data.
rqt
Select Plugin->Visualization->Plot

In the Topic field above, enter /cmd_vel/linear/x, then enter /cmd_vel/angular/z, and use the keyboard to control the robot's movement.


You can also view odom in rviz2.
rviz2
Press U on the keyboard control node to make the robot spin in circles.

Although the robot's trajectory is already displayed in RVIZ, there is no robot model, and the rotation of the wheels cannot be seen either. Let us work through this problem together.
Previously, we introduced that the node we use to publish the robot model isrobot_state_publisher, so we add this node ingazebo.launch.pyand also add the rviz2 launch node. The finalgazebo.launch.pycontent is as follows:
from launch import LaunchDescription
from launch_ros.actions import Node
# 封装终端指令相关类
from launch.actions import ExecuteProcess
# from launch.substitutions import FindExecutable
# 参数声明与获取
# from launch.actions import DeclareLaunchArgument
# from launch.substitutions import LaunchConfiguration
# 文件包含相关
# from launch.actions import IncludeLaunchDescription
# from launch.launch_description_sources import PythonLaunchDescriptionSource
# 分组相关
# from launch_ros.actions import PushRosNamespace
# from launch.actions import GroupAction
# 事件相关
# from launch.event_handlers import OnProcessStart,OnProcessExit
# from launch.actions import ExecuteProcess,RegisterEventHandler,LogInfo
# 获取功能包下share目录或路径
from ament_index_python.packages import get_package_share_directory
import os
def generate_launch_description():
robot_name_in_model = 'fishbot'
package_name = 'fishbot_description'
urdf_name = "fishbot_gazebo.urdf"
pkg_share = get_package_share_directory(f"{package_name}")
urdf_model_path = os.path.join(pkg_share, f'urdf/urdf/{urdf_name}')
# gazebo_world_path = os.path.join(pkg_share, 'world/fishbot.world')
# Start Gazebo server
# start_gazebo_cmd = ExecuteProcess(
# cmd=['gazebo', '--verbose','-s', 'libgazebo_ros_init.so', '-s', 'libgazebo_ros_factory.so',gazebo_world_path],
# output='screen')
start_gazebo_cmd = ExecuteProcess(
cmd=['gazebo', '--verbose','-s', 'libgazebo_ros_init.so', '-s', 'libgazebo_ros_factory.so'],
output='screen')
# Launch the robot
spawn_entity_cmd = Node(
package='gazebo_ros',
executable='spawn_entity.py',
arguments=['-entity', robot_name_in_model, '-file', urdf_model_path ], output='screen')
# Start Robot State publisher
start_robot_state_publisher_cmd = Node(
package='robot_state_publisher',
executable='robot_state_publisher',
arguments=[urdf_model_path]
)
# Launch RViz
start_rviz_cmd = Node(
package='rviz2',
executable='rviz2',
name='rviz2',
output='screen',
# arguments=['-d', default_rviz_config_path]
)
return LaunchDescription([start_gazebo_cmd,spawn_entity_cmd,start_robot_state_publisher_cmd,start_rviz_cmd])
Save and compile startup
colcon build
ros2 launch fishbot_description gazebo.launch.py
This way, the model will be available in rviz2.

You can save the rviz2 configuration.


Then add the path configuration for rviz2 in launch:
default_rviz_config_path = os.path.join(pkg_share, 'rviz/rviz2.rviz')
# Launch RViz
start_rviz_cmd = Node(
package='rviz2',
executable='rviz2',
name='rviz2',
output='screen',
arguments=['-d', default_rviz_config_path]
)
Below are all the launches:
from launch import LaunchDescription
from launch_ros.actions import Node
# 封装终端指令相关类
from launch.actions import ExecuteProcess
# from launch.substitutions import FindExecutable
# 参数声明与获取
# from launch.actions import DeclareLaunchArgument
# from launch.substitutions import LaunchConfiguration
# 文件包含相关
# from launch.actions import IncludeLaunchDescription
# from launch.launch_description_sources import PythonLaunchDescriptionSource
# 分组相关
# from launch_ros.actions import PushRosNamespace
# from launch.actions import GroupAction
# 事件相关
# from launch.event_handlers import OnProcessStart,OnProcessExit
# from launch.actions import ExecuteProcess,RegisterEventHandler,LogInfo
# 获取功能包下share目录或路径
from ament_index_python.packages import get_package_share_directory
import os
def generate_launch_description():
robot_name_in_model = 'fishbot'
package_name = 'fishbot_description'
urdf_name = "fishbot_gazebo.urdf"
pkg_share = get_package_share_directory(f"{package_name}")
urdf_model_path = os.path.join(pkg_share, f'urdf/urdf/{urdf_name}')
# gazebo_world_path = os.path.join(pkg_share, 'world/fishbot.world')
default_rviz_config_path = os.path.join(pkg_share, 'rviz/rviz2.rviz')
# Start Gazebo server
# start_gazebo_cmd = ExecuteProcess(
# cmd=['gazebo', '--verbose','-s', 'libgazebo_ros_init.so', '-s', 'libgazebo_ros_factory.so',gazebo_world_path],
# output='screen')
start_gazebo_cmd = ExecuteProcess(
cmd=['gazebo', '--verbose','-s', 'libgazebo_ros_init.so', '-s', 'libgazebo_ros_factory.so'],
output='screen')
# Launch the robot
spawn_entity_cmd = Node(
package='gazebo_ros',
executable='spawn_entity.py',
arguments=['-entity', robot_name_in_model, '-file', urdf_model_path ], output='screen')
# Start Robot State publisher
start_robot_state_publisher_cmd = Node(
package='robot_state_publisher',
executable='robot_state_publisher',
arguments=[urdf_model_path]
)
# Launch RViz
start_rviz_cmd = Node(
package='rviz2',
executable='rviz2',
name='rviz2',
output='screen',
arguments=['-d', default_rviz_config_path]
)
return LaunchDescription([start_gazebo_cmd,spawn_entity_cmd,start_robot_state_publisher_cmd,start_rviz_cmd])
You can compile and test it yourself.
1.7.2.Inertial Measurement Unit (IMU)
In the previous lesson, by configuring a two-wheel differential drive controller, we successfully got the fishbot moving in Gazebo. In this lesson, we will configure an IMU sensor plugin for the fishbot's URDF to make the IMU module work.
An inertial measurement unit is a device that measures the three-axis attitude angles (or angular rates) and acceleration of an object. Generally, an IMU contains three single-axis accelerometers and three single-axis gyroscopes; the accelerometers detect the acceleration signals of the object along the three independent axes of the carrier coordinate system, while the gyroscopes detect the angular velocity signals of the carrier relative to the navigation coordinate system, measuring the angular velocity and acceleration of the object in three-dimensional space, and thereby solving for the object's attitude. It has very important application value in navigation.
The above passage was excerpted by Xiaoyu from an encyclopedia. One key point you need to know is that an IMU can measure the following three sets of data:
-
3D accelerometer acceleration
-
3D gyroscope angular velocity
-
3D magnetometer (some do not have a magnetometer either)
With six-axis, nine-axis algorithms, or other algorithms, three-axis Euler angles (Yaw, Pitch, Roll) can be output, and Euler angles can be converted into quaternions.
What does an IMU look like? Just go find the control group offline and ask for one; they often play with these.
The cheap ones look like this (MPU6050, MPU9050, etc.):
The MPU6050 is six-axis, while the MPU9050 is nine-axis.

The expensive ones look like this (HWT101CT, HWT605, etc.):
Among them, the HWT101CT is three-axis, and the HWT605 is six-axis. (Each has its own advantages and disadvantages.)

What do the free ones look like?

Simulation costs nothing, haha. Next, let's configure the simulated IMU.
The message type corresponding to IMU issensor_msgs/msg/Imu
ROS IMU information only has three-axis acceleration, angular velocity, and quaternion. (There is no magnetometer or Euler angles. The magnetometer is not mandatory, and Euler angles and quaternions can be converted to each other. Quaternions are better for algorithm computation, so quaternions are chosen.)
For specific IMU interfaces, please refer to the hardware platform chapter.
It can be seen that in addition to the three covariances corresponding to each data point, each one also corresponds to a3*3covariance matrix.
With the experience from the previous lesson, we can easily add the IMU sensor, but there is still one point to note: to simulate the IMU sensor more realistically, we need to add some flavor to our simulated IMU sensor.
What to add? Add some Gaussian noise. Gaussian noise only requires specifying two parameters: the mean and the standard deviation. However, due to the special characteristics of IMU sensors, we also need to add two bias parameters to the model, which are 平均值偏差 and 标准差偏差.
For a more in-depth introduction to Gazebo simulation and noise models, you can refer to two posts published by Yuxiang ROS:
-
Advanced Gazebo Simulation Tutorial: Sensor Gaussian Noise (Part 1)
-
Advanced Gazebo Simulation Tutorial: Sensor Gaussian Noise (Part 2)
下面是IMU传感器的URDF配置代码,大家结合文章对应可以理解一下,IMU对应的插件库libgazebo_ros_imu_sensor.so:
<gazebo reference="imu_link">
<sensor name="imu_sensor" type="imu">
<plugin filename="libgazebo_ros_imu_sensor.so" name="imu_plugin">
<ros>
<namespace>/</namespace>
<remapping>~/out:=imu</remapping>
</ros>
<initial_orientation_as_reference>false</initial_orientation_as_reference>
</plugin>
<always_on>true</always_on>
<update_rate>100</update_rate>
<visualize>true</visualize>
<imu>
<angular_velocity>
<x>
<noise type="gaussian">
<mean>0.0</mean>
<stddev>2e-4</stddev>
<bias_mean>0.0000075</bias_mean>
<bias_stddev>0.0000008</bias_stddev>
</noise>
</x>
<y>
<noise type="gaussian">
<mean>0.0</mean>
<stddev>2e-4</stddev>
<bias_mean>0.0000075</bias_mean>
<bias_stddev>0.0000008</bias_stddev>
</noise>
</y>
<z>
<noise type="gaussian">
<mean>0.0</mean>
<stddev>2e-4</stddev>
<bias_mean>0.0000075</bias_mean>
<bias_stddev>0.0000008</bias_stddev>
</noise>
</z>
</angular_velocity>
<linear_acceleration>
<x>
<noise type="gaussian">
<mean>0.0</mean>
<stddev>1.7e-2</stddev>
<bias_mean>0.1</bias_mean>
<bias_stddev>0.001</bias_stddev>
</noise>
</x>
<y>
<noise type="gaussian">
<mean>0.0</mean>
<stddev>1.7e-2</stddev>
<bias_mean>0.1</bias_mean>
<bias_stddev>0.001</bias_stddev>
</noise>
</y>
<z>
<noise type="gaussian">
<mean>0.0</mean>
<stddev>1.7e-2</stddev>
<bias_mean>0.1</bias_mean>
<bias_stddev>0.001</bias_stddev>
</noise>
</z>
</linear_acceleration>
</imu>
</sensor>
</gazebo>
The fishbot we downloaded earlier already includes this content, so there is no need to add it again; just run it directly.
ros2 launch fishbot_description gazebo.launch.py
ros2 topic list

ros2 topic info /imu
ros2 topic echo /imu


header:
stamp:
sec: 150
nanosec: 599000000
frame_id: base_footprint
orientation:
x: 3.434713830866392e-07
y: 7.119913105768616e-06
z: -0.00028312437320413914
w: 0.9999999598948884
orientation_covariance:
- 0.0
- 0.0
- 0.0
- 0.0
- 0.0
- 0.0
- 0.0
- 0.0
- 0.0
angular_velocity:
x: -0.00013597855247901325
y: 0.0006306135617081868
z: -0.00015794894627685146
angular_velocity_covariance:
- 4.0e-08
- 0.0
- 0.0
- 0.0
- 4.0e-08
- 0.0
- 0.0
- 0.0
- 4.0e-08
linear_acceleration:
x: 0.08679200038530369
y: 0.07753419258567491
z: 9.687910969061628
linear_acceleration_covariance:
- 0.00028900000000000003
- 0.0
- 0.0
- 0.0
- 0.00028900000000000003
- 0.0
- 0.0
- 0.0
- 0.00028900000000000003
Visualize with rqt:

1.7.3.Radar Laser
In this section, we will get to know a new sensor, which is widely used in applications such as autonomous driving and indoor navigation. For example, it is used on robot vacuum cleaners as an important tool for perceiving the environment. This sensor is LiDAR.
Light Detection And Ranging (LiDAR), abbreviatedLiDAR; in English it is also called laser, which translates as—laser detection and ranging.
The principle of LiDAR is also very simple, just like the positioning method of bats. Everyone knows how bats locate, right? It's echolocation like the following.

A typical single-line LiDAR generally has one transmitter and one receiver. The transmitter emits a laser beam onto a target ahead, and the object reflects the laser back. The LiDAR's receiver can then detect the reflected laser.

By calculating the time interval between transmission and feedback and multiplying it by the speed of the laser, the laser flight distance can be calculated. This calculation method is called TOF (Time of Flight, also known as the time-of-flight method).
Besides TOF, there are other methods for distance measurement, such as triangulation. I will not expand on this here; I will include an article for everyone to read on their own.Detailed Explanation of the Principle of Laser Triangulation Distance Measurement
Currently, LiDARs on the market almost all use triangulation ranging, such as those from Slamtec:

It should be noted that although there is only one transmitter and one receiver, the LiDAR can rotate via a motor, thereby achieving 360-degree ranging of the surrounding environment.
The five-digit one looks like this:

Four-digit ones look like this (we have one).

Three-digit ones look like this (we have them too)

Two-digit numbers look like this

The free ones look like this
Simulated, free of charge

Because LiDAR is a ray-based sensor, this type of sensor is encapsulated as a dynamic library in Gazebo pluginslibgazebo_ros_ray_sensor.so.
Next, let's look at the LiDAR topic message interfacesensor_msgs/msg/LaserScan.
The radar data structure is somewhat complex, but through the comments and names I believe you can get a rough understanding; it's okay if you don't fully understand, as in general we won't directly operate on the radar data.
The radar model does not need collision, please delete it, otherwise it will block the laser from being emitted.
With the previous experience, we only need to add the following content to the URDF. However, what we downloaded is the one already prepared by Fish-Fragrant ROS, so there is no need to modify it:
<gazebo reference="laser_link">
<sensor name="laser_sensor" type="ray">
<always_on>true</always_on>
<visualize>true</visualize>
<update_rate>10</update_rate>
<pose>0 0 0.075 0 0 0</pose>
<ray>
<scan>
<horizontal>
<samples>360</samples>
<resolution>1.000000</resolution>
<min_angle>0.000000</min_angle>
<max_angle>6.280000</max_angle>
</horizontal>
</scan>
<range>
<min>0.120000</min>
<max>3.5</max>
<resolution>0.015000</resolution>
</range>
<noise>
<type>gaussian</type>
<mean>0.0</mean>
<stddev>0.01</stddev>
</noise>
</ray>
<plugin name="laserscan" filename="libgazebo_ros_ray_sensor.so">
<ros>
<remapping>~/out:=scan</remapping>
</ros>
<output_type>sensor_msgs/LaserScan</output_type>
<frame_name>laser_link</frame_name>
</plugin>
</sensor>
</gazebo>
It can be seen:
-
The radar can also set the update frequency
update_rate; here it is set to 5 -
The radar can set the resolution to 1, with 360 samples, and the final number of generated point cloud points will be 360
-
The radar also has noise, with the model being
gaussian -
The radar has a scanning range
range; here it is configured as 0.12-3.5 with a resolution of 0.015 -
The radar's
poseis the value set for the position in the radar's joint
The blue area below is the light coverage range of the LiDAR:

ros2 topic list

ros2 topic info /scan
ros2 topic echo /scan

Next, we attempt to use rviz2 to visualize the lidar data.
Add and modify RVIZ2 as follows: (laser data can be seen through the LaserScan plugin)

I believe that after you finish making the changes, you will still not be able to see any lidar data. If you look back at the data echoed from the topic, it is either 0 or inf (infinity). Then look at Gazebo, and you will find that the lidar is not hitting any object.

So we can manually add some objects around the LiDAR; click the cube, sphere, or cylinder in the Gazebo toolbar, and place a few of them arbitrarily within the maximum scanning radius of our LiDAR.



1.7.4.Ultrasonic
This is not very significant for the ROS2 algorithm; the control group can be implemented directly on the MCU, and those who need it can study this section.(This section can be skipped.)
In actual robot development, we may use ultrasonic sensors to implement real-time obstacle avoidance, since ultrasonic sensors are much cheaper than LiDAR (the cheap ones cost just a few yuan).
So in this section, let's discuss how to use ROS2+Gazebo to simulate an ultrasonic sensor.
百科来一段:
An ultrasonic sensor is a sensor that converts ultrasonic signals into other energy signals (usually electrical signals). Ultrasound is a mechanical wave with avibration frequencyhigher than 20 kHz. It has the characteristics of high frequency, short wavelength, small diffraction phenomenon, and in particular, good directivity, and can becomeraysand propagate directionally. Ultrasound has a strong penetrating ability into liquids and solids, especially in solids that are opaque to sunlight. When ultrasound encounters impurities or interfaces, it produces significant reflection to form reflected echoes, and when it encounters moving objects, it can produce theDoppler effect. Ultrasonic sensors are widely used in industry, national defense, biomedicine, and other fields.
Next, let's see what it looks like:

Cheap ones look like this, with two heads in total, one head for transmitting waves and one head for receiving waves. This one is a bit more advanced, with a photoresistor that can provide some compensation for the ultrasonic data.
What is the principle of an ultrasonic sensor?

距离=(发送时间-接收时间)*速度/2Copy to clipboardErrorCopied
After looking at the principle of ultrasonic waves, have you noticed that it is the same as the previous LiDAR sensor? Yes, so the ultrasonic sensor plugin and the LiDAR sensor plugin are the same in Gazebo plugins:
libgazebo_ros_ray_sensor.so
The ultrasonic sensor must always be mounted at some position on the robot, so we first add a joint and Joint. To save effort, for the link we will just write a name; if needed, you can add it as described in the previous chapters.
<link name="ultrasonic_sensor_link" />
<joint name="ultrasonic_sensor_joint" type="fixed">
<parent link="base_link"/>
<child link="ultrasonic_sensor_link"/>
<origin xyz="0.07 0.0 0.076" rpy="0 0 0"/>
</joint>
Now that the joints have been added, we can configure the Gazebo plugin. The Gazebo plugin configuration is as follows:
<gazebo reference="ultrasonic_sensor_link">
<sensor type="ray" name="ultrasonic_sensor">
<pose>0 0 0 0 0 0</pose>
<visualize>true</visualize>
<update_rate>5</update_rate>
<ray>
<scan>
<horizontal>
<samples>5</samples>
<resolution>1</resolution>
<min_angle>-0.12</min_angle>
<max_angle>0.12</max_angle>
</horizontal>
<vertical>
<samples>5</samples>
<resolution>1</resolution>
<min_angle>-0.01</min_angle>
<max_angle>0.01</max_angle>
</vertical>
</scan>
<range>
<min>0.02</min>
<max>4</max>
<resolution>0.01</resolution>
</range>
<noise>
<type>gaussian</type>
<mean>0.0</mean>
<stddev>0.01</stddev>
</noise>
</ray>
<plugin name="ultrasonic_sensor_controller" filename="libgazebo_ros_ray_sensor.so">
<ros>
<remapping>~/out:=ultrasonic_sensor_1</remapping>
</ros>
<output_type>sensor_msgs/Range</output_type>
<radiation_type>ultrasound</radiation_type>
<frame_name>ultrasonic_sensor_link</frame_name>
</plugin>
</sensor>
</gazebo>
After adding it, you can compile and test the code.
colcon build --packages-select fishbot_description
source install/setup.bash
ros2 launch fishbot_description gazebo.launch.py
In front of an object-free area, something can be placed.

Open the terminal and enter the following command
ros2 topic list
ros2 topic info /ultrasonic_sensor_1
ros2 topic echo /ultrasonic_sensor_1Copy to clipboardErrorCopied
As expected, the following data can be seen
header:
stamp:
sec: 4458
nanosec: 1000000
frame_id: ultrasonic_sensor_link
radiation_type: 0
field_of_view: 0.23999999463558197
min_range: 0.019999999552965164
max_range: 4.0
range: 2.6798219680786133
Here, range is exactly the distance from fishbot to the wall: 2.67982
Let's talk about the data types of ultrasonic sensors.sensor_msgs/msg/Range
# ros2 topic info /ultrasonic_sensor_1
Type: sensor_msgs/msg/Range
Publisher count: 1
Subscription count: 0
You can useros2 interface show sensor_msgs/msg/RangeSee the detailed explanation, let's translate it
# Single range reading from an active ranger that emits energy and reports
# one range reading that is valid along an arc at the distance measured.
# This message is not appropriate for laser scanners. See the LaserScan
# message if you are working with a laser scanner.
#
# This message also can represent a fixed-distance (binary) ranger. This
# sensor will have min_range===max_range===distance of detection.
# These sensors follow REP 117 and will output -Inf if the object is detected
# and +Inf if the object is outside of the detection range.
std_msgs/Header header # timestamp in the header is the time the ranger
# returned the distance reading
# Radiation type enums
# If you want a value added to this list, send an email to the ros-users list
uint8 ULTRASOUND=0
uint8 INFRARED=1
uint8 radiation_type # 传感器射线类型
# (sound, IR, etc) [enum]
float32 field_of_view # 距离数据对应的弧[rad]的大小,测量物体的范围介于
# -field_of_view/2 到 field_of_view/2 之间。
# 0 角度对应于传感器的 x 轴。
float32 min_range # 最小范围值 [m]
float32 max_range # 最大范围值 [m]
# 固定距离需要 min_range==max_range
float32 range # 范围数据 [m]
# (Note: values < range_min or > range_max should be discarded)
# Fixed distance rangers only output -Inf or +Inf.
# -Inf represents a detection within fixed distance.
# (Detection too close to the sensor to quantify)
# +Inf represents no detection within the fixed distance.
# (Object out of range)
For the conclusion, just focus mainly on the range.
Add ultrasonic data in rviz2
Add ->By topic->Range


1.8.Build a world map
In this section, we will set up a test environment in Gazebo. It is actually quite simple and can be done using Gazebo's wall-drawing tool.
world means world. A Gazebo world file is used to describe a world model, that is, an environment model.
Gazebo has already prepared many commonly used object models for us. In addition to the basic sphere, cylinder, and cube, there are actually airplanes, cars, houses, and more that you cannot have in reality.
However, when Gazebo is first installed, it does not download these models for you; you need to download them manually. Find a folder where you want to store the models, open a terminal, and copy and paste the following line.
git clone https://github.com/osrf/gazebo_models
And add the path where the model is stored to ~/.bashrc (the part after the second colon can be omitted; what follows the second colon is for multiple paths.)
GAZEBO_MODEL_PATH is a macro of the old Gazebo Classic.
IGN_GAZEBO_RESOURCE_PATH is a macro for the new version of Ignition Gazebo.
The models are all universal and can be configured together.

Refresh environment variables
source ~/.bashrc
At this point, open the terminal again and entergazebo,switch the tab to Insert

Under the Insert tab, you can see a directory along with the model names under it. As the download script continues downloading, there will be more and more models here.
Just drag a few around and build a beautiful environment~
Every successful man has a car, and we are no exception.

The above are open-source models that Gazebo has prepared for us; we can also use Gazebo's tools to draw an environment ourselves.
Then you can also use the wall tool to build walls
Gazebo upper-left corner -> Edit -> Building Editor
Then you can see an editing interface like this.

Click on Wall on the left, and you can build walls in the white area above. This is different from The Sims game: here you draw 2D walls to generate 3D walls, whereas The Sims lets you draw directly in 3D.

After building, you can choose Add Color or Add Texture, then click the wall below to add color or texture to the wall.
First, you need a map. Xiaoyu has prepared two for you, and both images are 800*600 pixels.


Open Gazebo -> top-left of Gazebo -> Edit -> Building Editor -> select Import at the bottom left

Save the above two images locally, select the images in this interface, and remember to choose Next

The correspondence of the size selected on the left

我们选择默认的,100像素/米。点击OK(需要手动将100改变一下才能点击OK哦),之后就可以用图片画墙了。
Note: The wall will not appear directly after importing the image; the image only provides the approximate position of the wall, and you need to manually trace the edges with the wall tool again.

After building, click File->Exit, and in the exit dialog box, select Exit.
Then the wall will be visible in the Gazebo interface. We can manually add a few more objects, and then it can be used for the navigation below.

After adding, click File->SaveWorld to save the file to our fishbot_descrption world.
Those who do not have the world directory can create it manually first.

Loading a world is actually quite simple. You can either start Gazebo first and then manually load the file, or load it when Gazebo starts:
比如在前面加载ROS2插件基础上再加载fishbot.world。
gazebo --verbose -s libgazebo_ros_init.so -s libgazebo_ros_factory.so 你的world文件目录/fishbot.world
修改launch文件,将上面的命令行写到gazebo.launch.py中即可。
gazebo_world_path = os.path.join(pkg_share, 'world/fishbot.world')
# Start Gazebo server
start_gazebo_cmd = ExecuteProcess(
cmd=['gazebo', '--verbose','-s', 'libgazebo_ros_init.so', '-s', 'libgazebo_ros_factory.so', gazebo_world_path],
output='screen')
Below is the entire launch file:
from launch import LaunchDescription
from launch_ros.actions import Node
# 封装终端指令相关类
from launch.actions import ExecuteProcess
# from launch.substitutions import FindExecutable
# 参数声明与获取
# from launch.actions import DeclareLaunchArgument
# from launch.substitutions import LaunchConfiguration
# 文件包含相关
# from launch.actions import IncludeLaunchDescription
# from launch.launch_description_sources import PythonLaunchDescriptionSource
# 分组相关
# from launch_ros.actions import PushRosNamespace
# from launch.actions import GroupAction
# 事件相关
# from launch.event_handlers import OnProcessStart,OnProcessExit
# from launch.actions import ExecuteProcess,RegisterEventHandler,LogInfo
# 获取功能包下share目录或路径
from ament_index_python.packages import get_package_share_directory
import os
def generate_launch_description():
robot_name_in_model = 'fishbot'
package_name = 'fishbot_description'
urdf_name = "fishbot_gazebo.urdf"
world_name = "fishbot.world"
pkg_share = get_package_share_directory(f"{package_name}")
urdf_model_path = os.path.join(pkg_share, f'urdf/urdf/{urdf_name}')
gazebo_world_path = os.path.join(pkg_share, f'world/{world_name}')
default_rviz_config_path = os.path.join(pkg_share, 'rviz/rviz2.rviz')
# Start Gazebo server
start_gazebo_cmd = ExecuteProcess(
cmd=['gazebo', '--verbose','-s', 'libgazebo_ros_init.so', '-s', 'libgazebo_ros_factory.so',gazebo_world_path],
output='screen')
# Launch the robot
spawn_entity_cmd = Node(
package='gazebo_ros',
executable='spawn_entity.py',
arguments=['-entity', robot_name_in_model, '-file', urdf_model_path ], output='screen')
# Start Robot State publisher
start_robot_state_publisher_cmd = Node(
package='robot_state_publisher',
executable='robot_state_publisher',
arguments=[urdf_model_path]
)
# Launch RViz
start_rviz_cmd = Node(
package='rviz2',
executable='rviz2',
name='rviz2',
output='screen',
arguments=['-d', default_rviz_config_path]
)
return LaunchDescription([start_gazebo_cmd,spawn_entity_cmd,start_robot_state_publisher_cmd,start_rviz_cmd])
Finally, remember to modify the cmakelists.txt file so that after compilation the world file is copied to the install directory.

colcon build
source install/setup.bash
ros2 launch fishbot_description gazebo.launch.py


1.9.Framework optimization
The senior also uploaded the code from this section to the repository; juniors who need it, please take a look:https://github.com/tungchiahui/ROS_WS/tree/main/ROS2_WS%2F6.ws_simulations%2Fsrc%2Ffishbot_description
For example, support for optimizations such as xacro.
First, split the contents of the original fishbot_gazebo.urdf into several URDF and xacro files, then use an overall fishbot.urdf.xacro to reference them. (Anyone who has learned xacro will definitely know how.)

The optimized launch is as follows:
from launch import LaunchDescription
from launch_ros.actions import Node
# 封装终端指令相关类
from launch.actions import ExecuteProcess
# from launch.substitutions import FindExecutable
# 参数声明与获取
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
# 文件包含相关
# from launch.actions import IncludeLaunchDescription
# from launch.launch_description_sources import PythonLaunchDescriptionSource
# 分组相关
# from launch_ros.actions import PushRosNamespace
# from launch.actions import GroupAction
# 事件相关
# from launch.event_handlers import OnProcessStart,OnProcessExit
# from launch.actions import ExecuteProcess,RegisterEventHandler,LogInfo
# 获取功能包下share目录或路径
from ament_index_python.packages import get_package_share_directory
from launch_ros.parameter_descriptions import ParameterValue
from launch.substitutions import Command,LaunchConfiguration
import os
def generate_launch_description():
robot_name_in_model = 'fishbot'
package_name = 'fishbot_description'
# urdf_xacro_name = "fishbot_gazebo.urdf"
world_name = "fishbot.world"
pkg_share = get_package_share_directory(f"{package_name}")
urdf_xacro_model_path = os.path.join(pkg_share, "urdf/xacro","fishbot.urdf.xacro")
gazebo_world_path = os.path.join(pkg_share, f'world/{world_name}')
default_rviz_config_path = os.path.join(pkg_share, 'rviz/rviz2.rviz')
model = DeclareLaunchArgument(name="model", default_value=urdf_xacro_model_path)
robot_description = ParameterValue(Command(["xacro ",LaunchConfiguration("model")]))
# Start Gazebo server
start_gazebo_cmd = ExecuteProcess(
cmd=['gazebo', '--verbose','-s', 'libgazebo_ros_init.so', '-s', 'libgazebo_ros_factory.so',gazebo_world_path],
output='screen')
# Launch the robot
spawn_entity_cmd = Node(
package='gazebo_ros',
executable='spawn_entity.py',
arguments=['-entity', robot_name_in_model, '-topic', '/robot_description' ], output='screen')
# Start Robot State publisher
start_robot_state_publisher_cmd = Node(
package='robot_state_publisher',
executable='robot_state_publisher',
parameters=[{"robot_description": robot_description}]
)
# Launch RViz
start_rviz_cmd = Node(
package='rviz2',
executable='rviz2',
name='rviz2',
output='screen',
arguments=['-d', default_rviz_config_path]
)
return LaunchDescription([model,start_gazebo_cmd,spawn_entity_cmd,start_robot_state_publisher_cmd,start_rviz_cmd])
colcon build
source install/setup.bash
ros2 launch fishbot_description gazebo.launch.py


Then you can go work on navigation! You can also continue learning the new version of Ignition Gazebo, but there are very few tutorials for it; as of 2024, only Teacher Zhao Xuzuo has covered it. ** It is recommended to learn the new Gazebo Harmonic (ROS2 the default version for Jazzy) **