机器人的网络配置和对时
abstract Welcome to my blog, enter password to read.
Read more
terminator的使用

在调试ROS程序时,经常需要运行多个节点程序,如果每个都打开一个终端然后输入指令非常繁琐,可以写一个脚本文件,每次运行这个脚本就能一次运行多个节点,方便高效。其实就是利用ubuntu的terminator

1
sudo apt-get -y install terminator

安装好以后在~/.config/terminator路径下的config是它的配置文件(例如设置窗口数量、字体、窗口大小等)。如果安装terminator后没有这个文件夹,那新建一个就可以了。下面以启动四个窗口为例说明怎么写脚本。

打开config文件,在terminal3下面,给command选项起个名字,例如command = COMMAND1。其它的terminal同理,后面的脚本会把这个名字改成运行的指令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
[global_config]
enabled_plugins = CustomCommandsMenu, ActivityWatch, LaunchpadCodeURLHandler, APTURLHandler, LaunchpadBugURLHandler
suppress_multiple_term_dialog = True
[keybindings]
[layouts]
[[default]]
[[[child0]]]
fullscreen = False
last_active_term = 6465ad81-9563-4994-a9ea-4c73e642264a
last_active_window = True
maximised = True
order = 0
parent = ""
position = 91:27
size = 1823, 861
title = q@ubuntu: ~
type = Window
[[[child1]]]
order = 0
parent = child0
position = 908
ratio = 0.499725726824
type = HPaned
[[[child2]]]
order = 0
parent = child1
position = 427
ratio = 0.499419279907
type = VPaned
[[[child5]]]
order = 1
parent = child1
position = 427
ratio = 0.499419279907
type = VPaned
[[[terminal3]]]
command = COMMAND1
order = 0
parent = child2
profile = default
type = Terminal
uuid = 6465ad81-9563-4994-a9ea-4c73e642264a
[[[terminal4]]]
command = COMMAND2
order = 1
parent = child2
profile = default
type = Terminal
uuid = 40bece0b-c0b1-4510-b0d4-555a76b0df61
[[[terminal6]]]
command = COMMAND3
order = 0
parent = child5
profile = default
type = Terminal
uuid = 54ebe730-2576-4963-a96f-bf5d63167da9
[[[terminal7]]]
command = COMMAND4
order = 1
parent = child5
profile = default
type = Terminal
uuid = 2941b7ee-3345-4062-bfc3-cd68f3a636d4
[plugins]
[profiles]
[[default]]
background_image = None
scrollback_lines = 5000

下面制作自己的脚本script.sh文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash

## change these to whatever you actually need
command1="cd ~/TEB;source devel/setup.bash;roslaunch teb_local_planner_tutorials robot_carlike_in_stage.launch; bash"
command2="sleep 2s;roslaunch yocs_velocity_smoother standalone.launch; bash"
command3="sleep 3s;source ~/control/devel/setup.bash; roslaunch control control.launch; bash"
command4="sleep 4s;source ~/ReedsShepp/devel/setup.bash; roslaunch steering_functions steering_functions.launch; bash"

## Modify terminator's config
sed -i.bak "s#COMMAND1#$command1#; s#COMMAND2#$command2#; s#COMMAND3#$command3#; s#COMMAND4#$command4#;" ~/.config/terminator/config

## Launch a terminator instance using the new layout
terminator -l default

## Return the original config file
mv ~/.config/terminator/config.bak ~/.config/terminator/config

其中的sleep命令是防止节点同时启动,因为如果同时启动会出错,所以延时一段时间以错开运行。执行脚本时,打开一个终端并输入./script.sh,即可看到terminator同时启动多个窗口的过程。

如果脚本启动不了terminator了,那就检查一下~/.config/terminator路径下的配置文件config,把它改回上面的代码即可。


ROS多机通信的设置

过程参考两篇博客
多机通信 1
多机通信 2
需要注意的地方

  • /etc/hosts里的是IP和计算机名,不是用户名

  • 环境变量ROS_MASTER_URI里最好用计算机名。也可以自己设置名称。不过这样如果其他人不知道,还按计算机名设置,就会失败。

  • 所有设置完成后,重启主机的ROS节点

但是我设置的多机通信失败,因为工控机有3个IP。与只有1个IP的电脑是正常通信的。


使用lego_loam进行室外SLAM
abstract Welcome to my blog, enter password to read.
Read more
DWA所有参数

速度

  • min_vel_theta 设置一个相对较小的值就可以了,一般0.1、0.2都可以,默认0.4会导致最小速度过大,无法准确到达目标点。
  • max_trans_vel: 0.25   平移速度最大值绝对值,默认0.55
  • min_trans_vel: 0.1 默认0.1,一个较小的最小平移速度是必须的,它会影响低速行驶的状态,尤其是到达目标点时,最小速度过大会导致越过目标点,始终无法到达,可以在0-0.1之间寻找合适的大小。
  • max_vel_x: 0.25   x方向最大速度的绝对值
  • min_vel_x 注意是否要有负值,也就是是否允许倒车,实际中过高的倒车速度是应当避免的。 如果后方没有传感器就应该禁止倒车。 机器人在180度左右的转向,且两侧障碍物状况类似时,会出现左右摆动无法选择方向的问题,应该是受全局路径影响了,偶然情况下我设置了0.05的后退速度,发现每次转弯会先往后退一点,然后转向也变得很好了
  • max_vel_y: 0.0   y方向最大速度的绝对值.
  • min_vel_y: 0.0   y方向最小速度的绝对值.
  • max_rot_vel: 0.25   最大旋转速度的绝对值.
  • min_rot_vel: 0.1   最小旋转速度的绝对值.

  • acc_lim_x: 0.1   x方向的加速度绝对值,单位m/sec^2 ,默认2.5

  • acc_lim_y: 0.0   y方向的加速度绝对值,该值只有全向移动的机器人才需配置.
  • acc_lim_th: 0.1   旋转加速度的绝对值.单位是radians/sec^2

注意加速度不能太小,否则导航时会特别慢,看上去是机器人扭来扭去地走向记录点。

Goal Tolerance Parameters

  • yaw_goal_tolerance: 0.2   到达目标点时偏行角允许的误差,单位弧度.
  • xy_goal_tolerance: 0.22   到达目标点时,在xy方向与目标点的距离误差.
  • latch_xy_goal_tolerance: false   若设置为true,如果到达容错距离内,机器人就会原地旋转,即使转动会跑出容错距离外.

前向仿真参数

  • sim_time: 1.7   给定轨迹上的点之间的间隔尺寸,单位为 meters (double, default: 1.7)

  • sim_granularity: 0.025    (double, default: 0.025)

  • vx_samples: 3   样本数 (integer, default: 3)

  • vy_samples: 10   (integer, default: 10)

  • vth_samples: 20   角速度的样本数 (integer, default: 20)

  • controller_frequency: 20.0   调用该控制器的频率 (double, default: 20.0)

Trajectory Scoring Parameters 路径评分参数

  • path_distance_bias: 32.0   控制器靠近给定路径的权重 (double, default: 32.0)

  • goal_distance_bias: 24.0    控制器尝试达到其本地目标and控制着速度的权重。(double, default: 24.0)

  • occdist_scale: 0.01   控制器尝试避免障碍物的权重 (double, default: 0.01)

  • forward_point_distance: 0.325   从机器人中心点到另一个得分点的距离 (double, default: 0.325)

  • stop_time_buffer: 0.2   机器人在碰撞前必须停止的时间 (double, default: 0.2)

  • scaling_speed: 0.25   缩放机器人足迹的速度的绝对值,单位为m/s (double, default: 0.25)

  • max_scaling_factor: 0.2   缩放机器人足迹的最大因子 (double, default: 0.2)

  • publish_cost_grid: false    (bool, default: false) 是否发布规划器在规划路径时使用的代价网格(cost grid). 如果设置为true,那么就会在~/cost_cloud话题上发布sensor_msgs/PointCloud2类型消息. Each point cloud represents the cost grid and has a field for each individual scoring function component as well as the overall cost for each cell, taking the scoring parameters into account.

衡量每次导航轨迹的公式如下:

1
2
3
4
cost =
path_distance_bias * (distance to path from the endpoint of the trajectory in meters)
+ goal_distance_bias * (distance to local goal from the endpoint of the trajectory in meters)
+ occdist_scale * (maximum obstacle cost along the trajectory in obstacle cost (0-254) )

Oscillation Prevention Parameters

  • oscillation_reset_dist: 0.05   机器人必须运动多少米远后才能复位震荡标记 (double, default: 0.05)

Debugging

  • publish_traj_pc : true   将规划的轨迹在RVIZ上进行可视化
  • prune_plan: false    机器人前进时,是否清除身后1m外的轨迹.默认是true
  • publish_cost_grid_pc: true   将代价值进行可视化显示 (bool, default: false),是否发布规划器在规划路径时的代价网格.如果设置为true,那么就会在~/cost_cloud话题上发布sensor_msgs/PointCloud2类型消息

TEB的实际问题累计
abstract Welcome to my blog, enter password to read.
Read more
DWA算法(五) 如何触发latched机制

DWA规划路径时,机器人到了位置误差范围内会开始转动,直到满足角度误差。但是如果两个误差设置太小,很可能在转动过程中机器人又不能满足位置误差了,这样就又进入局部规划了,这种现象被称为dance,也就是当机器人接近目标点时,会在附近左右摆来摆去的。这显然不是我们想要的。

latch_xy_goal_tolerance机制是确保机器人一旦到达目标的position就停止,默认是关闭的。有些人反复设置总是不起作用,原因其实是LatchedStopRotateController的bug,它的构造函数中的latch_xy_goal_tolerance参数设置为false,即使在yaml中设置为了true,参数服务器里也是true,用日志在源码里打印会发现变量latch_xy_goal_tolerance_还是false,只能去构造函数里修改。

但是latched机制不能一直打开,否则又会给全局路径造成问题。因为接收到新目标时,不能再latch机器人了,move_base将reset latching. 但是全局路径在机器人到达目标前是不会关闭的,结果会出现:第一次latch机制是正常的,然后再发导航目标,机器人会依然不动。

解决的方法是在DWAPlannerROS::setPlan里,在reset latch之前检查全局路径的目标是否改变,若没有改变就不要reset latch。 更省事的方法是到move_base里把全局规划频率planner_frequency设置为0,这样全局规划只有在接收到新目标或局部规划失败时才进行规划,看各自需求了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
bool DWAPlannerROS::setPlan(const std::vector<geometry_msgs::PoseStamped>& orig_global_plan)
{
if (! isInitialized()) {
ROS_ERROR("This planner has not been initialized, please call initialize() before using this planner");
return false;
}
//when we get a new plan, we also want to clear any latch we may have on goal tolerances
geometry_msgs::PoseStamped current_global_goal = orig_global_plan.back();

if (first_goal_)
{
latchedStopRotateController_.resetLatching();
previous_global_goal_ = current_global_goal;
first_goal_ = false;
}
else
{
double dist = distance(current_global_goal, previous_global_goal_);
if (dist > 0.2) {
latchedStopRotateController_.resetLatching();
}
previous_global_goal_ = current_global_goal;
}

//when we get a new plan, we also want to clear any latch we may have on goal tolerances
//latchedStopRotateController_.resetLatching();

ROS_INFO("Got new plan");
return dp_->setPlan(orig_global_plan);
}

DWA算法(一) 论文

DWA算法的优点是计算的复杂度较低,由于考虑到速度和加速度的限制,只有安全的轨迹会被考虑,且每次采样的时间较短,因此轨迹空间较小。采样的速度即形成了一个动态窗口

DWA的弊端时很明显的,他对heading的实现是一个软约束,并不能够很好的在终点达到期望的heading.

该论文相对于之前的创新点在于:

  • 从移动机器人的运动动力学推导出来的
  • 考虑到机器人的惯性,也就是刹车距离,对于限制机器人在高速行驶时很重要
  • 在动态杂乱环境中速度可以较快

建立的假设:
a. 运动学模型的速度设为随时间变化的分段函数,因此机器人轨迹可看做许多的圆弧积分组成
b. 加速度也不是一直变化,假设有n个时间片,在每个时间片内的线加速度和角加速度不变
c. 省略 的二次项

DWA的整体轨迹评价函数主要是三个方面:

  1. 与目标的接近程度
  2. 机器人前进的速度
  3. 与下一个障碍物的距离

也就是在局部规划出一条路径,希望与目标越来越近,且速度较快,与障碍物尽可能远。评价函数权衡以上三个部分得到一条最优路径。 这样一看确实和CMU张楫团队提出的路径算法挺像

根据机器人的速度加速度假设和运动学模型,可以得出

其中的 都可以用 表示,这个式子就是圆在平面的公式,根据此公式可以求出圆的轨迹,即通过一系列的分段圆弧和直线来拟合轨迹。

速度空间降采样

圆弧轨迹:动态窗口法仅仅考虑圆弧轨迹,该轨迹由采样速度 (v,w) 决定,这些速度构成一个速度搜索空间
允许速度:如果机器人能够在碰到最近的障碍物之前停止,则该采样速度参与打分,机器人不与障碍物碰撞的速度集合记做
动态窗口:由于机器人加速度的限制,只保留以当前加速度可到达的速度,速度集合记做

最终的搜索空间

有点cartographer分支定界算法的感觉

目标函数

说是使用最优化方法,但是我看目标函数方程十分简单,不用使用什么优化算法,求最大值即可

  • heading用于评价机器人与目标位置的夹角,当机器人朝着目标前进时,该值取最大
  • dist 用于表示与机器人轨迹相交的最近的障碍物距离
  • vel 表示机器人的前向移动速度,支持快速移动

实现细节:
当机器人陷入局部最优时(即不存在路径可以通过),使其原地旋转,直到找到可行路径。但这样看着会很奇怪,这就是一个缺点
安全裕度:在路径规划时,设定一安全裕度,即在路径和障碍物之间保留一定间隙,且该间隙随着速度增大线性增长。

占比重太大,机器人运动自由度大,窄的区域不容易通过, 占比重太小,机器人轨迹则不够平滑。因此 越大,越适合在窄区域, 越小,越适合在宽区域。


在不涉及TEB的情况下,ROS中经常使用的局部规划器,有base_local_plannerdwa_local_planner,简单来看,两者属并列关系,其实就是选择使用 TrajectoryPlannerROS 还是 DWAPlannerROS,我们用ROS就只看DWAPlannerROS。两种配置如下:

1
2
3
<arg name="base_local_planner" default="base_local_planner/TrajectoryPlannerROS"/>

<arg name="base_local_planner" default="dwa_local_planner/DWAPlannerROS"/>

其实base_local_planner也是包含dwa选项的,不过通常还是使用独立的DWA模块。 base_local_planner::TrajectoryPlanner是对DWA的实现和Trajectory Rollout。 可以向dwa_local_planner添加自己的代价函数或者trajectory generators

Matlab对DWA算法的仿真
DWA泊车算法的实现
Dynamic Window Approach_机器人局部避障的动态窗口法

参考:DWA算法的论文


DWA算法(四) dwaComputeVelocityCommands

接上一篇,分析如果还没到x y误差范围内,进行DWA规划的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
else
{
// 是否规划成功
bool isOk = dwaComputeVelocityCommands(current_pose_, cmd_vel);

if (isOk)
publishGlobalPlan(transformed_plan);
else
ROS_WARN_NAMED("dwa_local_planner", "DWA planner failed to produce path.");

std::vector<geometry_msgs::PoseStamped> empty_plan;
publishGlobalPlan(empty_plan);
}
return isOk;

关键就是dwaComputeVelocityCommands

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
bool DWAPlannerROS::dwaComputeVelocityCommands(
tf::Stamped<tf::Pose> &global_pose,
geometry_msgs::Twist& cmd_vel )
{
// dynamic window sampling approach to get useful velocity commands
if(! isInitialized() )
{
ROS_ERROR("This planner has not been initialized, please call initialize() before using this planner");
return false;
}

tf::Stamped<tf::Pose> robot_vel;
odom_helper_.getRobotVel(robot_vel);

/* For timing uncomment
struct timeval start, end;
double start_t, end_t, t_diff;
gettimeofday(&start, NULL);
*/

//compute what trajectory to drive along
tf::Stamped<tf::Pose> drive_cmds;
drive_cmds.frame_id_ = costmap_ros_->getBaseFrameID();

// call with updated footprint
base_local_planner::Trajectory path = dp_->findBestPath(global_pose, robot_vel, drive_cmds, costmap_ros_->getRobotFootprint());
//ROS_ERROR("Best: %.2f, %.2f, %.2f, %.2f", path.xv_, path.yv_, path.thetav_, path.cost_);

/* For timing uncomment
gettimeofday(&end, NULL);
start_t = start.tv_sec + double(start.tv_usec) / 1e6;
end_t = end.tv_sec + double(end.tv_usec) / 1e6;
t_diff = end_t - start_t;
ROS_INFO("Cycle time: %.9f", t_diff);
*/

//pass along drive commands
cmd_vel.linear.x = drive_cmds.getOrigin().getX();
cmd_vel.linear.y = drive_cmds.getOrigin().getY();
cmd_vel.angular.z = tf::getYaw(drive_cmds.getRotation());

//if we cannot move... tell someone
std::vector<geometry_msgs::PoseStamped> local_plan;
if(path.cost_ < 0)
{
ROS_DEBUG_NAMED("dwa_local_planner",
"The dwa local planner failed to find a valid plan, cost functions discarded all candidates. This can mean there is an obstacle too close to the robot.");
local_plan.clear();
publishLocalPlan(local_plan);
return false;
}
ROS_DEBUG_NAMED("dwa_local_planner", "A valid velocity command of (%.2f, %.2f, %.2f) was found for this cycle.",
cmd_vel.linear.x, cmd_vel.linear.y, cmd_vel.angular.z);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    // Fill out the local plan
for(unsigned int i = 0; i < path.getPointsSize(); ++i)
{
double p_x, p_y, p_th;
path.getPoint(i, p_x, p_y, p_th);

tf::Stamped<tf::Pose> p =
tf::Stamped<tf::Pose>(tf::Pose(
tf::createQuaternionFromYaw(p_th),
tf::Point(p_x, p_y, 0.0)),
ros::Time::now(),
costmap_ros_->getGlobalFrameID());
geometry_msgs::PoseStamped pose;
tf::poseStampedTFToMsg(p, pose);
local_plan.push_back(pose);
}

//publish information to the visualizer
publishLocalPlan(local_plan);
return true;
}

DWA算法(三) latched Stop Rotate

接着上一篇,继续DWAPlannerROS::computeVelocityCommands

如果x y坐标已经在目标的距离误差范围内

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if (latchedStopRotateController_.isPositionReached(
&planner_util_, current_pose_) )
{
// 发布空的路径,这段可以删除
std::vector<geometry_msgs::PoseStamped> local_plan;
std::vector<geometry_msgs::PoseStamped> transformed_plan;
publishGlobalPlan(transformed_plan);
publishLocalPlan(local_plan);
// 这个类保存了所有 DWA参数,只有一个getAccLimits函数
base_local_planner::LocalPlannerLimits limits = planner_util_.getCurrentLimits();
return latchedStopRotateController_.computeVelocityCommandsStopRotate(
cmd_vel,
limits.getAccLimits(),
dp_->getSimPeriod(),
&planner_util_,
odom_helper_,
current_pose_,
boost::bind(&DWAPlanner::checkTrajectory, dp_, _1, _2, _3) );
}

latchedStopRotateController_base_local_planner::LatchedStopRotateController,它用到一个参数latch_xy_goal_tolerance

isPositionReached

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* returns true if we have passed the goal position.
* Meaning we might have overshot on the position beyond tolerance, yet still return true.
* Also goal orientation might not yet be true
*/
bool LatchedStopRotateController::isPositionReached(LocalPlannerUtil* planner_util,
tf::Stamped<tf::Pose> global_pose)
{
double xy_goal_tolerance = planner_util->getCurrentLimits().xy_goal_tolerance;

// 认为 global goal 是 global plan的最后一个点
// 显然是把全局路径的最后一个点赋值给 goal_pose
tf::Stamped<tf::Pose> goal_pose;
if ( ! planner_util->getGoal(goal_pose))
{
return false;
}
double goal_x = goal_pose.getOrigin().getX();
double goal_y = goal_pose.getOrigin().getY();

// check to see if we've reached the goal position
if ( (latch_xy_goal_tolerance_ && xy_tolerance_latch_) ||
base_local_planner::getGoalPositionDistance(global_pose, goal_x, goal_y) <= xy_goal_tolerance)
{
xy_tolerance_latch_ = true;
return true;
}
return false;
}

这里有两种情况可以出现 if(true)getGoalPositionDistance不用看也知道是求 global_pose 和 goal的空间距离,再跟参数xy_goal_tolerance比较。

latch_xy_goal_tolerance_本意是在LatchedStopRotateController构造函数里获取设置的参数,但是源码在这里出现了bug,应当是命名空间导致的。在DWA参数里设置其为true没有效果,从参数服务器看到的确实是true,但是在源码里的latch_xy_goal_tolerance_还是默认的false. 可以直接修改构造函数的设置值。 剩下的关键就是变量xy_tolerance_latch_了,有一个关键函数void resetLatching() { xy_tolerance_latch_ = false; }。 如何实现这一情况,在下面函数。

注意isPositionReached只是判断,没有发布零速度,没有停止

computeVelocityCommandsStopRotate

机器人到达位置时,停止平移,只做旋转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
bool LatchedStopRotateController::computeVelocityCommandsStopRotate(
geometry_msgs::Twist& cmd_vel,
Eigen::Vector3f acc_lim,
double sim_period,
LocalPlannerUtil* planner_util,
OdometryHelperRos& odom_helper_,
tf::Stamped<tf::Pose> global_pose,
// obstacle_check 在调用里是 DWAPlanner::checkTrajectory
boost::function<bool (Eigen::Vector3f pos,
Eigen::Vector3f vel,
Eigen::Vector3f vel_samples)> obstacle_check)
{
// global goal is the last point in the global plan
tf::Stamped<tf::Pose> goal_pose;
if ( ! planner_util->getGoal(goal_pose) )
{
ROS_ERROR("Could not get goal pose");
return false;
}
base_local_planner::LocalPlannerLimits limits = planner_util->getCurrentLimits();
// latch goal tolerance, if we ever reach the goal location,
// we'll just rotate in place
// 这里就是latched机制了,在第5篇再详细解释。这里先不考虑latch,看下面的情况。
if (latch_xy_goal_tolerance_ && ! xy_tolerance_latch_ )
{
ROS_INFO("Goal position reached, stopping and turning in place");
xy_tolerance_latch_ = true;
}
// 检查是否达到了 goal orientation
double goal_th = tf::getYaw(goal_pose.getRotation());
double angle = base_local_planner::getGoalOrientationAngleDifference(global_pose, goal_th);
if (fabs(angle) <= limits.yaw_goal_tolerance)
{
// 到达, 停止转动
cmd_vel.linear.x = 0.0;
cmd_vel.linear.y = 0.0;
cmd_vel.angular.z = 0.0;
rotating_to_goal_ = false;
}

如果发布了零速度,会直接运行到最后的return true

最后看角度没有到导航误差范围的情况,比较复杂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
else 
{
tf::Stamped<tf::Pose> robot_vel;
odom_helper_.getRobotVel(robot_vel);
nav_msgs::Odometry base_odom;
odom_helper_.getOdom(base_odom);
// if we're not stopped yet... we want to stop...
// taking into account the acceleration limits of the robot

/* 这里是机器人在继续运动的情况
rotating_to_goal_ 在构造函数里是 false
stopped函数: 如果里程计的角速度小于 rot_stopped_vel,且线速度小于
trans_stopped_vel, 则返回 true,认为已经停止 */
if ( ! rotating_to_goal_ && !base_local_planner::stopped(
base_odom, limits.rot_stopped_vel, limits.trans_stopped_vel) )
{
if ( ! stopWithAccLimits(
global_pose,
robot_vel,
cmd_vel,
acc_lim,
sim_period,
obstacle_check) )
{
ROS_INFO("Error when stopping.");
return false;
}
ROS_DEBUG("Stopping...");
}
//if we're stopped... then we want to rotate to goal
else
{
//set this so that we know its OK to be moving
rotating_to_goal_ = true;
if ( ! rotateToGoal(
global_pose,
robot_vel,
goal_th,
cmd_vel,
acc_lim,
sim_period,
limits,
obstacle_check) )
{
ROS_INFO("Error when rotating.");
return false;
}
ROS_DEBUG("Rotating...");
}
}
return true;
}