${CMAKE_CURRENT_LIST_DIR}
和 ${CMAKE_CURRENT_SOURCE_DIR}
一般都是当前CMakeLists.txt
所在的目录。
file (GLOB_RECURSE SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/*.cpp)
把路径下的所有cpp文件名都加入变量SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}
和 ${CMAKE_CURRENT_SOURCE_DIR}
一般都是当前CMakeLists.txt
所在的目录。
file (GLOB_RECURSE SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/*.cpp)
把路径下的所有cpp文件名都加入变量SOURCE_FILES
C++中的优先队列(priority queue)是一种特殊的队列,它允许在队列中添加元素时自动根据元素的优先级进行排序,
以便能够快速访问具有最高优先级的元素。优先队列可以用来解决很多算法问题,例如Dijkstra算法、Prim算法等。
底层实现是二叉堆,特别是二叉最小堆(对于最大优先级队列)或二叉最大堆(对于最小优先级队列)。堆是一种特殊的完全二叉树,其中任一节点的值都不大于或不小于其子节点的值。这种属性让堆非常适合快速访问最大或最小元素。
1 |
|
优先队列不支持随机访问,只允许访问队头元素,不允许访问其余的数据
package.xml
中包含该功能包的依赖信息,它可以帮助编译工具colcon
确定多个功能包编译的顺序。
CMakeList.txt
当中,必须有1
2find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
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
是官方推荐的方式去添加依赖项。它将使依赖项的库、头文件和自身的依赖项被正常找到。
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了。
1 | class Test : public BT::SyncActionNode { |
1 | class Pipeline : public BT::ControlNode |
每次tickRoot()
函数执行都会遍历整个树,对于asynActionNode
,因为其本身有循环(在单独线程里),所以循环没有结束时会返回RUNNING状态。不同的控制流节点对RUNNING的处理不一样。
1 |
|
portslist
中没有的key是不能用getInput
或getOutput
来操作的。
getInput("hz", hz);
1 | task_ = config.blackboard->get<Task>("cur_task"); |
1 | <Action ID="GetPath"> |
行为树的优点:
缺点:
行为树本身并不具体实现机器人的执行内容,它只负责将执行内容进行编排。以Navigation2为例,具体的执行内容实现是放在各个server中的。行为树上的节点与server进行通信,请求具体的执行内容,然后获得反馈。根据反馈结果又可以请求另外的执行内容。这些不同执行内容间的跳转就是由行为树控制的。
另一种比较常见的组织机器人行为的方式是状态机。ROS1中的move_base就是基于状态机的。它与行为树最显著的区别是状态与执行内容是绑定在一起的。当执行内容需要在多个状态中执行时,各个状态下都需要放置执行内容的逻辑。当业务逻辑代码分散在各处时就不太好维护了
ROS2行为树动态库默认安装在/opt/ros/galactic/lib/libbehaviortree_cpp_v3.so
,头文件在/opt/ros/foxy/include/behaviortree_cpp_v3
行为树是由控制节点、装饰节点、行为节点组成的一棵树。中间节点一般为控制节点和装饰节,用于控制行为树的执行流程,它们相对固定,一旦确定几乎不会变化。叶子节点由Action节点或Condition节点组成,使用者的大部分工作都是设计树的逻辑和行为节点。
行为树的每个节点都有一个返回值,它们分别是:
控制节点一般为中间节点,用于控制行为树的执行流程,决定了其子节点是以顺序、并行、随机或其它方式执行。
依次执行所有子节点,若当前子节点返回成功,则继续执行下一个子节点;若子当前节点返回失败,则中断后续子节点的执行,并把结果返回给父节点。节点1返回成功,继续执行节点2;节点2返回失败,则把结果返回给Sequences的父节点,节点3并不会执行。顺序节点相当于and
语义。
依次执行所有子节点,若当前子节点返回成功,则中断后续节点运行,并把结果返回给父节点。相当于or
语义
当有的行为节点对应的代码执行较长,例如:播放动画,此时,这个行为节点会向父节点返回running
,于是选择节点便不再执行后续节点,直接向父节点返回 running
依次执行所有子节点,无论失败与否,都会把所有子节点执行一遍。至于Parallel节点该返回什么值给父节点,这要看需求。比如:成功数 > 失败数返回成功,否则返回失败。
随机选择一个子节点来运行。机器人领域应该不会使用。在游戏设计里,AI角色每天会根据自己的心情选择是呆在家里、工作或是出门游玩,可以采用随机选择节点
使用很少。功能和顺序节点、选择节点类似,唯一不同是会保存当前执行进度(比如:保存当前子节点索引),下一帧继续执行当前节点,如果当前节点是中间节点,则会跳过前面的节点。
执行一段时间(MaxTime):重复执行子节点一段时间
action
动作节点通常实现服务客户端和动作客户端,也可以是一些简单的执行程序。action通常作为行为树中的叶子节点,负责具体行为和功能的实现。但这些具体的功能代码并没有在叶子节点中而是在对应的服务端。执行这种节点,可能只需要一帧就可以完成,也可能需要多帧才能完成。
它至少包含两个函数:
Init
:用于初始化节点,比如读取配置数据初始化当前节点,只会执行一次。OnTick
:每一帧都会执行,节点的主要逻辑都在此函数中实现或调用。
condition
这是条件控制节点。比如判断电池电量,某一开关信号等等。
这是行为树中的控制流。类似c++语言中的if else,switch等等。它负责构建行为树的逻辑结构。sequeence,fallback等等就属于这个范畴。
行为树中的共有数据是存放在Blackboard中的。Blackboard是以键值对
的形式存储数据的。各个行为树的叶子节点均可以通过key访问到需要的数据。节点的输入端口可以从黑板读取数据,节点的输出端口可以写入黑板。
Ports是用于在节点间交换数据而存在的。一个行为树叶子节点可以定义输入的Ports和输出的Ports。当不同叶子节点的Port有相同的key名称时,可以认为它们是相通的。当在行为树叶子节点中声明了这些Ports时,也需要同时在xml文件中描述这些Ports。
注意设置子树的__shared_blackboard
属性为true,否则C++程序可能报错
获取全局黑板变量,也就是说它也用了单例模式。1
auto blackboard = DecisionBlackboard::GetInstance().GetBlackBoard();
程序中如果加载了多个行为树,黑板变量可以共享。
xml格式有以下几个要点
main_tree_to_execute
的属性,表示执行哪颗树BehaviorTree
的ID的属性值必须是不同的,此时必须指定main_tree_to_execute
标签main_tree_to_execute
属性可有可无TreeNodesModel
标签,主要用于Groot可视化。对于C++程序来说,可以没有。createTreeFromFile
的文件路径错误时,会报错 what(): Internal error in realpath(): No such file or directory
Groot1的日志工具很不好用,在fbl
文件比较大的时候,很容易卡死。如果总的行为树很大,需要放大才能观看流程,十分不方便。Groot2的日志工具据说很好用,但是收费。
拆分行为树后,在Groot里不方便直接打开子树,只能再开一个Groot打开子树的xml文件. 后来Groot2解决了这个问题,在设置的Editor标签里还可设置是否每次都加载<include>
标签对应的文件。
统一的代码规范对于整个团队来说十分重要,通过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
colcon build 编译所有包
colcon build —packages-select pkg 只编译一个包
colcon build —cmake-args -DCMAKE_BUILD_TYPE=Release
不编译测试单元
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
ROS2的build没有了ROS1中的devel概念
可以使用以下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
,放大前两个系数