前言

经过第一轮 ROS2 入门的学习,基本掌握了如何在各节点之间进行信息通信,但要构建一个完整的机器人系统仍有很长的路要走。

“机器人是一种高度复杂的系统性实现,机器人设计包含了机械结构设计、机械加工、硬件设计、嵌入式软件设计、上层软件设计等诸多模块,是各种硬件与软件的有机结合,甚至可以说机器人系统是当今工业体系的集大成者。机器人体系是相当庞大的,其复杂度之高,以至于没有任何个人、组织甚至公司能够独立完成系统性的机器人研发生产任务。

一种更合适的策略是:让机器人研发者专注于自己擅长的领域,其他模块则直接复用相关领域更专业研发团队的实现,当然自身的研究也可以被他人继续复用。这种基于“复用”的分工协作,遵循了不重复发明轮子的原则,显然是可以大大提高机器人的研发效率的,尤其是随着机器人硬件越来越丰富,软件库越来越庞大,这种复用性和模块化开发需求也愈发强烈。”

因此,接下来,我们需要具备调用功能包搭建机器人软件层的能力,再进一步,是深入自己感兴趣的领域,研究底层算法以优化机器人系统性能!到这里,鱼香ROS机器人是一个不错的参考,当然,还包括ROS2的各个社区,如Welcome to ROS 2 workshop! — ROS 2 workshop documentation

建模与仿真

通过这一节对机器人的建模与仿真,我们可以得到机器人结构tf、机器人控制与传感器信息

rqt计算图如下:

机器人部件tf_tree如下:

一份urdf-gazebo-rviz的launch示例如下:

from launch import LaunchDescription
from launch_ros.actions import Node
import os
from ament_index_python.packages import get_package_share_directory
from launch_ros.parameter_descriptions import ParameterValue
from launch.substitutions import Command,LaunchConfiguration
from launch.actions import DeclareLaunchArgument,IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.actions import ExecuteProcess
from launch.event_handlers import OnProcessExit
from launch.actions import RegisterEventHandler

def generate_launch_description():
    default_package_dir = get_package_share_directory("[package_name]")
    default_rviz_path = os.path.join(default_package_dir,"rviz","[config].rviz")
    default_model_path = os.path.join(default_package_dir,"urdf/xacro","[model].xacro")
    default_world_path = os.path.join(default_package_dir,"worlds","[env].world")

    model = DeclareLaunchArgument(name="model", default_value=default_model_path)
    world = DeclareLaunchArgument(name="world", default_value=default_world_path)
    
    robot_description = ParameterValue(Command(["xacro ",LaunchConfiguration("model")]))
    robot_state_publisher = Node(
        package="robot_state_publisher",
        executable="robot_state_publisher",
        parameters=[{"robot_description": robot_description}]
    )

    joint_state_publisher = Node(
        package="joint_state_publisher",
        executable="joint_state_publisher"
    )

    rviz2 = Node(
        package="rviz2",
        executable="rviz2",
        arguments=["-d", default_rviz_path]
    )

    action_launch_gazebo = IncludeLaunchDescription(
        launch_description_source = PythonLaunchDescriptionSource(
            launch_file_path=os.path.join(
                get_package_share_directory("gazebo_ros"),
                "launch",
                "gazebo.launch.py"
            )
        ),
        launch_arguments = {
            "world": LaunchConfiguration("world"),
            "verbose": "true",
        }.items()
    )

    action_spawn_entity = Node(
        package="gazebo_ros",
        executable="spawn_entity.py",
        arguments=["-topic", "/robot_description", "-entity", "[model_name]", ]
    )

    action_load_joint_state_controller = ExecuteProcess(
        cmd=["ros2 control load_controller robot_joint_state_broadcaster --set-state active"],
        output="both",
        shell=True
    )

    action_load_effort_controller = ExecuteProcess(
        cmd=["ros2 control load_controller robot_effort_controller --set-state active"],
        output="both",
        shell=True
    )

    return LaunchDescription([
        model,
        world,
        robot_state_publisher,
        # joint_state_publisher,
        action_launch_gazebo,
        action_spawn_entity,
        RegisterEventHandler(
            event_handler=OnProcessExit(
                target_action = action_spawn_entity,
                on_exit = [action_load_joint_state_controller],
            )
        ),
        RegisterEventHandler(
            event_handler=OnProcessExit(
                target_action = action_load_joint_state_controller,
                on_exit = [action_load_effort_controller],
            )
        ),
        # rviz2,
    ])
ros2 launch [package] [gazebo_sim].launch.py model:=`ros2 pkg prefix --share [package]`/urdf/urdf/[model].urdf

几个要注意的点:

  • robot_state_publisher发布机器人固定关节部件的static tf,活动关节部件需要joint_state_publisher发布joint states对应dynamic tf。然而,Gazebo与joint_state_publisher冲突,其根据urdf-gazebo标签导入活动关节配置生成对应dynamic tf。

  • 可在~/.gazebo载入gazebo可加载模型用于仿真

  • 由于功能包内部可能存在多处base_frame_id,建议frame_id使用常用名称

urdf:机器人建模

urdf

  • check_urdf:功能包,检查urdf文件语法,展示urdf机器人结构
  • urdf_to_graphviz:功能包,生成urdf机器人结构图
<?xml version="1.0"?>
<robot name="chassis">
    <link name="base_footprint">
        <visual>
            <geometry>
                <sphere radius="0.001" />
            </geometry>
        </visual>
    </link>

    <link name="base_link">    
        <visual>
            <geometry>
                <cylinder radius="0.1" length="0.08" />
                <!-- <box size="0.3 0.2 0.1" /> -->
                <!-- <sphere radius="1" /> -->
                <!-- <mesh filename="package://[package]/meshes/**.stl" scale="1.0 1.0 1.0"/> -->
            </geometry>
            <origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />     <!-- shift & rotate -->
            <material name="base_color">
                <color rgba="1.0 0.5 0.5 0.5" />
            </material>
        </visual>
        <!-- <collision>  -->
        <!-- </collision> -->
    	<!-- <Inertial> -->
    	<!-- </Inertial> -->
    </link>

    <joint name="base2footprint" type="fixed">
        <parent link="base_footprint" />
        <child link="base_link" />
        <origin xyz="0.0 0.0 0.055" rpy="0.0 0.0 0.0" />
        <axis xyz="0 0 0" />
    </joint>

    <link name="left_wheel">
        <visual>
            <geometry>
                <cylinder radius="0.0325" length="0.015" />
            </geometry>
            <origin xyz="0.0 0.0 0.0" rpy="1.5708 0.0 0.0" />
            <material name="wheel_color">
                <color rgba="0.0 0.0 0.0 0.5" />
            </material>
        </visual>
    </link>

    <joint name="left2base" type="continuous">
        <parent link="base_link" />
        <child link="left_wheel" />
        <origin xyz="0.0 0.1 -0.0225" rpy="0.0 0.0 0.0" />
        <axis xyz="0 1 0" />
    </joint>

    <link name="right_wheel">
        <visual>
            <geometry>
                <cylinder radius="0.0325" length="0.015" />
            </geometry>
            <origin xyz="0.0 0.0 0.0" rpy="1.5708 0.0 0.0" />
            <material name="wheel_color">
                <color rgba="0.0 0.0 0.0 0.5" />
            </material>
        </visual>
    </link>

    <joint name="right2base" type="continuous">
        <parent link="base_link" />
        <child link="right_wheel" />
        <origin xyz="0.0 -0.1 -0.0225" rpy="0.0 0.0 0.0" />
        <axis xyz="0 1 0" />
    </joint>

    <link name="front_wheel">
        <visual>
            <geometry>
                <sphere radius="0.0075" />
            </geometry>
            <origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
            <material name="wheel_color">
                <color rgba="1.0 0.5 0.5 0.5" />
            </material>
        </visual>
    </link>

    <joint name="front2base" type="continuous">
        <parent link="base_link" />
        <child link="front_wheel" />
        <origin xyz="0.08 0.0 -0.0475" rpy="0.0 0.0 0.0" />
        <axis xyz="1 1 1" />
    </joint>

    <link name="back_wheel">
        <visual>
            <geometry>
                <sphere radius="0.0075" />
            </geometry>
            <origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
            <material name="wheel_color">
                <color rgba="1.0 0.5 0.5 0.5" />
            </material>
        </visual>
    </link>

    <joint name="back2base" type="continuous">
        <parent link="base_link" />
        <child link="back_wheel" />
        <origin xyz="-0.08 0.0 -0.0475" rpy="0.0 0.0 0.0" />
        <axis xyz="1 1 1" />
    </joint>
</robot>

xacro

  • xacro:功能包,转化xacro文件为urdf文件
  • collision:link子标签,如果机器人link是标准的几何体形状,和link的 visual 属性设置一致即可
  • inertial:link子标签,惯性矩阵需结合link的质量与外形参数动态生成,标准球体、圆柱与立方体的惯性矩阵公式封装在inertialhead.xacro
  • gazebo:robot子标签,配置gazebo颜色、摩擦、刚度系数等等,添加各传感器的仿真插件(具体见[gazebo:环境仿真](# gazebo:环境仿真)),其需要reference到link
<robot name="model" xmlns:xacro="http://wiki.ros.org/xacro">
    <xacro:include filename="inertialhead.xacro" />
    <xacro:include filename="base.xacro" />
    <xacro:include filename="camera.xacro" />
    <xacro:include filename="laser.xacro" />

    <xacro:include filename="ros2_control.xacro" /> 
    <xacro:robot_ros2_control />
    <xacro:include filename="plugins/move.xacro" />
    <!-- <xacro:gazebo_control_plugin  /> -->
    
    <xacro:include filename="plugins/laser.xacro" />
    <xacro:gazebo_laser_plugin />
    <xacro:include filename="plugins/imu.xacro" />
    <xacro:gazebo_imu_plugin />
    <xacro:include filename="plugins/camera.xacro" />
    <xacro:gazebo_camera_plugin />
</robot>
<robot name="inertialhead" xmlns:xacro="http://wiki.ros.org/xacro">
<!-- macrofunc -->
    <xacro:macro name="sphere_inertial_matrix" params="m r">
        <inertial>
            <mass value="${m}" />
            <inertia ixx="${2*m*r*r/5}" ixy="0" ixz="0"
                iyy="${2*m*r*r/5}" iyz="0" 
                izz="${2*m*r*r/5}" />
        </inertial>
    </xacro:macro>

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

    <xacro:macro name="Box_inertial_matrix" params="m l w h">
       <inertial>
               <mass value="${m}" />
               <inertia ixx="${m*(h*h + l*l)/12}" ixy = "0" ixz = "0"
                   iyy="${m*(w*w + l*l)/12}" iyz= "0"
                   izz="${m*(w*w + h*h)/12}" />
       </inertial>
   </xacro:macro>
</robot>
<robot name="base" xmlns:xacro="http://wiki.ros.org/xacro">
<!-- property -->
    <xacro:property name="PI" value="3.1415927" />
    <xacro:property name="footprint_radius" value="0.001" />
    <xacro:property name="base_weight" value="2" />
    <xacro:property name="base_radius" value="0.1" />
    <xacro:property name="base_length" value="0.08" />
    <xacro:property name="ground_distance" value="0.015" />
    <xacro:property name="joint_base2footprint_z" value="${base_length / 2 + ground_distance}" />
    <xacro:property name="wheel_weight" value="0.05" />
    <xacro:property name="wheel_radius" value="0.0325" />
    <xacro:property name="wheel_length" value="0.015" />
    <xacro:property name="joint_wheel2base_z" value="${(joint_base2footprint_z - wheel_radius) * -1}" />
    <xacro:property name="spinwheel_weight" value="0.03" />
    <xacro:property name="spinwheel_radius" value="0.0075" />
    <xacro:property name="joint_spinwheel2base_z" value="${(joint_base2footprint_z - spinwheel_radius) * -1}" />

<!-- macrofunc -->
    <xacro:macro name="wheel_func" params="wheel_name flag" >
        <link name="${wheel_name}_wheel">
            <visual>
                <geometry>
                    <cylinder radius="${wheel_radius}" length="${wheel_length}" />
                    <!-- <cylinder radius="${}" length="${}" /> -->
                    <!-- <box size="${} ${} ${}" /> -->
                    <!-- <sphere radius="${}" /> -->
                    <!-- <mesh filename="package://simulation/meshes/autolabor_mini.stl" /> -->
                </geometry>
                <origin xyz="0.0 0.0 0.0" rpy="${PI / 2} 0.0 0.0" />
                <material name="wheel_color">
                    <color rgba="0.0 0.0 0.0 0.5" />
                </material>
            </visual>
            <collision>
                <geometry>
                    <cylinder radius="${wheel_radius}" length="${wheel_length}" />
                </geometry>
                <origin xyz="0.0 0.0 0.0" rpy="${PI / 2} 0.0 0.0" />
            </collision>
            <xacro:cylinder_inertial_matrix m="${wheel_weight}" r="${wheel_radius}" h="${wheel_length}" />
        </link>
        <gazebo reference="${wheel_name}_wheel">			
            <material>Gazebo/Black</material>  <!-- gazebo color -->
            <!-- <mu1 value="20.0"/> -->
            <!-- <mu2 value="20.0"/> -->
            <!-- <kp value="100000000.0"/> -->
            <!-- <kd value="1.0"/> -->
        </gazebo>    
        <joint name="${wheel_name}2base" type="continuous">
            <parent link="base_link"  />
            <child link="${wheel_name}_wheel" />
            <origin xyz="0.0 ${0.1 * flag} ${joint_wheel2base_z}" rpy="0.0 0.0 0.0" />
            <axis xyz="0 1 0" />
        </joint>
    </xacro:macro>

    <xacro:macro name="spinwheel_func" params="spinwheel_name flag" >
        <link name="${spinwheel_name}_wheel">
            <visual>
                <geometry>
                    <sphere radius="${spinwheel_radius}" />
                </geometry>
                <origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
                <material name="wheel_color">
                    <color rgba="1.0 0.5 0.5 0.5" />
                </material>
            </visual>
            <collision>
                <geometry>
                    <sphere radius="${spinwheel_radius}" />
                </geometry>
                <origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />   
            </collision>
            <xacro:sphere_inertial_matrix m="${spinwheel_weight}" r="${spinwheel_radius}" />
        </link>
        <gazebo reference="${spinwheel_name}_wheel">
            <material>Gazebo/Black</material>
        </gazebo>
        <joint name="${spinwheel_name}2base" type="continuous">
            <parent link="base_link" />
            <child link="${spinwheel_name}_wheel" />
            <origin xyz="${0.08 * flag} 0.0 ${joint_spinwheel2base_z}" rpy="0.0 0.0 0.0" />
            <axis xyz="1 1 1" />
        </joint>
    </xacro:macro>

<!-- xacro2urdf -->
    <link name="base_footprint">
        <visual>
            <geometry>
                <sphere radius="${footprint_radius}" />
            </geometry>
        </visual>
    </link>

    <link name="base_link">
        <visual>
            <geometry>
                <cylinder radius="${base_radius}" length="${base_length}" />
            </geometry>
            <origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
            <material name="base_color">
                <color rgba="1.0 0.5 0.5 0.5" />
            </material>
        </visual>
        <collision>
            <geometry>
                <cylinder radius="${base_radius}" length="${base_length}" />
            </geometry>
            <origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
        </collision>
        <xacro:cylinder_inertial_matrix m="${base_weight}" r="${base_radius}" h="${base_length}" />
    </link>

    <gazebo reference="base_link">
        <material>Gazebo/Green</material>
    </gazebo>

    <joint name="base2footprint" type="fixed">
        <parent link="base_footprint" />
        <child link="base_link" />
        <origin xyz="0.0 0.0 ${joint_base2footprint_z}" rpy="0.0 0.0 0.0" />
        <axis xyz="0 0 0" />
    </joint>

    <xacro:wheel_func wheel_name="left" flag="1" />
    <xacro:wheel_func wheel_name="right" flag="-1" />

    <xacro:spinwheel_func spinwheel_name="front" flag="1" />
    <xacro:spinwheel_func spinwheel_name="back" flag="-1" />

</robot>
<robot name="laser" xmlns:xacro="http://wiki.ros.org/xacro">
<!-- property -->
    <xacro:property name="support_weight" value="0.02" />
    <xacro:property name="support_radius" value="0.01" />
    <xacro:property name="support_length" value="0.15" />
    <xacro:property name="laser_weight" value="0.1" />
    <xacro:property name="laser_radius" value="0.03" />
    <xacro:property name="laser_length" value="0.05" />
    <xacro:property name="joint_support2base_x" value="0.0" />
    <xacro:property name="joint_support2base_y" value="0.0" />
    <xacro:property name="joint_support2base_z" value="${base_length / 2 + support_length / 2}" />
    <xacro:property name="joint_laser2support_x" value="0.0" />
    <xacro:property name="joint_laser2support_y" value="0.0" />
    <xacro:property name="joint_laser2support_z" value="${support_length / 2 + laser_length / 2}" />

<!-- xacro2urdf -->
    <link name="support">
        <visual>
            <geometry>
                <cylinder radius="${support_radius}" length="${support_length}" />
            </geometry>
            <origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
            <material name="support_color">
                <color rgba="0.5 1.0 0.5 1.0" />
            </material>
        </visual>
        <collision>
            <geometry>
                <cylinder radius="${support_radius}" length="${support_length}" />
            </geometry>
            <origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
        </collision>
        <xacro:cylinder_inertial_matrix m="${support_weight}" r="${support_radius}" h="${support_length}" />
    </link>

    <gazebo reference="support">
        <material>Gazebo/Orange</material>
    </gazebo>

    <joint name="support2base" type="fixed">
        <parent link="base_link" />
        <child link="support" />
        <origin xyz="${joint_support2base_x} ${joint_support2base_y} ${joint_support2base_z}" rpy="0.0 0.0 0.0" />
    </joint>

    <link name="laser">
        <visual>
            <geometry>
                <cylinder radius="${laser_radius}" length="${laser_length}" />
            </geometry>
            <origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
            <material name="laser_color">
                <color rgba="0.5 0.5 1.0 1.0" />
            </material>
        </visual>
        <collision>
            <geometry>
                <cylinder radius="${laser_radius}" length="${laser_length}" />
            </geometry>
            <origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
        </collision>
        <xacro:cylinder_inertial_matrix m="${laser_weight}" r="${laser_radius}" h="${laser_length}" />
    </link>

    <gazebo reference="laser">
        <material>Gazebo/Red</material>
    </gazebo>

    <joint name="laser2support" type="fixed">
        <parent link="support" />
        <child link="laser" />
        <origin xyz="${joint_laser2support_x} ${joint_laser2support_y} ${joint_laser2support_z}" rpy="0.0 0.0 0.0" />
    </joint>

</robot>
<robot name="camera" xmlns:xacro="http://wiki.ros.org/xacro">
<!-- property -->
    <xacro:property name="camera_weight" value="0.01" />
    <xacro:property name="camera_length" value="0.02" />
    <xacro:property name="camera_width" value="0.05" />
    <xacro:property name="camera_height" value="0.05" />
    <xacro:property name="joint_camera_x" value="0.08" />
    <xacro:property name="joint_camera_y" value="0.0" />
    <xacro:property name="joint_camera_z" value="${base_length / 2 + camera_height / 2}" />
    <xacro:property name="PI" value="3.1415927" />

<!-- xacro2urdf -->
    <link name="camera">
        <visual>
            <geometry>
                <box size="${camera_length} ${camera_width} ${camera_height}" />
            </geometry>
            <origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
            <material name="camera_color">
                <color rgba="0.5 0.5 0.5 0.5" />
            </material>
        </visual>
        <collision>
            <geometry>
                <box size="${camera_length} ${camera_width} ${camera_height}" />
            </geometry>
            <origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
        </collision>
        <xacro:Box_inertial_matrix m="${camera_weight}" l="${camera_length}" w="${camera_width}" h="${camera_height}" />
    </link>

    <gazebo reference="camera">
        <material>Gazebo/White</material>
    </gazebo>

    <joint name="camera2base" type="fixed">
        <parent link="base_link" />
        <child link="camera" />
        <origin xyz="${joint_camera_x} ${joint_camera_y} ${joint_camera_z}" rpy="0.0 0.0 0.0" />
    </joint>

    <link name="camera_deep"></link>
    <joint name="camera_deep2camera" type="fixed">
        <parent link="camera" />
        <child link="camera_deep" />
        <origin xyz="0.0 0.0 0.0" rpy="${-PI/2} 0.0 ${-PI/2}" />
    </joint>

</robot>

gazebo:环境仿真

<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
    <xacro:macro name="gazebo_control_plugin">
        <gazebo>
            <plugin name='diff_drive' filename='libgazebo_ros_diff_drive.so'>
                <ros>
                    <namespace>/</namespace>
                    <remapping>cmd_vel:=cmd_vel</remapping>
                    <remapping>odom:=odom</remapping>
                </ros>
                <update_rate>30</update_rate>
                <!-- wheels -->
                <left_joint>left2base</left_joint>
                <right_joint>right2base</right_joint>
                <!-- kinematics -->
                <wheel_separation>0.2</wheel_separation>
                <wheel_diameter>0.064</wheel_diameter>
                <!-- limits -->
                <max_wheel_torque>20</max_wheel_torque>
                <max_wheel_acceleration>1.0</max_wheel_acceleration>
                <!-- output -->
                <publish_odom>true</publish_odom>
                <publish_odom_tf>true</publish_odom_tf>
                <publish_wheel_tf>true</publish_wheel_tf>

                <odometry_frame>odom</odometry_frame>
                <robot_base_frame>base_footprint</robot_base_frame>
            </plugin>
        </gazebo>
   </xacro:macro>
</robot>
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
    <xacro:macro name="gazebo_laser_plugin">
        <gazebo reference="laser">
            <sensor name="laserscan" type="ray">
                <plugin name="laserscan" filename="libgazebo_ros_ray_sensor.so">
                    <ros>
                        <namespace>/</namespace>
                        <remapping>~/out:=scan</remapping>
                    </ros>
                    <output_type>sensor_msgs/LaserScan</output_type>
                    <frame_name>laser</frame_name>
                </plugin>

                <always_on>true</always_on>
                <visualize>true</visualize>
                <update_rate>5</update_rate>
                <pose>0 0 0 0 0 0</pose>
				<!-- 激光传感器配置 -->
                <ray>
                    <!-- 设置扫描范围 -->
                    <scan>
                        <horizontal>
                            <samples>360</samples>
                            <resolution>1.000000</resolution>
                            <min_angle>0.000000</min_angle>
                            <max_angle>6.280000</max_angle>
                        </horizontal>
                    </scan>
                    <!-- 设置扫描距离 -->
                    <range>
                        <min>0.120000</min>
                        <max>8.0</max>
                        <resolution>0.015000</resolution>
                    </range>
                    <!-- 设置噪声 -->
                    <noise>
                        <type>gaussian</type>
                        <mean>0.0</mean>
                        <stddev>0.01</stddev>
                    </noise>
                </ray>
            </sensor>
        </gazebo>
   </xacro:macro>
</robot>
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
    <xacro:macro name="gazebo_imu_plugin">
        <gazebo reference="support">
            <sensor name="imu_sensor" type="imu">
                <plugin name="imu_plugin" filename="libgazebo_ros_imu_sensor.so">
                    <ros>
                        <namespace>/</namespace>
                        <remapping>~/out:=imu</remapping>
                    </ros>
                    <initial_orientation_as_reference>false</initial_orientation_as_reference>
                </plugin>
                <update_rate>100</update_rate>
                <always_on>true</always_on>
                <!-- 六轴噪声设置 -->
                <imu>
                    <angular_velocity>
                        <x>
                            <noise type="gaussian">
                                <mean>0.0</mean>
                                <stddev>2e-4</stddev>
                                <bias_mean>0.0000075</bias_mean>
                                <bias_stddev>0.0000008</bias_stddev>
                            </noise>
                        </x>
                        <y>
                            <noise type="gaussian">
                                <mean>0.0</mean>
                                <stddev>2e-4</stddev>
                                <bias_mean>0.0000075</bias_mean>
                                <bias_stddev>0.0000008</bias_stddev>
                            </noise>
                        </y>
                        <z>
                            <noise type="gaussian">
                                <mean>0.0</mean>
                                <stddev>2e-4</stddev>
                                <bias_mean>0.0000075</bias_mean>
                                <bias_stddev>0.0000008</bias_stddev>
                            </noise>
                        </z>
                    </angular_velocity>
                    <linear_acceleration>
                        <x>
                            <noise type="gaussian">
                                <mean>0.0</mean>
                                <stddev>1.7e-2</stddev>
                                <bias_mean>0.1</bias_mean>
                                <bias_stddev>0.001</bias_stddev>
                            </noise>
                        </x>
                        <y>
                            <noise type="gaussian">
                                <mean>0.0</mean>
                                <stddev>1.7e-2</stddev>
                                <bias_mean>0.1</bias_mean>
                                <bias_stddev>0.001</bias_stddev>
                            </noise>
                        </y>
                        <z>
                            <noise type="gaussian">
                                <mean>0.0</mean>
                                <stddev>1.7e-2</stddev>
                                <bias_mean>0.1</bias_mean>
                                <bias_stddev>0.001</bias_stddev>
                            </noise>
                        </z>
                    </linear_acceleration>
                </imu>
            </sensor>
        </gazebo>
   </xacro:macro>
</robot>
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
    <xacro:macro name="gazebo_camera_plugin">
        <gazebo reference="camera">
            <sensor type="depth" name="camera_sensor">
                <plugin name="depth_camera" filename="libgazebo_ros_camera.so">
                    <frame_name>camera_deep</frame_name>
                </plugin>
                <always_on>true</always_on>
                <update_rate>10</update_rate>
                <camera name="camera">
                    <horizontal_fov>1.5009831567</horizontal_fov>
                    <image>
                        <width>800</width>
                        <height>600</height>
                        <format>R8G8B8</format>
                    </image>
                    <distortion>
                        <k1>0.0</k1>
                        <k2>0.0</k2>
                        <k3>0.0</k3>
                        <p1>0.0</p1>
                        <p2>0.0</p2>
                        <center>0.5 0.5</center>
                    </distortion>
                </camera>
            </sensor>
        </gazebo>
   </xacro:macro>
</robot>

ros2_control

一个数据接口和控制器算法框架,用于硬件集成—实体机器人的控制器与机器人tf的配置。与上面的gazebo插件具有类似的作用,只是数据接口做了统一

框架下常用的控制器管理命令:

# 框架下所有的控制器
ros2 control list_controller_types 
# 框架下正在运行的数据接口
ros2 control list_hardware_interfaces
# 框架下正在运行的硬件组件
ros2 control list_hardware_components
# 加载控制器
ros2 control load_cntroller [cntroller_name] --set-state active
# 框架下已加载的控制器
ros2 control list_controllers
# 设置已加载的控制器状态
ros2 control set_controller_state [cntroller_name] inactive|active
# 卸载控制器
ros2 control unload_controller [cntroller_name]

gazebo_ros2_control,提供符合控制器数据接口的数据:

<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
    <xacro:macro name="robot_ros2_control">
        <ros2_control name="RobotGazeboSystem" type="system">
            <hardware>
                <plugin>gazebo_ros2_control/GazeboSystem</plugin>
            </hardware>
            <joint name="left2base">
                <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>
            <joint name="right2base">
                <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>
            <plugin filename="libgazebo_ros2_control.so" name="gazebo_ros2_control">
                <parameters>$(find urdfDemo)/config/robot_ros2_controller.yaml</parameters>
                <ros>
                    <remapping>/robot_diff_drive_controller/cmd_vel_unstamped:=/cmd_vel</remapping>
                    <remapping>/robot_diff_drive_controller/odom:=/odom</remapping>
                </ros>
            </plugin>
        </gazebo>
    </xacro:macro>
</robot>

ros2_control要求启动的控制器:

controller_manager:
  ros__parameters:
    update_rate: 100  # Hz
    use_sim_time: true

    robot_joint_state_broadcaster:
      type: joint_state_broadcaster/JointStateBroadcaster
    robot_effort_controller:
      type: effort_controllers/JointGroupEffortController
    robot_diff_drive_controller:
      type: diff_drive_controller/DiffDriveController

robot_effort_controller:
  ros__parameters:
    joints:
      - left2base
      - right2base
    command_interfaces:
      - effort
    state_interfaces:
      - position
      - velocity
      - effort

robot_diff_drive_controller:
  ros__parameters:
    left_wheel_names: ["left2base"]
    right_wheel_names: ["right2base"]

    wheel_separation: 0.20
    #wheels_per_side: 1  # actually 2, but both are controlled by 1 signal
    wheel_radius: 0.032

    wheel_separation_multiplier: 1.0
    left_wheel_radius_multiplier: 1.0
    right_wheel_radius_multiplier: 1.0

    publish_rate: 50.0
    odom_frame_id: odom
    base_frame_id: base_footprint
    pose_covariance_diagonal : [0.001, 0.001, 0.0, 0.0, 0.0, 0.01]
    twist_covariance_diagonal: [0.001, 0.0, 0.0, 0.0, 0.0, 0.01]

    open_loop: true
    enable_odom_tf: true

    cmd_vel_timeout: 0.5
    #publish_limited_velocity: true
    use_stamped_vel: false
    #velocity_rolling_window_size: 10

如何根据机器人结构关系和传感器信息来控制机器人完成任务,包括SLAM和Navigation

SLAM

即时定位地图构建,使机器人在未知环境中通过传感器(如激光雷达、摄像头、IMU、超声波传感器和GPS),进行数据采集、特征提取、数据关联、位置估计和地图更新,从而确定自身位置并构建环境地图—map.yaml,yaml内容如下:

单位为m
image图像名称
mode图像模式,trinary(三进制)/scale(缩放的)/raw(原本的值)
resolution分辨率,一个栅格对应的物理尺寸
origin机器人原点坐标系下地图原点位置
negate是否反转图像
cooupied_thresh占据阈值
free_thresh空闲阈值
  • slam-toolbox
# 启动机器人仿真
ros2 launch slam_toolbox online_async_launch.py use_sim_time:=true				# 建图
ros2 run teleop_twist_keyboard teleop_twist_keyboard 							# 控制运动
ros2 run nav2_map_server map_saver_cli -f ./maps/map								# 保存地图
  • cartographer

导航框架,基于SLAM、目标点、激光雷达和摄像头,提供路径规划路径跟踪障碍物避让和恢复行为等功能,使机器人能够在复杂环境中根据目标点/路点/障碍物实现自主导航

  • Navagation 2:一个launch示例nav2_bringup

一份nav2_bringup/bring_up.launch.py示例如下:

# 启动机器人仿真
from launch import LaunchDescription
from launch_ros.actions import Node
import os
from ament_index_python.packages import get_package_share_directory
from launch.substitutions import LaunchConfiguration
from launch.actions import DeclareLaunchArgument,IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource

def generate_launch_description():
    default_package_dir = get_package_share_directory('[package_name]')
    nav2_bringup_dir = get_package_share_directory('nav2_bringup')
    default_rviz_path = os.path.join(nav2_bringup_dir,'rviz','nav2_default_view.rviz')
    
    use_sim_time = LaunchConfiguration('use_sim_time', default='true')
    map_yaml_path = LaunchConfiguration('map', default=os.path.join(default_package_dir, 'maps', 'map.yaml'))
    nav2_param_path = LaunchConfiguration('params_file', 
                            default=os.path.join(default_package_dir, 'config', '[nav2_params].yaml'))

    return LaunchDescription([
        DeclareLaunchArgument('use_sim_time', default_value=use_sim_time,
                                             description='Use simulation (Gazebo) clock if true'),
        DeclareLaunchArgument('map', default_value=map_yaml_path,
                                             description='Full path to map file to load'),
        DeclareLaunchArgument('params_file', default_value=nav2_param_path,
                                             description='Full path to param file to load'),

        IncludeLaunchDescription(
            PythonLaunchDescriptionSource(
                [nav2_bringup_dir, '/launch', '/bringup_launch.py']),
            launch_arguments={
                'use_sim_time': use_sim_time,
                'map': map_yaml_path,
                'params_file': nav2_param_path}.items(),
        ),

        Node(
            package='rviz2',
            executable='rviz2',
            arguments=['-d', default_rviz_path],
            parameters=[{'use_sim_time': use_sim_time}],
            output='screen'
        ),
    ])

cp /opt/ros/humble/share/nav2_bringup/params/nav2_params.yaml src/robot_navigation/config/

# 关注topic、frame、robot_radius、controller_server:FollowPath、costmap:inflation_radius、xy_goal_tolerance
amcl:
  ros__parameters:
    use_sim_time: True
    alpha1: 0.2
    alpha2: 0.2
    alpha3: 0.2
    alpha4: 0.2
    alpha5: 0.2
    base_frame_id: "base_footprint"
    beam_skip_distance: 0.5
    beam_skip_error_threshold: 0.9
    beam_skip_threshold: 0.3
    do_beamskip: false
    global_frame_id: "map"
    lambda_short: 0.1
    laser_likelihood_max_dist: 2.0
    laser_max_range: 100.0
    laser_min_range: -1.0
    laser_model_type: "likelihood_field"
    max_beams: 60
    max_particles: 2000
    min_particles: 500
    odom_frame_id: "odom"
    pf_err: 0.05
    pf_z: 0.99
    recovery_alpha_fast: 0.0
    recovery_alpha_slow: 0.0
    resample_interval: 1
    robot_model_type: "nav2_amcl::DifferentialMotionModel"
    save_pose_rate: 0.5
    sigma_hit: 0.2
    tf_broadcast: true
    transform_tolerance: 1.0
    update_min_a: 0.2
    update_min_d: 0.25
    z_hit: 0.5
    z_max: 0.05
    z_rand: 0.5
    z_short: 0.05
    scan_topic: scan

bt_navigator:
  ros__parameters:
    use_sim_time: True
    global_frame: map
    robot_base_frame: base_link
    odom_topic: /odom
    bt_loop_duration: 10
    default_server_timeout: 20
    wait_for_service_timeout: 1000
    # 'default_nav_through_poses_bt_xml' and 'default_nav_to_pose_bt_xml' are use defaults:
    # nav2_bt_navigator/navigate_to_pose_w_replanning_and_recovery.xml
    # nav2_bt_navigator/navigate_through_poses_w_replanning_and_recovery.xml
    # They can be set here or via a RewrittenYaml remap from a parent launch file to Nav2.
    plugin_lib_names:
      - nav2_compute_path_to_pose_action_bt_node
      - nav2_compute_path_through_poses_action_bt_node
      - nav2_smooth_path_action_bt_node
      - nav2_follow_path_action_bt_node
      - nav2_spin_action_bt_node
      - nav2_wait_action_bt_node
      - nav2_assisted_teleop_action_bt_node
      - nav2_back_up_action_bt_node
      - nav2_drive_on_heading_bt_node
      - nav2_clear_costmap_service_bt_node
      - nav2_is_stuck_condition_bt_node
      - nav2_goal_reached_condition_bt_node
      - nav2_goal_updated_condition_bt_node
      - nav2_globally_updated_goal_condition_bt_node
      - nav2_is_path_valid_condition_bt_node
      - nav2_initial_pose_received_condition_bt_node
      - nav2_reinitialize_global_localization_service_bt_node
      - nav2_rate_controller_bt_node
      - nav2_distance_controller_bt_node
      - nav2_speed_controller_bt_node
      - nav2_truncate_path_action_bt_node
      - nav2_truncate_path_local_action_bt_node
      - nav2_goal_updater_node_bt_node
      - nav2_recovery_node_bt_node
      - nav2_pipeline_sequence_bt_node
      - nav2_round_robin_node_bt_node
      - nav2_transform_available_condition_bt_node
      - nav2_time_expired_condition_bt_node
      - nav2_path_expiring_timer_condition
      - nav2_distance_traveled_condition_bt_node
      - nav2_single_trigger_bt_node
      - nav2_goal_updated_controller_bt_node
      - nav2_is_battery_low_condition_bt_node
      - nav2_navigate_through_poses_action_bt_node
      - nav2_navigate_to_pose_action_bt_node
      - nav2_remove_passed_goals_action_bt_node
      - nav2_planner_selector_bt_node
      - nav2_controller_selector_bt_node
      - nav2_goal_checker_selector_bt_node
      - nav2_controller_cancel_bt_node
      - nav2_path_longer_on_approach_bt_node
      - nav2_wait_cancel_bt_node
      - nav2_spin_cancel_bt_node
      - nav2_back_up_cancel_bt_node
      - nav2_assisted_teleop_cancel_bt_node
      - nav2_drive_on_heading_cancel_bt_node
      - nav2_is_battery_charging_condition_bt_node

bt_navigator_navigate_through_poses_rclcpp_node:
  ros__parameters:
    use_sim_time: True

bt_navigator_navigate_to_pose_rclcpp_node:
  ros__parameters:
    use_sim_time: True

controller_server:
  ros__parameters:
    use_sim_time: True
    controller_frequency: 20.0
    min_x_velocity_threshold: 0.001
    min_y_velocity_threshold: 0.5
    min_theta_velocity_threshold: 0.001
    failure_tolerance: 0.3
    progress_checker_plugin: "progress_checker"
    goal_checker_plugins: ["general_goal_checker"] # "precise_goal_checker"
    controller_plugins: ["FollowPath"]

    # Progress checker parameters
    progress_checker:
      plugin: "nav2_controller::SimpleProgressChecker"
      required_movement_radius: 0.5
      movement_time_allowance: 10.0
    # Goal checker parameters
    #precise_goal_checker:
    #  plugin: "nav2_controller::SimpleGoalChecker"
    #  xy_goal_tolerance: 0.25
    #  yaw_goal_tolerance: 0.25
    #  stateful: True
    general_goal_checker:
      stateful: True
      plugin: "nav2_controller::SimpleGoalChecker"
      xy_goal_tolerance: 0.25
      yaw_goal_tolerance: 0.25
    # DWB parameters
    FollowPath:
      plugin: "dwb_core::DWBLocalPlanner"
      debug_trajectory_details: True
      min_vel_x: 0.0
      min_vel_y: 0.0
      max_vel_x: 0.26
      max_vel_y: 0.0
      max_vel_theta: 1.0
      min_speed_xy: 0.0
      max_speed_xy: 0.26
      min_speed_theta: 0.0
      # Add high threshold velocity for turtlebot 3 issue.
      # https://github.com/ROBOTIS-GIT/turtlebot3_simulations/issues/75
      acc_lim_x: 2.5
      acc_lim_y: 0.0
      acc_lim_theta: 3.2
      decel_lim_x: -2.5
      decel_lim_y: 0.0
      decel_lim_theta: -3.2
      vx_samples: 20
      vy_samples: 5
      vtheta_samples: 20
      sim_time: 1.7
      linear_granularity: 0.05
      angular_granularity: 0.025
      transform_tolerance: 0.2
      xy_goal_tolerance: 0.25
      trans_stopped_velocity: 0.25
      short_circuit_trajectory_evaluation: True
      stateful: True
      critics: ["RotateToGoal", "Oscillation", "BaseObstacle", "GoalAlign", "PathAlign", "PathDist", "GoalDist"]
      BaseObstacle.scale: 0.02
      PathAlign.scale: 32.0
      PathAlign.forward_point_distance: 0.1
      GoalAlign.scale: 24.0
      GoalAlign.forward_point_distance: 0.1
      PathDist.scale: 32.0
      GoalDist.scale: 24.0
      RotateToGoal.scale: 32.0
      RotateToGoal.slowing_factor: 5.0
      RotateToGoal.lookahead_time: -1.0

local_costmap:
  local_costmap:
    ros__parameters:
      update_frequency: 5.0
      publish_frequency: 2.0
      global_frame: odom
      robot_base_frame: base_link
      use_sim_time: True
      rolling_window: true
      width: 3
      height: 3
      resolution: 0.05
      robot_radius: 0.12
      plugins: ["voxel_layer", "inflation_layer"]
      inflation_layer:
        plugin: "nav2_costmap_2d::InflationLayer"
        cost_scaling_factor: 3.0
        inflation_radius: 0.55
      voxel_layer:
        plugin: "nav2_costmap_2d::VoxelLayer"
        enabled: True
        publish_voxel_map: True
        origin_z: 0.0
        z_resolution: 0.05
        z_voxels: 16
        max_obstacle_height: 2.0
        mark_threshold: 0
        observation_sources: scan
        scan:
          topic: /scan
          max_obstacle_height: 2.0
          clearing: True
          marking: True
          data_type: "LaserScan"
          raytrace_max_range: 3.0
          raytrace_min_range: 0.0
          obstacle_max_range: 2.5
          obstacle_min_range: 0.0
      static_layer:
        plugin: "nav2_costmap_2d::StaticLayer"
        map_subscribe_transient_local: True
      always_send_full_costmap: True

global_costmap:
  global_costmap:
    ros__parameters:
      update_frequency: 1.0
      publish_frequency: 1.0
      global_frame: map
      robot_base_frame: base_link
      use_sim_time: True
      robot_radius: 0.12
      resolution: 0.05
      track_unknown_space: true
      plugins: ["static_layer", "obstacle_layer", "inflation_layer"]
      obstacle_layer:
        plugin: "nav2_costmap_2d::ObstacleLayer"
        enabled: True
        observation_sources: scan
        scan:
          topic: /scan
          max_obstacle_height: 2.0
          clearing: True
          marking: True
          data_type: "LaserScan"
          raytrace_max_range: 3.0
          raytrace_min_range: 0.0
          obstacle_max_range: 2.5
          obstacle_min_range: 0.0
      static_layer:
        plugin: "nav2_costmap_2d::StaticLayer"
        map_subscribe_transient_local: True
      inflation_layer:
        plugin: "nav2_costmap_2d::InflationLayer"
        cost_scaling_factor: 3.0
        inflation_radius: 0.55
      always_send_full_costmap: True

map_server:
  ros__parameters:
    use_sim_time: True
    # Overridden in launch by the "map" launch configuration or provided default value.
    # To use in yaml, remove the default "map" value in the tb3_simulation_launch.py file & provide full path to map below.
    yaml_filename: ""

map_saver:
  ros__parameters:
    use_sim_time: True
    save_map_timeout: 5.0
    free_thresh_default: 0.25
    occupied_thresh_default: 0.65
    map_subscribe_transient_local: True

planner_server:
  ros__parameters:
    expected_planner_frequency: 20.0
    use_sim_time: True
    planner_plugins: ["GridBased"]
    GridBased:
      plugin: "nav2_navfn_planner/NavfnPlanner"
      tolerance: 0.5
      use_astar: false
      allow_unknown: true

smoother_server:
  ros__parameters:
    use_sim_time: True
    smoother_plugins: ["simple_smoother"]
    simple_smoother:
      plugin: "nav2_smoother::SimpleSmoother"
      tolerance: 1.0e-10
      max_its: 1000
      do_refinement: True

behavior_server:
  ros__parameters:
    costmap_topic: local_costmap/costmap_raw
    footprint_topic: local_costmap/published_footprint
    cycle_frequency: 10.0
    behavior_plugins: ["spin", "backup", "drive_on_heading", "assisted_teleop", "wait"]
    spin:
      plugin: "nav2_behaviors/Spin"
    backup:
      plugin: "nav2_behaviors/BackUp"
    drive_on_heading:
      plugin: "nav2_behaviors/DriveOnHeading"
    wait:
      plugin: "nav2_behaviors/Wait"
    assisted_teleop:
      plugin: "nav2_behaviors/AssistedTeleop"
    global_frame: odom
    robot_base_frame: base_link
    transform_tolerance: 0.1
    use_sim_time: true
    simulate_ahead_time: 2.0
    max_rotational_vel: 1.0
    min_rotational_vel: 0.4
    rotational_acc_lim: 3.2

robot_state_publisher:
  ros__parameters:
    use_sim_time: True

waypoint_follower:
  ros__parameters:
    use_sim_time: True
    loop_rate: 20
    stop_on_failure: false
    waypoint_task_executor_plugin: "wait_at_waypoint"
    wait_at_waypoint:
      plugin: "nav2_waypoint_follower::WaitAtWaypoint"
      enabled: True
      waypoint_pause_duration: 200

velocity_smoother:
  ros__parameters:
    use_sim_time: True
    smoothing_frequency: 20.0
    scale_velocities: False
    feedback: "OPEN_LOOP"
    max_velocity: [0.26, 0.0, 1.0]
    min_velocity: [-0.26, 0.0, -1.0]
    max_accel: [2.5, 0.0, 3.2]
    max_decel: [-2.5, 0.0, -3.2]
    odom_topic: "odom"
    odom_duration: 0.1
    deadband_velocity: [0.0, 0.0, 0.0]
    velocity_timeout: 1.0

初始化位姿,nav2 包中amcl需要初始位姿估计才能运行,否则无法发布tf【map -> odom】

ros2 topic pub /initialpose geometry_msgs/msg/PoseWithCovarianceStamped "{header: {frame_id: map}}" --once

from geometry_msgs.msg import PoseStamped
from nav2_simple_commander.robot_navigator import BasicNavigator
import rclpy
def main():
    rclpy.init()
    nav = BasicNavigator()

    init_pose = PoseStamped()
    init_pose.header.frame_id = 'map'
    init_pose.header.stamp = nav.get_clock().now().to_msg()
    init_pose.pose.position.x = 0.0
    init_pose.pose.position.y = 0.0
    init_pose.pose.position.z = 0.0
    init_pose.pose.orientation.w = 1.0

    nav.setInitialPose(init_pose)
    nav.waitUntilNav2Active()
    rclpy.spin(nav)
    rclpy.shutdown()

if __name__ == '__main__':
    main()

使用tf获取机器人实时位姿

import rclpy
from rclpy.node import Node
from tf2_ros.buffer import Buffer
from tf2_ros.transform_listener import TransformListener
from rclpy.time import Time

class TFListener(Node):
    def __init__(self):
        super().__init__("tf_listener")
        self.buffer = Buffer()
        self.listener = TransformListener(self.buffer,self)
        self.timer = self.create_timer(1.0,self.on_timer)

    def on_timer(self):
        if self.buffer.can_transform("map","base_footprint",Time()):
            ts = self.buffer.lookup_transform("map","base_footprint",Time())
            self.get_logger().info(
                "转换的结果,父坐标系:%s,子坐标系:%s,偏移量:(%.2f,%.2f,%.2f)"
                % (ts.header.frame_id,ts.child_frame_id,
                ts.transform.translation.x,
                ts.transform.translation.y,
                ts.transform.translation.z)
            )
        else:
            self.get_logger().info("转换失败......")

def main():
    rclpy.init()
    rclpy.spin(TFListener())
    rclpy.shutdown()

if __name__ == '__main__':
    main()

动作通信发布目标点

ros2 action send_goal /navigate_to_pose nav2_msgs/action/NavigateToPose "{pose: {header: {frame_id: map}, pose: {position: {x: 2.0, y: 1.0}}}}" --feedback

from geometry_msgs.msg import PoseStamped
from nav2_simple_commander.robot_navigator import BasicNavigator
import rclpy

def main():
    rclpy.init()
    nav = BasicNavigator()
    nav.waitUntilNav2Active()

    to_pose = PoseStamped()
    to_pose.header.frame_id = 'map'
    to_pose.header.stamp = nav.get_clock().now().to_msg()
    to_pose.pose.position.x = 2.0
    to_pose.pose.position.y = 1.0
    to_pose.pose.position.z = 0.0
    to_pose.pose.orientation.w = 1.0

    nav.goToPose(to_pose)
    while not nav.isTaskComplete():
        feedback = nav.getFeedback()
        nav.get_logger().info(f'distance remain: {feedback.distance_remaining}')
        # nav.cancelTask()
    result = nav.getResult()
    nav.get_logger().info(f'nav2 result: {result}')

    rclpy.spin(nav)
    rclpy.shutdown()

if __name__ == '__main__':
    main()

动作通信发布多目标点

# ...
from geometry_msgs.msg import PoseStamped
from nav2_simple_commander.robot_navigator import BasicNavigator
import rclpy

def main():
    rclpy.init()
    nav = BasicNavigator()
    nav.waitUntilNav2Active()

    to_poses = [ ]
    to_pose = PoseStamped()
    to_pose.header.frame_id = 'map'
    to_pose.header.stamp = nav.get_clock().now().to_msg()
    to_pose.pose.position.x = 2.0
    to_pose.pose.position.y = 1.0
    to_pose.pose.position.z = 0.0
    to_pose.pose.orientation.w = 1.0
    to_poses.append(to_pose)
    to_pose1 = PoseStamped()
    to_pose1.header.frame_id = 'map'
    to_pose1.header.stamp = nav.get_clock().now().to_msg()
    to_pose1.pose.position.x = 2.0
    to_pose1.pose.position.y = 1.5
    to_pose1.pose.position.z = 0.0
    to_pose1.pose.orientation.w = 1.0
    to_poses.append(to_pose1)

    nav.followWaypoints(to_poses)
    while not nav.isTaskComplete():
        feedback = nav.getFeedback()
        nav.get_logger().info(f'current waypoint: {feedback.current_waypoint}')
        # nav.cancelTask()
    result = nav.getResult()
    nav.get_logger().info(f'nav2 result: {result}')

    rclpy.spin(nav)
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Demo

yaml多点导航

import rclpy
from geometry_msgs.msg import PoseStamped, Pose
from nav2_simple_commander.robot_navigator import BasicNavigator, TaskResult
from tf2_ros import TransformListener, Buffer
from tf_transformations import euler_from_quaternion, quaternion_from_euler
from rclpy.duration import Duration
# 添加服务接口
from autopatrol_interfaces.srv import SpeachText
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
import cv2

class PatrolNode(BasicNavigator):
    def __init__(self, node_name='patrol_node'):
        super().__init__(node_name)
        # 导航相关定义
        self.declare_parameter('initial_point', [0.0, 0.0, 0.0])
        self.declare_parameter('target_points', [0.0, 0.0, 0.0, 1.0, 1.0, 1.57])
        self.initial_point_ = self.get_parameter('initial_point').value
        self.target_points_ = self.get_parameter('target_points').value
        # 实时位置获取 TF 相关定义
        self.buffer_ = Buffer()
        self.listener_ = TransformListener(self.buffer_, self)
        self.speach_client_ = self.create_client(SpeachText, 'speech_text')

        # 订阅与保存图像相关定义
        self.declare_parameter('image_save_path', '')
        self.image_save_path = self.get_parameter('image_save_path').value
        self.bridge = CvBridge()
        self.latest_image = None
        self.subscription_image = self.create_subscription(
            Image, '/camera_sensor/image_raw', self.image_callback, 10)

    def image_callback(self, msg):
        """
        将最新的消息放到 latest_image 中
        """
        self.latest_image = msg

    def record_image(self):
        """
        记录图像
        """
        if self.latest_image is not None:
          pose = self.get_current_pose()
          cv_image = self.bridge.imgmsg_to_cv2(self.latest_image)
          cv2.imwrite(f'{self.image_save_path}image_{pose.translation.x:3.2f}_{pose.translation.y:3.2f}.png', cv_image)


    def speach_text(self, text):
        """
        调用服务播放语音
        """
        while not self.speach_client_.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('语合成服务未上线,等待中。。。')

        request = SpeachText.Request()
        request.text = text
        future = self.speach_client_.call_async(request)
        rclpy.spin_until_future_complete(self, future)
        if future.result() is not None:
            result = future.result().result
            if result:
                self.get_logger().info(f'语音合成成功:{text}')
            else:
                self.get_logger().warn(f'语音合成失败:{text}')
        else:
            self.get_logger().warn('语音合成服务请求失败')

    def get_pose_by_xyyaw(self, x, y, yaw):
        """
        通过 x,y,yaw 合成 PoseStamped
        """
        pose = PoseStamped()
        pose.header.frame_id = 'map'
        pose.pose.position.x = x
        pose.pose.position.y = y
        rotation_quat = quaternion_from_euler(0, 0, yaw)
        pose.pose.orientation.x = rotation_quat[0]
        pose.pose.orientation.y = rotation_quat[1]
        pose.pose.orientation.z = rotation_quat[2]
        pose.pose.orientation.w = rotation_quat[3]
        return pose

    def init_robot_pose(self):
        """
        初始化机器人位姿
        """
        # 从参数获取初始化点
        self.initial_point_ = self.get_parameter('initial_point').value
        # 合成位姿并进行初始化
        self.setInitialPose(self.get_pose_by_xyyaw(
            self.initial_point_[0], self.initial_point_[1], self.initial_point_[2]))
        # 等待直到导航激活
        self.waitUntilNav2Active()

    def get_target_points(self):
        """
        通过参数值获取目标点集合        
        """
        points = []
        self.target_points_ = self.get_parameter('target_points').value
        for index in range(int(len(self.target_points_)/3)):
            x = self.target_points_[index*3]
            y = self.target_points_[index*3+1]
            yaw = self.target_points_[index*3+2]
            points.append([x, y, yaw])
            self.get_logger().info(f'获取到目标点: {index}->({x},{y},{yaw})')
        return points

    def nav_to_pose(self, target_pose):
        """
        导航到指定位姿
        """
        self.waitUntilNav2Active()
        result = self.goToPose(target_pose)
        while not self.isTaskComplete():
            feedback = self.getFeedback()
            if feedback:
                self.get_logger().info(f'预计: {Duration.from_msg(feedback.estimated_time_remaining).nanoseconds / 1e9} s 后到达')
        # 最终结果判断
        result = self.getResult()
        if result == TaskResult.SUCCEEDED:
            self.get_logger().info('导航结果:成功')
        elif result == TaskResult.CANCELED:
            self.get_logger().warn('导航结果:被取消')
        elif result == TaskResult.FAILED:
            self.get_logger().error('导航结果:失败')
        else:
            self.get_logger().error('导航结果:返回状态无效')

    def get_current_pose(self):
        """
        通过TF获取当前位姿
        """
        while rclpy.ok():
            try:
                tf = self.buffer_.lookup_transform(
                    'map', 'base_footprint', rclpy.time.Time(seconds=0), rclpy.time.Duration(seconds=1))
                transform = tf.transform
                rotation_euler = euler_from_quaternion([
                    transform.rotation.x,
                    transform.rotation.y,
                    transform.rotation.z,
                    transform.rotation.w
                ])
                self.get_logger().info(
                    f'平移:{transform.translation},旋转四元数:{transform.rotation}:旋转欧拉角:{rotation_euler}')
                return transform
            except Exception as e:
                self.get_logger().warn(f'不能够获取坐标变换,原因: {str(e)}')
    
def main():
    rclpy.init()
    patrol = PatrolNode()
    patrol.speach_text(text='正在初始化位置')
    patrol.init_robot_pose()
    patrol.speach_text(text='位置初始化完成')

    while rclpy.ok():
        for point in patrol.get_target_points():
            x, y, yaw = point[0], point[1], point[2]
            # 导航到目标点
            target_pose = patrol.get_pose_by_xyyaw(x, y, yaw)
            patrol.speach_text(text=f'准备前往目标点{x},{y}')
            patrol.nav_to_pose(target_pose)
            patrol.speach_text(text=f"已到达目标点{x},{y},准备记录图像")
            patrol.record_image()
            patrol.speach_text(text=f"图像记录完成")
    rclpy.shutdown()

if __name__ == '__main__':
    main()
import rclpy
from rclpy.node import Node
from autopatrol_interfaces.srv import SpeachText
import espeakng

class Speaker(Node):
    def __init__(self, node_name):
        super().__init__(node_name)
        self.speech_service = self.create_service(
            SpeachText, 'speech_text', self.speak_text_callback)
        self.speaker = espeakng.Speaker()
        self.speaker.voice = 'zh'

    def speak_text_callback(self, request, response):
        self.get_logger().info('正在朗读 %s' % request.text)
        self.speaker.say(request.text)
        self.speaker.wait()
        response.result = True
        return response

def main(args=None):
    rclpy.init(args=args)
    node = Speaker('speaker')
    rclpy.spin(node)
    rclpy.shutdown()