260515

继我们上次的进度,我们的整个结构可以理解为:

nav2_params.yaml
├── bt_navigator        # 接收 NavigateToPose action,管理导航行为树
├── controller_server   # 跟踪路径,输出速度
├── planner_server      # 根据地图规划全局路径
├── behavior_server     # 处理恢复行为,比如旋转、后退、等待
├── local_costmap       # 机器人附近的局部代价地图
└── global_costmap      # 全局地图上的代价地图

这里我们接触到了一个新概念——代价地图。

这个概念的用途是什么呢?

要知道,我们地图上的点是没有体积的,而我们的小车是有物理宽度的,如果让导航算法让小车的中心点紧贴着 SLAM 的黑线走,那小车很可能就会卡进墙壁里,或者直接撞墙。

所以代价地图存在的目的,就是解决机器人的物理体积和动态躲避的问题,它可以将我们黑白的二维地图,给小车翻译为 0~254 危险指数的危险指数“等高图”。

可以这么认为:

  • 0(空旷区):随便开,非常安全。
  • 1~252(膨胀危险区):离障碍物有点近了,虽然不会撞上,但最好是绕着走。
  • 253(内切区):非常危险,此时机器人肯定已经至少蹭到障碍物了。
  • 254(致命障碍):绝对的障碍物,必然已经撞上去了。

这部分是对于代价地图的理解。

然后就是 bt_navigator 了,这部分是管理小车的行为树的,不多做解释。

controller_server则是根据当前的坐标系位置、当前速度、planner_server 给出的 path 等,在 path 上找一个前视点(在当前规划路径上选一个距离机器人不远的一个临时跟踪点),最后根据当前位置和前视点来计算所需的线速度、角速度,同时用 local costmap 检查附近障碍物和碰撞风险。

behavior_server 和 controller_server 虽然都是控制机器人具体行为的,但是两者的功能上是不一样的。

behavior_server 用于处理需要恢复的行为,比如失败、卡住或需要从不可导航到可导航等动作的时候,就由它来负责后退、旋转等这些短动作。

如果恢复动作都失败了,nav2 就会返回失败。

总的来说,整个链路为:

flowchart LR goal[​"目标点​"] --​> bt[​"bt_navigator​"] bt --​> planner[​"planner_server​"] planner --​> global[​"global_costmap​"] planner --​> path[​"全局路径​"] path --​> controller[​"controller_server​"] controller --​> local[​"local_costmap​"] controller --​> raw[​"/cmd_vel_raw​"] raw --​> watchdog[​"watchdog​"] watchdog --​> cmd[​"/cmd_vel​"] cmd --​> robot[​"机器人​"] controller --​>|​"失败/卡住​"| bt bt --​> behavior[​"behavior_server​"] behavior --​> raw behavior --​>|​"恢复后​"| planner

即,bt_navigator 为调度者,planner_server负责怎么从当前位置到目标点规划出一条路,global_costmap 是给 planner 看全局哪里可以走,controller_server负责沿着这条路实际的开过去,local_costmap 给机器人看看附近有没有障碍,behavior_server 只在失败或恢复分支里执行短动作。

这就是我们 nav2 的 config 文件的写法了。

接下来,我们要写一个启动文件来启动 nav2,我们可以在 src/kibot_one_sim/launch/nav2.launch.py 中写入如下内容:

# mypy: disable-error-code="import-untyped"

from pathlib import Path

from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node


def generate_launch_description() -> LaunchDescription:
    sim_pkg_share = Path(get_package_share_directory("kibot_one_sim"))
    default_params_file = sim_pkg_share / "config" / "nav2_params.yaml"

    use_sim_time_arg = DeclareLaunchArgument(
        name="use_sim_time",
        default_value="true",
        description="Use Gazebo /clock.",
    )
    params_file_arg = DeclareLaunchArgument(
        name="params_file",
        default_value=str(default_params_file),
        description="Nav2 parameters file.",
    )
    autostart_arg = DeclareLaunchArgument(
        name="autostart",
        default_value="true",
        description="Automatically activate Nav2 lifecycle nodes.",
    )

    use_sim_time = LaunchConfiguration("use_sim_time")
    params_file = LaunchConfiguration("params_file")
    autostart = LaunchConfiguration("autostart")

    common_params = [params_file, {"use_sim_time": use_sim_time}]
    common_remappings = [
        ("/tf", "tf"),
        ("/tf_static", "tf_static"),
    ]
    velocity_remappings = common_remappings + [
        ("/cmd_vel", "/cmd_vel_raw"),
        ("cmd_vel", "/cmd_vel_raw"),
    ]

    controller_server = Node(
        package="nav2_controller",
        executable="controller_server",
        name="controller_server",
        output="screen",
        parameters=common_params,
        remappings=velocity_remappings,
    )

    planner_server = Node(
        package="nav2_planner",
        executable="planner_server",
        name="planner_server",
        output="screen",
        parameters=common_params,
        remappings=common_remappings,
    )

    behavior_server = Node(
        package="nav2_behaviors",
        executable="behavior_server",
        name="behavior_server",
        output="screen",
        parameters=common_params,
        remappings=velocity_remappings,
    )

    bt_navigator = Node(
        package="nav2_bt_navigator",
        executable="bt_navigator",
        name="bt_navigator",
        output="screen",
        parameters=common_params,
        remappings=common_remappings,
    )

    lifecycle_manager = Node(
        package="nav2_lifecycle_manager",
        executable="lifecycle_manager",
        name="lifecycle_manager_navigation",
        output="screen",
        parameters=[
            {
                "use_sim_time": use_sim_time,
                "autostart": autostart,
                "node_names": [
                    "controller_server",
                    "planner_server",
                    "behavior_server",
                    "bt_navigator",
                ],
            }
        ],
    )

    return LaunchDescription(
        [
            use_sim_time_arg,
            params_file_arg,
            autostart_arg,
            controller_server,
            planner_server,
            behavior_server,
            bt_navigator,
            lifecycle_manager,
        ]
    )

这里主要是配置文件,包括参数配置等,以及 lifecycle 等节点,我们不做多的解释了。

我们重点关注的是下面的内容,主要是 remapping 这个东西,重映射,和我们 ROS 和 Gazebo 的 bridge 的重映射不同的是,bridge 的重映射是将 Gazebo 和我们的 ROS 进行连接,干的是将 Gazebo/ROS 的话题进行连接起来。

而我们这里的启动文件则是将 Nav2 的默认配置的话题映射到我们所指定的话题,比如这里我们就将速度发布的话题从 cmd_vel 给映射到了 cmd_vel_raw 。

然后我们安装一下所需的依赖:

sudo apt install ros-jazzy-navigation2 ros-jazzy-nav2-bringup