ROS2常用的命令

只记录ROS2 和 ROS1 不同的命令

pkg

1
2
3
4
5
6
7
8
9
10
# 列出某个包的所有可执行文件
ros2 pkg executables pkg_name
# 列出所有的包
ros2 pkg list

# 某个包所在路径的前缀
ros2 pkg prefix pkg_name

# 查看包对应的 package.xml 文件
ros2 pkg xml pkg_name

topic

  • ros2 topic info topic_name

现在必须加/

1
2
3
4
5
6
7
user@robot:~$ ros2 topic info imu
Unknown topic 'imu'

user@robot:~$ ros2 topic info /imu
Type: sensor_msgs/msg/Imu
Publisher count: 1
Subscription count: 1

  • 输出话题的详细信息,包括发布订阅者: ros2 topic info /odom -v
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
Type: nav_msgs/msg/Odometry

Publisher count: 1

Node name: turtlebot3_diff_drive
Node namespace: /
Topic type: nav_msgs/msg/Odometry
Endpoint type: PUBLISHER
GID: 01.0f.ea.0f.41.6a.0c.34.01.00.00.00.00.00.a5.03.00.00.00.00.00.00.00.00
QoS profile:
Reliability: RMW_QOS_POLICY_RELIABILITY_RELIABLE
Durability: RMW_QOS_POLICY_DURABILITY_VOLATILE
Lifespan: 2147483651294967295 nanoseconds
Deadline: 2147483651294967295 nanoseconds
Liveliness: RMW_QOS_POLICY_LIVELINESS_AUTOMATIC
Liveliness lease duration: 2147483651294967295 nanoseconds

Subscription count: 1

Node name: my_node
Node namespace: /
Topic type: nav_msgs/msg/Odometry
Endpoint type: SUBSCRIPTION
GID: 01.0f.ea.0f.0d.6a.5d.f0.01.00.00.00.00.00.13.04.00.00.00.00.00.00.00.00
QoS profile:
Reliability: RMW_QOS_POLICY_RELIABILITY_RELIABLE
Durability: RMW_QOS_POLICY_DURABILITY_VOLATILE
Lifespan: 2147483651294967295 nanoseconds
Deadline: 2147483651294967295 nanoseconds
Liveliness: RMW_QOS_POLICY_LIVELINESS_AUTOMATIC
Liveliness lease duration: 2147483651294967295 nanoseconds
  • ros2 interface show msg_name

显示 Topic 发送的消息定义 ros2 interface show sensor_msgs/msg/Imu

  • ros2 topic find msg_name

获知某个消息类型是谁在用

1
2
user@robot:~$ ros2 topic find sensor_msgs/msg/Imu
/imu

  • 手动发消息到话题
1
ros2 topic pub /chatter std_msgs/msg/String 'data:"123"

interface

  • ros2 interface list

分类显示消息、动作、服务的所有类型

  • ros2 interface package pkg_name
1
2
3
4
5
user@robot:~$ ros2 interface package action_msgs
action_msgs/srv/CancelGoal
action_msgs/msg/GoalInfo
action_msgs/msg/GoalStatus
action_msgs/msg/GoalStatusArray

获知某个消息类型是谁在用,可以这样查

1
2
3
4
5
6
7
8
user@robot:~$ ros2 interface packages | grep pendulum_msgs
pendulum_msgs
user@robot:~$ ros2 interface package pendulum_msgs
pendulum_msgs/msg/RttestResults
pendulum_msgs/msg/JointState
pendulum_msgs/msg/JointCommand

user@robot:~$ ros2 topic find pendulum_msgs/msg/RttestResults

  • 显示消息的成员
1
2
user@robot:~$ ros2 interface proto std_msgs/msg/String
"data: ''"

service

  • ros2 service type

可以看到服务的定义。

  • ros2 service find

找出指定类型的所有服务

  • ros2 service find std_srvs/srv/Empty

  • ros2 interface show

显示服务的定义

param

  • ros2 param list
  • ros2 param describe
1
2
3
4
5
6
7
8
ros2 param describe /turtlesim background_r
Parameter name: background_r
Type: integer
Description: Red channel of the background color
Constraints:
Min value: 0
Max value: 255
Step: 1
  • ros2 param get
1
2
ros2 param get /turtlesim background_r
Integer value is: 69
  • ros2 param set (省略)

  • ros2 run + param

  • ros2 run —ros-args —params-file

rqt_graph 能够可视化节点和主题之间的连接。这个命令和ROS1一样,也是ROS2为数不多的GUI可视界面

tf

  • 可视化ROS2的tf树

ros2 run tf2_tools view_frames.py

  • ros2 run tf2_ros static_transform_publisher 1 2 3 0.5 0.1 -1.0 foo bar

  • ros2 run tf2_ros tf2_echo foo bar

运行结果:

1
2
3
4
5
6
7
8
9
10
At time 197.27000000
- Translation: [0.008, -0.000, 0.064]
- Rotation: in Quaternion [-0.000, -0.001, -0.010, 1.000]
- Rotation: in RPY (radian) [-0.000, -0.002, -0.020]
- Rotation: in RPY (degree) [-0.001, -0.092, -1.160]
- Matrix:
1.000 0.020 -0.002 0.008
-0.020 1.000 0.000 -0.000
0.002 -0.000 1.000 0.064
0.000 0.000 0.000 1.000


ros_map_editor

用GIMP等工具来进行地图的常规清理和编辑是一项费时费力的任务,为了解决这种情况,项目ros_map_editor提供一个强大而灵活的工具,使机器人领域的从业者和研究人员能够更轻松地编辑导航地图,以满足他们的特定碧求。

特点:

  1. 添加整行区域: 用户可以轻松地添加禁行区域,以确保机器人避开潜在危险区域
  2. 地图对齐:工具允许将导航地图与真实世界地图对齐,以便进行精确的导航
  3. 一般地图清理: 与传统工具相比,使用ros map editor进行地图清理更加高效

编译安装openCV 3.2
C++的新类型 pair, tie, array

std::pair

需要 #include <utility>

std::pair主要的作用是将两个数据组合成一个数据,两个数据可以是同一类型或者不同类型。pair实质上是一个结构体,其主要的两个成员变量是firstsecond,这两个变量可以直接使用。初始化一个pair可以使用构造函数,也可以使用std::make_pair函数

一般make_pair都使用在需要pair做参数的位置,可以直接调用make_pair生成pair对象。另外pair可以接受隐式的类型转换,这样可以获得更高的灵活度。

1
2
3
4
5
6
7
pair <string,double> product1 ("tomatoes",3.25);
pair <string,double> product2;
pair <string,double> product3;

product2.first = "lightbulbs"; // type of first is string
product2.second = 0.99; // type of second is double
product3 = make_pair ("shoes",20.0);

pair是将2个数据组合成一个数据,当需要这样的需求时就可以使用pair。另一个应用是,当一个函数需要返回2个数据的时候,可以选择pair。

由于pair类型的使用比较繁琐,因为如果要定义多个pair类型的时候,可以使用typedef简化声明

std::tie

std::tie 是在 <tuple> 头文件中定义的。std::tie 是一个函数模板,用于创建一个元组并将多个变量绑定到该元组的元素上。它通常用于同时获取或设置多个值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <tuple>

int main() {
int x = 10;
int y = 20;
int z = 30;

// 将变量 x, y, z 绑定到元组中
std::tie(x, y, z) = std::make_tuple(z, x, y);

std::cout << "x: " << x << std::endl; // 输出: 30
std::cout << "y: " << y << std::endl; // 输出: 10
std::cout << "z: " << z << std::endl; // 输出: 20

return 0;
}

三个整数变量 x、y 和 z,分别初始化为 10、20 和 30。然后,我们使用 std::tie 将这些变量绑定到一个元组中,并使用 std::make_tuple 创建了一个新的元组,其中元素的顺序为 z, x, y。通过将该元组赋值给 std::tie(x, y, z),变量 x、y 和 z 的值发生了重新排列。

最后,我们输出变量 x、y 和 z 的值,可以看到它们已经按照元组的顺序进行了重新赋值。这种方式可以方便地交换变量的值或同时获取多个返回值。

std::array

需要include <array>

std::array仍然是有固定大小的数组,可以随机访问。不像vector那样支持添加或删除元素等改变大小的操作,没有内存重新分配的行为。当定义一个array时,除了指定元素类型,还要指定容器大小。

之所以加入了std::array,就是为了解决内置数组的老问题,比如无法直接对象赋值,无法直接拷贝等等,同时内置的数组又有很多比较难理解的地方,比如数组名是数组的起始地址等等。

简单地说,std::array作为固定大小的数组,又拥有vector的一些特点,比如迭代器访问、获取容量、获得原始指针等高级功能,但它又不会退化成指针给开发人员造成困惑。

1
2
3
std::array<int, 5> a0 = {0, 1, 2, 3, 4};    //正确
std::array<int, 5> a1 = a0; //正确
std::array<int, m> a3; //错误,array不可以用变量指定

std::array不要和数组混合使用。

std::array提供了[]、at、front、back、data的方式来访问元素。array还提供了迭代器的方式进行元素遍历和访问,比如begin, end。以及其它一些函数,比如empty, size, fill,但没有capacity函数。


std重要库函数

std::exchange

std::exchage未出现之前, 我们交换两个变量的值,需要先定义一个临时的中间变量,这是经典老方法了。但是std::exchange让我们优雅地解决这个问题。

它的主要作用是替换一个对象的值,并返回该对象的旧值。这个函数在 C++14 中引入,主要用于简化和优化代码。原型是

1
2
template< class T, class U = T >
T exchange( T& obj, U&& new_value );

在C++ 20里给函数加上了constexpr

1
2
3
4
std::string name = "Alice";
std::string new_name = "Bob";

std::string old_name = std::exchange(name, new_name);

还可以用于容器

1
2
3
4
5
6
7
std::vector<int> v;
std::exchange(v, {1,2,3,4});
cout << v.size() << endl;
for (auto a : v)
{
cout << a << " ";
}

std::exchange在处理移动语义时非常有用。

std::exchange是原子操作的,所以在多线程环境下是安全的,如果对程序性能有严格要求,可以换std::swap或临时变量的方式。

std::transform

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
int multiply(int num)
{
return 3*num;
}

vector<int> vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
vec.push_back(4);
vec.push_back(5);

for(const auto &it: vec)
{
cout << it << endl;
}

// 将 vec 元素范围 ( 起始迭代器 和 末尾迭代器 ) 作为输入容器 , 将 vec 的起始迭代器作为输出容器起始点 , 也就是将输入容器的元素进行修改, 再次放回到该容器
std::transform(vec.begin(), vec.end(), vec.begin(), multiply );

for(const auto &it: vec)
{
cout << it << endl;
}

// 使用lambda表达式作为转换方式,如果输出容器的起始点改为 vec.end(),vec不会有变化
std::transform(vec.begin(), vec.end(), vec.begin(), [](int num){return 2*num; } );

for(const auto &it: vec)
{
cout << it << endl;
}

再看一个配合std::back_inserter使用的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
vector<int>  vec;
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
vec.push_back(4);
vec.push_back(5);

vector<int> new_vec;
std::transform(vec.begin(), vec.end(), std::back_inserter(new_vec), [](int v){ return 3*v; } );

cout << "new vec size: " << new_vec.size() << endl;
for(auto it : new_vec)
{
cout << it << " ";
}

运行结果
1
2
new vec size: 5
3 6 9 12 15


纯跟踪的实现的效果

纯跟踪被我改的只适合走折线了


全局路径的优化问题
abstract Welcome to my blog, enter password to read.
Read more
TEB和DWA算法的对比
  • DWA的轨迹目标函数太简单了,只有三方面:到目标的距离、到障碍物的距离、速度
  • DWA不考虑速度和路径平滑,容易导致机器人震动和轨迹扭动。
  • DWA 动态避障效果差。
  • DWA考虑了刹车距离,导致能达到的最大速度不高,加速也不够快。
  • DWA每次都选择下一步的最佳路径,而非全局最优路径。陷入局部最优时(即不存在路径可以通过),会在原地旋转一段时间,直到找到可行路径。
  • DWA难过窄通道,或者说参数难以调整。


  • TEB在运动过程中会调整自己的位姿朝向,当到达目标点时,通常机器人的朝向也是目标朝向而不需要旋转。 DWA常常先到达目标坐标点,然后原地旋转到目标朝向。
  • TEB的速度和角度波动较大、控制不稳定。在计算资源足够的情况下,提高控制频率可以改善此现象。另一种方法是使用优化,即修改TEB算法的评价函数,把每次速度和角度的变化量除以时间再乘一个代价系数。

避障及存在的问题
abstract Welcome to my blog, enter password to read.
Read more
move_base中的多线程

move_base.cpp中用到的多线程:

1
2
3
4
5
boost::condition_variable_any  planner_cond_;

boost::thread* planner_thread_;

boost::recursive_mutex planner_mutex_;

代价地图里有个锁mutex_t,其实就是boost::recursive_mutex,膨胀层里还用到了读写锁boost::shared_mutex* access_

递归锁

move_base里用的锁主要是boost::recursive_mutex planner_mutex_,而且是和boost::unique_lock, boost::recursive_mutex::scoped_lock 搭配使用。

使用到planner_mutex_的函数有planThread, executeCb, executeCycle(6次), resetState。其他3个函数,每个都只使用了一次planner_mutex_

只有一次是在生成路径后,交互变量。其他全是和planThread有关的,基本是对runPlanner赋值,比如

1
2
3
4
5
boost::unique_lock<boost::recursive_mutex> lock(planner_mutex_);
planner_goal_ = goal;
runPlanner_ = true;
planner_cond_.notify_one();
lock.unlock();


经过我大量修改,move_base在有导航任务时,如果ctrl+C退出,终端必定会出现下面的错误,虽然毫无影响,但是毕竟不优雅
move_base 退出时报错死锁
这个错误是在有锁的情况下,仍然加锁。 也就是多调用了lock()函数。

搜索move_base,发现只有7个lock()。逐个查看,由于是退出时的报错,所以不可能是makePlan的那个,很容易发现是planThread函数最后的那个。加上判断即可解决

1
2
if( !lock.owns_lock() )
lock.lock();

对于其他使用lock()的地方,最好也加上这个判断。