Gazebo问题累计

当不正确关闭Gazebo时,再次启动Gazebo会遇到sever无法启动的问题。

1
Exception [Master.cc:50] Unable to start server[bind: Address already in use]. There is probably another Gazebo process running.

解决方法:用ps命令查找gzserver进程,将其kill。或是干脆重启系统


报错: Unable to convert from SDF version 1.7 to 1.6
把world文件的第一行改成 <sdf version = '1.6'>



文件使用了OBJ meshes,这是从Gazebo 7.4.0才可支持,使用gazebo -v发现我的gazebo版本是 7.0


报错 [joint_state_publisher-3] process has died 或者 UnicodeEncodeError: ‘ascii’ codec can’t encode characters in position xxx ordinal

去掉urdf、xacro、launch文件中的中文注释,或者改为英文,而且第一行不能有中文。


1
2
3
4
<plugin name='front_right_middle_sonar_sensor' filename='libgazebo_ros_range.so'>
<topicName>/prius/front_sonar/right_middle_range</topicName>
<frameName>front_right_middle_sonar_link</frameName>
</plugin>

.urdf.xacro文件中的颜色不识别。需要在文件开头定义颜色值,否则默认不识别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<material name="blue">
<color rgba="0.0 0.0 0.8 1.0"/>
</material>

<material name="red">
<color rgba="0.8 0.0 0.0 1.0"/>
</material>

<material name="white">
<color rgba="1.0 1.0 1.0 1.0"/>
</material>

<material name="yellow">
<color rgba="0.8 0.8 0.0 1.0"/>
</material>


运行Gazebo竟然出现错误,Gazebo页面卡住黑屏,在终端出现 Gazebo [Err] [REST.cc:205] Error in REST request

解决方案sudo gedit ~/.ignition/fuel/config.yaml,然后将 url : https://api.ignitionfuel.org注释掉,添加 url: https://api.ignitionrobotics.org


在Gazebo中进行小车仿真的过程中会出现如下警告 The root link base_link has an inertia specified in the URDF, but KDL does not support a root link with an inertia. As a workaround, you can add an extra dummy link to your URDF 该警告的意思是根关节的base_link在urdf中具有惯性参数,但是KDL不支持,建议的解决办法是,增加一个额外的dummy link

这个问题看似很麻烦,实际解决起来是非常简单的,只需添加一个link和一个joint即可:

1
2
3
4
5
6
<link name="dummy">
</link>
<joint name="dummy_joint" type="fixed">
<parent link="dummy"/>
<child link="base_link"/>
</joint>

当然link的名称是可以自己改的


gazebo版本升级以及环境太暗的解决方法


ubuntu18.04 装的Gazebo版本是9,升级方式,但是能不升级就不升级。


使用Gazebo时出现下面报错:

1
2
[spawn_entity.py-5] [ERROR] [1715655287.539237215] [spawn_entity]: Service %s/spawn_entity unavailable. Was Gazebo started with GazeboRosFactory?
[spawn_entity.py-5] [ERROR] [1715655287.540067060] [spawn_entity]: Spawn service failed. Exiting.

1
[ERROR] [gzserver-1]: process has died [pid 239512, exit code 255, cmd 'gzserver -slibgazebo_ros_init.so -slibgazebo_ros_factory.so -slibgazebo_ros_force_system.so'].

这是因为关闭的过程中有些Gazebo的程序没有完全关闭,导致再次启动时,无法正常开启。所以,只要把gazebo的相关程序关闭即可: killall gzserver。 此时,再次启动gazebo,就能正常打开了。

如果担心是自己程序的问题,使用ros2 launch gazebo_ros spawn_entity_demo.launch.py验证是不是Gazebo自身的问题


joint_state_publisher报错 UnicodeEncodeError: ‘ascii’ codec can’t encode characters in position 30-36: ordinal not in range(128)

  1. 可能是urdf文件里有中文注释,将其去掉
  2. 如果没有注释,修改两个文件 /opt/ros/melodic/lib/joint_state_publisher_gui/joint_state_publisher/opt/ros/melodic/lib/joint_state_publisher,在开头import sys之后增加
    1
    2
    reload(sys)  
    sys.setdefaultencoding('utf8')

或者修改文件/opt/ros/melodic/lib/python2.7/dist-packages/joint_state_publisher/__init__.py,也是添加

1
2
reload(sys) 
sys.setdefaultencoding("utf-8")

运行spawn_model也可能会同样的报错,到/opt/ros/melodic/lib/python2.7/dist-packages文件夹,然后新建一个sitecustomize.py文件,内容为

1
2
3
4
#coding=utf8
import sys
reload(sys)
sys.setdefaultencoding('utf8')

保存后给文件权限。


Git分支相关的命令

我使用Git几年了,至今还会碰上各种各样的新问题,实在不胜其烦

工具 gitk 可以查看和管理分支

  • origin 远程服务器
  • origin/master 远程分支
  • master 本地分支

origin并不特别,就像分支名 master 在git中没有任何特殊意义一样.当执行git init时,master会作为初始分支的默认名字,因此使得master分支名被广泛使用。而origin是执行git clone时的默认服务器名称,当然可以通过指令git clone -o cat,使得默认服务器名称为cat,而默认远程分支为cat/master

从远程分支check out一个本地分支,该本地分支被称为追踪分支(tracking branch),被追踪的分支被称为上游分支(upstream branch),追踪分支可以理解为是和远程分支有直接关联的本地分支. 如果我们在追踪分支时执行git pull,git会自动知道需要获取和merge的分支的服务器.

feat 分支

在Git 中,feat 分支指的是 ==feature 分支,也叫做功能分支==。它主要用于开发新的功能或特性,确保开发过程不会影响到主干代码的稳定性。

feat 分支是从主开发分支(如 developmain)派生出来的,用于隔离新功能的开发工作。这样做的好处是,多个开发者可以并行地在不同的 feat 分支上开发不同的功能,而互不干扰,提高了开发效率。

工作流程:

  1. 当需要开发一个新功能时,从主分支(比如 develop)创建一个新的 feat 分支。
  2. feat 分支上进行代码开发和修改。
  3. 开发完成后,测试无误,将 feat 分支合并回主分支。
  4. 合并后,feat 分支可以被删除,以保持仓库的整洁。

进一步的解释

拉取远程指定分支

1
2
git remote add origin project_url
git pull origin branch_name

在本地创建分支develop并切换到该分支

1
git checkout -b develop(本地分支名称)  origin/develop (远程分支名称)

远程分支上的内容都拉取到本地

1
2
# develop 为远程分支名称
git pull origin develop (远程分支名称)

回到本地文件夹查看,已完成拉取远程某个分支到本地。拉去更新也是这个命令,如果是git pull,可能会把所有分支的内容都拉下来,用时较长,没有必要。

提交到某分支

1
git push origin 本地分支名:远程分支名

查看分支和合并

git branch -a list both remote-tracking and local branches

git branch -vv 会列出所有的分支信息,包含追踪分支的关联关系

常见的情况是,我自己有一个分支me,还要拉取另一个人的分支,两人共用部分文件,合并到分支master。在自己分支修改文件结束后,提交到自己的分支。然后切换到master分支,git checkout master。由于不知道另一个人是否更新了代码,所以先执行git pull更新一下,然后git merge me,这就合并了我的分支,再git push origin master

有时会出现冲突,这是由于两人修改了同一个文件,冲突的提示:

1
2
3
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Automatic merge failed; fix conflicts and then commit the result.

git status 也可以告诉我们冲突的文件. 在冲突的文件里,Git用<<<<<<<,=======,>>>>>>>标记出不同分支的内容,只能自己手动修改,然后再提交。解决冲突的过程一直是在master分支。 用带参数的git log也可以看到分支的合并情况

将user分支合并到master分支

1
2
3
4
5
6
7
8
9
# 当前是user分支
git add test.yaml
git commit -m ":wrench:"
git push origin user
# 切换到 master 分支
git checkout master
git merge user
git push origin master
git checkout user

删除本地分支

git branch -d user

1
2
3
4
5
6
user@user:~/dev_ws/$ git branch -d user
error: The branch 'user' is not fully merged.
If you are sure you want to delete it, run 'git branch -D user'.

user@user:~/dev_ws/$ git branch -D user
Deleted branch user (was cf64937b)

submodule

一个工程包含一个module,两部分的分支可能是不同的。先把工程拉取下来后,看看隐藏文件.gitmodules中的url和分支是否正确,此时的module部分应该还没有更新,使用下面的命令更新

1
2
git submodule init
git submodule update

本地文件修改后,执行git pull出现冲突

执行git pull出现下面提示

1
2
Please commit your changes or stash them before you merge.
Aborting

这是因为本地修改了一些文件,但远程仓库已经有别人提交更新了,所以有冲突。一般不会直接commit本地的修改。

  1. 放弃本地修改的改法 (这种方法会丢弃本地修改的代码,而且不可找回!)
1
2
git reset --hard
git pull

将最新的主分支更新到个人分支

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 当前在个人分支
git add . # 将本地修改文件加入暂存区
git commit -m "修改内容" # 提交日志

git checkout master # 切换到主分支(建议操作到这里的时候利用git branch
git pull # 将本地主分支代码更新
git checkout self-branch # 切换到自分支

git merge master # 将主分支代码合并更新到自己分支

git push # 提交到自分支远程端

git checkout master # 切换到主分支

git merge self-branch # 将自己代码合并更新到本地主分支master

git push # 将本地代码推到远程主分支master上

auto和decltype

声明一个变量为 auto 类型时, 编译器将自动帮助你推导出合适的数据类型. 这个变量可以是 :

  • 声明后立即赋值的普通变量;
  • 函数的返回值;
  • 函数的形参 (C++14 起)

当你需要某个表达式的返回值类型而又不想实际执行它时用decltype

1
2
3
4
5
6
7
int  a=8, b=3;

//运行时需要实际执行a+b,哪怕编译时就能推导出类型
auto c = a+b;

//编译期类型推导不可以用auto c; 直接声明变量,必须同时初始化。
decltype(a+b) d;

decltype 的推导规则遵循如下几点 :

  • 若表达式是一个 不带括号的标记符表达式 或 类/结构体成员访问表达式, 那么推导的结果是所代表实体的类型;
  • 若表达式是一个函数调用(包括操作符重载), 那么推导的结果是函数的返回类型, 若返回值是基础类型则抛弃 const 限定符;
  • 若表达式是一个字符串字面量, 则推到为 const 左值引用;
  • 上述情况以外, 若表达式结果为左值则推导为左值引用, 否则推导为本类型;

拆分行为树

这里说的是官方教程中的拆分,实际开发中,如果行为树太大,应该根据程序逻辑拆分成几个xml文件,不同子逻辑加载不同的xml

拆分行为树

拆分行为树有两种方法,我倾向用不修改C++的方法

main_tree.xml如下

1
2
3
4
5
6
7
8
9
10
11
<root main_tree_to_execute = "MainTree">
<include path="./subtree_A.xml" />
<include path="./subtree_B.xml" />
<BehaviorTree ID="MainTree">
<Sequence>
<SaySomething message="starting MainTree" />
<SubTree ID="SubTreeA" />
<SubTree ID="SubTreeB" />
</Sequence>
</BehaviorTree>
<root>

1
2
3
4
5
6
7
8
9
10
11
<root>
<BehaviorTree ID="SubTreeA">
<SaySomething message="Executing Sub_A" />
</BehaviorTree>
</root>

<root>
<BehaviorTree ID="SubTreeB">
<SaySomething message="Executing Sub_B" />
</BehaviorTree>
</root>

include path可以用绝对路径,也可以用相对路径。

1
factory.createTreeFromFile("main_tree.xml")

拆分后,用Groot2打开main_tree.xml,可以正常编辑和跳转到子树。


函数Tree BT::BehaviorTreeFactory::createTreeFromFile写的不好,加载xml失败也不知道,最好返回类型是Bool,false代表加载失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Tree BehaviorTreeFactory::createTreeFromFile(const std::string& file_path,
Blackboard::Ptr blackboard)
{
if(!parser_->registeredBehaviorTrees().empty()) {
std::cout << "WARNING: You executed BehaviorTreeFactory::createTreeFromFile "
"after registerBehaviorTreeFrom[File/Text].\n"
"This is NOTm probably, what you want to do.\n"
"You should probably use BehaviorTreeFactory::createTree, instead"
<< std::endl;
}

XMLParser parser(*this);
parser.loadFromFile(file_path);
auto tree = parser.instantiateTree(blackboard);
tree.manifests = this->manifests();
return tree;
}

stack和queue

虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为容器的适配器,这是因为stack和queue只是对其他容器的接口进行了包装,STL中stack和queue默认使用deque。

适配器其实是一种设计模式,该种模式是将一个类的接口转换成用户希望的另外一个接口。

deuqe平时很少作为单独的数据结构容器来使用,而是仅仅作为stack和queue等数据结构的适配容器。

stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()pop_back()操作的线性结构,都可以作为stack的底层容器,比如vectorlist都可以;queue是先进先出的特殊线性数据结构,只要具有push_back和pop_front操作的线性结构,都可以作为queue的底层容器,比如list。但是STL中对stack和queue默认选择deque作为其底层容器,主要是因为:

  1. stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。

  2. 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长时,deque不仅效率高,而且内存使用率高。

deque支持stack和queue的全部操作,因此,只需要在stack和queue接口的实现中调用deque容器对应的接口,就可以完成对stack和deque的模拟实现。


CMake 常用宏和技巧

${CMAKE_CURRENT_LIST_DIR}${CMAKE_CURRENT_SOURCE_DIR} 一般都是当前CMakeLists.txt所在的目录。

file (GLOB_RECURSE SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/*.cpp) 把路径下的所有cpp文件名都加入变量SOURCE_FILES


优先队列 std::priority_queue

C++中的优先队列(priority queue)是一种特殊的队列,它允许在队列中添加元素时自动根据元素的优先级进行排序,
以便能够快速访问具有最高优先级的元素。优先队列可以用来解决很多算法问题,例如Dijkstra算法、Prim算法等。

底层实现是二叉堆,特别是二叉最小堆(对于最大优先级队列)或二叉最大堆(对于最小优先级队列)。堆是一种特殊的完全二叉树,其中任一节点的值都不大于或不小于其子节点的值。这种属性让堆非常适合快速访问最大或最小元素。

  • push() 基于优先级在适当的位置加入新的元素
  • top() 返回最高优先级的元素,但不删除该元素
  • pop() 删除最高优先级的元素
  • size() 返回队列中元素的个数
  • empty() 如果队列为空返回true,否则返回false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <queue>
#include <iostream>
using namespace std;

int main()
{
priority_queue<int> pq;
pq.push(3);
pq.push(1);
pq.push(4);
pq.push(1);
pq.push(5);

while (!pq.empty()) {
int top = pq.top();
pq.pop();
cout << top << " ";
}
// 如果要保存队列中的所有元素,可以将它们复制到另一个数据结构中,例如vector或数组
return 0;
}

优先队列不支持随机访问,只允许访问队头元素,不允许访问其余的数据


glog记录程序崩溃及扩展
abstract Welcome to my blog, enter password to read.
Read more
ROS2的package.xml和cmake

创建包,相关文件

创建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.xmlCMakeLists.txt

比如在package.xml<buildtool_depend> 之后添加

1
2
<depend>rclcpp</depend>
<depend>std_msgs</depend>

CMakeLists.txt添加
1
2
3
4
find_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
2
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)

ament_cmake是cmake的增强版

find_package(rclcpp_lifecycle REQUIRED)用于生命周期管理,平时可以不添加

例如package名称为project(first_node),必须修改的部分如下

1
2
3
4
5
add_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了。


行为树安装和配置

安装

安装依赖项,然后去github的release里下载最新的版本编译安装。

1
2
3
4
5
sudo 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设置如下

1
2
3
4
5
6
7
8
9
10
cmake_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)

如果是通过sudo apt-get install ros-humble-behavior-cpp安装的,只能在ROS2环境的设置,因为so文件依赖 ament_index_cpp:

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
cmake_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
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<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