Bootstrap

【一看就会】Autoware.universe的路径点生成逻辑


前言

【一看就会】Autoware.universe的车道规划逻辑文章中,我们介绍了车道规划逻辑。
车道规划是指在两个路径点之间规划一条连续可行的车道。

也就是说,在我们给了起点和终点之后,会先生成路径点,将起点和终点分割成几段,再将这几段进行车道规划。

那路径点又是如何生成的呢?
本文便详细介绍了路径点的生成逻辑。

mission_planner通过两个回调函数开始整个流程,分别是on_set_lanelet_route和on_set_waypoint_route,我们先来讲解这两个函数。


一、on_set_lanelet_route:根据道路生成路线

处理基于 Lanelet 的路线设置请求。

使用场景:当你已经知道具体的车道(lanelet)序列时使用
输入参数包含 segments,这是一个预定义好的车道序列
直接使用给定的车道序列创建路线,不需要额外的路径规划
适用于当你需要车辆严格按照特定车道序列行驶的场景

一般不会调用这个函数,像是我们在仿真里面给了起点和终点,一般不会进这里面。

void MissionPlanner::on_set_lanelet_route(
  const SetLaneletRoute::Request::SharedPtr req, const SetLaneletRoute::Response::SharedPtr res)
{
  // 定义一个别名,方便引用响应消息中的错误代码
  using ResponseCode = autoware_adapi_v1_msgs::srv::SetRoute::Response;

  // 判断当前是否处于重新规划路线的状态
  const auto is_reroute = state_.state == RouteState::SET;

  // 检查当前状态是否允许设置路线
  if (state_.state != RouteState::UNSET && state_.state != RouteState::SET) {
    throw service_utils::ServiceException(
      ResponseCode::ERROR_INVALID_STATE, "The route cannot be set in the current state.");
  }

  // 检查路径规划器是否准备好
  if (!planner_->ready()) {
    throw service_utils::ServiceException(
      ResponseCode::ERROR_PLANNER_UNREADY, "The planner is not ready.");
  }

  // 检查是否接收到车辆的里程信息
  if (!odometry_) {
    throw service_utils::ServiceException(
      ResponseCode::ERROR_PLANNER_UNREADY, "The vehicle pose is not received.");
  }

  // 如果是重新规划路线,检查是否接收到操作模式状态
  if (is_reroute && !operation_mode_state_) {
    throw service_utils::ServiceException(
      ResponseCode::ERROR_PLANNER_UNREADY, "Operation mode state is not received.");
  }

  // 判断车辆是否处于自动驾驶模式
  const bool is_autonomous_driving =
    operation_mode_state_ ? operation_mode_state_->mode == OperationModeState::AUTONOMOUS &&
                              operation_mode_state_->is_autoware_control_enabled
                          : false;

  // 如果是重新规划路线且车辆处于自动驾驶模式,检查重新规划路线的可用性
  if (is_reroute && is_autonomous_driving) {
    const auto reroute_availability = sub_reroute_availability_.takeData();
    if (!reroute_availability || !reroute_availability->availability) {
      throw service_utils::ServiceException(
        ResponseCode::ERROR_INVALID_STATE,
        "Cannot reroute as the planner is not in lane following.");
    }
  }

  // 根据当前是否是重新规划路线,切换状态机状态
  change_state(is_reroute ? RouteState::REROUTING : RouteState::ROUTING);

  // 根据请求消息创建路线
  const auto route = create_route(*req);

  // 检查创建的路线是否为空
  if (route.segments.empty()) {
    cancel_route(); // 取消当前路线
    change_state(is_reroute ? RouteState::SET : RouteState::UNSET); // 切换状态
    throw service_utils::ServiceException(
      ResponseCode::ERROR_PLANNER_FAILED, "The planned route is empty.");
  }

  // 如果是重新规划路线且车辆处于自动驾驶模式,检查新路线是否安全
  if (is_reroute && is_autonomous_driving && !check_reroute_safety(*current_route_, route)) {
    cancel_route(); // 取消当前路线
    change_state(RouteState::SET); // 切换状态
    throw service_utils::ServiceException(
      ResponseCode::ERROR_REROUTE_FAILED, "New route is not safe. Reroute failed.");
  }

  // 设置新路线
  change_route(route);

  // 切换状态机状态为已设置路线
  change_state(RouteState::SET);

  // 设置响应消息的成功标志
  res->status.success = true;

  // 发布初始姿态和目标姿态的日志信息
  publish_pose_log(odometry_->pose.pose, "initial");
  publish_pose_log(req->goal_pose, "goal");
}

二、on_set_waypoint_route:根据路径点生成路线

用于处理设置航点路线的请求。

使用场景:当你只知道途经点(waypoints)时使用
输入参数包含 waypoints,这是一系列需要经过的位置点
会调用planner来规划一条通过所有途经点的完整路线
系统会自动规划如何在这些途经点之间选择合适的车道
更灵活,适用于只知道大致路线但具体车道选择可以由系统决定的场景

一般使用这个函数,我们点了起点和终点就进这个函数里面了。

这个里面会调用create_route,这个就和我们另外一篇文章Autoware.universe的车道规划逻辑衔接起来了。

调用create_route时,传入的参数是*req,这个是on_set_waypoint_route传入的参数,也就是路径点。

而void MissionPlanner::on_set_waypoint_route是作为一个服务的回调函数进行使用的。

void MissionPlanner::on_set_waypoint_route(
  const SetWaypointRoute::Request::SharedPtr req, const SetWaypointRoute::Response::SharedPtr res)
{
  // 定义一个别名,方便引用响应消息中的错误代码
  using ResponseCode = autoware_adapi_v1_msgs::srv::SetRoutePoints::Response;

  // 判断当前是否处于重新规划路线的状态
  const auto is_reroute = state_.state == RouteState::SET;

  // 检查当前状态是否允许设置路线
  if (state_.state != RouteState::UNSET && state_.state != RouteState::SET) {
    throw service_utils::ServiceException(
      ResponseCode::ERROR_INVALID_STATE, "The route cannot be set in the current state.");
  }

  // 检查路径规划器是否准备好
  if (!planner_->ready()) {
    throw service_utils::ServiceException(
      ResponseCode::ERROR_PLANNER_UNREADY, "The planner is not ready.");
  }

  // 检查是否接收到车辆的里程信息
  if (!odometry_) {
    throw service_utils::ServiceException(
      ResponseCode::ERROR_PLANNER_UNREADY, "The vehicle pose is not received.");
  }

  // 如果是重新规划路线,检查是否接收到操作模式状态
  if (is_reroute && !operation_mode_state_) {
    throw service_utils::ServiceException(
      ResponseCode::ERROR_PLANNER_UNREADY, "Operation mode state is not received.");
  }

  // 判断车辆是否处于自动驾驶模式
  const bool is_autonomous_driving =
    operation_mode_state_ ? operation_mode_state_->mode == OperationModeState::AUTONOMOUS &&
                              operation_mode_state_->is_autoware_control_enabled
                          : false;

  // 如果是重新规划路线且车辆处于自动驾驶模式,检查重新规划路线的可用性
  if (is_reroute && is_autonomous_driving) {
    const auto reroute_availability = sub_reroute_availability_.takeData();
    if (!reroute_availability || !reroute_availability->availability) {
      throw service_utils::ServiceException(
        ResponseCode::ERROR_INVALID_STATE,
        "Cannot reroute as the planner is not in lane following.");
    }
  }

  // 根据当前是否是重新规划路线,切换状态机状态
  change_state(is_reroute ? RouteState::REROUTING : RouteState::ROUTING);

  // 根据请求消息创建路线
  const auto route = create_route(*req);

  // 检查创建的路线是否为空
  if (route.segments.empty()) {
    cancel_route(); // 取消当前路线
    change_state(is_reroute ? RouteState::SET : RouteState::UNSET); // 切换状态
    throw service_utils::ServiceException(
      ResponseCode::ERROR_PLANNER_FAILED, "The planned route is empty.");
  }

  // 如果是重新规划路线且车辆处于自动驾驶模式,检查新路线是否安全
  if (is_reroute && is_autonomous_driving && !check_reroute_safety(*current_route_, route)) {
    cancel_route(); // 取消当前路线
    change_state(RouteState::SET); // 切换状态
    throw service_utils::ServiceException(
      ResponseCode::ERROR_REROUTE_FAILED, "New route is not safe. Reroute failed.");
  }

  // 设置新路线
  change_route(route);

  // 切换状态机状态为已设置路线
  change_state(RouteState::SET);

  // 设置响应消息的成功标志
  res->status.success = true;

  // 发布初始姿态和目标姿态的日志信息
  publish_pose_log(odometry_->pose.pose, "initial");
  publish_pose_log(req->goal_pose, "goal");
}

三、调用on_set_waypoint_route的流程

为了搞清楚create_route里面的*req的生成过程,我们需要搞清楚on_set_waypoint_route的调用流程。

首先需要说明,on_set_waypoint_route是作为一个服务的回调函数进行使用的。
在mission_planner中: srv_set_waypoint_route = create_service( “~/set_waypoint_route”,service_utils::handle_exception(&MissionPlanner::on_set_waypoint_route, this));

它还有上层,具体的结构如下:
1.首先,有一个RouteSelector节点,它创建了两组服务:
// 主路由服务
main_.srv_set_waypoint_route = create_service(
“~/main/set_waypoint_route”,
service_utils::handle_exception(&RouteSelector::on_set_waypoint_route_main, this));

// MRM路由服务
mrm_.srv_set_waypoint_route = create_service(
“~/mrm/set_waypoint_route”,
service_utils::handle_exception(&RouteSelector::on_set_waypoint_route_mrm, this));

2.RouteSelector同时也是一个客户端,它会连接到实际的规划器服务:
cli_set_waypoint_route_ = create_client(“~/planner/set_waypoint_route”, service_qos, group_);

3.当收到路由请求时,处理流程如下:
void RouteSelector::on_set_waypoint_route_main(
SetWaypointRoute::Request::SharedPtr req, SetWaypointRoute::Response::SharedPtr res)
{
// 生成UUID(如果为空)
req->uuid = uuid::generate_if_empty(req->uuid);
// 保存请求
main_request_ = req;
main_.change_route();

// 如果在MRM模式,只改变状态
if (mrm_operating_) {
main_.change_state(RouteState::INTERRUPTED);
res->status.success = true;
return;
}

// 如果不在MRM模式,转发请求到实际的规划器
res->status = service_utils::sync_call(cli_set_waypoint_route_, req);
}

大家需要先理解ros2中的service机制。
总而言之,在运行过程中。
首先由其他节点设置路径点,然后再调用这个服务并等待相应,比如:
// 在其他节点中
auto client = node->create_client(“~/main/set_waypoint_route”);
auto request = std::make_sharedSetWaypointRoute::Request();
// 设置路径点
request->waypoints = …;
request->goal_pose = …;
// 调用服务并等待响应
auto future = client->async_send_request(request);

这个方式不是通过pub发布话题,再由sub接受话题并调用回调函数。
而是其他节点主动调用这个服务,传入路径点,从而进行后续操作,使用起来更像是一个远程函数的调用。

四、设置路径点

上面说了需要别的节点设置完路径点之后主动调用set_waypoint_route从而启动on_set_waypoint_route这个回调函数。

在进行on_set_waypoint_route处理时,会传入SetWaypointRoute::Request::SharedPtr req参数,而SetWaypointRoute::Request::SharedPtr的结构在SetWaypointRoute.srv中定义:
header (std_msgs/Header类型)
frame_id:坐标系的ID
stamp:时间戳
goal_pose (geometry_msgs/Pose类型)
最终目标位置和姿态
包含位置(position)和方向(orientation)
waypoints (geometry_msgs/Pose[]类型)
路径点数组
每个路径点都包含位置和姿态信息
这些是车辆需要经过的中间点
uuid (unique_identifier_msgs/UUID类型)
路径的唯一标识符
用于追踪和管理不同的路径请求
allow_modification (bool类型)
是否允许系统修改路径
如果为true,系统可以根据需要优化或调整路径

我使用过程中打印了一下,无论如何选择起点和终点,路径点都是0.


总结

整理了半天,发现实际上并没有用到这部分,之后再看看。

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;