第一个Gazebo入门项目源码:让你的机器人动起来

创建工作空间与环境

创建工作空间chap6,创建src目录。

src目录库下创建c++功能包fishbot_description,fishbot_description功能包下创建urdf文件夹,用于存放urdf文件。

fishbot_description功能包下创建launch文件夹,用于存放launch.py文件。在launch文件夹下新建gazebo_sim.launch.py

创建一个config文件夹,用于存放一些配置文件。

在config文件夹下创建一个rviz文件夹,用于存放rviz配置文件。

在fishbot_description创建world文件夹,用于存储地图模型,再创建一个custom_room.world 文件,存储地图模型

在src/fishbot_description/urdf/fishbot 目录下新建plugins目录,用于存放gazebo插件


机器人外观建模部分:

质量与惯性宏定义:

结合惯性公式,可以编写常用图形的惯性矩阵和质量的宏定义,在urdf/fishbot 目录下新建common_inertia.xacro文件,编写代码

<?xml version="1.0"?>
<robot xmlns:xacro="http://ros.org/wiki/xacro">
    <xacro:macro name="box_inertia" params="m w h d">
        <inertial>
            <mass value="${m}" />
            <inertia ixx="${(m/12) * (h*h + d*d)}" ixy="0.0" ixz="0.0" iyy="${(m/12) * (w*w + d*d)}" iyz="0.0" izz="${(m/12) * (w*w + h*h)}" />
        </inertial>
    </xacro:macro>

    <xacro:macro name="cylinder_inertia" params="m r h">
        <inertial>
            <mass value="${m}" />
            <inertia ixx="${(m/12) * (3*r*r + h*h)}" ixy="0" ixz="0" iyy="${(m/12) * (3*r*r + h*h)}" iyz="0" izz="${(m/2) * (r*r)}" />
        </inertial>
    </xacro:macro>

    <xacro:macro name="sphere_inertia" params="m r">
        <inertial>
            <mass value="${m}" />
            <inertia ixx="${(2/5) * m * (r*r)}" ixy="0.0" ixz="0.0" iyy="${(2/5) * m * (r*r)}" iyz="0.0" izz="${(2/5) * m * (r*r)}" />
        </inertial>
    </xacro:macro>

</robot>

🔄 作用:  依次定义了长方体、圆柱体和球体相关的宏,在宏中,使用标签inertial描述机器人的惯量,其中子标签mass用于描述质量,子标签inertia用于描述惯量。编写完成后,我们就可以在其他Xacro文件导入并使用该宏了

创建机器人传感器部件:

(1)在fishbot 目录下新建sensor子目录,然后在sensor下新建imu.urdf.xacro,编写代码

<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
   <xacro:include filename="$(find fishbot_description)/urdf/fishbot/common_inertia.xacro" />

    <xacro:macro name="imu_xacro" params="xyz">
        <link name="imu_link">
            <visual>
                <origin xyz="0 0 00" rpy="0 0 0" />
                <geometry>
                    <box size="0.02 0.02 0.02" />
                </geometry>
                <material name="black">
                    <color rgba="0 0 0 0.8" />
                </material>
            </visual>
            <collision>
                <origin xyz="0 0 00" rpy="0 0 0" />
                <geometry>
                    <box size="0.02 0.02 0.02" />
                </geometry>
                <material name="black">
                    <color rgba="0 0 0 0.8" />
                </material>
            </collision>
            <xacro:box_inertia m="0.01" w="0.02" d="0.02" h="0.02"/>
        </link>

        <joint name="imu_joint" type="fixed">
            <parent link="base_link" />
            <child link="imu_link" />
            <origin xyz="${xyz}" />
        </joint>
    </xacro:macro>
</robot>

🔄 作用: 定义了一个imu_xacro的宏,在宏内定义了一个部件和关节,关节imu_joint将imu_link和base_link固定连接,调用box_inertia宏描述了该imu传感器的惯性与质量,固定的位置通过参数xyz进行传递。IMU(惯性测量单元)是一种用于测量物体加速度和角速度的传感器。

(2)在sensor目录下新建camera.urdf.xacro,然后编写代码

<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
   <xacro:include filename="$(find fishbot_description)/urdf/fishbot/common_inertia.xacro" />

<!-- ============相机模块================ -->
    <xacro:macro name="camera_xacro" params="xyz">
        <link name="camera_link">
            <visual>
                <origin xyz="0 0 0.0" rpy="0 0 0" />
                <geometry>
                    <box size="0.02 0.10 0.02" />
                </geometry>
                <material name="black">
                    <color rgba="0.0 0.0 0.0 0.8"/>
                </material>
            </visual>
            <collision>
                <origin xyz="0 0 0.0" rpy="0 0 0" />
                <geometry>
                    <box size="0.02 0.10 0.02" />
                </geometry>
                <material name="black">
                    <color rgba="0.0 0.0 0.0 0.8"/>
                </material>
            </collision>
            <xacro:box_inertia m="0.01" w="0.02" h="0.10" d="0.02" />
        </link>

        <link name="camera_optical_link">
        </link>

        <joint name="camera_optical_joint" type="fixed">
            <parent link="camera_link" />
            <child link="camera_optical_link" />
            <origin xyz="0 0 0" rpy="${-pi/2} 0 ${-pi/2}" />
        </joint>

        <joint name="camera_joint" type="fixed">
            <parent link="base_link" />
            <child link="camera_link" />
            <origin xyz="${xyz}" />
        </joint>
    </xacro:macro>

</robot>

🔄 作用:

深度相机是一种可以获取深度信息的特殊相机,往往会配合彩色相机使用,彩色相机可
以通过图像识别获取物体的像素坐标,结合深度就可以得到识别对象的三维坐标,机器人就
可以针对目标做出相应操作。

该宏代码包含了common_inertia.xacro宏代码,调用了box_inertia宏,来定义相机的质量与惯性。

代码中定义了一个名为camera_xacro的宏, 相机部件固定的位置通过参数xyz进行传递。

该宏中包含了两个连杆与一个关节,一个连杆是相机,一个连杆是虚拟部件(因为深度相机坐标系默认前方是z轴,所以我们先在URDF中添加一个虚拟部件并添加一个关节将其与相机链接,通过参数xyz修改关节的rpy,实现对相机关节的矫正来调整方位)

(3)在sensor目录下新建laser.urdf.xacro,然后编写代码

<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
   <xacro:include filename="$(find fishbot_description)/urdf/fishbot/common_inertia.xacro" />


<xacro:macro name="laser_xacro" params="xyz">


<!-- ============雷达支撑杆================ -->
        <link name="laser_cylinder_link">

            <visual>
                <origin xyz="0 0 0" rpy="0 0 0" />
                <geometry>
                    <cylinder length="0.10" radius="0.01" />
                </geometry>
                <material name="black">
                    <color rgba="0.0 0.0 0.0 0.8" />
                </material>
            </visual>

            <collision>
                <origin xyz="0 0 0" rpy="0 0 0" />
                <geometry>
                    <cylinder length="0.10" radius="0.01" />
                </geometry>
                <material name="black">
                    <color rgba="0.0 0.0 0.0 0.8" />
                </material>
            </collision>
            <xacro:cylinder_inertia m="0.01" r="0.01" h="0.10" />
        </link>



        <joint name="laser_cylinder_joint" type="fixed">
            <parent link="base_link" />
            <child link="laser_cylinder_link" />
            <origin xyz="${xyz}" />
        </joint>



        <gazebo reference="laser_cylinder_link">
            <material>Gazebo/Black</material>
        </gazebo>


        <!-- ============雷达================ -->
        <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.8" />
                </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.8" />
                </material>
            </collision>
            <xacro:cylinder_inertia m="0.03" r="0.02" h="0.02" />
        </link>


        <joint name="laser_joint" type="fixed">
            <parent link="laser_cylinder_link" />
            <child link="laser_link" />
            <origin xyz="0 0 0.05" />
        </joint>


        <gazebo reference="laser_link">
            <material>Gazebo/Black</material>
        </gazebo>

    </xacro:macro>

</robot>

🔄 作用:

该宏代码包含了common_inertia.xacro宏代码,调用了box_inertia宏,来定义相机的质量与惯性。

上面的代码用于描述一个雷达传感器组件宏laser_xacro,在宏内定义了一个雷达支撑杆
和一个雷达,并将雷达固定在雷达支撑杆的顶端。

在URDF中,gazebo标签是比较特殊的,这一类标签是写给Gazebo看的,所以它们都是和Gazebo仿真相关的配置。

创建机器人身体部件与虚拟部件:

在chapt6_ws/src/fishbot_description/urdf/目录下新建fishbot目录,然后在该目录下新建
base.urdf.xacro,编写如代码

<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">


   <xacro:include filename="$(find fishbot_description)/urdf/fishbot/common_inertia.xacro" />


    <xacro:macro name="base_xacro" params="length radius">


     <link name="base_footprint" />


        <link name="base_link">
            <visual>
                <origin xyz="0 0 0.0" rpy="0 0 0" />
                <geometry>
                    <cylinder length="${length}" radius="${length}" />
                </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="${length}" radius="${length}" />
                </geometry>
                <material name="blue">
                    <color rgba="0.1 0.1 1.0 0.5"/>
                </material>
            </collision>
            <xacro:cylinder_inertia m="1.0" r="${radius}" h="${length}"/>
        </link>


        <joint name="base_joint" type="fixed">
            <parent link="base_footprint" />
            <child link="base_link" />
            <origin xyz="0.0 0.0 ${length/2.0+0.032-0.001}" rpy="0 0 0" />
        </joint>


    </xacro:macro>

</robot>

🔄 作用:

创建了身体组件,定义了一个名为base_xacro的宏,并将半径和长度设置成参数,方便调整。

这里首先声明了名称为base_footprint的空部件,然后将base_link固定在这个部件的上方,高度则设置为机器人身体高度的一半加上轮子的半径,再稍微减去1mm,让轮子可以贴紧地面。

 添加雷达,相机,惯性测量传感器 gazebo插件传感器:

只有里程计传感器还不够,想要完成复杂的交互,还需各种传感器来感知环境,下一步我们来添加常用的传感器插件

在src/fishbot_description/urdf/fishbot/plugins/下新建gazebo_sensor_plugin.xacro,输入代码:

<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">


<!-- 
    这是一个Gazebo仿真环境中激光雷达(LiDAR)的配置文件
    激光雷达通过发射激光束测量距离,常用于机器人导航、避障和建图
    就像机器人的眼睛,能"看到"周围环境的距离信息
-->
<gazebo reference="laser_link">
    <!-- 
        定义一个激光雷达传感器:
        - 这个传感器将附着在URDF文件中定义的"laser_link"连杆上
        - laser_link通常代表激光雷达的物理安装位置
    -->
    <sensor type="ray" name="laser_sensor">
        
        <!-- 
            ROS插件配置:
            - name="laserscan":插件的名称,可以自定义
            - filename="libgazebo_ros_ray_sensor.so":要加载的ROS插件文件
              这个文件是Gazebo与ROS通信的桥梁,负责将激光数据发布到ROS话题
        -->
        <plugin name="laserscan" filename="libgazebo_ros_ray_sensor.so">
            <!-- ROS相关设置 -->
            <ros>
                <!-- 
                    命名空间设置:
                    - <namespace>/</namespace> 表示使用根命名空间
                    - 如果设为<namespace>/my_robot</namespace>,则话题会发布在/my_robot下
                -->
                <namespace>/</namespace>
                
                <!-- 
                    话题重映射:
                    - <remapping>~/out:=scan</remapping>
                    - 将插件默认输出话题"~out"重映射为标准的"scan"话题
                    - 在ROS中,激光数据通常发布在"/scan"话题
                    - 可以通过"rostopic echo /scan"查看数据
                -->
                <remapping>~/out:=scan</remapping>
            </ros>
            
            <!-- 
                指定输出消息类型:
                - <output_type>sensor_msgs/LaserScan</output_type>
                - 这是ROS中标准的激光扫描消息类型
                - 包含角度范围、距离数据等信息
            -->
            <output_type>sensor_msgs/LaserScan</output_type>
            
            <!-- 
                指定坐标系:
                - <frame_name>laser_link</frame_name>
                - 表示激光数据是相对于"laser_link"坐标系的
                - 在TF变换中非常重要,告诉ROS数据来自哪个坐标系
            -->
            <frame_name>laser_link</frame_name>
        </plugin>

        <!-- 传感器基本参数 -->
        
        <!-- 
            传感器激活状态:
            - <always_on>true</always_on>
            - true:即使没有订阅者,传感器也会持续生成数据
            - false:只有当有订阅者时才生成数据
            - 激光雷达通常设为true,保证数据连续
        -->
        <always_on>true</always_on>
        
        <!-- 
            可视化设置:
            - <visualize>true</visualize>
            - true:在Gazebo仿真界面中显示激光射线(绿色线条)
            - false:不显示激光射线(只生成数据,不显示)
            - 调试时设为true可以看到激光如何扫描环境
        -->
        <visualize>true</visualize>
        
        <!-- 
            传感器更新频率:
            - <update_rate>5</update_rate>
            - 设置为5Hz,即每秒扫描5次
            - 真实激光雷达通常10-20Hz,这里设得较低是为了节省计算资源
            - 值越大越流畅,但对电脑要求越高
        -->
        <update_rate>5</update_rate>
        
        <!-- 
            传感器位姿:
            - <pose>0 0 0 0 0 0</pose>
            - 表示传感器相对于"laser_link"的位姿(无偏移)
            - 格式:x y z roll pitch yaw(单位:米和弧度)
            - 这里全为0,表示传感器就在laser_link坐标系原点,无旋转
        -->
        <pose>0 0 0 0 0 0</pose>
                            
        <!-- 
            激光雷达核心参数(ray部分):
            - 模拟激光雷达如何发射和接收激光束
        -->
        <ray>
            <!-- 扫描参数设置 -->
            <scan>
                <horizontal>
                    <!-- 
                        水平方向采样点数:
                        - <samples>360</samples>
                        - 表示一圈扫描360个点
                        - 360个点意味着每1度一个测量点(360度/360点)
                        - 值越大分辨率越高,但数据量越大
                    -->
                    <samples>360</samples>
                    
                    <!-- 
                        水平角分辨率:
                        - <resolution>1.000000</resolution>
                        - 表示相邻两个测量点之间的角度间隔
                        - 1.0表示1弧度?不,这里有点误导
                        - 实际上,Gazebo中这个值通常设为1.0,真正的分辨率由samples和角度范围决定
                    -->
                    <resolution>1.000000</resolution>
                    
                    <!-- 
                        最小扫描角度:
                        - <min_angle>0.000000</min_angle>
                        - 扫描起始角度,0弧度=0度
                        - 通常设为0,从正前方开始扫描
                    -->
                    <min_angle>0.000000</min_angle>
                    
                    <!-- 
                        最大扫描角度:
                        - <max_angle>6.280000</max_angle>
                        - 扫描结束角度,6.28弧度≈360度(2π)
                        - 0到6.28弧度表示完整的360度扫描
                    -->
                    <max_angle>6.280000</max_angle>
                </horizontal>
            </scan>
            

            <!-- 测距参数设置 -->
            <range>
                <!-- 
                    最小检测距离:
                    - <min>0.120000</min>
                    - 0.12米=12厘米
                    - 激光雷达无法测量太近的物体,有"盲区"
                -->
                <min>0.120000</min>
                
                <!-- 
                    最大检测距离:
                    - <max>8.0</max>
                    - 8.0米
                    - 超过这个距离的物体将无法检测到
                    - 根据实际激光雷达性能设置(便宜的可能只有5-6米)
                -->
                <max>8.0</max>
                
                <!-- 
                    距离分辨率:
                    - <resolution>0.015000</resolution>
                    - 0.015米=1.5厘米
                    - 表示能区分的最小距离差
                    - 值越小精度越高,但真实传感器通常在厘米级
                -->
                <resolution>0.015000</resolution>
            </range>
            

            <!-- 噪声模型设置(模拟真实传感器的误差) -->
            <noise>
                <!-- 
                    噪声类型:
                    - <type>gaussian</type>
                    - 使用高斯噪声(正态分布),最常用的噪声模型
                -->
                <type>gaussian</type>
                
                <!-- 
                    噪声均值:
                    - <mean>0.0</mean>
                    - 0表示没有系统性偏差
                    - 如果设为0.01,表示测量值平均比真实值大1厘米
                -->
                <mean>0.0</mean>
                
                <!-- 
                    噪声标准差:
                    - <stddev>0.01</stddev>
                    - 0.01米=1厘米
                    - 表示噪声的波动范围,值越大测量越不准确
                    - 真实激光雷达通常有1-2厘米的标准差
                -->
                <stddev>0.01</stddev>
            </noise>
        </ray>
    </sensor>
</gazebo>
        
<!-- 
    这是一个Gazebo仿真环境中IMU(惯性测量单元)传感器的配置文件
    IMU用于测量机器人的角速度(陀螺仪)和线性加速度(加速度计)
    通常用于机器人姿态估计、导航等任务
-->
<gazebo reference="imu_link">
    <!-- 
        定义一个IMU传感器:
        - name="imu_sensor":传感器的名称,可以自定义
        - type="imu":指定传感器类型为IMU
        - 这个传感器将附着在URDF文件中定义的"imu_link"连杆上
    -->
    <sensor name="imu_sensor" type="imu">
        
        <!-- 
            ROS插件配置:
            - name="imu_plugin":插件的名称
            - filename="libgazebo_ros_imu_sensor.so":要加载的ROS插件文件
              这个文件是Gazebo与ROS通信的桥梁,负责将传感器数据发布到ROS话题
        -->
        <plugin name="imu_plugin" filename="libgazebo_ros_imu_sensor.so">
            <!-- ROS相关配置 -->
            <ros>
                <!-- 
                    命名空间设置:
                    - <namespace>/</namespace> 表示使用根命名空间
                    - 如果设为<namespace>/my_robot</namespace>,则话题会发布在/my_robot下
                -->
                <namespace>/</namespace>
                
                <!-- 
                    话题重映射:
                    - <remapping>~/out:=imu</remapping>
                    - 将插件默认输出话题"~out"(当前节点的私有话题)重映射为"/imu"
                    - 这样在ROS中可以通过"/imu"话题获取IMU数据
                -->
                <remapping>~/out:=imu</remapping>
            </ros>
            
            <!-- 
                初始方向参考设置:
                - <initial_orientation_as_reference>false</initial_orientation_as_reference>
                - false:使用绝对方向参考(相对于世界坐标系)
                - true:将启动时的方向作为参考零点(之后的测量都是相对于这个初始方向)
                - 通常设为false,除非你有特殊需求
            -->
            <initial_orientation_as_reference>false</initial_orientation_as_reference>
        </plugin>
        
        <!-- 
            传感器更新频率:
            - <update_rate>100</update_rate>
            - 设置为100Hz,即每秒发布100次数据
            - 根据实际IMU硬件性能设置,常见值有100Hz、200Hz等
        -->
        <update_rate>100</update_rate>
        
        <!-- 
            传感器激活状态:
            - <always_on>true</always_on>
            - true:即使没有订阅者,传感器也会持续生成数据
            - false:只有当有订阅者时才生成数据
            - 通常设为true以保证数据连续性
        -->
        <always_on>true</always_on>
        
        <!-- 
            IMU噪声模型配置:
            - 真实的IMU传感器都有噪声和偏差,这里模拟这些特性
            - 包含角速度(陀螺仪)和线性加速度(加速度计)两部分
        -->
        <imu>
            <!-- 角速度(陀螺仪)噪声设置 -->
            <angular_velocity>
                <!-- X轴角速度噪声 -->
                <x>
                    <noise type="gaussian">
                        <!-- 
                            高斯噪声参数:
                            - mean:噪声均值,0表示无系统性偏差
                            - stddev:标准差,值越大噪声越大(2e-4 = 0.0002 rad/s)
                            - bias_mean:偏置均值,模拟传感器固定偏差(0.0000075 rad/s)
                            - bias_stddev:偏置标准差,模拟偏置的随机变化(0.0000008 rad/s)
                        -->
                        <mean>0.0</mean>
                        <stddev>2e-4</stddev>
                        <bias_mean>0.0000075</bias_mean>
                        <bias_stddev>0.0000008</bias_stddev>
                    </noise>
                </x>
                <!-- Y轴角速度噪声(参数与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轴角速度噪声(参数与X轴相同) -->
                <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轴加速度噪声 -->
                <x>
                    <noise type="gaussian">
                        <!-- 
                            注意:加速度计的噪声参数通常比陀螺仪大
                            - stddev:1.7e-2 = 0.017 m/s²
                            - bias_mean:0.1 m/s²(较大的固定偏差)
                            - bias_stddev:0.001 m/s²
                        -->
                        <mean>0.0</mean>
                        <stddev>1.7e-2</stddev>
                        <bias_mean>0.1</bias_mean>
                        <bias_stddev>0.001</bias_stddev>
                    </noise>
                </x>
                <!-- Y轴加速度噪声(参数与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轴加速度噪声(参数与X轴相同) -->
                <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仿真环境中深度相机的配置文件
    深度相机可以同时获取彩色图像和深度信息(每个像素的距离)
    常用于机器人视觉、SLAM、物体识别等任务
    注意:虽然type="depth",但这里实际配置的是RGBD相机(彩色+深度)
-->
<gazebo reference="camera_link">
    <!-- 
        定义一个深度相机传感器:
        - type="depth":指定传感器类型为深度相机
        - name="camera_sensor":传感器的名称,可以自定义
        - 这个传感器将附着在URDF文件中定义的"camera_link"连杆上
        - 注意:在Gazebo中,type="depth"实际上会创建一个RGBD相机
    -->
    <sensor type="depth" name="camera_sensor">
        
        <!-- 
            ROS插件配置:
            - name="depth_camera":插件的名称,可以自定义
            - filename="libgazebo_ros_camera.so":要加载的ROS插件文件
              这个文件是Gazebo与ROS通信的桥梁,负责将相机数据发布到ROS话题
            
            注意:虽然文件名是"camera"而不是"depth_camera",但它同时支持深度相机
            深度信息会发布在/camera/depth/image_raw话题,彩色图像在/camera/image_raw
        -->
        <plugin name="depth_camera" filename="libgazebo_ros_camera.so">
            <!-- 
                相机光学坐标系:
                - <frame_name>camera_optical_link</frame_name>
                - 指定相机的光学坐标系名称
                - 光学坐标系是计算机视觉中使用的标准坐标系:
                  * +X 向右
                  * +Y 向下
                  * +Z 向前(指向场景)
                - 这与机器人常用的坐标系(+Z向上)不同,需要转换
            -->
            <frame_name>camera_optical_link</frame_name>
        </plugin>
        
        <!-- 
            传感器激活状态:
            - <always_on>true</always_on>
            - true:即使没有订阅者,相机也会持续生成数据
            - false:只有当有订阅者时才生成数据
            - 深度相机数据量大,有时会设为false节省资源,但通常设为true
        -->
        <always_on>true</always_on>
        
        <!-- 
            传感器更新频率:
            - <update_rate>10</update_rate>
            - 设置为10Hz,即每秒发布10帧图像
            - 深度相机数据量大,频率通常比IMU低(IMU可能100Hz,相机10-30Hz)
            - 根据计算资源和应用需求调整
        -->
        <update_rate>10</update_rate>
        
        <!-- 
            相机参数配置:
            - name="camera":相机的内部名称,一般不需要修改
        -->
        <camera name="camera">
            <!-- 
                水平视场角:
                - <horizontal_fov>1.5009831567</horizontal_fov>
                - 单位是弧度(radians),1.5弧度约等于86度
                - 视场角越大,看到的范围越广,但远处物体越小
                - 常见值:60-90度(约1.0-1.57弧度)
                - 计算公式:弧度 = 角度 × π/180
            -->
            <horizontal_fov>1.5009831567</horizontal_fov>
            
            <!-- 
                图像参数配置:
                - 定义输出图像的尺寸和格式
            -->
            <image>
                <!-- 
                    图像宽度:
                    - <width>800</width>
                    - 设置图像宽度为800像素
                    - 分辨率越高,细节越多,但数据量越大
                -->
                <width>800</width>
                
                <!-- 
                    图像高度:
                    - <height>600</height>
                    - 设置图像高度为600像素
                    - 常见分辨率:640x480, 800x600, 1280x720等
                -->
                <height>600</height>
                
                <!-- 
                    图像格式:
                    - <format>R8G8B8</format>
                    - 表示每个像素由8位红色、8位绿色、8位蓝色组成(24位真彩色)
                    - 其他常见格式:B8G8R8(OpenCV常用顺序)、L8(灰度图)等
                -->
                <format>R8G8B8</format>
            </image>
            
            <!-- 
                镜头畸变参数:
                - 真实相机镜头会有畸变,这里可以模拟或校正
                - 这里全部设为0,表示无畸变(理想相机)
            -->
            <distortion>
                <!-- 
                    径向畸变系数:
                    - k1, k2, k3:模拟镜头的桶形或枕形畸变
                    - 0表示无畸变
                    - 真实相机可能有非零值(如k1=0.1, k2=-0.01等)
                -->
                <k1>0.0</k1>
                <k2>0.0</k2>
                <k3>0.0</k3>
                
                <!-- 
                    切向畸变系数:
                    - p1, p2:模拟镜头与图像平面不平行导致的畸变
                    - 0表示无畸变
                -->
                <p1>0.0</p1>
                <p2>0.0</p2>
                
                <!-- 
                    畸变中心:
                    - <center>0.5 0.5</center>
                    - 表示畸变中心在图像中心(归一化坐标)
                    - 0.5 0.5 = (width/2, height/2)
                    - 如果设为0.4 0.6,则畸变中心在图像左下方
                -->
                <center>0.5 0.5</center>
            </distortion>
        </camera>
    </sensor>
</gazebo>


    </xacro:macro>
</robot>

🔧 雷达插件工作原理

你可以把这个插件想象成一个非常精密的激光手电筒,它的工作流程是这样的:

  1. 发射激光:插件模拟激光雷达,从 laser_link的位置向周围环境(在你的配置中是水平360度)发射出无数条看不见的激光束。

  2. 测量距离:Gazebo的物理引擎会计算每一条激光束是否打到了障碍物,以及从发射到返回用了多少时间,从而精确计算出机器人到障碍物的距离。

  3. 处理数据:将测量到的距离信息,结合你设置的噪声模型,处理成更真实的数据。

  4. 发布信息:最后,插件将所有数据打包成ROS标准格式的 LaserScan消息,并通过 /scan这个话题发布出去。这样,其他程序(比如导航或SLAM建图算法)就能订阅这个话题,知道机器人周围的环境是什么样的了。

🔧IMU 插件工作原理

IMU就是我们手机里的陀螺仪+加速度计的组合,能感知旋转和加速度

你可以把IMU想象成机器人的“耳朵”,它的工作流程非常直接:

  1. 感知运动:插件实时监测 imu_link这个连杆在Gazebo仿真世界中的运动变化。

  2. 测量数据

    • 陀螺仪angular_velocity):测量机器人绕X、Y、Z三个轴旋转的角速度(单位通常是弧度/秒)。比如,机器人左转时,Z轴的角速度值就会变化。

    • 加速度计linear_acceleration):测量机器人沿X、Y、Z三个轴方向的线性加速度(单位通常是米/秒²)。需要注意的是,它永远会测到重力加速度(约9.8 m/s²),当机器人加速前进时,X轴的加速度值会叠加变化。

  3. 添加真实感:根据你设置的噪声参数,在完美的理论数据上叠加各种误差,使数据更接近真实IMU的输出。

  4. 发布信息:最后,插件将所有数据打包成ROS标准格式的 Imu消息,并通过 /imu这个话题发布出去。这样,其他程序(比如用于稳定控制的算法或姿态解算器)就能订阅这个话题,知道机器人自身的运动状态了。

🔧 相机插件如何工作

这双“智能眼睛”的工作原理可以概括为以下几步:

  1. 感知环境:插件模拟深度相机,在Gazebo仿真环境中,从 camera_link的位置和朝向“观察”世界。

  2. 生成图像

    • RGB图像:生成普通的彩色图片,就像手机拍照一样,发布到类似 /camera/image_raw的话题。

    • 深度图像这是关键!对于视野内的每个物体,计算其与相机的距离,生成一张“距离图”(深度图)。这张图上每个像素的值不是颜色,而是距离(单位通常是米),并发布到类似 /camera/depth/image_raw的话题。

    • 点云数据:有时插件还会将深度信息转换成三维点云(PointCloud2),发布到类似 /camera/depth/points的话题,直观显示物体的三维轮廓。

  3. 发布信息:插件将上述数据打包成ROS标准消息,通过相应的话题发布出去。这样,其他程序(如视觉SLAM、物体识别、导航避障算法)就能订阅这些话题,让机器人“看懂”周围环境了。

 创建机器人执行(运动)部件:

只有传感器没有执行器,机器人不会动可不行,接下来我们给机器人添加执行器。执行器由驱动轮和万向轮组成。

(1)在actuator中新建caster.urdf.xacro文件,然后编写代码

1<?xml version="1.0"?>
2<robot xmlns:xacro="http://www.ros.org/wiki/xacro">


3    <!-- 包含惯性计算宏文件 -->
4    <!-- 这个文件提供了计算球体惯性矩阵的宏,用于简化物理计算 -->
5    <xacro:include filename="$(find fishbot_description)/urdf/fishbot/common_inertia.xacro" />
6
7    <!-- 定义一个名为caster_xacro的Xacro宏,用于创建万向轮(Caster Wheel) -->
8    <!-- 参数说明: -->
9    <!--   caster_name: 万向轮的名称(例如:front_caster) -->
10    <!--   xyz: 万向轮在机器人底盘(base_link)上的位置坐标(x y z) -->
11    <xacro:macro name="caster_xacro" params="caster_name xyz">
12        
13        <!-- 1. 创建万向轮的物理链接(轮子本身) -->
14        <link name="${caster_name}_caster_link">
15            <!-- 1.1 视觉模型(用于仿真中的显示) -->
16            <visual>
17                <!-- 位置和朝向(原点在轮子中心) -->
18                <origin xyz="0 0 0" rpy="0 0 0" />
19                <!-- 几何形状:球体 -->
20                <geometry>
21                    <sphere radius="0.016" />  <!-- 球体半径:1.6厘米 -->
22                </geometry>
23                <!-- 材质:黑色,透明度80% -->
24                <material name="black">
25                    <color rgba="0.0 0.0 0.0 0.8"/>
26                </material>
27            </visual>
28
29            <!-- 1.2 碰撞模型(用于物理仿真中的碰撞检测) -->
30            <collision>
31                <origin xyz="0 0 0" rpy="0 0 0" />
32                <geometry>
33                    <sphere radius="0.016" />
34                </geometry>
35                <material name="black">
36                    <color rgba="0.0 0.0 0.0 0.8"/>
37                </material>
38            </collision>
39            
40            <!-- 1.3 惯性计算(用于物理仿真中的质量分布) -->
41            <!-- 调用包含的宏:计算球体的惯性矩阵 -->
42            <xacro:sphere_inertia m="0.01" r="0.016" />
43            <!-- 参数说明: -->
44            <!--   m="0.01": 质量 0.01kg (10克) -->
45            <!--   r="0.016": 半径 0.016m (1.6厘米) -->
46        </link>
47
48        <!-- 2. 为万向轮添加Gazebo物理属性 -->
49        <gazebo reference="${caster_name}_caster_link">
50            <!-- 摩擦系数:设置为0.0,表示无摩擦 -->
51            <!-- 注意:万向轮通常需要低摩擦以实现自由旋转 -->
52            <mu1 value="0.0" />
53            <mu2 value="0.0" />
54            
55            <!-- 弹簧常数:非常大值(1e9),表示刚性约束 -->
56            <!-- 确保轮子不会穿透地面 -->
57            <kp value="1000000000.0" />
58            
59            <!-- 阻尼系数:1.0,用于控制旋转阻尼 -->
60            <kd value="1.0" />
61        </gazebo>
62
63        <!-- 3. 创建固定关节,将万向轮连接到机器人底盘 -->
64        <joint name="${caster_name}_caster_joint" type="fixed">
65            <!-- 父链接:机器人底盘 -->
66            <parent link="base_link" />
67            <!-- 子链接:万向轮 -->
68            <child link="${caster_name}_caster_link" />
69            <!-- 万向轮在底盘上的位置 -->
70            <origin xyz="${xyz}" />
71            <!-- 关节轴:固定关节,没有旋转自由度 -->
72            <axis xyz="0 0 0" />
73        </joint>
74
75    </xacro:macro>
76</robot>

🔄 作用: 万向轮的几何形状采用的是圆球形状,半径为0.016,关节部分采用的是固定的类型

(2)在fishbot目录下新建actuator子目录,然后在actuator下新建wheel.urdf.xacro

<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">


   <xacro:include filename="$(find fishbot_description)/urdf/fishbot/common_inertia.xacro" />


    <xacro:macro name="wheel_xacro" params="wheel_name xyz">

        <link name="${wheel_name}_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.8"/>
                </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.8"/>
                </material>
            </collision>
            <xacro:cylinder_inertia m="0.1" h="0.04" r="0.032"/>
        </link>

        <gazebo reference="${wheel_name}_wheel_link">
            <mu1 value="20.0" />
            <mu2 value="20.0" />
            <kp value="1000000000.0" />
            <kd value="1.0" />
        </gazebo>

        <joint name="${wheel_name}_wheel_joint" type="continuous">
            <parent link="base_link" />
            <child link="${wheel_name}_wheel_link" />
            <origin xyz="${xyz}" />
            <axis xyz="0 1 0" />
        </joint>

    </xacro:macro>
</robot>

🔄 作用:

完成驱动轮的定义,声明了名称为wheel_xacro的宏,参数是轮子名称和固定位置。

因为默认轮子是躺平状态,所以这里调整了轮子部件中rpy的值,将r部分调整成了1.57079rad,即90°,将轮子竖起来。

除此之外,在关节定义部分,关节类型采用连续关节continuous,该类型关节可以绕着某个轴进行无限制的旋转。在关节标签中添加了axis子标签,用于表示旋转轴和方向,<axis xyz="010"/>则表示绕y轴正方向旋转。

建模于此结束,接下来准备让机器人动起来


创建机器人控制系统: 使用ros2_control接入Gazebo,控制机器人运动

其实想要控制机器人运动,有两种方式,一种是直接使用插件,一种是使用ros2control系统。第二种方法才是主流,所以本文将采取这种方法。

gazebo_ros2_control对硬件资源的描述使用的也是XML格式,所以我们可以将配置写到URDF中,在src/fishbot_description/urdf/fishbot/中新建 fishbot.ros2_control.xacro,在该文件中编写代码

<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
    
    <!-- 
        定义一个Xacro宏:
        - name="fishbot_ros2_control":宏的名称
        - 宏就像一个代码模板,可以在其他URDF文件中被调用
    -->
    <xacro:macro name="fishbot_ros2_control">
        
        <!-- 
            ROS 2控制系统配置:
            - name="FishBotGazeboSystem":给这个控制系统起个名字
            - type="system":表示这是一个完整的系统控制系统,type也可以选取为传感器控制系统,或者执行器控制系统
            - ROS 2 Control是ROS 2的机器人控制框架,负责硬件与软件之间的通信
        -->
        <ros2_control name="FishBotGazeboSystem" type="system">
            
            <!-- 
                硬件接口配置:
                - 指定Gazebo仿真的硬件插件
                - plugin>gazebo_ros2_control/GazeboSystem</plugin>
                  这是连接Gazebo仿真器和ROS 2 Control的桥梁
            -->
            <hardware>
                <plugin>
                    gazebo_ros2_control/GazeboSystem
                </plugin>
            </hardware>
            
            <!-- 
                左轮关节配置:
                - name="left_wheel_joint":对应URDF中定义的左轮关节名称
                - 这里定义了如何控制左轮以及获取其状态
            -->
            <joint name="left_wheel_joint">
                
                <!-- 
                    速度命令接口:
                    - name="velocity":表示可以通过发送速度命令来控制轮子
                    - min/max:速度范围限制
                      * -1:最大后退速度
                      * 1:最大前进速度
                      * 单位可能是m/s或rad/s,取决于具体实现
                -->
                <command_interface name="velocity">
                    <param name="min">-1</param>
                    <param name="max">1</param>
                </command_interface>
                
                <!-- 
                    力矩命令接口:
                    - name="effort":表示可以通过发送力矩命令来控制轮子
                    - min/max:力矩范围限制
                      * -0.1:最大反向力矩
                      * 0.1:最大正向力矩
                      * 单位可能是牛·米(Nm)
                -->
                <command_interface name="effort">
                    <param name="min">-0.1</param>
                    <param name="max">0.1</param>
                </command_interface>
                
                <!-- 
                    状态接口(获取关节信息):
                    - position:可以获取轮子的当前位置(角度)
                    - velocity:可以获取轮子的当前速度
                    - effort:可以获取轮子的当前力矩
                    - 这些接口没有参数,只是声明可以获取这些状态
                -->
                <state_interface name="position" />
                <state_interface name="velocity" />
                <state_interface name="effort" />
            </joint>
            
            <!-- 
                右轮关节配置:
                - 与左轮配置完全相同
                - name="right_wheel_joint":对应URDF中定义的右轮关节名称
                - 差速驱动机器人需要独立控制左右两个轮子
            -->
            <joint name="right_wheel_joint">
                <command_interface name="velocity">
                    <param name="min">-1</param>
                    <param name="max">1</param>
                </command_interface>
                <command_interface name="effort">
                    <param name="min">-0.1</param>
                    <param name="max">0.1</param>
                </command_interface>
                <state_interface name="position" />
                <state_interface name="velocity" />
                <state_interface name="effort" />
            </joint>
        </ros2_control>
        
        <!-- 
            Gazebo仿真插件配置:
            - 这部分专门用于Gazebo仿真器
            - 连接ROS 2 Control与Gazebo仿真环境
        -->
        <gazebo>
            <!-- 
                加载Gazebo ROS 2控制插件:
                - filename="libgazebo_ros2_control.so":插件的共享库文件
                - name="gazebo_ros2_control":给插件起个名字
            -->
            <plugin filename="libgazebo_ros2_control.so" name="gazebo_ros2_control">
                
                <!-- 
                    控制器参数文件:
                    - <parameters>$(find fishbot_description)/config/fishbot_ros2_controller.yaml</parameters>
                    - 指定ROS 2控制器的配置文件路径
                    - $(find fishbot_description):ROS查找包路径的命令
                    - 这个YAML文件包含更详细的控制器参数,如PID增益、发布频率等
                -->
                <parameters>$(find fishbot_description)/config/fishbot_ros2_controller.yaml</parameters>
                
                <!-- 
                    ROS话题重映射:
                    - 将控制器内部话题重映射到标准话题名称
                    - 这样其他ROS节点可以使用标准话题名称与机器人交互
                -->
                <ros>
                    <!-- 
                        速度命令重映射:
                        - /fishbot_diff_drive_controller/cmd_vel_unstamped:=/cmd_vel
                        - 将控制器的内部速度命令话题重映射到标准的/cmd_vel话题
                        - 这样你只需发布到/cmd_vel话题就能控制机器人移动
                        - 例如:ros2 topic pub /cmd_vel geometry_msgs/Twist "{linear: {x: 0.2}, angular: {z: 0.5}}"
                    -->
                    <remapping>/fishbot_diff_drive_controller/cmd_vel_unstamped:=/cmd_vel</remapping>
                    
                    <!-- 
                        里程计重映射:
                        - /fishbot_diff_drive_controller/odom:=/odom
                        - 将控制器生成的里程计数据发布到标准的/odom话题
                        - /odom是ROS中标准的里程计话题名称
                        - 导航、SLAM等算法会订阅这个话题获取机器人位置
                    -->
                    <remapping>/fishbot_diff_drive_controller/odom:=/odom</remapping>
                </ros>
            </plugin>
        </gazebo>
    </xacro:macro>
</robot>

1. 这是什么?

  • 这是一个机器人控制系统配置文件,专门用于FishBot(一种差速驱动机器人)
  • 它连接了Gazebo仿真器ROS 2控制系统,让你能在Gazebo仿真器中使用ROS 2控制系统控制FishBot。定义了控制系统与FishBot的轮子交互(通信)的具体方式

2. 核心概念:ROS 2 Control

  • ROS 2 Control 是ROS 2的机器人控制框架
  • 就像机器人的"神经系统",负责:
    • 接收cmd的控制命令(如"前进0.5m/s")
    • 翻译命令,发送命令到硬件(或gazebo中的虚拟硬件)
    • 获取传感器反馈(如轮子转速)

3. 两个关键部分

A. ros2_control部分(定义接口)
  • 命令接口:你发送给机器人的指令

    • velocity:速度控制(-1到1,负值表示后退)
    • effort:力矩控制(-0.1到0.1,控制电机力量)
  • 状态接口:机器人反馈给你的信息

    • position:轮子转了多少角度
    • velocity:轮子当前转速
    • effort:轮子当前受力情况

想象一下汽车:

  • 命令接口 = 你踩油门/刹车的力度
  • 状态接口 = 速度表、转速表显示的信息
B. Gazebo插件部分(连接仿真)
  • 参数文件fishbot_ros2_controller.yaml

    • 包含更详细的控制参数(如PID控制器的增益)
  • 话题重映射

    • /cmd_vel:标准的"命令速度"话题

      • 你发送Twist(扭转)消息到这里控制机器人
      • 格式:{linear: {x: 0.2}, angular: {z: 0.5}}(前进+左转)
    • /odom:标准的"里程计"话题

      • 包含机器人在环境中的位置和朝向
      • 导航系统依赖这个数据

c. gazebo_ros2_control/GazeboSystem

想象你有一个机器人仿真世界(Gazebo) 和一个机器人控制中心(ROS 2 Control),它们说不同的"语言":

  • Gazebo 说:"我要模拟物理世界,知道每个关节的位置、速度和受力"
  • ROS 2 Control 说:"我想要控制机器人,需要发送命令并获取反馈"

gazebo_ros2_control/GazeboSystem 就是这两个系统之间的**专业翻译+通信中介**,让它们能够完美协作。

4. 实际工作流程

  1. 你发送命令

    ros2 topic pub /cmd_vel geometry_msgs/Twist "{linear: {x: 0.3}, angular: {z: 0.0}}"
    
    #将geometry_msgs/Twist格式消息linear: {x: 0.3}, angular: {z: 0.5}发送到cmd_vel话题频道

    (让机器人以0.3m/s前进,同时以0.5m/s速度左转)

  2. ROS 2 Control接收命令

    • 通过重映射,命令到达/cmd_vel话题
    • 控制器计算左右轮应该以什么速度转动才能实现0.3m/s前进,同时以0.5m/s速度左转。并将命令发送给gazebo
  3. Gazebo仿真执行

    • 插件将速度命令发送给Gazebo中的虚拟电机
    • Gazebo模拟轮子转动,推动机器人在虚拟环境中移动
  4. 反馈循环

    • Gazebo计算轮子的实际位置和速度
    • 通过状态接口反馈给ROS 2 Control
    • 生成里程计数据发布到/odom话题

Q:如果我想让机器人跑得更快怎么办? 

A:可以修改max值(如改为2),但也要相应调整控制器配置文件中的参数,否则可能导致不稳定。

在功能包的config目录下新建fishbot_ros2_controller.yaml,然后输入代码:

# 这是ROS 2控制器管理器的配置文件
# 控制器管理器就像机器人的"大脑中枢",负责协调所有控制器
# 整个文件分为三大部分:全局设置、力矩控制器、差速驱动控制器

# ------------------------------
# 第一部分:控制器管理器全局配置
# ------------------------------
controller_manager:
  ros__parameters:
    # 控制循环更新频率:100 Hz(每秒执行100次控制计算)
    # 值越大控制越精确,但对计算资源要求越高
    update_rate: 100  # Hz
    
    # 使用仿真时间标志,确保控制与仿真同步
    # true:使用Gazebo提供的仿真时间(不是电脑系统时间)
    use_sim_time: true
    
    # 关节状态广播器配置 -负责发布所有关节的状态
    fishbot_joint_state_broadcaster:
      # 插件类型:标准的关节状态广播器
      type: joint_state_broadcaster/JointStateBroadcaster
      # 同样使用仿真时间
      use_sim_time: true
    
    # 力矩控制器配置-控制电机
    fishbot_effort_controller:
      # 插件类型:关节组力矩控制器,用于同时控制多个关节的力矩输出
      type: effort_controllers/JointGroupEffortController
    
    # 差速驱动控制器配置 - 专为两轮差速驱动机器人设计
    fishbot_diff_drive_controller:
      # 插件类型:差速驱动控制器, 将速度命令转换为左右轮的速度
      type: diff_drive_controller/DiffDriveController

# ------------------------------
# 第二部分:力矩控制器配置
# ------------------------------
fishbot_effort_controller:
  ros__parameters:
    # 要控制的关节列表,这里指定控制左轮和右轮两个关节
    joints:
      - left_wheel_joint  # 左轮关节名称(必须与URDF中一致)
      - right_wheel_joint # 右轮关节名称(必须与URDF中一致)
    
    # 命令接口类型 - 我们要发送什么类型的控制命令,这里选择"effort"(力矩控制),即直接控制电机输出力矩
    command_interfaces:
      - effort  # 也可以是"velocity"(速度控制)或"position"(位置控制)
    
    # 状态接口类型 - 我们要获取什么类型的反馈,这里获取位置、速度和力矩三方面信息
    state_interfaces:
      - position  # 关节当前位置(角度)
      - velocity  # 关节当前速度(角速度)
      - effort    # 关节当前受力(力矩)

# ------------------------------
# 第三部分:差速驱动控制器配置
# ------------------------------
fishbot_diff_drive_controller:
  ros__parameters:
    # 左轮关节名称列表,差速驱动机器人通常有1个或多个左轮,这里只有一个
    left_wheel_names: ["left_wheel_joint"]
    
    # 右轮关节名称列表,同样,这里只有一个右轮
    right_wheel_names: ["right_wheel_joint"]

    # 两轮之间的距离(米),从FishBot的URDF模型测量得到,这个值影响转弯半径,非常重要!
    wheel_separation: 0.17  # 17厘米
    
    # 轮子半径(米),从FishBot实际测量或URDF中获取,这个值影响速度计算,非常重要!
    wheel_radius: 0.032  # 3.2厘米

    # 轮间距乘数(通常为1.0,用于微调)
    wheel_separation_multiplier: 1.0
    
    # 左右轮半径乘数(通常为1.0,用于微调)
    left_wheel_radius_multiplier: 1.0
    right_wheel_radius_multiplier: 1.0

    # 里程计发布频率:50 Hz,每秒发布50次机器人位置信息
    publish_rate: 50.0
    
    # 里程计坐标系名称   odom 是ROS中标准的里程计坐标系
    odom_frame_id: odom
    
    # 机器人基座坐标系名称  , /base_footprint 通常指机器人底部中心点
    base_frame_id: base_footprint
    
    # 位置估计的不确定性(协方差对角线),值越小表示估计越准确, [x, y, z, roll, pitch, yaw]
    pose_covariance_diagonal : [0.001, 0.001, 0.0, 0.0, 0.0, 0.01]
    
    # 速度估计的不确定性(协方差对角线),[vx, vy, vz, wx, wy, wz]
    twist_covariance_diagonal: [0.001, 0.0, 0.0, 0.0, 0.0, 0.01]

    # 开环控制模式
    # true:不使用反馈校正(简单但可能有累积误差)
    # false:使用反馈校正(更精确但需要更多传感器)
    open_loop: true
    
    # 是否发布odom到base_footprint的TF变换
    # true:发布,这样其他节点可以看到机器人位置
    enable_odom_tf: true

    # 速度命令超时时间(秒),如果超过0.5秒没收到新命令,机器人会停止, 防止命令丢失导致机器人一直运动
    cmd_vel_timeout: 0.5
    
    # 是否使用带时间戳的速度命令
    # false:使用当前时间(更简单)
    # true:使用消息中的时间戳(更精确,但需要同步)
    use_stamped_vel: false
    
    # 注意:以下参数被注释掉了,表示不使用
    # publish_limited_velocity: true  # 发布限制后的速度
    # velocity_rolling_window_size: 10  # 速度计算的滑动窗口大小

三个关键控制器

A. 关节状态广播器 (JointStateBroadcaster)
  • 作用:从Gazebo仿真环境中获取关节状态(位置、速度等)
  • 类比:就像汽车的转速表和里程表,实时显示车轮状态
  • 重要性:(1)将这些状态转换为ROS 2标准的sensor_msgs/msg/JointState格式发布到/joint_states话题,方便导航系统获取到这些数据知道机器人在哪。(2)与robot_state_publisher协作robot_state_publisher订阅/joint_states话题,结合URDF模型计算机器人的坐标变换,发布到TF系统供其他节点使用。

fishbot_joint_state_broadcaster 如何“看到”关节状态?

第一步:Gazebo 仿真引擎驱动关节运动

  • 当你在 Gazebo 中运行机器人时,Gazebo 物理引擎会根据力、速度等计算每个关节的 位置(position)、速度(velocity)、力矩(effort)
  • 这些数据存在于 Gazebo 内部

第二步:gazebo_ros2_control/GazeboSystem 插件读取 Gazebo 状态

  • 在你的 URDF/Xacro 中,有如下配置:
    <hardware>
      <plugin>gazebo_ros2_control/GazeboSystem</plugin>
    </hardware>
  • 启动仿真时,Gazebo 会加载 libgazebo_ros2_control.so 插件
  • 该插件会:
    • 订阅 Gazebo 的关节状态(通过 Gazebo 的 C++ API)
    • 将这些状态写入 ROS 2 Control 的 state_interface

✅ 关键点:GazeboSystem 是唯一与 Gazebo 直接交互的部分。

第三步:ROS 2 Control 框架维护一个“硬件抽象层”

  • 在 ros2_control 配置中,你为每个关节声明了状态接口:
    <joint name="left_wheel_joint">
      <state_interface name="position"/>
      <state_interface name="velocity"/>
      <state_interface name="effort"/>
    </joint>
  • GazeboSystem 在每次仿真步(simulation step)中:
    • 从 Gazebo 读取实际的 positionvelocityeffort
    • 更新到对应的 state_interface 中

第四步:joint_state_broadcaster 从 ROS 2 Control 读取状态

  • joint_state_broadcaster 是一个 只读控制器(read-only controller)
  • 它不发送命令,只读取已注册的 state_interface
  • 启动后,它会:
    • 遍历所有在 ros2_control 中声明的关节
    • 从 state_interface 中提取 positionvelocityeffort
    • 打包成 sensor_msgs/msg/JointState 消息
    • 发布到 /joint_states 话题

🔄 所以:
Gazebo → GazeboSystemstate_interfacejoint_state_broadcaster/joint_states

B. 力矩控制器 (EffortController)
  • 作用:直接控制电机输出多大的力量
  • 类比:就像直接控制汽车油门的深度(而不是控制速度)
  • 使用场景:高级控制或需要精确力控制时使用
C. 差速驱动控制器 (DiffDriveController) - 最常用
  • 作用:将"前进0.3m/s,左转0.5rad/s"转换为左右轮速度
  • 类比:就像汽车的"驾驶系统",你控制方向盘和油门,它自动分配左右轮动力
  • 为什么重要:你通常只用这个控制器,发送/cmd_vel命令即可


组合机器人各部件与控制功能:

将各个部件组合起来,在urdf/fishbot/下新建fishbot.urdf.xacro,然后编写如代码

作用: 通过xacro:include标签来包含其他的Xacro文件,通过该标签的filename属性可以指定要包含的Xacro文件的文件名称,需要注意的是,通过$(find fishbot_description)可以查找功能包的安装目录。关于传感器位置值的设定,文件中的位置可作为参考,你也可以根据显示效果进行调整。


 创建launch文件,启动节点,运行仿真:

在launch文件夹创建gazebo_sim.launch.py文件如下,运行,在gazebo中观察与运行机器人

import launch  # 导入ROS2的启动管理模块,用于创建和管理启动描述
import launch_ros  # 导入ROS2的ROS特定启动管理模块
from ament_index_python.packages import get_package_share_directory  # 用于查找ROS包的共享目录路径
from launch.launch_description_sources import PythonLaunchDescriptionSource  # 用于指定包含的启动文件是Python格式

def generate_launch_description():  # 这是ROS2启动文件的必需函数,ROS2会自动调用这个函数来生成启动描述
    # 1. 设置机器人相关参数变量
    robot_name_in_model = "fishbot"  # 机器人在Gazebo中的名称(就像给机器人起个名字)
    urdf_tutorial_path = get_package_share_directory('fishbot_description')  # 获取fishbot_description包的共享目录路径
    default_model_path = urdf_tutorial_path + '/urdf/fishbot/fishbot.urdf.xacro'  # 机器人URDF模型的默认路径(xacro是URDF的扩展格式)
    default_world_path = urdf_tutorial_path + '/world/custom_room.world'  # Gazebo世界文件的默认路径(定义了Gazebo中的环境)

    # 2. 声明启动参数(允许用户在启动时修改模型路径)
    #等价于ros2 launch fishbot_description fishbot.launch.py   model:=/path/to/your/model.urdf.xacro
    action_declare_arg_mode_path = launch.actions.DeclareLaunchArgument(
        name='model',  # 参数名称,用户可以通过ros2 launch命令指定这个参数
        default_value=str(default_model_path),  # 默认值,使用上面定义的默认路径
        description='URDF的绝对路径'  # 参数说明,告诉用户这个参数是做什么的
    )

    # 3. 处理URDF文件(将xacro文件转换为URDF格式)
    # ROS2的robot_state_publisher节点只能处理标准的URDF格式,而不能直接处理Xacro文件。所以我们需要在启动过程中将Xacro文件转换为URDF。
    #substitutions意思是替代,将xacro文件替换成urdf文件
    #LaunchConfiguration('model')作用是:获取之前通过DeclareLaunchArgument声明的参数model的值。,返回用户指定的路径/path/to/custom_model.urdf.xacro
    #等价于 xacro /urdf/fishbot/fishbot.urdf.xacro
    robot_description = launch_ros.parameter_descriptions.ParameterValue(
        launch.substitutions.Command(  # 用于执行命令的替代方式
            ['xacro ', launch.substitutions.LaunchConfiguration('model')]  # 构建xacro命令,'model'是上面声明的参数
        ),
        value_type=str  # 转换后的URDF内容被作为字符串传递给robot_state_publisher节点,robot_state_publisher节点使用这个URDF来发布TF变换和关节状态
    )

    # 4. 创建机器人状态发布节点(发布机器人的TF变换和关节状态)
    robot_state_publisher_node = launch_ros.actions.Node(
        package='robot_state_publisher',  # 节点所属的包
        executable='robot_state_publisher',  # 节点的可执行文件
        parameters=[{'robot_description': robot_description}]  # 传递参数,将处理后的URDF模型作为robot_description参数
    )

    # 5. 启动Gazebo仿真环境
    # 等价于ros2 launch gazebo_ros gazebo.launch.py world:=/path/to/world.world verbose:=true
    launch_gazebo = launch.actions.IncludeLaunchDescription(
        PythonLaunchDescriptionSource(  # 指定包含的启动文件是Python格式
            [get_package_share_directory('gazebo_ros'), '/launch', '/gazebo.launch.py']  # 构建gazebo.launch.py的路径
        ),
        # 传递参数给gazebo.launch.py
        launch_arguments=[('world', default_world_path), ('verbose', 'true')]  # 设置Gazebo的世界文件和详细输出
    )

    # 6. 在Gazebo中加载机器人
    #等价于ros2 run gazebo_ros spawn_entity.py -topic /robot_description -entity fishbot
    spawn_entity_node = launch_ros.actions.Node(
        package='gazebo_ros',  # 包名
        executable='spawn_entity.py',  # 可执行文件
        arguments=['-topic', '/robot_description',  # 从哪个topic获取机器人描述
                   '-entity', robot_name_in_model]  # 机器人在Gazebo中的名称
    )
    
    # 7. 加载并激活关节状态广播控制器(用于发布关节状态)
    #等价于ros2 control load_controller --set-state active fishbot_joint_state_broadcaster
    load_joint_state_controller = launch.actions.ExecuteProcess(
        cmd=['ros2', 'control', 'load_controller', '--set-state', 'active',
             'fishbot_joint_state_broadcaster'],  # 执行ros2控制命令
        output='screen'  # 将输出打印到屏幕
    )

    # 8. 加载并激活努力控制器(用于控制机器人关节力矩)
    load_fishbot_effort_controller = launch.actions.ExecuteProcess(
        cmd=['ros2', 'control', 'load_controller', '--set-state', 'active',
             'fishbot_effort_controller'], 
        output='screen'   #verbose参数:设置为true,启用详细输出
    )

    # 9. 加载并激活差速驱动控制器(用于控制机器人的运动)
    # 等价于ros2 control load_controller --set-state active fishbot_effort_controller
    load_fishbot_diff_drive_controller = launch.actions.ExecuteProcess(
        cmd=['ros2', 'control', 'load_controller', '--set-state', 'active',
             'fishbot_diff_drive_controller'],   #加载后立即激活控制器
        output='screen'
    )

    # 10. 返回启动描述(定义启动顺序和事件)
    #这段代码定义了整个启动流程的执行顺序,确保机器人在Gazebo中正确加载后,再按顺序加载必要的控制器。
  return launch.LaunchDescription([
    action_declare_arg_mode_path,  # 声明模型路径参数(如:/path/to/model.urdf.xacro)
    robot_state_publisher_node,  # 发布机器人模型和TF变换的节点(必须最先启动)
    launch_gazebo,  # 启动Gazebo仿真环境(需要机器人模型才能加载机器人)
    spawn_entity_node,  # 在Gazebo中加载机器人实体(需要Gazebo已启动)
    
    # 事件处理器1:当机器人实体加载完成(spawn_entity_node结束)后
    # 执行加载关节状态控制器
    launch.actions.RegisterEventHandler(
        event_handler=launch.event_handlers.OnProcessExit(
            target_action=spawn_entity_node,  # 监听的目标动作:机器人实体加载
            on_exit=[load_joint_state_controller],  # 当目标动作结束后执行的动作
        )
    ),
    
    # 事件处理器2:当关节状态控制器加载完成(load_joint_state_controller结束)后
    # 执行加载差速驱动控制器
    launch.actions.RegisterEventHandler(
        event_handler=launch.event_handlers.OnProcessExit(
            target_action=load_joint_state_controller,  # 监听的目标动作:关节状态控制器加载
            on_exit=[load_fishbot_diff_drive_controller],  # 当目标动作结束后执行的动作
        )
    ),
    
    # 事件处理器3:当差速驱动控制器加载完成(load_fishbot_diff_drive_controller结束)后
    # 执行加载努力控制器
    launch.actions.RegisterEventHandler(
        event_handler=launch.event_handlers.OnProcessExit(
            target_action=load_fishbot_diff_drive_controller,  # 监听的目标动作:差速驱动控制器加载
            on_exit=[load_fishbot_effort_controller],  # 当目标动作结束后执行的动作
        )
    )
])
launch.actions.ExecuteProcess:这是ROS2中用于执行外部命令的类,cmd与output是他的属性,

package、executable和arguments确实是launch_ros.actions.Node类的属性。

在ROS2中,robot_state_publisher节点会发布/robot_description话题,包含机器人的URDF模型。spawn_entity.py需要从这个话题获取模型信息,然后在Gazebo中创建机器人实体。



问题:
# 4. 创建机器人状态发布节点(发布机器人的TF变换和关节状态)
    robot_state_publisher_node = launch_ros.actions.Node(
        package='robot_state_publisher',  # 节点所属的包
        executable='robot_state_publisher',  # 节点的可执行文件
        parameters=[{'robot_description': robot_description}]  # 传递参数,将处理后的URDF模型作为robot_description参数
    )是如何接收到urdf对应的字符串文件的?

答案:
1. 启动文件系统的详细工作流程
启动文件系统不是直接传递"robot_description"这个变量,而是:

看到 parameters=[{'robot_description': robot_description}]
知道 robot_description 是一个 ParameterValue 对象
执行 ParameterValue 对象中存储的命令:
xacro /home/user/robot_description/urdf/fishbot/fishbot.urdf.xacro
获取命令的输出(URDF字符串)
设置 robot_description 参数的值为这个URDF字符串
2. 机器人组装机(robot_state_publisher)的工作
启动时,从ROS参数服务器请求 robot_description 参数
收到参数的值(URDF字符串)
使用这个URDF字符串来发布TF变换和关节状态


5. 实际工作流程
步骤1:机器人启动
机器人模型(URDF)和TF变换已经发布(通过robot_state_publisher)
Gazebo仿真环境已经启动(通过launch_gazebo)
步骤2:加载并激活控制器
ros2 control load_controller --set-state active fishbot_effort_controller
控制器管理器(controller_manager)收到指令
加载名为fishbot_effort_controller的控制器
立即激活该控制器
步骤3:控制器开始工作
控制器开始接收关节力矩指令
机器人关节开始根据指令移动


ROS2控制器如何接收指令及开始工作
控制器接收指令的来源
在ROS2中,控制器(如努力控制器)接收指令的来源是ROS2话题(Topic)。具体来说:

努力控制器(Effort Controller) 通常订阅一个特定的话题,例如:
/fishbot_effort_controller/command(控制器名称+/command)
或者更通用的 /command(取决于控制器的配置)
话题类型:std_msgs/msg/Float64MultiArray,其中包含每个关节的目标力矩值
控制器开始工作的完整流程
1. 控制器加载与激活
load_fishbot_effort_controller = launch.actions.ExecuteProcess(
    cmd=['ros2', 'control', 'load_controller', '--set-state', 'active',
         'fishbot_effort_controller'], 
    output='screen'
)
这个命令会加载并激活fishbot_effort_controller
2. 控制器开始监听话题
当控制器被激活后,它会:

向ROS2系统注册自己
开始监听其配置的ROS话题(例如/fishbot_effort_controller/command)
准备接收力矩指令
3. 指令发送(关键步骤)
控制器开始工作的关键在于有节点向其订阅的话题发布指令。例如:
# 发送关节力矩指令到控制器
ros2 topic pub /fishbot_effort_controller/command std_msgs/msg/Float64MultiArray "{data: [1.0, 1.0, -1.0, -1.0]}"
这个命令会向控制器发送力矩指令,其中[1.0, 1.0, -1.0, -1.0]表示每个关节的目标力矩值。

通俗类比
想象一下你有一个遥控机器人手臂:
控制器 = 机器人手臂的"控制板"
它需要被"打开"(激活)
打开后,它会"等待"你的遥控信号
话题 = 遥控信号的传输通道
例如:/fishbot_effort_controller/command
就像无线电频率,控制板只监听这个频率
指令 = 你的遥控信号
例如:[1.0, 1.0, -1.0, -1.0] 表示"让关节1和2向前转,关节3和4向后转"
就像你按遥控器上的按钮
工作流程:
你先"打开"控制板(加载并激活控制器)
然后你"按下按钮"(发布力矩指令)
控制板"接收信号"并"移动关节"


核心语法结构
python
编辑
launch.actions.RegisterEventHandler(
    event_handler=launch.event_handlers.OnProcessExit(
        target_action=spawn_entity_node,
        on_exit=[load_joint_state_controller],
    )
)

逐层解析
1. RegisterEventHandler - 这是ROS2启动文件中用于注册事件处理器的类。它的作用是告诉启动系统:"当特定事件发生时,请执行指定的动作"。

参数:event_handler=... - 指定要注册的事件处理器
返回:注册了事件处理器的启动描述
2. OnProcessExit - 事件处理器类型,这是ROS2中的一种事件处理器,表示"当某个进程退出时触发"。
参数:
target_action=...:指定要监听的目标动作
on_exit=...:指定当目标动作退出时要执行的动作


spawn_entity.py的执行顺序非常重要:

首先需要robot_state_publisher发布/robot_description话题
然后spawn_entity.py才能从这个话题获取模型信息
最后在Gazebo中加载机器人
在启动文件中,通过事件处理(RegisterEventHandler)确保了正确的执行顺序:
launch.actions.RegisterEventHandler(
    event_handler=launch.event_handlers.OnProcessExit(
        target_action=spawn_entity_node,
        on_exit=[load_joint_state_controller],
    )
)
这确保了在机器人加载完成后(spawn_entity_node执行完毕)才加载关节状态控制器。

在launch文件夹创建display_robot.launch.py文件如下,在rviz中查看一下机器人建模

import launch
import launch_ros
from ament_index_python.packages import get_package_share_directory


def generate_launch_description():
    # 获取默认路径
    urdf_tutorial_path = get_package_share_directory('fishbot_description')
    default_model_path = urdf_tutorial_path + '/urdf/first_robot.urdf'
    default_rviz_config_path = urdf_tutorial_path + '/config/rviz/display_model.rviz'
    # 为 Launch 声明参数
    action_declare_arg_mode_path = launch.actions.DeclareLaunchArgument(
        name='model', default_value=str(default_model_path),
        description='URDF 的绝对路径')
    # 获取文件内容生成新的参数
    robot_description = launch_ros.parameter_descriptions.ParameterValue(
        launch.substitutions.Command(
            ['xacro ', launch.substitutions.LaunchConfiguration('model')]),
        value_type=str)
    # 状态发布节点
    robot_state_publisher_node = launch_ros.actions.Node(
        package='robot_state_publisher',
        executable='robot_state_publisher',
        parameters=[{'robot_description': robot_description}]
    )
    # 关节状态发布节点
    joint_state_publisher_node = launch_ros.actions.Node(
        package='joint_state_publisher',
        executable='joint_state_publisher',
    )
    # RViz 节点
    rviz_node = launch_ros.actions.Node(
        package='rviz2',
        executable='rviz2',
        arguments=['-d', default_rviz_config_path]
    )
    return launch.LaunchDescription([
        action_declare_arg_mode_path,
        joint_state_publisher_node,
        robot_state_publisher_node,
        rviz_node
    ])

 配置“package.xml”和“CMakeLists.txt”

编译,配置环境变量

运行代码进行仿真:

ros2 launch fishbot_description gazebo_sim.launch.py

ros2 topic pub /cmd_vel geometry_msgs/Twist "{linear: {x: 0.3}, angular: {z: 0.0}}"   #差速控制

ros2 topic pub /fishbot_effort_controller/commands std_msgs/msg/Float64MultiArray "{data: [0.0001, 0.0001]}"  #力控,记得把差速控诉取消激活先

启动gazebo后,打开新终端,在rviz中打开机器中模型进行观察:

ros2 launch fishbot_description display_robot.launch.py model:=/home/crj/ros2bookcode/chapt6/chapt6_ws/src/fishbot_description/urdf/fishbot/fishbot.urdf.xacro    #把模型路径更换为你自己的

 在rviz中显示雷达点云与相机点云:

整体架构回顾

在 ROS 2 + Gazebo 的仿真系统中,关键组a

  1. URDF/Xacro 文件:定义机器人模型(包括关节名称、类型等)
  2. gazebo_ros2_control 插件:Gazebo ↔ ROS 2 Control 的桥梁
  3. ros2_control 系统配置(如你之前提到的第二段代码):声明哪些关节、接口可用
  4. Controller Manager(控制器管理器):加载并运行控制器(如 joint_state_broadcaster
  5. fishbot_joint_state_broadcaster 控制器:读取关节状态并发布到 /joint_states
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值