创建C++包: ros2 pkg create --build-type ament_cmake --node-name node_name package_name
指明依赖项: ros2 pkg create --build-type ament_cmake --node-name node_name package_name --dependencies rclcpp std_msgs
创建python包: ros2 pkg create --build-type ament_python --node-name node_name package_name
如果在创建包时,忘了指定依赖项,需要手动修改package.xml
和 CMakeLists.txt
比如在package.xml
的 <buildtool_depend>
之后添加1
2<depend>rclcpp</depend>
<depend>std_msgs</depend>
在CMakeLists.txt
添加1
2
3
4find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
ament_target_dependencies(my_node rclcpp std_msgs)
两个依赖项不能都放进find_package
里面,否则报错
build目录放的是中间文件.例如调用CMake时,每个包都会生成一个子文件夹.
install目录是放包的安装文件.默认情况下,每个包会被安装到一个独立的子目录.
log目录包含每次执行colcon的各种日志信息.
对比catkin
,没有了devel
目录
package.xml
中包含该功能包的依赖信息,它可以帮助编译工具colcon
确定多个功能包编译的顺序。
CMakeList.txt
当中,必须有1
2find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)ament_cmake
是cmake的增强版
find_package(rclcpp_lifecycle REQUIRED)
用于生命周期管理,平时可以不添加
例如package
名称为project(first_node)
,必须修改的部分如下1
2
3
4
5add_executable(test_node src/test.cpp)
ament_target_dependencies(test_node rclcpp std_msgs)
install(TARGETS test_node DESTINATION lib/install_node)
target_link_libraries(test_node behaviortree_cpp_v3)
add_executable
不必解释
ament_target_dependencies
是官方推荐的方式去添加依赖项。它将使依赖项的库、头文件和自身的依赖项被正常找到。如果不添加库名称,include
头文件时会找不到
install
是安装库的语句。它将在工作空间生成文件 install/first_node/lib/install_node/test_node
ament_package()
最后一句,不要修改。项目安装是通过ament_package()
完成的,并且每个软件包必须恰好执行一次这个调用。ament_package()
会安装package.xml文件,用ament索引注册该软件包,并安装CMake的配置(和可能的目标)文件,以便其他软件包可以用find_package
找到该软件包。由于ament_package()
会从CMakeLists.txt
中收集大量信息,因此它应该是CMakeLists.txt
中的最后一个调用。ament_export_dependencies(${dependencies})
这句会将依赖项导出到下游软件包。这样该库使用者也就不必为那些依赖项调用find_package了。
尽量不要用行为树,除非公司里有个人,本来就很擅长行为树,那么可以交给他,其他人没必要参与了。行为树入门难,原因不是它本身多么复杂,而是网上的资料太少,尤其groot的资料更少,全靠自己摸索,一个并不复杂的问题,摸索一整天。有不少资料是游戏设计方面的,用于机器人领域的开源方案目前只有ROS2。资料这么少,肯定是有原因的。
最近从行为树版本3切换到版本4,发现有一些变化,又走了一遍坑之后,我还是认为最好不要用行为树。
准确地说,是BehaviorTree.CPP
和Groot
太垃圾,行为树本身在游戏行业已经用了很多年,不需要讨论其可用性。
优点:
Timeout
节点疑似不能生效网上找不到修饰节点Timeout
的使用方法,从API里找到了说明:The TimeoutNode will halt() a running child if the latter has been RUNNING for more than a give time. The timeout is in milliseconds and it is passed using the port “msec”.
但是我反复试验,发现这个节点总不生效,它修饰的节点运行时间早就超过Timeout
规定时间了,但是不会停止
安装依赖项,然后去github的release里下载最新的版本1
2
3
4
5sudo apt-get sqlite3
sudo apt-get install libzmq3-dev libboost-dev
sudo apt-get install libboost-coroutine-dev # 需要用到协程
sudo apt-get install qtbase5-dev libqt5svg5-dev libzmq3-dev libdw-dev
cmake老三样编译安装。
普通的cmake设置如下1
2
3
4
5
6
7
8
9
10cmake_minimum_required(VERSION 3.10.2)
project(simple_bt)
set(CMAKE_CXX_SaTANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(behaviortree_cpp)
add_executable(${PROJECT_NAME} "simple_bt.cpp")
target_link_libraries(${PROJECT_NAME} BT::behaviortree_cpp)
ROS2环境的设置如下: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
34cmake_minimum_required(VERSION 3.8)
project(test_node)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
set(CMAKE_CXX_STANDARD 17)
add_compile_options(-Wextra -Wpedantic -Wno-unused-parameter -g)
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
find_package(ament_index_cpp)
find_package(behaviortree_cpp)
INCLUDE_DIRECTORIES(/home/user/catkin_ws/src/test_node/include)
add_executable(test_node src/test.cpp)
ament_target_dependencies(test_node
rclcpp
behaviortree_cpp
${BTCPP_LIBRARY}
)
install(TARGETS
test_node
DESTINATION lib/${PROJECT_NAME}
)
ament_package()
package.xml
如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<package format="3">
<name>test_node</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="user@todo.todo">zzp</maintainer>
<license>TODO: License declaration</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>rclcpp</depend>
<depend>std_msgs</depend>
<depend>behaviortree_cpp</depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
ROS2行为树动态库默认安装在/opt/ros/galactic/lib/libbehaviortree_cpp_v3.so
,头文件在/opt/ros/foxy/include/behaviortree_cpp_v3
统一的代码规范对于整个团队来说十分重要,通过git/svn在提交前进行统一的 ClangFormat 格式化,可以有效避免由于人工操作带来的代码格式问题。1
2
3
4
5
6
7// 以 LLVM 代码风格格式化main.cpp, 结果输出到 stdout
clang-format -style=LLVM main.cpp
// 以 LLVM 风格格式化 main.cpp, 结果直接写到 main.cpp
clang-format -style=LLVM -i main.cpp
// 以 Google 风格格式化 main.cpp, 结果直接写到 main.cpp
clang-format -style=google -i main.cpp
除了LLVM
和Google
外,还有 Chromium, Mozilla, WebKit
find . -path '*/src/*.cpp' -o -path '*/include/*.h' ! -name 'sigslot.h' | xargs clang-format -style=file -i
在使用时,出现一个奇怪的bug,clang-format
把我修改的文件的所有者都改成了root,再修改时还得先把用户改回来,极其不方便,不过我是在VMware里遇到的bug
安装 colcon : sudo apt install python3-colcon-common-extensions
。 ROS2的build没有了ROS1中的devel概念
与ROS1不同,如果一个包不放在src
目录,而放在工作空间目录,colcon也会编译它。
colcon build 编译所有包
colcon build —packages-select pkg 只编译一个包
colcon build --packages-select
指令并不会编译该包的依赖,往往会报错。可以用下面这条指令进行包和其依赖编译
1 | colcon build --packages-up-to package_name |
colcon test --packages-select YOUR_PKG_NAME --cmake-args -DBUILD_TESTING = 0
colcon test
colcon build --symlink-install
colcon build —symlink-install pkg
colcon build —symlink-install —packages-ignore pkg
指定并行的线程数1
colcon build --symlink-install --parallel-workers 1
不使用并行编译1
colcon build --symlink-install --executor sequential
两种编译不要混合用,否则每次都会重新编译
colcon build —event-handlers console_direct+ —packages-select test_node
console_direct
换成 console_cohesion
也可以
如果不想编译特定的包,在该包目录里面创建一个名为COLCON_IGNORE
文件,这样子这个包就不会被索引到了
执行source后再编译
一开始我以为是/opt
里的安装有问题,后来发现是ament_target_dependencies
里没有添加nav_msgs
,导致include
头文件时会找不到
把对应的目录删掉
在相应的包里加入1
2
3<export>
<build_type>ament_cmake</build_type>
</export>
1 | <depend>common</depend> |
告诉编译器,编译B包的时候依赖A包,A包需要先编译出来。
1 | Starting >>> test_node |
删掉build
目录中的test_node
文件夹
一般都安装了相应的库:1
sudo apt install ros-jazzy-rosidl-typesupport-c
source /opt/ros/jazzy/setup.bash
即可
相应的包里明明没有用CATKIN
相关的东西,不知是哪里报警,只好在CMake里加一句 unset( CATKIN_INSTALL_INTO_PREFIX_ROOT )
source /opt/ros/jazzy/setup.bash
即使删除了install
, build
, log
也无法解决,要想彻底解决只能重建一个工作空间。或者用临时方法,但新终端又会失效1
2unset AMENT_PREFIX_PATH
unset CMAKE_PREFIX_PATH
在路径 /build/my_package/ament_cmake_core
可以看到编译一个包后生成的cmake文件,比如my_packageConfig.cmake
和 my_packageConfig-version.cmake
如果包A需要包B,编译时报错找不到BConfig.cmake
,在工作空间执行 source
可以使用以下JSON格式的示例进行配置:1
2
3
4
5
6
7
8
9{
"search.exclude": {
"**/node_modules": true,
"**/build": true,
"**/dist": true,
"**/.git": true,
"**/.vscode": true
}
}
上面的示例中,我们配置了五个排除规则:
"**/node_modules"
: true - 这将排除项目中的node_modules文件夹,通常包含依赖库。
"**/build": true
- 这将排除build文件夹,如果您的项目使用构建工具生成构建文件,可以排除它。
"**/dist": true
- 这将排除dist文件夹,如果您的项目包含编译后的分发文件,可以排除它。
"**/.git": true
- 这将排除.git文件夹,以防止搜索Git版本控制文件。
"**/.vscode": true
- 这将排除.vscode文件夹,以防止搜索Visual Studio Code配置文件。
VS Code 按快捷键 ctrl+p
可以弹出一个小窗,在上面的 输入框输入文件名,下拉框点击一个文件
设置VsCode 多文件分行(栏)排列显示,打开vscode的设置,搜索wrap tabs,勾选上就可以了
修改鼠标滚轮效果: VSCode 设置页面搜索 mouseWheelScrollSensitivity
,放大前两个系数
1 | # 列出某个包的所有可执行文件 |
ros2 node 不识别kill
命令,只有info
和 list
1 | ros2 topic list # 查看话题列表 |
现在必须加/
了1
2
3
4
5
6
7user :~$ ros2 topic info imu
Unknown topic 'imu'
user :~$ ros2 topic info /imu
Type: sensor_msgs/msg/Imu
Publisher count: 1
Subscription count: 1
1 | Type: nav_msgs/msg/Odometry |
显示 Topic 发送的消息定义 ros2 interface show sensor_msgs/msg/Imu
获知某个消息类型是谁在用1
2user :~$ ros2 topic find sensor_msgs/msg/Imu
/imu
1 | ros2 topic pub /chatter std_msgs/msg/String 'data:"123" |
用GIMP等工具来进行地图的常规清理和编辑是一项费时费力的任务,为了解决这种情况,项目ros_map_editor提供一个强大而灵活的工具,使机器人领域的从业者和研究人员能够更轻松地编辑导航地图,以满足他们的特定碧求。
特点:
ros map editor
进行地图清理更加高效需要 #include <utility>
std::pair主要的作用是将两个数据组合成一个数据,两个数据可以是同一类型或者不同类型。pair实质上是一个结构体,其主要的两个成员变量是first
和second
,这两个变量可以直接使用。初始化一个pair可以使用构造函数,也可以使用std::make_pair
函数
一般make_pair
都使用在需要pair
做参数的位置,可以直接调用make_pair生成pair对象。另外pair
可以接受隐式的类型转换,这样可以获得更高的灵活度。
1 | pair <string,double> product1 ("tomatoes",3.25); |
pair是将2个数据组合成一个数据,当需要这样的需求时就可以使用pair。另一个应用是,当一个函数需要返回2个数据的时候,可以选择pair。
由于pair类型的使用比较繁琐,因为如果要定义多个pair类型的时候,可以使用typedef
简化声明
std::tie
是在 <tuple>
头文件中定义的。std::tie
是一个函数模板,用于创建一个元组并将多个变量绑定到该元组的元素上。它通常用于同时获取或设置多个值。
1 |
|
三个整数变量 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 的值,可以看到它们已经按照元组的顺序进行了重新赋值。这种方式可以方便地交换变量的值或同时获取多个返回值。
需要include <array>
std::array
仍然是有固定大小的数组,可以随机访问。不像vector那样支持添加或删除元素等改变大小的操作,没有内存重新分配的行为。当定义一个array时,除了指定元素类型,还要指定容器大小。
之所以加入了std::array
,就是为了解决内置数组的老问题,比如无法直接对象赋值,无法直接拷贝等等,同时内置的数组又有很多比较难理解的地方,比如数组名是数组的起始地址等等。
简单地说,std::array
作为固定大小的数组,又拥有vector
的一些特点,比如迭代器访问、获取容量、获得原始指针等高级功能,但它又不会退化成指针给开发人员造成困惑。
1 | std::array<int, 5> a0 = {0, 1, 2, 3, 4}; //正确 |
std::array
不要和数组混合使用。
std::array
提供了[]、at、front、back、data的方式来访问元素。array还提供了迭代器的方式进行元素遍历和访问,比如begin
, end
。以及其它一些函数,比如empty
, size
, fill
,但没有capacity
函数。
在std::exchage
未出现之前, 我们交换两个变量的值,需要先定义一个临时的中间变量,这是经典老方法了。但是std::exchange
让我们优雅地解决这个问题。
它的主要作用是替换一个对象的值,并返回该对象的旧值。这个函数在 C++14 中引入,主要用于简化和优化代码。原型是1
2template< class T, class U = T >
T exchange( T& obj, U&& new_value );
在C++ 20里给函数加上了constexpr
1 | std::string name = "Alice"; |
还可以用于容器1
2
3
4
5
6
7std::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
或临时变量的方式。
1 | int multiply(int num) |
再看一个配合std::back_inserter
使用的例子1
2
3
4
5
6
7
8
9
10
11
12
13
14
15vector<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
2new vec size: 5
3 6 9 12 15