为什么引入话题通信
为了接收如:摄像头,雷达这些外设发布的数据信息并进行二次处理,Cyber RT提供了话题通信的机制,其主要功能就是以发布订阅的方式实现不同节点之间数据交互的通信模式,简而言之通过话题机制将发布方与接受方链接在一起,实现数据传递。这种机制应用于不断更新且少数据交互的通信模式。
基本实现流程
- 编写消息载体文件(protobuf)
- 编写发布方代码 &话题
- 编写接收方代码
- 编译并执行
实列测试
功能:创建一个发布订阅模型,发布方周期性的发布消息,订阅方接收学生信息,并输出终端。
消息载体文件(protobuf)编写
什么是protobuf
序列号数据常用的数据格式,类似于XML,JSON。但是protobuf的优势是体积更小,速度更快可以被编译为C++、Python、Java、C#等等,方便实现对数据流的读写或者操作,不需要进行特殊的解析工作。
总体而言,我们可以将数据的格式存入Protobuf文件中,需要时转换为我们能够处理的文件类型。
第一个Protobuf代码
创建protobuf项目文件 demo_base_probuf,在该文件夹下面编写proto源文件以及build源文件
需求:在protobuf中添加学生信息:姓名,年龄,身高,课程
//使用的 proto版本,cyber RT中目前是proto2
syntax = "proto2";
//包
package apollo.demo.cyber.proto;
//消息 --message 是关键字,student消息名称 可以当成类去理解
message Student {
//字段
//字段格式:字段规则 数据类型 字段名称 字段编号
required string name =1; //必须的 类型是string
optional uint64 age =2; //可选项 无符号整形
optional double height =3;
repeated string myclass =4; //可重复的
}
创建build文件
load("//tools:python_rules.bzl", "py_proto_library")
package(default_visibility = ["//visibility:public"])
proto_library(
name = "student_proto",
srcs = ["stuProto.proto"],
)
cc_proto_library(
name = "student_cc_proto",
deps = [":student_proto"],
)
编写发布方代码
创建demo_cc文件夹,新建BUILD文件,demo_talker.cc文件。
/****
* 包含头文件
* 初始化cyber框架
* 创建节点
* 创建发布方
* 组织并发布数据
* 等待节点关闭,释放资源
* *****/
#include "demo/cyber/proto/stuProto.pb.h"
#include "cyber/cyber.h"
//使用student这个类
using apollo::demo::cyber::proto::Student;
int main(int argc, char* argv[]) {
apollo::cyber::Init(argv[0]); // argv[0]指代的是当前文件
/*std::unique_ptr<Node> apollo::cyber::
*CreateNode(const std::string& node_name, const std::string& name_space ="");
*node_name:节点名称,全局唯一标识符
*name_space:节点所在空间的名称
*name_space默认为空。
*它是与node_name串联的空间的名称。格式为/namespace/node_name
*/
auto talker_node = apollo::cyber::CreateNode("talk_Node");
// create node函数创建节点
//参数
//intput一个string类型的nodename(给当前的这个节点起一个名字,注意不能呢个重复)
//返回值是一个指针
//创建发布方 发布方依赖于节点
auto talker = talker_node->CreateWriter<Student>("message_trans");
// message_trans是话题 channel
// create writer<类名>(话题名称,作用是将订阅者与话题关联到一起的关键字)
// createwriter 创建一个写出对象
//循环发布数据
//频率函数 单位 次/s
//需要调用rate.sleep();进行休眠,每次循化执行休眠一段时间
apollo::cyber::Rate rate(0.5);
uint64_t seq = 0;
while (apollo::cyber::OK()) //判断节点是否在运行
{
seq++;
AINFO << "第" << seq << "条数据";
//组织数据,创建一个student类的指针
auto stu_ptr = std::make_shared<Student>();
stu_ptr->set_name("bobo_demo");
stu_ptr->set_age(seq);
stu_ptr->set_height(174.5);
// repeated 用add
stu_ptr->add_myclass("yu wen");
stu_ptr->add_myclass("shue xue");
//发布数据,通过发布指针来调用他的发布函数
talker->Write(stu_ptr); //这个write可以传递student模板的指针或者变量
rate.Sleep();
}
//等待关闭
apollo::cyber::WaitForShutdown();
return 0;
}
编写接收方代码
//包含头文件
//初始化 cyber 框架
//创建节点
//创建订阅者
//解析数据
//等待节点关闭,释放资源
#include "demo/cyber/proto/stuProto.pb.h"
#include "cyber/cyber.h"
using apollo::demo::cyber::proto::Student;
void cb(const std::shared_ptr<Student>& stu) {
//解析学生并打印
AINFO << "name = " << stu->name();
AINFO << "age = " << stu->age();
AINFO << "height = " << stu->height();
for (int i = 0; i < stu->myclass_size(); i++) {
AINFO << "calss = "
<< stu->myclass(i); //内部数据较多情况下使用index去索引
}
AINFO << "----------------------------------";
}
//实现消息订阅并进行打印
int main(int argc, char* argv[]) {
//初始化框架
apollo::cyber::Init(argv[0]);
//创建节点
auto listener_node = apollo::cyber::CreateNode("lis_Node");
//创建订阅者
//这个reader是网络中用于接收消息的基本工具。创建reader时,必须将其绑定到回调函数。
//当新消息到达频道时,将调用回调。reader是由CreateReader节点类的接口创建的。
auto listener = listener_node->CreateReader<Student>("message_trans", cb);
//释放操作
apollo::cyber::WaitForShutdown();
return 0;
}
编写build文件
package(default_visibility = ["//visibility:public"])
# https://docs.bazel.build/versions/master/be/c-cpp.html#cc_binary
cc_binary(
name = "talker",
srcs = ["talk.cc"],
deps = [
"//cyber",
"//demo/cyber/proto:student_cc_proto",
],
)
cc_binary(
name = "listener",
srcs = ["lis.cc"],
deps = [
"//cyber",
"//demo/cyber/proto:student_cc_proto",
],
)
编写执行脚本 .sh文件,可以不写,直接在终端执行
这个是lis的脚本
#!/bin/sh
cd /apollo
source cyber/setup.bash
./bazel-bin/demo/cyber/talk_lis/listener
这个是talker的脚本
#!/bin/sh
cd /apollo
source cyber/setup.bash
./bazel-bin/demo/cyber/talk_lis/talker