ZeroMQ的学习

使用zmqpp-4.2.0,但是用函数zmq_version发现版本是 4.3.4

ZeroMQ在CMake的设置

1
2
3
4
5
6
7
8
9
10
11
12
cmake_minimum_required(VERSION 3.5)

project(untitled LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include_directories(zmqpp-4.2.0/src/zmqpp)
LINK_DIRECTORIES(zmqpp-4.2.0/lib/)

add_executable(untitled main.cpp)
target_link_libraries(untitled zmq)

查看版本

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <zmq.h>
// 显示当前的zeromq版本
int main()
{
int major, minor, patch;
zmq_version(&major, &minor, &patch);
printf("Current ZeroMQ version is %d.%d.%d\n", major, minor, patch);
}

发布和订阅

pub端:

  • 创建context
  • 创建socket,设置ZMQ_PUB模式
  • bind端口
  • 循环发布消息send

sub端:

  • 创建context
  • 创建socket,设置ZMQ_SUB模式
  • connect到pub端
  • setsockopt设置ZMQ_SUBSCRIBE订阅的消息
  • 循环接收recv

注意事项:

  1. 我们在pub中bind,在sub中connect,在zmq的使用中无论是在pub还是sub中都可以bind,但是一般我们在pub中bind,在sub中connect。反之sub端可能收不到消息
  2. zmq_setsockopt – 设置zmq的socket属性,sub端必须使用此方法,否则是收不到消息的。
  3. pub端不能使用recv函数,sub端不能使用send函数
  4. pub端socket的zmq_send()函数永远不会阻塞

zmq_msg_init_size

使用一个指定的空间大小初始化ZMQ消息对象。原型int zmq_msg_init_size (zmq_msg_t *msg, size_t size);

分配任何被请求的资源来存储一个size大小字节的消息,并且初始化msg参数指定的消息,用来表示新分配到的消息。

在函数执行的时候,会选择是否把消息存储在栈里面(小消息),还是堆里面(大消息)。考虑到性能原因,函数不会清除消息数据。

永远不要直接对zmq_msg_t对象进行直接操作,而是要使用zmq_msg函数族进行操作。

zmq_msg_init(), zmq_msg_init_data()zmq_msg_init_size()这三个函数是互斥的。永远不要把一个zmq_msg_t对象初始化两次。

执行成功时返回0。否则返回 -1,并且设置errno的值为下列定义的值。

zmq_msg_data

原型void *zmq_msg_data (zmq_msg_t *msg); 返回msg参数指定的消息内容的指针。

函数执行成功返回0,否则返回 -1

参考:
zmq pub/sub使用详解


proto基本使用

安装

安装Protobuf需要两个zip文件,以 3.0.0 为例,到github下载页面下载 protoc-3.0.0-linux-x86_64.zipprotobuf-cpp-3.0.0.zip

安装前者:

1
2
3
4
5
6
7
8
9
10
# Unzip
unzip protoc-3.2.0-linux-x86_64.zip -d protoc3

sudo mv protoc3/bin/* /usr/local/bin/

sudo mv protoc3/include/* /usr/local/include/

# change owner,也可不执行
sudo chwon [user] /usr/local/bin/protoc
sudo chwon -R [user] /usr/local/include/google

再安装后者

1
2
3
4
5
6
7
8
9
10
11
12
13
# 解压后执行
./autogen.sh

./configure # 或者 ./configure --prefix=/usr

make
sudo make install
sudo ldconfig

protoc --version
# 输出:libprotoc 3.0.0

locate libprotobuf.so


所用protobuf的版本是 3.12.4

Test.proto文件:

1
2
3
4
5
6
7
8
9
syntax = "proto3";
// 包名:在生成对应的C++文件时,将被替换为名称空间,在代码中会有体现
package Test;

message Pose {
float x = 1;
float y = 2;
float yaw = 3;
}

根据proto文件生成.h 和 .cc文件

1
./protoc --proto_path=/home/user/qt_projects --cpp_out=/home/user/qt_projects/proto_gen /home/user/qt_projects/Test.proto

简略形式: protoc message.proto --cpp_out=.


CMake中的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cmake_minimum_required(VERSION 3.5)

project(test_proto LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include_directories(/home/user/zmqpp-4.2.0/src/zmqpp)
include_directories(/home/user/protobuf/src)

LINK_DIRECTORIES(/home/user/x86/zmqpp-4.2.0/lib/)
LINK_DIRECTORIES(/home/user/x86/protobuf/lib)
# 上面生成的cc文件
add_executable(test_proto main.cpp Test.pb.cc)
target_link_libraries(test_proto zmq protobuf)

注意 #include "Test.pb.h"

可以这样使用

1
2
3
4
Test::Pose*  pose_msg = new Test::Pose();
pose_msg->set_x(1.5);
pose_msg->set_y(2.7);
pose_msg->set_yaw(0);


ccache加速编译

ccache

先使用apt-get install ccache进行安装。

1
2
3
4
sudo vim /etc/environment
PATH='/usr/lib/ccache:keep the rest'

source /etc/environment

输入which gcc,应该得到/usr/lib/ccache/gcc

修改CMakeLists.txt

1
2
set(CMAKE_CXX_COMPILER_LAUNCHER ccache)
set(CMAKE_C_COMPILER_LAUNCHER ccache)

但是在ROS环境下,没发现起作用

distcc

Setting up distcc (server)

So, go ahead and install distcc on the server computer (in my case, the desktop)

And then spin up the server. (The following assumes I’m on the 10.8.33.0 subnet, and I’m allowing all hosts on that subnet to send jobs)

1
sudo distccd --log-file=/tmp/distccd.log --daemon -a 10.8.33.0/24

Setting up distcc (client) So, now you have to tell ccache to use the distcc distributed compilation servers. To do this, just add the following line to your ~/.bashrc file.
1
export CCACHE_PREFIX=distcc

Next, we have to tell distcc where to go to find the server. This also, just add to the bottom of your ~/.bashrc (my desktop is at 10.8.33.182 and it has 8 cores, my laptop is at localhost and has 4)
1
export DISTCC_HOSTS='localhost/4 10.8.33.182/8'


在另一个终端,使用下面命令检验ccache的运行效果

1
watch -n1 'ccache -s -v'

或者watch -n1 'ccache --show-stats -v'

编译时,使用top看 distcc process starts spooling up its threads.


Linux程序运行时被killed

linux程序运行时突然出现Killed,可以使用dmesg | egrep -i -B100 'killed process 查看原因,输出最近killed的信息。

参考: linux 程序被Killed,查看原因


有用的程序 1
  • 不断判断随机数的大小,如果它能大于某个值维持一段时间,输出时间,否则重新计时。
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
int main()
{
int num = 0;
int count = 0;
std::chrono::steady_clock::time_point start, end;
double duration;

while(1)
{
std::srand(time(0));
count = rand() % 1000000;
if(count > 680000)
{
if(num==0)
start = std::chrono::steady_clock::now();

num++;
end = std::chrono::steady_clock::now();
duration = std::chrono::duration<double>(end - start).count();
cout << "count: " << count << endl;
cout << "time elapsed " << static_cast<int>(duration) << endl;
}
else
{
duration = 0;
num = 0;
cout << endl;
}
}
return 0;
}
  • 判断一个点是否在多边形中

opencv函数: double pointPolygonTest(InputArray contour, Point2f pt, bool measureDist)

measureDist设置为true时,返回实际距离值。若返回值为正,表示点在多边形内部,返回值为负,表示在多边形外部,返回值为0,表示在多边形上。
当measureDist设置为false时,返回 -1、0、1三个固定值。若返回值为+1,表示点在多边形内部,返回值为-1,表示在多边形外部,返回值为0,表示在多边形上。

  • 计算点到直线的距离
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
* @tparam T point type
* @param first Given the starting point of a line segment
* @param last The endpoint of a given line segment
* @param third Given point
* @return T Distance from point to line
*/
template <typename T>
static T PointToLineDistance(const Point<T>& first, const Point<T>& last, const Point<T>& third) {
float dis_suqare =
((first.y - last.y) * third.x + (last.x - first.x) * third.y +
(first.x * last.y - last.x * first.y)) *
((first.y - last.y) * third.x + (last.x - first.x) * third.y +
(first.x * last.y - last.x * first.y)) /
((last.x - first.x) * (last.x - first.x) + (last.y - first.y) * (last.y - first.y));
return std::sqrt(dis_suqare);
}
  • 求图形的外接圆

OpenCV绘制最小外接矩形、最小外接圆
pointPolygonTest函数


cmp accumulate 函数

cmp函数

cmp 函数的特点:

  1. 返回值为 bool 类型,用来表示当前的排序是否正确
  2. 参数为两个相同类型的变量,且类型与要排序的容器模板类型相同
    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
    // 从小到大排列
    bool cmp( const Data& d1, const Data& d2)
    {
    // return(d1.dist < d2.dist);
    // 由于重载了 <= ,可以直接这么写
    return(d1 <= d2);
    }

    vector<Data> vec;
    Data temp;
    for ( int i = 0; i < 8; ++i )
    {
    temp.dist = rand() %50;
    temp.confidence = 100;
    vec.push_back( temp );
    }
    for(int i=0; i<8; i++)
    {
    cout << vec.at(i).dist << " ";
    }
    cout << endl;
    // 第三个参数可以是一个函数指针,一般使用cmp函数
    stable_sort( vec.begin(), vec.end(), cmp );
    for(int i=0; i<8; i++)
    {
    cout << vec.at(i).dist << " ";
    }

std::accumulate 函数

用来计算特定范围内(包括连续的部分和初始值)所有元素的和,除此之外,还可以用指定的二进制操作来计算特定范围内的元素结果,需要 #include <numeric>。 三个形参:头两个形参指定要累加的元素范围,第三个形参则是累加的初值。

1
2
3
vector<int> v;
// 给 v 赋值
cout << std::accumulate(v.begin(), v.end(), 0) << endl;

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分支相关的命令
工具 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的分支的服务器.

拉取远程指定分支

1
2
3
4
5
6
git remote add origin https://github.com/user/user.github.com.git
# 或者是 git仓库链接
# git remote add origin git@github.com:XXXX/nothing2.git

# develop为远程仓库的分支名
git fetch origin develop

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

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

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

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

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

查看分支和合并

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)

删除远程分支, 千万小心使用 !

1
git push origin --delete remoteBranchName

难题 1

1
2
3
4
5
6
7
# 执行 git status 的结果
On branch master
Your branch and 'origin/master' have diverged,
and have 5 and 21 different commits each, respectively.
(use "git pull" to merge the remote branch into yours)

nothing to commit, working tree clean

这发生在多人可能操作 master 分支的时候,我自己的分支提交完了,切换到master分支,要执行合并时,出现这个问题,并不是文件冲突的问题。按如下方法解决:

如果不需要保留本地的修改,只要执行下面两步:

1
2
git fetch origin
git reset --hard origin/master

当我们在本地提交到远程仓库的时候,如果遇到上述问题,我们可以首先使用如下命令:
1
2
3
4
git rebase origin/master
git pull --rebase
# 提交到远程仓库
git push origin master

本地文件修改后,执行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上

一次合并冲突的解决

我在自己的分支me开发,主分支比我领先很多。有一次,我从git上下载了主分支的压缩包,然后手动覆盖了一部分文件以更新,这其实是不正确的操作。

第二天我先切换到主分支,更新到最新,然后切换到me分支,提交到远程后,合并主分支,结果出现的冲突比我自己修改的多很多,因为把我昨天手动覆盖的那些也包括了。这种情况下,只能手动解决冲突,但是大部分文件一定是用主分支,如果一个个打开会很麻烦。

用VSCode打开工程,分支管理里会显示文件冲突,对使用主分支版本的文件,右键选择就可以,而且能同时选多个文件。解决完之后,选择暂存文件,之后可以push了。


拆分行为树

拆分行为树

拆分行为树有两种方法,我倾向用不修改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;
}

CMake 常用宏和技巧

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

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