定制基本消息类型
当ROS提供的消息类型不满足你的需求时,你就需要考虑制作自己的消息类型了。比如你想要发布一则消息,这则消息包含一个double类型的向量,一个整数,一个字符串,他们都是基本的消息类型(属于std_msgs里的)。 下面我们来看步骤 1:在pub_sub_test这个package下建立一个新的文件夹,文件夹的名字叫msg 2:在msg里创建一个新的文件,名字叫MyBasicMessage.msg。 3:打开MyBasicMessage.msg,并在其中输入下面内容
string message_id
int64 message_data1
float64[] message_data2
为什么是上面这个格式定义message内部呢?回想我们在第二讲里讲到,如果我们想发布某种类型的消息,我们需要了解这个消息包含哪些东西,这样我们才好为它赋值等。比如你想发布Float64,我们已经知道这是属于std_msgs这个命名空间下的消息类型,打开下面这个网页http://docs.ros.org/api/std_msgs/html/msg/Float64.html你能看到Float64怎么定义的。
从这个页面我们知道Float64这个消息包含一个消息类型是float64名字叫data的成员。于是我们在pub_float64.cpp代码里类似如下使用
...
double abc = 123.456;
std_msgs::Float64 msg; //定义Float64对象msg
msg.data = abc;//为类成员data赋值,赋值类型为double,即float64
...
当我们要自定义消息时,我们要做的就是模仿就行了。在.msg文件夹里,模仿上面的定义方式即可
成员类型 自定义成员名字
那么我们怎么知道成员类型是什么呢?比如字符串是string,这个到简单,double变成float64,double类型的向量竟然是float64[]。其实这个我们同样在第一二讲讲了,ROS把这些基本类型重新定义了一番,具体可见http://wiki.ros.org/msg。在Built-in types下就写明了ROS自身的基本消息类型和C++,python中消息类型的对应关系,比如C++里的double在ROS中对应的是float64。所以我们会在msg文件里使用float64 name
这种方式定义一个数据成员。同样在该页面中Array handling部分我们可以看到ROS对某种数据类型的数组的定义方式就是在基本类型后面加了个[]
符号。比如bool[]
对应c++的std::vector<uint8_t>
. 现在我们知道如何定义以及为什么如此定义基本类型了,在msg文件写好后,我们需要让我们的package知道我们新定义了消息类型,接着上面如下做 4:打开pub_sub_test的CMakeLists.txt 找到
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
geometry_msgs
)
添加一行
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
geometry_msgs
message_generation
)
依赖项 message_generation是要自定义message所必须添加的。 找到
# add_message_files(
# FILES
# Message1.msg
# Message2.msg
# )
改为
add_message_files(
FILES
TestBasicMessage.msg
)
这相当于让这个package知道我们定义了新的message了。 找到
# generate_messages(
# DEPENDENCIES
# geometry_msgs# std_msgs#
# )
改为
generate_messages(
DEPENDENCIES
std_msgs
)
这是让package知道我们定义的消息是依赖于std_msgs的。因为我们上面定义的消息类型都属于基础消息类型。我们得指明这一点。 关闭保存CMakeLists.txt。打开pub_sub_test的package.xml。 找到
<!-- <build_depend>message_generation</build_depend> -->
...
<!-- <build_export_depend>message_generation</build_export_depend> -->
...
<!-- <exec_depend>message_runtime</exec_depend> -->
把注释都去掉,变为
<build_depend>message_generation</build_depend>
...
<build_export_depend>message_generation</build_export_depend>
...
<exec_depend>message_runtime</exec_depend>
保存并关闭package.xml。这些都是死步骤,需要自定义message这么做即可。 之后使用catkin_make编译。你自定义的消息类型就已经产生了。如何使用呢?和一般消息类型使用没有差别。 我们在pub_sub_test/src里新创建一个cpp文件,名字叫pub_my_basic_message.cpp
,把pub_string.cpp里或者之前写的其他基础pub程序赋值进去,改成下面的样子.
#include "ros/ros.h"
#include "pub_sub_test/MyBasicMessage.h"//#include "std_msgs/String.h"
#include <sstream>
int main(int argc, char **argv)
{
ros::init(argc, argv, "talker");
ros::NodeHandle n;
ros::Publisher chatter_pub = n.advertise<pub_sub_test::MyBasicMessage>("chatter", 1000); //ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
ros::Rate loop_rate(10);
int count = 0;
double data2 = 1;
while (ros::ok())
{
pub_sub_test::MyBasicMessage msg; //std_msgs::String msg;
// std::stringstream ss;
// ss << "hello world " << count;
msg.message_id = "1";
msg.message_data1 = count;
msg.message_data2.push_back(data2);
ROS_INFO("%ld", msg.message_data1); //ROS_INFO("%f", msg.data.c_str())
chatter_pub.publish(msg);
ros::spinOnce();
loop_rate.sleep();
++count;
}
return 0;
}
变化 1: #include "std_msgs/String.h"
变成了#include "pub_sub_test/MyBasicMessage.h"
,即包含package名/msg文件名.h 2:在advertise函数使用的地方 <std_msgs::String>变成了<pub_sub_test::MyBasicMessage> 3:std_msgs::String msg 变成了pub_sub_test::MyBasicMessage msg; 4:为我们定义的消息类型赋值 在MyBasicMessage.msg里我们定义了类型为string名字叫message_id的成员,所以我们使用msg.message_id
,并赋值为字符串“1”。定义了float64(std::vector<double>)类型的成员message_data2,使用vector的函数push_back传入一个float64的变量。ROS_INFO就print出mesage_data1大家感受一下就是了。 其余没变。总的来说程序中pub你自定义的消息和你想pub任何ROS自带的消息的步骤一样。 你把这个cpp写入CMakeLists里编译即可。同样使用rosrun可以跑这个程序。sub文件就不再写了,很类似地改。不再赘述。
定制高级消息
所谓高级消息,即是想PoseStamped那样的东西。其实步骤也一模一样,我们在msg文件夹再创建一个MyAdvancedMessage.msg
。并在其中写入下面内容。
geometry_msgs/Inertia SiHuan
geometry_msgs/Pose WuHuan
std_msgs/Header LiuHuan
add_message_files(
FILES
MyBasicMessage.msg
)
中添加一行
add_message_files(
FILES
MyBasicMessage.msg
MyAdvancedMessage.msg
)
即我们刚刚新建立的message的名字。 在
generate_messages(
DEPENDENCIES
std_msgs
)
中添加一行
generate_messages(
DEPENDENCIES
std_msgs
geometry_msgs
)
由于我们新建立的消息类型不仅有来自于std_msgs的,还有来自于geometry_msgs的,所以我们需要把这个包添加到消息的dependency里。保存退出,使用catkin_make编译即可。 之后你如果想发布这个类型的消息,写一个pub_my_advanced_message.cpp之类的文件,包含#include "pub_sub_test/MyAdvancedMessage.h"
,定义变量,赋值等,和前面一样。至于如何为Pose等类型的成员Wuhuan之类的赋值,我想看过第三讲应该不会有问题。
在packageA中使用packageB的中定义的消息类型
如果我们在pub_sub_test里定义的MyBasicMessage想在另一个包,比如我们的在讲如何使用roslaunch时建立的read_param_test这个package里,使用,应该怎么办呢? 我们可以先试一下,在read_param_test/src中我们写了一个show_param.cpp文件。我们可以先试一下添加头文件看成功否。打开show_param.cpp文件,在头文件那几行添加一行#include "pub_sub_test/MyBasicMessage.h"
,保存退出,使用catkin_make编译。编译不成功,显示找不到头文件。
那么如何找到它呢?如下
1:打开read_param_test里的CMakeLists.txt,在
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
geometry_msgs
)
中添加一行
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
geometry_msgs
pub_sub_test
)
add_executable(read_param src/show_param.cpp)
target_link_libraries(read_param ${catkin_LIBRARIES})
后添加一行
add_executable(read_param src/show_param.cpp)
target_link_libraries(read_param ${catkin_LIBRARIES})
add_dependencies(read_param ${catkin_EXPORTED_TARGETS})
即添加add_dependencies(可执行文件名 ${catkin_EXPORTED_TARGETS})
。关闭保存CMakeLists.txt。打开read_param_test的package.xml,在<build_depend>roscpp</build_depend>
周围的位置添加
<build_depend>pub_sub_test</build_depend>
<exec_depend>pub_sub_test</exec_depend>
把自定义的消息类型单独制成一个package/在不同workspace使用自定义的消息
现在我们能在同一个workspace(catkin_ws)的不同package里使用我们在pub_sub_test中定义的消息了,但是如果以后我们建立了一个其他的workspace我们还想用自定义的消息MyBasicMessage类型怎么办呢? 方法1:把pub_sub_test这个package直接复制到新的workspace(比如名字叫my_ws)my_ws/src里,使用catkin_make编译之后,pub_sub_test就在你的新的workspace里了,这时候你可以根据上面在packageA中使用pakcageB中定义的消息类型中的内容在新的workspace的不同package里使用自定义的消息了。 而且这个方法有一个bug,是ROS自己的问题。比如当我们把pub_sub_test移动到另一个新建的test_ws/src之后,使用catkin_make会编译失败,提示找不到MyBasicMessage.h
。
因为首先得编译成功新的消息类型,才可以使用。现在这个workspace里并没有记载有这个类型的消息,然而我们的cpp文件
add_executable(pub_my_basic_message src/pub_my_basic_message.cpp)
target_link_libraries(pub_my_basic_message ${catkin_LIBRARIES})
已经包含了头文件并且试图编译使用了。这时候我们需要先把这两行文件注释,就能编译成功,编译成功后,新的消息类型在workspace里有了记录,在去掉那两行的注释,再编译,就能成功了= =…. 方法2:方法1的弊端是我们只是想使用自定义的消息,却把整个package都复制过去了,那个package里所有内容(cpp文件什么的)都用不到呢,非常’划不来’。当你意识到你自定义的消息类型需要被很多不同的workspace里的很多不同package使用时,把它单独制成一个package。这个package里没有任何的cpp文件或者python文件,只有msg的定义。这样你把这个package复制到各个不同workspace,将不会有任何多余的累赘复制过去,其实本质上和方法1是一样的。我们可以简单地试一下,cd 到catkin_ws/src,建立一个新的package,假设我们现在自定义的消息同样只包含std_msgs中的内容
catkin_create_package my_custom_message std_msgs message_generation message_runtime
由于我们并不会写任何执行文件,所以连roscpp和rospy这两个元老都省了。 这时候你新创建了一个pakcage,在pakcage中新建一个叫msg的文件夹。在文件夹中新建一个MyNewMsg.msg
,在其中随便写点内容,如下
float64 data
string id
之后打开该pakcage的CMakeLists.txt,和前面的内容类似了
# add_message_files(
# FILES
# Message1.msg
# Message2.msg
# )
去掉注释改为
add_message_files(
FILES
MyNewMsg.msg
)
另外
# generate_messages(
# DEPENDENCIES
# std_msgs
# )
去掉注释,改为
generate_messages(
DEPENDENCIES
std_msgs
)
系列总结
这一讲之后马上就结束ROS系列的讲解了。还有很多大课题都没讲到,比如ros service之类,还有许多细节没有讲到,比如关于ros spin,同步接收消息等等等等。不过我还是打算这一讲之后的就结束了。ROS只是作为一个工具帮助你实现一些东西,当你理解了前三章的东西,对发布订阅消息这个机制了解了之后,可以说所有东西你都可以google到了。基础的内容已经说地很详细,剩下的就是根据你在project中自己学习了。所以我也不打算再继续了。以后如果关于ROS有些新的记录和感想我会时不时记录,但不会系统地写了。 接下来的内容是关于SLAM的毕竟这才是我专业hhhh。 以后的文章主题,会讲解古老的EKF-SLAM,现在使用的graphSLAM以及基于graph-SLAM的各种应用单目slam以及visual-inertial SLAM。从2D到3D,从理论到程序每一个都详细的讲解出来,内容质量应该会比ROS系列更高。一方面为contribute for the community,另一方面讲解的过程当中自己也可以学到不少东西。所有SLAM程序也都会在ROS的大环境下完成这样你也可以了解到稍微大型的程序中ROS如何发挥作用。 不过更新肯定很慢要保证质量地用业余时间完成这些课题,想ROS的tutorial时不时写一点儿都用了快半年…如果是想现在做SLAM的tutorial还想把各种SLAM都详细地讲一遍加上程序,估计得一两年hhhh。所以主要面向未来读者吧hhhh。如果你需要现在就学习,可以找我私信推荐一些别人写的文章。