话题通信下ROS项目的结构配置
① 创建符合ROS标准的项目框架
② 添加构建ROS标准项目文件的库
当我们使用vscode打开ROS标准项目文件时,按下Ctrl+Shift+B可以进行编译,但是我们需要设置构建ROS标准项目文件所需的功能包:
{
// 有关 tasks.json 格式的文档,请参见
// https://go.microsoft.com/fwlink/?LinkId=733558
"version": "2.0.0",
"tasks": [
{
"label": "catkin_make:debug", //代表提示的描述性信息
"type": "shell", //可以选择shell或者process,如果是shell代码是在shell里面运行一个命令,如果是process代表作为一个进程来运行
"command": "catkin_make",//这个是我们需要运行的命令
"args": [],//如果需要在命令后面加一些后缀,可以写在这里,比如-DCATKIN_WHITELIST_PACKAGES=“pac1;pac2”
"group": {"kind":"build","isDefault":true},
"presentation": {
"reveal": "always"//可选always或者silence,代表是否输出信息
},
"problemMatcher": "$msCompile"
}
]
}
③ 创建功能包
选定 src 右击 ---> create catkin package ,然后给功能包取个名字、给功能包添加依赖项(roscpp,std_msgs)。
④ 配置package.xml和CMakelist.txt文件
package.xml文件中指明了“将项目文件编译成符合ROS标准的项目文件所需的功能包”以及“将符合ROS标准的项目文件编译成计算机可以执行的可执行文件所需的功能包”;CMakelist.txt文件则说明了“如何应用这些功能包来将项目文件一步一步地编译成可执行文件”。
在package.xml中,由于我们使用C++编写的节点文件,因此:
<buildtool_depend>catkin</buildtool_depend>
<build_depend>roscpp</build_depend>
<build_depend>std_msgs</build_depend>
<build_depend>message_generation</build_depend> // 手动添加
<build_export_depend>roscpp</build_export_depend>
<build_export_depend>std_msgs</build_export_depend>
<exec_depend>roscpp</exec_depend>
<exec_depend>std_msgs</exec_depend>
<exec_depend>message_runtime</exec_depend> // 手动添加
在上述代码中,我们在构建符合ROS标准的项目文件时,需要用到roscpp,std_msgs,message_generation这三个功能包,而在将符合ROS标准的项目文件编译成计算机可以识别的可执行文件时,我们需要用到roscpp,std_msgs,message_runtime三个功能包。
还记得我们一开始创建功能包时添加的依赖项吗?就是roscpp和std_msgs这两个功能包。我们在package.xml中也看到了这两个功能包贯穿项目文件编译流程的始终。但是message_generation和message_runtime这两个功能包不会在编译全周期内起作用,因此我们不可以在创建功能包时将它们作为编译依赖项。我们需要人为添加这两个功能包:message_generation和message_runtime分别在“将项目文件编译成符合ROS标准的项目文件”和“将符合ROS标准的项目文件编译成计算机可以识别的可执行文件”阶段起作用。
比起配置package.xml文件,配置CMakelist.txt文件稍微复杂一些:
find_package(catkin REQUIRED COMPONENTS
roscpp
std_msgs
message_generation
)
add_message_files(
FILES
Person.msg
)
generate_messages(
DEPENDENCIES
std_msgs
)
这一部分为“将项目文件编译成符合ROS标准的项目文件”所需的功能包,以及指明自定义.msg消息文件(初始ROS项目框架中并没有自定义消息文件,我们必须在功能包下自定义msg文件夹,再在msg文件夹中创建XXX.msg文件即可)。上述代码的含义为:
1. “将项目文件编译成符合ROS标准的项目文件”所需的功能包是:roscpp,std_msgs,message_generation;
2. 我们自定义的消息文件是Person.msg;
3. 我们自定义的消息文件其实就是ROS基本数据类型的一个集合,因此我们需要用std_msgs功能包来对我们的自定义,msg消息文件进行编译。
catkin_package(
CATKIN_DEPENDS roscpp std_msgs message_runtime
)
add_executable(publisher src/publisher.cpp)
add_executable(subscriber src/subscriber.cpp)
add_dependencies(publisher ${PROJECT_NAME}_generate_messages_cpp)
add_dependencies(subscriber ${PROJECT_NAME}_generate_messages_cpp)
target_link_libraries(publisher
${catkin_LIBRARIES}
)
target_link_libraries(subscriber
${catkin_LIBRARIES}
)
1. 将符合ROS标准的项目文件编译成计算机可执行的文件需要的功能包有:roscpp,std_msgs,message_runtime;
2. 在这个功能包中,C++源文件为publisher.cpp,subscriber.cpp,我们要将这两个文件进行编译链接最终生成可执行文件。
⑤ 编写XXX.msg自定义消息文件(这里我自定义的消息名称为Person.msg)
Header header
float32 height
string name
待编写玩全部文件之后,按下Ctrl+Shift+B既可编译,编译得到devel/include下Person.h
但是,这时候由于Vscode的工作目录并没有包含Person.h文件的目录,因此,当我们在其他文件调用Person.h文件时,会报错。我们可以在c_cpp_properties.json文件中添加devel/include下Person.h的工作目录即可解决上述问题。
⑥ 编写C++源文件:
1. 订阅端.cpp源文件:
#include "ros/ros.h"
#include "Topic/Person.h"
void CallbackFunc(const Topic::Person::ConstPtr& ptr) // 回调函数——负责处理接收到的消息
{
ROS_INFO("Time:%d,Seq:%d,Frame:%s\n\t",ptr->header.stamp.sec,ptr->header.seq,ptr->header.frame_id.c_str());
ROS_INFO("height:%f,name:%s\n\t",ptr->height,ptr->name.c_str());
}
int main(int argc, char* argv[])
{
setlocale(LC_ALL,""); // 将设置本地化——确保写中文不会出乱码
ros::init(argc,argv,"subscriber"); // 节点接收参数的入口
ros::NodeHandle nh; // 节点句柄——节点与master主节点交流的通道
ros::Subscriber sub = nh.subscribe<Topic::Person>("chatter",10,CallbackFunc); // 将订阅者的信息利用subscriber函数发布至master主节点之中等待与对应的发布者配对,一旦配对成功节点句柄就会返回一个订阅者对象——相当于接收消息的通道
ros::spin(); // 阻塞函数:等待发布端消息,接收到消息立刻跳转至回调函数
return 0;
}
2. 发布端.cpp源文件:
#include "ros/ros.h"
#include "Topic/Person.h"
int main(int argc, char* argv[])
{
setlocale(LC_ALL,"");
ros::init(argc,argv,"publisher");
ros::NodeHandle nh;
ros::Publisher pub = nh.advertise<Topic::Person>("chatter",10);
Topic::Person p;
p.header.frame_id = "earth"; // Header头文件中用于说明数据所在的坐标系
p.height = 10.0;
p.name = "father";
ros::Rate r(1); // 每1s发送一次数据
ros::Duration(3); // 在此等待3秒,等待收发双方准备完毕
while(ros::ok())
{
p.header.seq++; // 接受数据的序号
p.header.stamp = ros::Time::now(); // 时间戳——接收到数据时的时间
pub.publish(p); // 通过TCP数据传输通道发布数据
r.sleep(); // 如果一次循环不够1s,那么再次等待直至1s到达,这样可使得while循环得时间周期为1s与数据发布时间相匹配
}
return 0;
}