ROS2学习笔记本2-仿真软件与避障机器人实例

| |

仿真机器人实例已上传至本网站,点击此处下载
原写于2023秋,2024年夏稍有修改
这是笔记本,不是教程。
几位耶jiweiyecau@163.com

机械、物理、数学--仿真软件前瞻

这些建模与仿真平台有很多相似的概念。
solidworks:机械设计,达索公司
CATIA:汽车飞机船舶,曲面设计,达索公司
PTC proe(Creo):CAD、CAM、CAE,模具设计,参数化设计,PTC公司
UG-NX:汽车、模具,西门子公司
AotuCAD:CAD二维,Autodesk公司
Edgecam:数控编程

Adams:动力学和运动学仿真
Ansys系列:有限元
Abaqus(达索系列)
comsol:有限元,偏微分方程

matlab:数值分析、数学建模等,几乎无所不能

机器人仿真平台:gazebo, webots, unity等

Gazebo软件安装及简介

shell

# 安装gazebo仿真软件
sudo apt install gazebo
# 安装gazebo-ros功能包(所有)
sudo apt install ros-humble-gazebo-*
# 安装gazebo_ros插件
sudo apt install ros-humble-gazebo-ros
# 测试是否安装成功
gazebo --verbose -s libgazebo_ros_init.so -s libgazebo_ros_factory.so 

ubuntu系统中,可以按Alt-F2,键入Gazebo,然后按Enter键。
常规操作有:“shift+鼠标左键”转换视角,“鼠标左键”平移视角,“滚轮”缩放大小。

Gazebo was started in 2002. After over 15 years of development it was time for a significant upgrade and modernization. This upgrade also provided the opportunity to move away from a monolithic architecture to a collection of loosely coupled libraries. We branded the new development effort Igntion in order to differentiate it from Gazebo that had come before, which is now referred to as Gazebo Classic. Those changes have gone very well, thanks in no small part to contributions and support from our worldwide community of users and other stakeholders.
In 2022 we encountered a trademark obstacle regarding the use of the name "Ignition". We used this as an opportunity to switch back the widely known name "Gazebo". Going forward, the modern robotics software collection formerly known as Ignition, is now branded Gazebo.
Gazebo成立于2002年。经过15年的发展,现在是进行重大升级和现代化的时候了。这种升级还提供了一个机会,可以从单一的体系结构转移到松散耦合的库集合。 我们将新的开发工作命名为Ignation,以将其与之前的Gazebo区别开来,后者现在被称为Gazebo Classic。这些变化进展顺利,这在很大程度上要归功于我们全球用户社区和其他利益相关者的贡献和支持。
2022年,我们在使用“Ignition”这一名称时遇到了商标障碍。我们以此为契机,重新使用了广为人知的名称“Gazebo”。展望未来,以前被称为Ignition的现代机器人软件系列现在被命名为Gazebo.

现在的名称:Gazebo
参阅:
官网下载及安装指南
鱼香ROS【安装Gazebo】

建模

Gazebo创建简单模型

参考:
Gazebo 11新手课程指南——初级教程(三) - 深圳季连AIGRAPHX的文章 - 知乎
Gazebo官方教程入门篇(二)模型编辑器 - Bruce的文章 - 知乎
构建和修改机器人,包括构建轮式机器人、安装传感器、安装执行器和启动模型动画。
SDF模型的范围可以从简单的形状到复杂的机器人。它指的是<model> SDF标签,本质上是链接(links)、关节(joints)、碰撞对象(collision objects)、视觉(visuals)和插件(plugins)的集合。步骤:

  1. 添加一个链接。
  2. 设置碰撞元素。
  3. 设置视觉元素。
  4. 设置惯性属性。
  5. 回到#1,直到添加了所有链接。
  6. 添加所有关节(如果有的话)。
  7. 添加所有插件(如果有的话)

URDF模型设计

参考:
Building a visual robot model from scratch
XML Specifications wiki.ros.org

单个link机器人模型

xml

<?xml version="1.0"?>
<robot name="myfirst">
  <link name="base_link">
    <visual>
      <geometry>
        <cylinder length="0.6" radius="0.2"/>
      </geometry>
    </visual>
  </link>
</robot>

多link间通过joint连接

The joint is defined in terms of a parent and a child. URDF is ultimately a tree structure with one root link. This means that the leg’s position is dependent on the base_link’s position.
Big Boat

xml

<?xml version="1.0"?>
<robot name="multipleshapes">
  <link name="base_link">
    <visual>
      <geometry>
        <cylinder length="0.6" radius="0.2"/>
      </geometry>
    </visual>
  </link>

  <link name="right_leg">
    <visual>
      <geometry>
        <box size="0.6 0.1 0.2"/>
      </geometry>
    </visual>
  </link>

  <joint name="base_to_right_leg" type="fixed">
    <parent link="base_link"/>
    <child link="right_leg"/>
  </joint>

</robot>

origins参数

xml

<?xml version="1.0"?>
<robot name="origins">
  <link name="base_link">
    <visual>
      <geometry>
        <cylinder length="0.6" radius="0.2"/>
      </geometry>
    </visual>
  </link>

  <link name="right_leg">
    <visual>
      <geometry>
        <box size="0.6 0.1 0.2"/>
      </geometry>
      <origin rpy="0 1.57075 0" xyz="0 0 -0.3"/>
    </visual>
  </link>

  <joint name="base_to_right_leg" type="fixed">
    <parent link="base_link"/>
    <child link="right_leg"/>
    <origin xyz="0 -0.22 0.25"/>
  </joint>

</robot>

material color参数

xml

  <material name="blue">
    <color rgba="0 0 0.8 1"/>
  </material>

  <material name="white">
    <color rgba="1 1 1 1"/>
  </material>

  <link name="base_link">
    <visual>
      <geometry>
        <cylinder length="0.6" radius="0.2"/>
      </geometry>
      <material name="blue"/>
    </visual>
  </link>

导入外部模型文件

The meshes here were borrowed from the PR2. They are separate files which you have to specify the path for. You should use the package://NAME_OF_PACKAGE/path notation. The meshes for this tutorial are located within the urdf_tutorial package, in a folder called meshes.

xml

<link name="left_gripper">
  <visual>
    <origin rpy="0.0 0 0" xyz="0 0 0"/>
    <geometry>
      <mesh filename="package://urdf_tutorial/meshes/l_finger.dae"/>
    </geometry>
  </visual>
</link>

The meshes can be imported in a number of different formats. STL is fairly common, but the engine also supports DAE, which can have its own color data, meaning you don’t have to specify the color/material. Often these are in separate files. These meshes reference the .tif files also in the meshes folder.
Meshes can also be sized using relative scaling parameters or a bounding box size.
We could have also referred to meshes in a completely different package.

机器人关节设计

连续旋转关节

连续旋转关节可以呈现从负无穷大到正无穷大的任何角度。车轮也是这样建模的,因此它们可以永远向两个方向滚动。
我们必须添加的唯一附加信息是旋转轴,此处由 xyz 三元组指定,它指定头部将围绕其旋转的向量。 由于我们希望它绕 z 轴移动,因此我们指定向量 “0 0 1”。

xml

<joint name="head_swivel" type="continuous">
  <parent link="base_link"/>
  <child link="head"/>
  <axis xyz="0 0 1"/>
  <origin xyz="0 0 0.3"/>
</joint>

有限旋转关节

revolute joints 旋转方式与连续旋转关节相同,但必须指定关节上限和下限(以弧度为单位)的极限标签。我们还必须为此关节指定最大速度和关节力。

xml

<joint name="left_gripper_joint" type="revolute">
  <axis xyz="0 0 1"/>
  <limit effort="1000.0" lower="0.0" upper="0.548" velocity="0.5"/>
  <origin rpy="0 0 0" xyz="0.2 0.01 0"/>
  <parent link="gripper_pole"/>
  <child link="left_gripper"/>
</joint>

直线关节

The gripper arm is a different kind of joint, namely a prismatic joint. This means that it moves along an axis, not around it. This translational movement is what allows our robot model to extend and retract its gripper arm.
The limits of the prismatic arm are specified in the same way as a revolute joint, except that the units are meters, not radians.

xml

<joint name="gripper_extension" type="prismatic">
  <parent link="base_link"/>
  <child link="gripper_pole"/>
  <limit effort="1000.0" lower="-0.38" upper="0" velocity="0.5"/>
  <origin rpy="0 0 0" xyz="0.19 0 0.2"/>
</joint>

平面关节和浮动关节

There are two other kinds of joints that move around in space. Whereas(鉴于) the prismatic joint can only move along one dimension, a planar joint can move around in a plane, or two dimensions. Furthermore, a floating joint is unconstrained, and can move around in any of the three dimensions. These joints cannot be specified by just one number, and therefore aren’t included in this tutorial.

物理和碰撞属性

参阅:Adding physical and collision properties

collision碰撞属性

这里的碰撞属性概念与SW里面的接触属性类似,与gazebo建模时设置的碰撞属性概念一样。

The collision element is a direct subelement of the link object, at the same level as the visual tag.
The collision element defines its shape the same way the visual element does, with a geometry tag. The format for the geometry tag is exactly the same here as with the visual.
You can also specify an origin in the same way as a subelement of the collision tag (as with the visual).

xml

<link name="base_link">
    <visual>
      <geometry>
        <cylinder length="0.6" radius="0.2"/>
      </geometry>
      <material name="blue">
        <color rgba="0 0 .8 1"/>
      </material>
    </visual>
    <collision>
      <geometry>
        <cylinder length="0.6" radius="0.2"/>
      </geometry>
    </collision>
  </link>

无论是什么仿真/有限元软件,都应该考虑模型简化问题,这是常识,本文略过不表。
在碰撞属性中可以设置安全距离,这与碰撞属性的概念有关,本文略过不表。

inertia惯性

Every link element being simulated needs an inertial tag. Here is a simple one.

xml

<link name="base_link">
  <visual>
    <geometry>
      <cylinder length="0.6" radius="0.2"/>
    </geometry>
    <material name="blue">
      <color rgba="0 0 .8 1"/>
    </material>
  </visual>
  <collision>
    <geometry>
      <cylinder length="0.6" radius="0.2"/>
    </geometry>
  </collision>
  <inertial>
    <mass value="10"/>
    <inertia ixx="1e-3" ixy="0.0" ixz="0.0" iyy="1e-3" iyz="0.0" izz="1e-3"/>
  </inertial>
</link>
转动惯量
ixx ixy ixz
ixy iyy iyz
ixz iyz izz

接触系数

You can also define how the links behave when they are in contact with one another. This is done with a subelement of the collision tag called contact_coefficients. There are three attributes to specify:
Friction coefficient-摩擦系数-mu
Stiffness coefficient-刚度系数kp
Dampening coefficient-阻尼系数-kd

关节动力学Joint Dynamics

默认是0

代码简化xacro

修改launch文件

python

path_to_urdf = get_package_share_path('pr2_description') / 'robots' / 'pr2.urdf.xacro'
robot_state_publisher_node = launch_ros.actions.Node(
    package='robot_state_publisher',
    executable='robot_state_publisher',
    parameters=[{
        'robot_description': ParameterValue(
            Command(['xacro ', str(path_to_urdf)]), value_type=str
        )
    }]
)

xacro文件前两行:

xml

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

常量代替

xml

<xacro:property name="width" value="0.2" />
<xacro:property name="bodylen" value="0.6" />

<link name="base_link">
    <visual>
        <geometry>
            <cylinder radius="${width}" length="${bodylen}"/>
        </geometry>
        <material name="blue"/>
    </visual>
    <collision>
        <geometry>
            <cylinder radius="${width}" length="${bodylen}"/>
        </geometry>
    </collision>
</link>
xml

<xacro:property name=”robotname” value=”marvin” />
<link name=”${robotname}s_leg” />

数学计算,可以使用sin,cos

xml

<cylinder radius="${wheeldiam/2}" length="0.1"/>
<origin xyz="${reflect*(width+.02)} 0 0.25" />

宏示例一:

xml

<xacro:macro name="default_inertial" params="mass">
    <inertial>
            <mass value="${mass}" />
            <inertia ixx="1e-3" ixy="0.0" ixz="0.0"
                 iyy="1e-3" iyz="0.0"
                 izz="1e-3" />
    </inertial>
</xacro:macro>

<xacro:default_inertial mass="10"/>

宏示例二:

xml

<xacro:macro name="leg" params="prefix reflect">
    <link name="${prefix}_leg">
        <visual>
            <geometry>
                <box size="${leglen} 0.1 0.2"/>
            </geometry>
            <origin xyz="0 0 -${leglen/2}" rpy="0 ${pi/2} 0"/>
            <material name="white"/>
        </visual>
        <collision>
            <geometry>
                <box size="${leglen} 0.1 0.2"/>
            </geometry>
            <origin xyz="0 0 -${leglen/2}" rpy="0 ${pi/2} 0"/>
        </collision>
        <xacro:default_inertial mass="10"/>
    </link>

    <joint name="base_to_${prefix}_leg" type="fixed">
        <parent link="base_link"/>
        <child link="${prefix}_leg"/>
        <origin xyz="0 ${reflect*(width+.02)} 0.25" />
    </joint>
    <!-- A bunch of stuff cut -->
</xacro:macro>
<xacro:leg prefix="right" reflect="1" />
<xacro:leg prefix="left" reflect="-1" />

另:
向社区贡献自己的原创机器人
本学习笔记的重点是学习导航算法,故不对xacro作进一步学习。

插件详解Gazebo Plugins

初步认识Gazebo Plugins

参阅:

在gazebo中可以通过插入一些插件,来仿真机器人传感器、执行器的特性,这些插件通过<gazebo>元素中的<plugin>标签描述。

当前有6种类型的插件:1. 仿真世界插件2. 模型插件3. 传感器插件4. 系统插件5. 视觉插件6. 图形用户界面插件。(World,Model,Sensor,System,Visual,GUI)
其中,能直接在urdf中添加的有:

  1. 模型插件ModelPlugins, to provide access to the physics::Model API
  2. 传感器插件SensorPlugins, to provide access to the sensors::Sensor API
  3. 视觉插件VisualPlugins, to provide access to the rendering::Visual API
插件列表
Camera Multicamera Depth Camera Openni Kinect
GPU Laser Laser Block Laser F3D
Force IMU IMU sensor Joint Pose Trajectory
P3D Projector Prosilica Camera Bumper
Differential Drive Skid Steering Drive Video Plugin Planar Move Plugin

添加模型插件示例:

xml

<robot>
  ... robot description ...
  <gazebo>
    <plugin name="differential_drive_controller" filename="libdiffdrive_plugin.so">
      ... plugin parameters ...
    </plugin>
  </gazebo>
  ... robot description ...
</robot>

添加传感器插件示例:
gazebo中的传感器必须连接到连杆(link),因此描述该传感器的元素的reference=""必须指向对应连杆。

Specifying sensor plugins is slightly different. Sensors in Gazebo are meant to be attached to links, so the element describing that sensor must be given a reference to that link.

xml

<robot>
  ... robot description ...
  <link name="sensor_link">
    ... link description ...
  </link>

  <gazebo reference="sensor_link">
    <sensor type="camera" name="camera1">
      ... sensor parameters ...
      <plugin name="camera_controller" filename="libgazebo_ros_camera.so">
        ... plugin parameters ..
      </plugin>
    </sensor>
  </gazebo>

</robot>

单相机(Camera)与多相机(Multicamera)模块

建模

xml

  <joint name="camera_joint" type="fixed">
    <axis xyz="0 1 0" />
    <origin xyz="${camera&#95;link} 0 ${height3 - axel_offset*2}" rpy="0 0 0"/>
    <parent link="link3"/>
    <child link="camera_link"/>
  </joint>

  <!-- Camera -->
  <link name="camera_link">
    <collision>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
    <box size="${camera&#95;link} ${camera_link} ${camera_link}"/>
      </geometry>
    </collision>

    <visual>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
    <box size="${camera&#95;link} ${camera_link} ${camera_link}"/>
      </geometry>
      <material name="red"/>
    </visual>

    <inertial>
      <mass value="1e-5" />
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <inertia ixx="1e-6" ixy="0" ixz="0" iyy="1e-6" iyz="0" izz="1e-6" />
    </inertial>
  </link>

A Xacro property is also defined:

xml

 <xacro:property name="camera_link" value="0.05" /> <!-- Size of square 'camera' box -->

You should be able to launch the RRBot and see a red box attached to the end of the arm.
Next we will review the Gazebo plugin that gives us the camera functionality and publishes the image to a ROS message.

xml

  <!-- camera -->  
  <!-- The link name "camera_link" must match the name of the link we added to the Xacro URDF. -->
  <gazebo reference="camera_link">

    <!-- The sensor name "camera1" must be unique from all other sensor names. The name is not used many places except for within Gazebo plugins you can access.   -->
    <sensor type="camera" name="camera1">

      <!-- Gazebo内每秒拍摄新相机图像的次数。这是传感器在模拟过程中尝试的最大更新速率,但如果物理模拟运行速度快于传感器生成速度,则可能会落后于此目标速率。 -->
      <update_rate>30.0</update_rate>

        <!-- 填写这些值以匹配制造商对物理相机硬件的规格。需要注意的一点是,像素被假定为正方形。 -->
<!-- 此外,the near and far clips是模拟特定的参数,它们为摄影机在模拟中看到对象的距离提供上限和下限。这是在相机的验光框架中指定的。 -->
      <camera name="head">
        <horizontal_fov>1.3962634</horizontal_fov>
        <image>
          <width>800</width>
          <height>800</height>
          <format>R8G8B8</format>
        </image>
        <clip>
          <near>0.02</near>
          <far>300</far>
        </clip>


        <noise>
          <type>gaussian</type>
          <!-- Noise is sampled independently per pixel on each frame.
               That pixel's noise value is added to each of its color
               channels, which at that point lie in the range [0,1]. -->
          <mean>0.0</mean>
          <stddev>0.007</stddev>
        </noise>
      </camera>

<!-- This is where the actual file is linked to, as a shared object. -->
      <plugin name="camera_controller" filename="libgazebo_ros_camera.so">

        <alwaysOn>true</alwaysOn>
        <updateRate>0.0</updateRate>

        <!-- Here we define the rostopic the camera will be publishing to, for both the image topic and the camera info topic.  -->
        <cameraName>rrbot/camera1</cameraName>
        <imageTopicName>image_raw</imageTopicName>
        <cameraInfoTopicName>camera_info</cameraInfoTopicName>

        <!-- 在tf树中发布图像的坐标系。 -->
        <frameName>camera_link</frameName>
        <hackBaseline>0.07</hackBaseline>
        <distortionK1>0.0</distortionK1>
        <distortionK2>0.0</distortionK2>
        <distortionK3>0.0</distortionK3>
        <distortionT1>0.0</distortionT1>
        <distortionT2>0.0</distortionT2>
      </plugin>
    </sensor>
  </gazebo>

多相机Multicamera
同步多个相机的快门,以便它们一起发布图像。
Note: currently only supports stereo cameras.(目前仅支持立体相机)
In this code example there is both a left and right camera:

xml

  <gazebo reference="left_camera_frame">
    <sensor type="multicamera" name="stereo_camera">
      <update_rate>30.0</update_rate>
      <camera name="left">
        <horizontal_fov>1.3962634</horizontal_fov>
        <image>
          <width>800</width>
          <height>800</height>
          <format>R8G8B8</format>
        </image>
        <clip>
          <near>0.02</near>
          <far>300</far>
        </clip>
        <noise>
          <type>gaussian</type>
          <mean>0.0</mean>
          <stddev>0.007</stddev>
        </noise>
      </camera>
      <camera name="right">
        <pose>0 -0.07 0 0 0 0</pose>
        <horizontal_fov>1.3962634</horizontal_fov>
        <image>
          <width>800</width>
          <height>800</height>
          <format>R8G8B8</format>
        </image>
        <clip>
          <near>0.02</near>
          <far>300</far>
        </clip>
        <noise>
          <type>gaussian</type>
          <mean>0.0</mean>
          <stddev>0.007</stddev>
        </noise>
      </camera>
      <plugin name="stereo_camera_controller" filename="libgazebo_ros_multicamera.so">
        <alwaysOn>true</alwaysOn>
        <updateRate>0.0</updateRate>
        <cameraName>multisense_sl/camera</cameraName>
        <imageTopicName>image_raw</imageTopicName>
        <cameraInfoTopicName>camera_info</cameraInfoTopicName>
        <frameName>left_camera_optical_frame</frameName>
        <!--<rightFrameName>right_camera_optical_frame</rightFrameName>-->
        <hackBaseline>0.07</hackBaseline>
        <distortionK1>0.0</distortionK1>
        <distortionK2>0.0</distortionK2>
        <distortionK3>0.0</distortionK3>
        <distortionT1>0.0</distortionT1>
        <distortionT2>0.0</distortionT2>
      </plugin>
    </sensor>
  </gazebo>

两类雷达模块

GPU Laser
如sensor_msgs中所述,通过广播LaserScan消息模拟激光测距传感器。
示例:

xml

  <joint name="hokuyo_joint" type="fixed">
    <axis xyz="0 1 0" />
    <origin xyz="0 0 ${height3 - axel_offset/2}" rpy="0 0 0"/>
    <parent link="link3"/>
    <child link="hokuyo_link"/>
  </joint>

  <!-- Hokuyo Laser -->
  <link name="hokuyo_link">
    <collision>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
    <box size="0.1 0.1 0.1"/>
      </geometry>
    </collision>

    <visual>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
        <mesh filename="package://rrbot_description/meshes/hokuyo.dae"/>
      </geometry>
    </visual>

    <inertial>
      <mass value="1e-5" />
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <inertia ixx="1e-6" ixy="0" ixz="0" iyy="1e-6" iyz="0" izz="1e-6" />
    </inertial>
  </link>

Most of the properties are self-explanatory.(大多数属性都是不言自明的)

xml

  <!-- hokuyo -->
  <gazebo reference="hokuyo_link">
    <sensor type="gpu_ray" name="head_hokuyo_sensor">
      <pose>0 0 0 0 0 0</pose>

      <!-- 当为true时,在gpu激光器的扫描区域内可见半透明激光射线。这可能是一个有用的信息可视化,也可能是一种麻烦。 -->
      <visualize>false</visualize>

      <update_rate>40</update_rate>
      <ray>
        <scan>
          <horizontal>
            <samples>720</samples>
            <resolution>1</resolution>
            <min_angle>-1.570796</min_angle>
            <max_angle>1.570796</max_angle>
          </horizontal>
        </scan>
        <range>
          <min>0.10</min>
          <max>30.0</max>
          <resolution>0.01</resolution>
        </range>
        <noise>
          <type>gaussian</type>
          <!-- Noise parameters based on published spec for Hokuyo laser
               achieving "+-30mm" accuracy at range < 10m.  A mean of 0.0m and
               stddev of 0.01m will put 99.7% of samples within 0.03m of the true
               reading. -->
          <mean>0.0</mean>
          <stddev>0.01</stddev>
        </noise>
      </ray>
      <plugin name="gazebo_ros_head_hokuyo_controller" filename="libgazebo_ros_gpu_laser.so">

      <!-- 将这些设置为您希望发布激光扫描的ROS主题名称,以及您希望TF使用的转换帧。 -->
        <topicName>/rrbot/laser/scan</topicName>
        <frameName>hokuyo_link</frameName>


      </plugin>
    </sensor>
  </gazebo>

Laser
the non-GPU version of , but essentially uses the same code. See GPU Laser for documentation

laserlaser
截图引自:文档

两类IMU模块

IMU (GazeboRosImu)
Description: simulates IMU sensor. Measurements are computed by the ROS plugin, not by Gazebo. See usage snippet sample below for implementation.
描述:模拟IMU传感器。测量是由ROS插件计算的,而不是由Gazebo计算的。

xml

<robot>
:
  <gazebo>
    <plugin name="imu_plugin" filename="libgazebo_ros_imu.so">
      <alwaysOn>true</alwaysOn>
      <bodyName>base_footprint</bodyName>
      <topicName>imu</topicName>
      <serviceName>imu_service</serviceName>
      <gaussianNoise>0.0</gaussianNoise>
      <updateRate>20.0</updateRate>
    </plugin>
  </gazebo>
</robot>

IMU sensor (GazeboRosImuSensor)
模拟惯性运动单元传感器,与IMU(GazeboRosIMU)的主要区别是:-继承SensorPlugin而不是ModelPlugin,-测量由gazebo ImuSensor给出而不是由ros插件计算,-重力包含在惯性测量中。

xml

  <gazebo reference="imu_link">
    <gravity>true</gravity>
    <sensor name="imu_sensor" type="imu">
      <always_on>true</always_on>
      <update_rate>100</update_rate>
      <visualize>true</visualize>
      <topic>__default_topic__</topic>
      <plugin filename="libgazebo_ros_imu_sensor.so" name="imu_plugin">
        <topicName>imu</topicName>
        <bodyName>imu_link</bodyName>
        <updateRateHZ>10.0</updateRateHZ>
        <gaussianNoise>0.0</gaussianNoise>
        <xyzOffset>0 0 0</xyzOffset>
        <rpyOffset>0 0 0</rpyOffset>
        <frameName>imu_link</frameName>
        <initialOrientationAsReference>false</initialOrientationAsReference>
      </plugin>
      <pose>0 0 0 0 0 0</pose>
    </sensor>
  </gazebo>

Differential Drive(差速驱动器)

Description: model plugin that provides a basic controller for differential drive robots in Gazebo. You need a well defined differential drive robot to use this plugin.
描述:模型插件,为Gazebo的差速驱动机器人提供基本控制器。你需要一个定义良好的差速驱动机器人来使用这个插件。
配置列表模板:

xml

<gazebo>
  <plugin name="differential_drive_controller" filename="libgazebo_ros_diff_drive.so">

    <!-- Plugin update rate in Hz -->
    <updateRate>${update_rate}</updateRate>

    <!-- Name of left joint, defaults to `left_joint` -->
    <leftJoint>base_link_left_wheel_joint</leftJoint>

    <!-- Name of right joint, defaults to `right_joint` -->
    <rightJoint>base_link_right_wheel_joint</rightJoint>

    <!-- The distance from the center of one wheel to the other, in meters, defaults to 0.34 m -->
    <wheelSeparation>0.5380</wheelSeparation>

    <!-- Diameter of the wheels, in meters, defaults to 0.15 m -->
    <wheelDiameter>0.2410</wheelDiameter>

    <!-- Wheel acceleration, in rad/s^2, defaults to 0.0 rad/s^2 -->
    <wheelAcceleration>1.0</wheelAcceleration>

    <!-- Maximum torque which the wheels can produce, in Nm, defaults to 5 Nm -->
    <wheelTorque>20</wheelTorque>

    <!-- Topic to receive geometry_msgs/Twist message commands, defaults to `cmd_vel` -->
    <commandTopic>cmd_vel</commandTopic>

    <!-- Topic to publish nav_msgs/Odometry messages, defaults to `odom` -->
    <odometryTopic>odom</odometryTopic>

    <!-- Odometry frame, defaults to `odom` -->
    <odometryFrame>odom</odometryFrame>

    <!-- Robot frame to calculate odometry from, defaults to `base_footprint` -->
    <robotBaseFrame>base_footprint</robotBaseFrame>

    <!-- Odometry source, 0 for ENCODER, 1 for WORLD, defaults to WORLD -->
    <odometrySource>1</odometrySource>

    <!-- Set to true to publish transforms for the wheel links, defaults to false -->
    <publishWheelTF>true</publishWheelTF>

    <!-- Set to true to publish transforms for the odometry, defaults to true -->
    <publishOdom>true</publishOdom>

    <!-- Set to true to publish sensor_msgs/JointState on /joint_states for the wheel joints, defaults to false -->
    <publishWheelJointState>true</publishWheelJointState>

    <!-- Set to true to swap right and left wheels, defaults to true -->
    <legacyMode>false</legacyMode>
  </plugin>
</gazebo>

其他插件模块

详情请移步文档:https://classic.gazebosim.org/tutorials?tut=ros_gzplugins#Tutorial:UsingGazebopluginswithROS

Joint Pose Trajectory
Description: listens to a jointtrajectoryaction and plays back the set of joint positions. Sets the set of joints to exact positions without regards to simulated physics and forces.
描述:收听一个联合目标动作并回放一组联合位置。将关节集设置为精确的位置,而不考虑模拟的物理和力

P3D (3D Position Interface for Ground Truth)(地面实况的3D位置接口)
Description: broadcasts the inertial pose of any body in simulation via Odometry message as described in nav_msgs via ROS topic.
描述:通过ROS主题通过nav_msgs中描述的Odometry消息广播模拟中任何物体的惯性姿态。

Projector
Description: projects a static texture from a source outwards, such as used with the PR2's original head camera sensor. See API documentation for more information.
描述:从源向外投射静态纹理,例如与PR2的原始头部摄像头传感器一起使用。有关更多信息,请参阅API文档

Prosilica Camera
Description: simulates interfaces exposed by a ROS Prosilica Camera. Here's an example URDF Xacro macro.
描述:模拟ROS Prosilica相机暴露的界面。这是一个URDF Xacro宏示例

Bumper
Description: provides contact feedback via ContactsState message.
描述:通过ContactsState message提供联系人反馈。

另有:

进阶:创建自定义插件

另可参阅:

仿真实例

实验设计

这个实例的源文件已上传至本网站,点击此处下载
设计一个利用超声波避障模块的机器小车,在一个世界环境中自主行走。
为简便起见,轮式机器人源文件参考了鱼香ros教程,本篇笔记对其中部分代码作进一步解释。

考虑将来的实验,采用C++方案,编译方式:ament_cmake
避障节点发布机器人运动的cmd_vel话题,话题接口类型geometry_msgs/msg/twist,订阅超声波发布的话题,话题接口类型为sensor_msgs/msg/range。
因此需要的依赖有:rclcpp sensor_msgs geometry_msgs

创建工作包

shell

#在src目录下
ros2 pkg create --build-type ament_cmake --node-name avoid_node johnbot2 --dependencies rclcpp sensor_msgs geometry_msgs

参阅:

shell

# 阅读帮助文档
ros2 pkg create -h

创建功能包创建功能包

创建机器人模型

  1. 创建文件夹
  2. 创建机器人物理模型
  3. 添加机器人仿真插件

在pro_ws/src/package_name/下创建models文件夹,用于放置模型文件
(文件夹目录层级参阅笔记1第3.3.2章节

shell

cd pro_ws/src/package_name
mkdir models
cd models

别忘了修改CMakeList.txt

CMake

install(DIRECTORY
  models
  DESTINATION share/${PROJECT_NAME}/
)

机器人的物理模型包括一个虚拟的Footprint连接到地面,一个base_link,一个left_wheel_link,一个right_wheel_link,一个前轮caster_link,一个超声波模块ultrasonic_sensor_link,各Link均与base_link连接。物理模型不多解释,文件见src文件夹。下面详细介绍超声波模块插件的设计。

根据第4.3章节中雷达模块编写超声波模块插件

Lidar is an acronym for "light detection and ranging". This sensor can help us detect obstacles around the robot.
原雷达插件模板

xml

  <!-- 原雷达(Laser)插件模板-->
  <gazebo reference="hokuyo_link">
    <sensor type="ray" name="head_hokuyo_sensor">
      <pose>0 0 0 0 0 0</pose>
      <visualize>false</visualize>
      <update_rate>40</update_rate>
      <ray>
        <scan>
          <horizontal>
            <samples>720</samples>
            <resolution>1</resolution>
            <min_angle>-1.570796</min_angle>
            <max_angle>1.570796</max_angle>
          </horizontal>
        </scan>
        <range>
          <min>0.10</min>
          <max>30.0</max>
          <resolution>0.01</resolution>
        </range>
        <noise>
          <type>gaussian</type>
          <!-- Noise parameters based on published spec for Hokuyo laser
               achieving "+-30mm" accuracy at range < 10m.  A mean of 0.0m and
               stddev of 0.01m will put 99.7% of samples within 0.03m of the true
               reading. -->
          <mean>0.0</mean>
          <stddev>0.01</stddev>
        </noise>
      </ray>
      <plugin name="gazebo_ros_head_hokuyo_controller" filename="libgazebo_ros_laser.so">
        <topicName>/rrbot/laser/scan</topicName>
        <frameName>hokuyo_link</frameName>
      </plugin>
    </sensor>
  </gazebo>

修改为我们需要的超声波插件:

xml

  <!--超声波模块-->
  <!-- Link名称更换为模型中的名称 -->
  <gazebo reference="ultrasonic_sensor_link">
    <!-- 传感器类型是ray,插件名称自定义,不能重复 -->
    <sensor type="ray" name="ultrasonic_sensor">
      <pose>0 0 0 0 0 0</pose>
      <visualize>true</visualize>
      <update_rate>40</update_rate>
      <ray>
        <scan>
          <horizontal>
          <!-- 水平方向 -->
              <samples>50</samples>
              <!-- 每圈产生的激光数量num = samples * resolution -->
              <resolution>1</resolution>
              <!-- 0.8弧度约为45度角度,这里根据实际自定义即可 -->
              <min_angle>-0.4</min_angle>
              <max_angle>0.4</max_angle>
          </horizontal>
          <vertical>
          <!-- 竖直方向,不需要扫描 -->
              <samples>1</samples>
              <resolution>1</resolution>
              <min_angle>0</min_angle>
              <max_angle>0</max_angle>
          </vertical>
        </scan>
        <!-- 超声波检测的范围和数据分辨率单位m,考虑实际情况修改测量范围 -->
        <range>
          <min>0.02</min>
          <max>4.0</max>
          <resolution>0.01</resolution>
        </range>
        <!-- 数据噪声采用高斯噪声 -->
        <noise>
          <type>gaussian</type>
          <!-- Noise parameters based on published spec for Hokuyo laser
               achieving "+-30mm" accuracy at range < 10m.  A mean of 0.0m and
               stddev of 0.01m will put 99.7% of samples within 0.03m of the true
               reading.此处可以考虑修改,在实际工程应用中参考实际模块参数即可 -->
          <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_range</remapping>
        </ros>
        <!-- 输出消息的类型,参阅/opt/ros/humble/include/gazebo_plugins/gazebo_ros_ray_sensor.hpp文件 -->
        <output_type>sensor_msgs/Range</output_type>
        <!-- 射线类型有两种,参阅opt/ros/humble/include/sensor_msgs/msg/detail/range_struct.h文件-->
        <radiation_type>ultrasound</radiation_type>
        <!-- 坐标系名称,要与reference名称一致 -->
        <frame_name>ultrasonic_sensor_link</frame_name>
      </plugin>
    </sensor>
  </gazebo>

引自/opt/ros/humble/include/gazebo_plugins/gazebo_ros_ray_sensor.hpp文件,此文件中解释了上面代码中的一些参数设置:

C++

/// Plugin to attach to a gazebo ray or gpu_ray sensor and publish its data to ROS
/**
  \details
  SDF parameters:
  \verbatim
  <output_type>: Optional. The message type of the plugin's output. Can be any of the following:
    * sensor_msgs/PointCloud2: 3D cloud of points, Default
    * sensor_msgs/PointCloud:  3D cloud of points
    * sensor_msgs/LaserScan:   2D scan, uses center vertical ray if there are multiple
    * sensor_msgs/Range:       Single distance value, minimum of all ray ranges of parent sensor
  \endverbatim

  \verbatim
  <frame_name>: TF Frame id of output header.
               If not set, frame id will be name of sensor's parent link
  \endverbatim

  \verbatim
  <min_intensity>: Clip intensity values for output to this value.
                  Default: 0.0
  \endverbatim

  \verbatim
  <radiation_type>: The radiation type to publish when the output type is sensor_msgs/Range.
                   Can be either "infrared"(红外线) or "ultrasonic"(超声波).
                   Default: "infrared".
  \endverbatim

  Example SDF:
  \code{.xml}
    <plugin name="my_ray_sensor_plugin" filename="libgazebo_ros_ray_sensor.so">
      <ros>
        <!-- Configure namespace and remap to publish to /ray/pointcloud2 -->
        <namespace>/ray</namespace>
        <remapping>~/out:=pointcloud2</remapping>
      </ros>
      <!-- Output as a PointCloud2, see above for other types -->
      <output_type>sensor_msgs/PointCloud2</output_type>
      <!-- Clip intensity values so all are above 100, optional -->
      <min_intensity>100.0</min_intensity>
      <!-- Frame id for header of output, defaults to sensor's parent link name -->
      <frame_name>ray_link</frame_name>
    </plugin>
  \endcode
*/

默认输出的话题是

shell

/ultrasonic_sensor_controller/out

可以按照上面的方式重定向。

以下内容引自:opt/ros/humble/include/sensor_msgs/msg/detail/range_struct.h,指出了radiation_type的可取值。

C

/// Constant 'ULTRASOUND'.
/**
  * Radiation type enums
  * If you want a value added to this list, send an email to the ros-users list
 */
enum
{
  sensor_msgs__msg__Range__ULTRASOUND = 0
};

/// Constant 'INFRARED'.
enum
{
  sensor_msgs__msg__Range__INFRARED = 1
};

另可参阅:https://github.com/gazebosim/docs/blob/master/harmonic/sensors.md#lidar-sensor

至此,机器人模型设计完毕。下面先通过launch文件打开看看。

Launch文件设计

同样的,在pro_ws/src/package_name/下创建launch文件夹,在launch文件夹中创建avoid.launch.py文件
同时修改CMakeList.txt

CMake

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

参阅笔记1第四章

python

from launch_ros.actions import Node       # 节点启动的描述类
import os
from ament_index_python.packages import get_package_share_directory  # 查询功能包路径的方法
from launch import LaunchDescription                 # launch文件的描述类
from launch.actions import ExecuteProcess

def generate_launch_description():        # 自动生成launch文件的函数
    ld=LaunchDescription()
    package_name = 'johnbot2'
    pkg_path = os.path.join(get_package_share_directory(package_name))

    world_file_path = 'models/wall01.world'
    world_path = os.path.join(pkg_path, world_file_path) 
    robot_name_in_model = 'johnbot2'

    urdf_file_path = 'models/avoid_bot2.urdf'
    urdf_model_path = os.path.join(pkg_path,urdf_file_path)


    # Start Gazebo server
    start_gazebo_cmd = ExecuteProcess(
        cmd=['gazebo', '--verbose','-s', 'libgazebo_ros_init.so', '-s', 'libgazebo_ros_factory.so', world_path],
        output='screen'
    )
    ld.add_action(start_gazebo_cmd)

    # # Launch the robot
    # Pose where we want to spawn the robot,这一组参数指定起始位置和姿态,否则默认机器人出生在原点,有点类似MC游戏《我的世界》中“出生点”的概念
    spawn_x_val = '0.0'
    spawn_y_val = '-2.0'
    spawn_z_val = '0.0'
    spawn_yaw_val = '0.00'
    spawn_entity_cmd = Node(
        package='gazebo_ros', 
        executable='spawn_entity.py',
        arguments=['-entity', robot_name_in_model,  '-file', urdf_model_path ,'-x', spawn_x_val,
                                '-y', spawn_y_val,
                                '-z', spawn_z_val,
                                '-Y', spawn_yaw_val], output='screen')
    ld.add_action(spawn_entity_cmd)

    # Start Robot State publisher
    start_robot_state_publisher_cmd = Node(
        package='robot_state_publisher',
        executable='robot_state_publisher',
        arguments=[urdf_model_path]
    )
    ld.add_action(start_robot_state_publisher_cmd)

    # Launch RViz
    start_rviz_cmd = Node(
        package='rviz2',
        executable='rviz2',
        name='rviz2',
        output='screen',
        # arguments=['-d', default_rviz_config_path]
        )
    ld.add_action(start_rviz_cmd)

    # start_avoid_node = Node(
    #     package='johnbot2',
    #     executable= 'avoid_node',
    #     # arguments=[output = 'screen']
    # )
    # ld.add_action(start_avoid_node)
    # Launch them all!
    return ld

尝试运行:

shell

# 在proj_ws文件夹下
colcon build
source install/setup.bash
ros2 launch johnbot2 avoid.launch.py

运行完毕,一切正常,下面设计避障节点。

避障节点设计

参阅:API文档 rclcpp: ROS Client Library for C++
避障节点发布机器人运动的cmd_vel话题,话题接口类型geometry_msgs/msg/twist,订阅超声波发布的ultrasonic_sensor_range话题,话题接口类型为sensor_msgs/msg/range。
可以先查看两种消息接口的详细信息

shell

ros2 interface show geometry_msgs/msg/Twist

参阅:geometry_msgs/Twist Message
linear[3]:对应3维线性速度,angular[3]对应3轴旋转速度

简单示例如下:

C++

#include <cstdio>
#include <chrono>
#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "geometry_msgs/msg/twist.hpp"
#include "sensor_msgs/msg/range.hpp"

class Node_avoid : public rclcpp::Node
{
    public:
        Node_avoid(std::string name) : Node(name)
        {
            RCLCPP_INFO(this -> get_logger(), "Node_avoid init here,name: %s",name.c_str());
            //创建发布者

            command_publisher_ = this->create_publisher<geometry_msgs::msg::Twist>("cmd_vel",10);
            // timer_ = this->create_wall_timer(std::chrono::milliseconds(500), std::bind(&Node_avoid::timer_callback, this));

            distance_monitor_ = this->create_subscription<sensor_msgs::msg::Range>("ultrasonic_sensor_range",10,
            std::bind(&Node_avoid::distance_callback, this, std::placeholders::_1));

        }
    private:
        rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr command_publisher_;
        rclcpp::TimerBase::SharedPtr timer_;

        //超声波,距离监视器
        rclcpp::Subscription<sensor_msgs::msg::Range>::SharedPtr distance_monitor_;

        void distance_callback(const sensor_msgs::msg::Range distance_real)
        {
            geometry_msgs::msg::Twist run_command;
            float divide = 0.3;
            if(distance_real.range >= divide)
            {
                run_command = direct_run();
            }
            else{
                run_command = turn_left();
            }
            command_publisher_->publish(run_command);
        }


        //直行
        geometry_msgs::msg::Twist direct_run()
        {
            geometry_msgs::msg::Twist loc;
            loc.linear.x = 0.2;
            loc.linear.y = 0.0;
            loc.linear.z = 0.0;
            loc.angular.x = 0.0;
            loc.angular.y = 0.0;
            loc.angular.z = 0.0;
            return loc;
        }

        //转弯
        geometry_msgs::msg::Twist turn_left()
        {
            geometry_msgs::msg::Twist loc;
            loc.linear.x = 0.0;
            loc.linear.y = 0.0;
            loc.linear.z = 0.0;
            loc.angular.x = 0.0;
            loc.angular.y = 0.0;
            loc.angular.z = 0.8;
            return loc;
        }


};

/*
 @brief: 避障节点,没有障碍就直行,有则转弯。
 @note:订阅超声波ultrasonic_sensor_range话题,话题接口类型为sensor_msgs/msg/range,
 发布机器人运动的cmd_vel话题,话题接口类型geometry_msgs/msg/twist

*/
int main(int argc, char ** argv)
{
  rclcpp::init(argc,argv);
  auto node = std :: make_shared<Node_avoid>("avoid_node");
  rclcpp::spin(node);
  rclcpp::shutdown();
  return 0;
}

节点设计完毕,修改launch文件。

文件在project_examples/avoid_bot/下。

设计完毕,运行,查看。

shell

colcon build
source install/setup.bash
ros2 launch johnbot2 avoid.launch.py

没什么问题,准备进入下一步——建图与导航。