Turtlebot 2e 导航之`move_base` 参数详解:速度平滑功能包

本文详细解析Turtlebot2e中move_base的velocity_smoother.launch.xml文件,介绍其如何通过速度平滑控制器防止机器人速度及加速度过大,避免运动问题。文章深入探讨了smoother节点的工作原理,包括它订阅与发布的话题,以及关键参数如线性加速度最大值、角加速度最大值等的配置。

Turtlebot 2e 导航之move_base 参数详解:速度平滑功能包

速度平滑功能包velocity_smoother.launch.xml

velocity_smoother.launch.xml为速度平滑包,是一个速度平滑控制器,用来防止robot navigation的速度/转速过快,加速度/快减速过大。smoother主要针对navigation或者其他一些例子。robot有一个命令选择node cmd_vel_mux,它可以防止robot被多个ros app下发的运动命令控制从而出现运动问题。见下图:
在这里插入图片描述

velocity_smoother.launch.xml的内容为:

<!-- 
         Velocity smoother
-->
<launch>
  <node pkg="nodelet" type="nodelet" name="navigation_velocity_smoother" args="load yocs_velocity_smoother/VelocitySmootherNodelet mobile_base_nodelet_manager">
    <rosparam file="$(find turtlebot_bringup)/param/defaults/smoother.yaml" command="load"/>
    <remap from="navigation_velocity_smoother/smooth_cmd_vel" to="cmd_vel_mux/input/navi"/>

    <!-- Robot velocity feedbacks; use the default base configuration -->
    <remap from="navigation_velocity_smoother/odometry" to="odom"/>
    <remap from="navigation_velocity_smoother/robot_cmd_vel" to="mobile_base/commands/velocity"/>
  </node>
</launch>

虽然在move_base脚本中启动,但smoother是一个nodelete,被moibile_base_nodelete_manager加载。其中smoothrt.yaml参数配置文件更是在turtlebot_bringup package下面。最后将节点中的发布的topic “navigation_velocity_smoother/smooth_cmd_vel” 重新映射成 “cmd_vel_mux/input/navi”,这样cmd_vel_mux就可以订阅到smoother发布的主题。

订阅的主题


~raw_cmd_vel (geometry_msgs/Twist):输入的速度命令
~odometry (nav_msgs/Odometry):接收里程计信息,确保下发的运动命令没有大的跳跃。根据feedback参数配置
~robot_cmd_vel (geometry_msgs/Twist):接受robot 命令信息,确保下发的运动命令没有大的跳跃。根据feedback参数配置

发布的主题

~smooth_cmd_vel (geometry_msgs/Twist):发出来的命令,通常会将其remap到cmd_vel_mux订阅的运动命令topic上

参数


~accel_lim_v (double):强制必须设置,线性加速度最大值
~accel_lim_w (double):强制必须设置,角加速度最大值
~speed_lim_v (double):强制必须设置,线速度最大值
~speed_lim_w (double):强制必须设置,角速度最大值
~decel_factor (double, default: 1.0):加减速比,对于惯性大的机器人
~frequency (double, default: 20.0):输出速度频率。不论输入命令的频率。必要时插值
~robot_feedback (int, default: 0):速度反馈(0 - none, 1 - odometry, 2 - end robot commands). 

参数配置文件smoother.yaml

# Default parameters used by the yocs_velocity_smoother module.
# This isn't used by minimal.launch per se, rather by everything
# which runs on top.
 
# Mandatory parameters
speed_lim_v: 0.8
speed_lim_w: 5.4
 
accel_lim_v: 1.0 # maximum is actually 2.0, but we push it down to be smooth
accel_lim_w: 2.0
 
# Optional parameters
frequency: 20.0
decel_factor: 1.5
 
# Robot velocity feedback type:
#  0 - none (default)
#  1 - odometry
#  2 - end robot commands
robot_feedback: 2

其他

  1. 除了frequency其它参数都是动态可配置的

  2. 如果有一个恒定的旋转半径,线/角速度会更加平滑

  3. 输入的最后一个topic信息不为0时(命令下发node 崩溃/不活跃),为了保证robot不出现意外,会在最后加一个0速度。

  4. 使用yocs_cmd_vel_mux进行机器人速度控制切换,可参考以下博文:
    https://www.cnblogs.com/21207-iHome/p/8228356.html

  5. 关于velocity smooth算法,有篇比较好的综述文件:

Ravankar A, Ravankar A, Kobayashi Y, et al. Path smoothing techniques in robot navigation: State-of-the-art, current and future challenges[J]. Sensors, 2018, 18(9): 3170.

# Move base node parameters. For full documentation of the parameters in this file, please see # # http://www.ros.org/wiki/move_base # shutdown_costmaps: false controller_frequency: 5.0 controller_patience: 3.0 planner_frequency: 1.0 planner_patience: 5.0 oscillation_timeout: 10.0 oscillation_distance: 0.2 # local planner - default is trajectory rollout #base_local_planner: "dwa_local_planner/DWAPlannerROS" base_local_planner: "teb_local_planner/TebLocalPlannerROS" #alternatives: global_planner/GlobalPlanner, carrot_planner/CarrotPlanner base_global_planner: "navfn/NavfnROS" #We plan to integrate recovery behaviors for turtlebot but currently those belong to gopher and still have to be adapted. ## recovery behaviors; we avoid spinning, but we need a fall-back replanning recovery_behavior_enabled: true recovery_behaviors: #- name: 'super_conservative_reset1' #type: 'clear_costmap_recovery/ClearCostmapRecovery' #- name: 'conservative_reset1' #type: 'clear_costmap_recovery/ClearCostmapRecovery' #- name: 'aggressive_reset1' #type: 'clear_costmap_recovery/ClearCostmapRecovery' - name: 'clearing_rotation1' type: 'rotate_recovery/RotateRecovery' - name: 'super_conservative_reset2' type: 'clear_costmap_recovery/ClearCostmapRecovery' #- name: 'conservative_reset2' #type: 'clear_costmap_recovery/ClearCostmapRecovery' #- name: 'aggressive_reset2' #type: 'clear_costmap_recovery/ClearCostmapRecovery' - name: 'clearing_rotation2' type: 'rotate_recovery/RotateRecovery' #super_conservative_reset1: #reset_distance: 3.0 #conservative_reset1: #reset_distance: 1.5 #aggressive_reset1: #reset_distance: 0.0 super_conservative_reset2: reset_distance: 2.0 #conservative_reset2: #reset_distance: 1.5 #aggressive_reset2: #reset_distance: 0.0
06-25
#include "RelativeMove.h" namespace rei_relative_move { RelativeMove::RelativeMove() : motionModel_(0), getPoseReady_(false), moveFinished_(true), robotModel_(0) {} RelativeMove::~RelativeMove() { if (listenTfThread_.joinable()) { listenTfThread_.join(); } geometry_msgs::Twist stopCmd; velPub_.publish(stopCmd); } int8_t RelativeMove::Init(ros::NodeHandle& nh) { tfBuffer_ = std::make_unique<tf2_ros::Buffer>(); tfListener_ = std::make_shared<tf2_ros::TransformListener>(*tfBuffer_); xPid_ = std::make_shared<rei_tools::ReiPID>(1.0, 0, 0.0); yPid_ = std::make_shared<rei_tools::ReiPID>(1.0, 0, 0.0); thetaPid_ = std::make_shared<rei_tools::ReiPID>(2.0, 0, 0.0); xPid_->setOutputLimit(0.5, 0.05); yPid_->setOutputLimit(0.5, 0.05); thetaPid_->setOutputLimit(1.0, 0.2); relativeMoveServer_ = nh_.advertiseService( "relative_move", &RelativeMove::RelativeMoveCallback, this); velPub_ = nh_.advertise<geometry_msgs::Twist>("cmd_vel", 5); xErrPub_ = nh_.advertise<std_msgs::Float64>("relative/x_err", 5); yErrPub_ = nh_.advertise<std_msgs::Float64>("relative/y_err", 5); thetaErrPub_ = nh_.advertise<std_msgs::Float64>("relative/theta_err", 5); expectXErr_ = 0.01; expectYErr_ = 0.01; expectThetaErr_ = 0.01; return 0; } void RelativeMove::SetXPid(int p, int i, int d) { xPid_->setP(p); xPid_->setI(i); xPid_->setD(d); } void RelativeMove::SetYPid(int p, int i, int d) { yPid_->setP(p); yPid_->setI(i); yPid_->setD(d); } void RelativeMove::SetThetaPid(int p, int i, int d) { thetaPid_->setP(p); thetaPid_->setI(i); thetaPid_->setD(d); } void RelativeMove::ListenTf(std::string frameId, std::string childFrameId) { geometry_msgs::TransformStamped transform; std::string errMsg; if (!tfBuffer_->canTransform(frameId, childFrameId, ros::Time(0), ros::Duration(2.0), &errMsg)) { ROS_ERROR("Unable to get pose from TF: %s", errMsg.c_str()); return; } { std::unique_lock<std::mutex> lock(getFlagMutex_); getPoseReady_ = true; } ros::Rate rate(10.0); while (ros::ok() && (!GetFinishFlag())) { try { transform = tfBuffer_->lookupTransform(frameId, childFrameId, ros::Time(0)); SetTfPose(transform.transform); } catch (tf2::TransformException& ex) { ROS_WARN("%s", ex.what()); ros::Duration(0.1).sleep(); continue; } rate.sleep(); } } void RelativeMove::SetTfPose(geometry_msgs::Transform& tfPose) { std::unique_lock<std::mutex> lock(getTfPoseMutex_); tfPose_ = tfPose; } void RelativeMove::GetTfPose(geometry_msgs::Transform& trans) { std::unique_lock<std::mutex> lock(getTfPoseMutex_); trans = tfPose_; } geometry_msgs::Pose2D RelativeMove::GetTargetGoal( const geometry_msgs::Pose2D& goal) { tf2::Quaternion goal_quat; goal_quat.setRPY(0, 0, goal.theta); geometry_msgs::Transform robotPose; GetTfPose(robotPose); tf2::Transform robotPoseTrans, goalPoseTrans, goalBaseRobotTrans; goalBaseRobotTrans.setOrigin(tf2::Vector3(goal.x, goal.y, 0)); goalBaseRobotTrans.setRotation(goal_quat); geometry_msgs::Transform goalPose; tf2::fromMsg(robotPose, robotPoseTrans); goalPoseTrans = robotPoseTrans * goalBaseRobotTrans; // ROS_INFO("robotPose: %lf, %lf, %lf, %lf", robotPose.translation.x, // robotPose.translation.y, robotPose.rotation.z, // robotPose.rotation.w); // ROS_INFO("goalBaseRobotTrans: %lf, %lf, %lf, %lf", // goalBaseRobotTrans.getOrigin().x(), // goalBaseRobotTrans.getOrigin().y(), // goalBaseRobotTrans.getRotation().z(), // goalBaseRobotTrans.getRotation().w()); // ROS_INFO("goalPoseTrans: %lf, %lf, %lf, %lf", // goalPoseTrans.getOrigin().x(), // goalPoseTrans.getOrigin().y(), // goalPoseTrans.getRotation().z(), // goalPoseTrans.getRotation().w()); geometry_msgs::Pose2D goalReuslt; goalReuslt.x = goalPoseTrans.getOrigin().getX(); goalReuslt.y = goalPoseTrans.getOrigin().getY(); tf2::Matrix3x3 mat(goalPoseTrans.getRotation()); double roll, pitch; mat.getRPY(roll, pitch, goalReuslt.theta); return goalReuslt; } int8_t RelativeMove::GetBaseToGoal(std::string frameId, const geometry_msgs::Pose2D& inGoal) { std::string errMsg; if (!tfBuffer_->canTransform(frameId, "base_link", ros::Time(0), ros::Duration(2.0), &errMsg)) { ROS_ERROR("Unable to get pose from TF: %s", errMsg.c_str()); return -1; } try { geometry_msgs::TransformStamped transform = tfBuffer_->lookupTransform(frameId, "base_link", ros::Time(0)); SetTfPose(transform.transform); } catch (tf2::TransformException& ex) { ROS_WARN("%s", ex.what()); ros::Duration(0.1).sleep(); return -1; } baseToTargetPose_ = GetTargetGoal(inGoal); return 0; } int8_t RelativeMove::MovXY(double x, double y) { xPid_->reset_integral(); yPid_->reset_integral(); geometry_msgs::Pose2D goalReal; geometry_msgs::Pose2D targetGoal; targetGoal.x = x; targetGoal.y = y; double xErr, yErr; ros::Rate loop(10); while (ros::ok()) { goalReal = GetTargetGoal(targetGoal); // ROS_WARN("goalReal: %lf, %lf", goalReal.x, goalReal.y); xErr = .0; yErr = .0; if (x != 0) xErr = goalReal.x; if (y != 0) yErr = goalReal.y; std_msgs::Float64 errMsg; errMsg.data = xErr; xErrPub_.publish(errMsg); errMsg.data = yErr; yErrPub_.publish(errMsg); // ROS_WARN("err: %lf, %lf", xErr, yErr); if ((fabs(xErr) < expectXErr_) && (fabs(yErr) < expectYErr_)) { velPub_.publish(stopCmd_); break; } else { geometry_msgs::Twist velCmd; if (fabs(xErr) > expectXErr_) velCmd.linear.x = xPid_->compute(0.0, xErr); if (fabs(yErr) > expectYErr_) velCmd.linear.y = yPid_->compute(0.0, yErr); velPub_.publish(velCmd); } loop.sleep(); } return 0; } int8_t RelativeMove::MovTheta(double theta) { thetaPid_->reset_integral(); geometry_msgs::Pose2D goalReal; geometry_msgs::Pose2D targetGoal; targetGoal.theta = theta; double thetaErr; ros::Rate loop(10); while (ros::ok()) { goalReal = GetTargetGoal(targetGoal); // ROS_WARN("goalReal: %lf, %lf", goalReal.x, goalReal.y); thetaErr = goalReal.theta; // ROS_WARN("thetaErr: %lf",thetaErr); if (fabs(thetaErr) < expectThetaErr_) { velPub_.publish(stopCmd_); break; } else { geometry_msgs::Twist velCmd; velCmd.angular.z = thetaPid_->compute(0.0, thetaErr); velPub_.publish(velCmd); } loop.sleep(); } return 0; } bool RelativeMove::RelativeMoveCallback( relative_move::SetRelativeMove::Request& req, relative_move::SetRelativeMove::Response& res) { if (listenTfThread_.joinable()) { res.message = "last move task still run"; return false; } getPoseReady_ = false; moveFinished_ = false; res.message = "Get tf trans error"; if (GetBaseToGoal(req.global_frame, req.goal) < 0) return true; listenTfThread_ = std::thread(&RelativeMove::ListenTf, this, "base_link", req.global_frame); double lastTime = ros::Time::now().toSec(); ros::Rate loop(10); while (ros::ok() && (!GetReadyFlag())) { if ((ros::Time::now().toSec() - lastTime) > 2) return true; loop.sleep(); } if (robotModel_) { MovXY(baseToTargetPose_.x, baseToTargetPose_.y); if (req.goal.theta != 0) MovTheta(baseToTargetPose_.theta); } else { geometry_msgs::Pose2D tmpPose; tmpPose = GetTargetGoal(req.goal); double tmpTheta = atan2(tmpPose.y, tmpPose.x); if(tmpTheta>3.1415927) tmpTheta = tmpTheta - M_PI; else if(tmpTheta<-3.1415927) tmpTheta = tmpTheta + M_PI; MovTheta(tmpTheta); MovXY(baseToTargetPose_.x, 0.0); MovTheta(baseToTargetPose_.theta); } SetFinishFlag(true); if (listenTfThread_.joinable()) listenTfThread_.join(); res.message = ""; res.success = true; return true; } } // namespace rei_relative_move解释上述代码
最新发布
06-29
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值