在调试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
## 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,把它改回上面的代码即可。
/etc/hosts
里的是IP和计算机名,不是用户名
环境变量ROS_MASTER_URI
里最好用计算机名。也可以自己设置名称。不过这样如果其他人不知道,还按计算机名设置,就会失败。
所有设置完成后,重启主机的ROS节点
但是我设置的多机通信失败,因为工控机有3个IP。与只有1个IP的电脑是正常通信的。
min_rot_vel: 0.1 最小旋转速度的绝对值.
acc_lim_x: 0.1 x方向的加速度绝对值,单位m/sec^2 ,默认2.5
注意加速度不能太小,否则导航时会特别慢,看上去是机器人扭来扭去地走向记录点。
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)
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
4cost =
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) )
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 | bool DWAPlannerROS::setPlan(const std::vector<geometry_msgs::PoseStamped>& orig_global_plan) |
DWA算法的优点是计算的复杂度较低,由于考虑到速度和加速度的限制,只有安全的轨迹会被考虑,且每次采样的时间较短,因此轨迹空间较小。采样的速度即形成了一个动态窗口
DWA的弊端时很明显的,他对heading的实现是一个软约束,并不能够很好的在终点达到期望的heading.
该论文相对于之前的创新点在于:
建立的假设:
a. 运动学模型的速度设为随时间变化的分段函数,因此机器人轨迹可看做许多的圆弧积分组成
b. 加速度也不是一直变化,假设有n个时间片,在每个时间片内的线加速度和角加速度不变
c. 省略 的二次项
DWA的整体轨迹评价函数主要是三个方面:
也就是在局部规划出一条路径,希望与目标越来越近,且速度较快,与障碍物尽可能远。评价函数权衡以上三个部分得到一条最优路径。 这样一看确实和CMU张楫团队提出的路径算法挺像
根据机器人的速度加速度假设和运动学模型,可以得出
其中的 都可以用 表示,这个式子就是圆在平面的公式,根据此公式可以求出圆的轨迹,即通过一系列的分段圆弧和直线来拟合轨迹。
圆弧轨迹:动态窗口法仅仅考虑圆弧轨迹,该轨迹由采样速度 (v,w) 决定,这些速度构成一个速度搜索空间
允许速度:如果机器人能够在碰到最近的障碍物之前停止,则该采样速度参与打分,机器人不与障碍物碰撞的速度集合记做
动态窗口:由于机器人加速度的限制,只保留以当前加速度可到达的速度,速度集合记做
最终的搜索空间
有点cartographer分支定界算法的感觉
说是使用最优化方法,但是我看目标函数方程十分简单,不用使用什么优化算法,求最大值即可
实现细节:
当机器人陷入局部最优时(即不存在路径可以通过),使其原地旋转,直到找到可行路径。但这样看着会很奇怪,这就是一个缺点
安全裕度:在路径规划时,设定一安全裕度,即在路径和障碍物之间保留一定间隙,且该间隙随着速度增大线性增长。
占比重太大,机器人运动自由度大,窄的区域不容易通过, 占比重太小,机器人轨迹则不够平滑。因此 越大,越适合在窄区域, 越小,越适合在宽区域。
在不涉及TEB的情况下,ROS中经常使用的局部规划器,有base_local_planner
和 dwa_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算法的论文
接上一篇,分析如果还没到x y误差范围内,进行DWA规划的情况1
2
3
4
5
6
7
8
9
10
11
12
13
14else
{
// 是否规划成功
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
53bool 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 | // Fill out the local plan |
接着上一篇,继续DWAPlannerROS::computeVelocityCommands
如果x y坐标已经在目标的距离误差范围内1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19if (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
1 | /** |
这里有两种情况可以出现 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
只是判断,没有发布零速度,没有停止
机器人到达位置时,停止平移,只做旋转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
39bool 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
52else
{
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;
}