1.1 话题通信
模式:
以发布订阅的方式实现不同节点之间数据交互的通信模式。
如图1-1所示,Listener-Talker通信首先创建了两个Node,分别是Talker Node和 Listener Node。
每个Node实例化Writer类和Reader类对Channel进行消息的读写。
Writer和Reader通过Topic连接,对同一块共享内存(Channel)进行读写处理。
Talker Node 为了实现其“诉说”的功能,实例化Writer,通过Writer来对Channel进行消息写操作。
Listener Node为了实现其“聆听”功能,实例化reader类,通过Reader来对channel进行读操作。
场景:
话题通信方式适合于持续性通信的应用场景,比如雷达信号,摄像头图像信息这类数据的传输。
使用:
Listener-Talker通信一方主动送消息,一方被动接收。
我们想要一直获取车的速度,该需求不需要向发送方返回什么消息,也不需要发送方对消息进行进一步处理。所以我们选择了Listener-Talker通信方式实现该功能。
数据定义:
话题通信中用的的数据格式的定义car message 定义在car.proto中。
1.2 服务通信
模式:
以请求响应的方式实现不同节点之间数据交互的通信模式。
如图1-2所示,Server-Client通信可以在客户端发出消息请求时,服务端才进⾏请求回应,并将客户端所需的数据返回给客户端。
场景:
我们想要获得⼩⻋的详细信息,⽐如⻋牌这些,但是⼜不需要⼀直获得该信息,想要在需要知道这些信息的时候请求⼀下就好,于是考虑⽤Server- Client通信实现该功能。
使用:
该通信模式适合临时的消息传输,适⽤于不需要持续性发送数据的场景。
数据定义:
其传输的数据定义依然在对应的proto⽂件中。
1.3 参数通信
模式:
以共享的方式实现不同节点之间数据交互的通信模式。
参数服务器是基于服务实现的,包含客户端和服务器端,服务端节点可以存储数据,客户端节点可以访问服务端节点操作数据,这个过程虽然基于请求响应的,但是无需自己实现请求与响应,此过程已经被封装,调用者只需要通过比较简单友好的API就可以实现参数操作。
场景:
自动驾驶场景中有一些参数比如该车的最高限速、最多乘客以及是否自动驾驶等需要被各个模块使用数据,比如是否自动驾驶这个参数可能同时影响这很多模块,也可能被很多模块运行时所更改。
这些数据如何实现在不同模块之间的共享呢?
使用:
类似于“全局变量”的方式来存储这些参数,并定义一些自定义参数来进行使用。
数据定义:
Cyber中设计了全局参数服务器来实现这个功能,其通信基于RTPS协议。该通信方式服务端和客户端都可以设置参数和更改参数。
第二节:数据通信基础Protobuf
1.3 Protobuf简介
2.1 Protobuf简介
Protobuf 是 Google 公司开发的一种跨语言和平台的序列化数据结构的方式,是一个灵活的、高效的用于序列化数据的协议,与 XML 和 JSON 格式相比,Protobuf 更小、更快、更便捷。
Protobuf 是跨语言的,并且自带一个编译器( protoc ),只需要用protoc进行编译,就可以编译成 Java、Python、C++、C#、Go 等多种语言代码,然后可以直接使用,不需要再写其它代码,自带有解析的代码。只需要将要被序列化的结构化数据定义一次(在 .proto 文件定义),便可以使用特别生成的源代码(使用protobuf提供的生成工具)轻松的使用不同的数据流完成对结构数据的读写操作。甚至可以更新 .proto 文件中对数据结构的定义而不会破坏依赖旧格式编译出来的程序。其优点如下:
syntax = “proto2”;
package apollo.cyber.examples.proto;
message SamplesTest1 {
optional string class_name = 1;
optional string case_name = 2;
};
message Chatter {
optional uint64 timestamp = 1;
optional uint64 lidar_timestamp = 2;
optional uint64 seq = 3;
optional bytes content = 4;
};
message Driver {
optional string content = 1;
optional uint64 msg_id = 2;
optional uint64 timestamp = 3;
};
2.3 Protobuf编译
Protobuf的编译要分为两个步骤:
load(“//tools/install:install.bzl”, “install”, “install_src_files”)
install(
name = “install”,
data = [
“cyber_demo.BUILD”,
“cyberfile.xml”,
],
deps = [
“//cyber_demo/cyber_03/test_proto:install”,
],
)
install_src_files(
name = “install_src”,
src_dir = [“.”],
dest = “cyber_demo/src”,
filter = “*”,
deps = [
“//cyber_demo/cyber_03/test_proto:install_src”,
]
)
编写cyberfile文件:
cyber_demo
1.0.0
cyber_demo
[email protected]
module
BSD
Apollo
cyber-dev
3rd-protobuf-dev
bazel
<3> 编写proto源文件及BUILD文件;
编写proto文件,文件中定义了车辆的信息:
syntax = “proto2”;
package apollo.cyber.test.proto;
message CarMsg {
required string owner = 1;
optional string license_plate = 2;
optional uint64 max_passenger = 3;
repeated string car_info = 4;
}
编写proto的BUILD文件:
load(“@rules_proto//proto:defs.bzl”, “proto_library”)
load(“@rules_cc//cc:defs.bzl”, “cc_proto_library”)
load(“//tools:python_rules.bzl”, “py_proto_library”)
package(default_visibility = [“//visibility:public”])
proto_library(
name = “car_msg_proto”,
srcs = [“car_msg.proto”],
)
cc_proto_library(
name = “car_msg_cc_proto”,
deps = [“:car_msg_proto”],
)
<4> 编写主代码及BUILD文件
通过car.cc输出车辆基本信息:
#include “test/proto/car_msg.pb.h”
using namespace std;
int main()
{
apollo::cyber::test::proto::CarMsg car;
car.set_owner("apollo");
car.set_license_plate("京A88888");
car.set_max_passenger(6);
car.add_car_info("SUV"); //车型
car.add_car_info("Red"); //车身颜色
car.add_car_info("electric"); //电动
string owner = car.owner();
string license_plate = car.license_plate();
uint64_t max_passenger = car.max_passenger();
cout << "owner:" << owner << endl;
cout << "license_plate:" << license_plate << endl;
cout << "max_passenger:" << max_passenger << endl;
for (int i = 0; i < car.car_info_size(); ++i){
string info = car.car_info(i);
cout << info << " ";
}
cout << endl;
return 0;
}
注意
本段代码中内容需要进行
#include “test/proto/car_msg.pb.h”
--------------------需要替换为---------------------------
#include “cyber_demo/cyber_03/proto/car_msg.pb.h”
编辑car.cc的BUILD文件:
load(“@rules_cc//cc:defs.bzl”, “cc_binary”, “cc_library”)
load(“//tools/install:install.bzl”, “install”, “install_src_files”)
load(“//tools:cpplint.bzl”, “cpplint”)
package(default_visibility = [“//visibility:public”])
cc_binary(
name = “car”,
srcs = [“car.cc”],
deps = [“//test/proto:car_msg_cc_proto”],
)
install(
name = “install”,
runtime_dest = “test/bin”,
targets = [
“:car”
],
)
install_src_files(
name = “install_src”,
src_dir = [“.”],
dest = “test/src/cyberatest”,
filter = “*”,
)
注意
本段BUILD代码中内容需要进行修改
cc_binary(
name = “car”,
srcs = [“car.cc”],
deps = [“//test/proto:car_msg_cc_proto”],
)
--------------------需要替换为---------------------------
cc_binary(
name = “car”,
srcs = [“car.cc”],
deps = [“//cyber_demo/cyber_03/proto:car_msg_cc_proto”],
)
<5> 编译代码
cd /apollo_workspace
buildtool build -p cyber_demo/
编译结果如图所示:
编译完成后会在 /opt/apollo/neo/bin/的目录下生成可执行文件car,如下图所示:
<6> 运行可执行文件
cd /opt/apollo/neo/bin/
./car
运行结果如下图:
所有者:apollo
车牌号:京A88888
最大乘客人数:6
SUV 红色 电动
内容导入:
第三节:Cyber RT通信机制解析与实践
//定义包名,在cc文件中调用
package apollo.cyber.test.proto;
//定义一个车的消息,车的型号,车主,车的车牌号,已跑公里数,车速
message Car{
optional string plate = 1;
optional string type = 2;
optional string owner = 3;
optional uint64 kilometers = 4;
optional uint64 speed = 5;
};
2.1 实现小车的实时速度获取——Listener-Talker通信
//car数据定义的引用,可以看出其定义来源于一个proto
using apollo::cyber::examples::cyber_test_proto::Car;
int main(int argc, char *argv[]) {
// 初始化一个cyber框架
apollo::cyber::Init(argv[0]);
// 创建talker节点
auto talker_node = apollo::cyber::CreateNode(“talker”);
// 从节点创建一个Topic,来实现对车速的查看
auto talker = talker_node->CreateWriter("car_speed");
AINFO << "I'll start telling you the current speed of the car.";
//设置初始速度为0,然后速度每秒增加5km/h
uint64_t speed = 0;
while (apollo::cyber::OK()) {
auto msg = std::make_shared();
msg->set_speed(speed);
//假设车速持续增加
speed += 5;
talker->Write(msg);
sleep(1);
}
return 0;
}
using apollo::cyber::examples::cyber_test_proto::Car;
//接收到消息后的响应函数
void message_callback(
const std::shared_ptr& msg) {
AINFO << "now speed is: " << msg->speed();
}
int main(int argc, char* argv[]) {
//初始化cyber框架
apollo::cyber::Init(argv[0]);
//创建监听节点
auto listener_node = apollo::cyber::CreateNode("listener");
//创建监听响应进行消息读取
auto listener = listener_node->CreateReader(
"car_speed", message_callback);
apollo::cyber::WaitForShutdown();
return 0;
}
package(default_visibility = [“//visibility:public”])
cc_binary(
name = “talker”,
srcs = [“talker.cc”],
deps = [
“//cyber”,
“//test/proto:car_cc_proto”,
],
linkstatic = True,
)
cc_binary(
name = “listener”,
srcs = [“listener.cc”],
deps = [
“//cyber”,
“//test/proto:car_cc_proto”,
],
linkstatic = True,
)
2. 编译:
使用apollo 包管理开发方式提供的buildtool工具
buildtool build -p test/communication/
如下图所示,则编译成功。
./bazel-bin/test/communication/listener
结果显示:
总体运行效果如下图所示。
第四节:Cyber RT 本地部署之话题通信实践案例
Apollo 本地安装部署
Apollo 本地部署安装方式
创建和进入 Apollo 环境容器
创建工作空间
创建并进入目录
mkdir application-demo
cd application-demo
启动 apollo 环境容器
aem start
如果一切正常,将会见到类似下图的提示:
进入 apollo 环境容器
aem enter
脚本执行成功后,将显示以下信息,您将进入 Apollo 的运行容器:
工作空间文件夹将被挂载到容器的 /apollo_workspace 中。
至此 Apollo 环境管理工具及容器已经安装完成。
3. Protobuf、talker-listener实践案例演示
Protobuf 案例:
打印日志
export GLOG_alsologtostderr=1
运行car:
cd /apollo_workspace/bazel-bin/test_proto/
./car
car 运行结果如图所示
talker-listener 案例:
talker进入环境配置打印日志:
aem start
aem enter
export GLOG_alsologtostderr=1
运行talker:
cd /apollo_workspace/bazel-bin/test/communication/
./talker
talker 运行结果如图所示
listener进入环境配置打印日志:
aem start
aem enter
export GLOG_alsologtostderr=1
运行listener:
cd /apollo_workspace/bazel-bin/test/communication/
./listener
listener 运行结果如图所示
课后学习内容:
作业:
编写Python话题通信实现。
练习一: 实现小车详细信息的获取——Server-Client通信
1.1 Server-Client 简介
我们想要获得小车的详细信息,比如车牌这些,但是又不需要一直获得该信息,想要在需要知道这些信息的时候请求一下就好,于是考虑用Server- Client通信实现该功能。如图2-1所示,Server-Client通信可以在客户端发出消息请求时,服务端才进行请求回应,并将客户端所需的数据返回给客户端。该通信模式适合临时的消息传输,适用于不需要持续性发送数据的场景。其传输的数据定义依然在对应的proto文件中。
1.2 Server-Client 案例实现
编写一个Server来对Client请求的消息进行回应,这里我们返回车的车牌号、车主、已行驶公里数等信息。
#include “test/proto/car.pb.h”
#include “cyber/cyber.h”
//使用proto中定义好的car数据结构,还记得这个数据结构在那定义的吗?
using apollo::cyber::test::proto::Car;
int main(int argc, char* argv[]) {
apollo::cyber::Init(argv[0]);
std::shared_ptrapollo::cyber::Node node(
apollo::cyber::CreateNode(“server”));
//创建server node并设置topic名称,定义响应函数
auto server = node->CreateService("service_client",
[](const std::shared_ptr& request,std::shared_ptr& response) {
AINFO << "Hi, I am your car's server.";
response->set_type("apollo");
response->set_plate("京A8888888");
response->set_owner("xxx");
response->set_kilometers(5);
});
apollo::cyber::WaitForShutdown();
return 0;
}
编写一个Client来请求车的详细信息,并获取数据。
#include “test/proto/car.pb.h”
#include “cyber/cyber.h”
//使用proto中定义好的car数据结构
using apollo::cyber::examples::cyber_test_proto::Car;
int main(int argc, char* argv[]){
//初始化cyber框架
apollo::cyber::Init(argv[0]);
//创建client node
std::shared_ptr node(
apollo::cyber::CreateNode("client"));
auto client = node->CreateClient("service_client");
AINFO << "Hi server, I want to know car's information";
//创建一个请求
auto request = std::make_shared();
//发送请求并获得回应数据
auto response = client->SendRequest(request);
if(apollo::cyber::OK){
if (response != nullptr) {
AINFO << "this car owner is : " << response->owner();
AINFO << "this car plate is : " << response->plate();
AINFO << "this car type is : " << response->type();
AINFO << "this car has run : " << response->kilometers()<<"kilometers";
} else {
AINFO << "service may not ready.";
}
}
apollo::cyber::WaitForShutdown();
return 0;
}
编写bazel编译文件(编写在cyber/examples/cyber_test/BUILD中)。
cc_binary(
name = “server”,
srcs = [“server.cc”],
deps = [
“//cyber”,
“//test/proto:car_cc_proto”,
],
linkstatic = True,
)
cc_binary(
name = “client”,
srcs = [“client.cc”],
deps = [
“//cyber”,
“//test/proto:car_cc_proto”,
],
linkstatic = True,
)
编译:
仍然打开两个终端,进入apollo的docker环境后,进行编译,其BUILD文件和talker.cc等为同一个文件,所以编译语句相同,也可以精准编译,先编译server再编译client, 这里为了方便整体编译了。
buildtool build test/communication/
运行:
./bazel-bin/test/communication/server
./bazel-bin/test/communication/client
运行成功后结果如下:
练习二: 实现小车通用参数的设置——Parameter Server-Client
2.1 Parameter Server-Client 简介
有一些参数比如该车的最高限速,最多乘客以及是否自动驾驶等需要被各个模块所使用,比如是否自动驾驶这个参数可能同时影响这很多模块,也可能被很多模块运行时所更改。我们希望有一个类似于“全局变量”的方式来存储这些参数,并定义一些自定义参数来进行使用。Cyber中设计了全局参数服务器来实现这个功能,其通信依然基于RTPS协议,如图2-8所示。该通信方式服务端和客户端都可以设置参数和更改参数。
2.2 Parameter Server-Client 案例实现
#include “cyber/cyber.h”
#include “cyber/parameter/parameter_client.h”
#include “cyber/parameter/parameter_server.h”
using apollo::cyber::Parameter;
using apollo::cyber::ParameterServer;
int main(int argc, char** argv) {
//初始化cyber框架
//创建一个node,该node的名字和Topic名字同名
apollo::cyber::Init(*argv);
std::shared_ptrapollo::cyber::Node server_node =
apollo::cyber::CreateNode(“parameters_server”);
AINFO << “Hi,I’am your car’s parameter server.”;
//从该node创建参数服务器
auto param_server = std::make_shared(server_node);
//服务端进行参数设置,对小车的最高速度、最大人数、是否自动驾驶等参数进行了设置
param_server->SetParameter(Parameter("max_speed", 120));
param_server->SetParameter(Parameter("max_people", 5));
param_server->SetParameter(Parameter("if_auto", true));
//尝试客户端修改一个参数,如max_speed,查看服务端目前的值
Parameter car_parameter;
param_server->GetParameter("max_speed", &car_parameter);
AINFO << "origin max_speed " << car_parameter.AsInt64();
apollo::cyber::WaitForShutdown();
return 0;
}
编写param_client.cc。
#include “cyber/cyber.h”
#include “cyber/parameter/parameter_client.h”
using apollo::cyber::Parameter;
using apollo::cyber::ParameterClient;
using apollo::cyber::Parameter;
using apollo::cyber::ParameterClient;
int main(int argc, char** argv){
apollo::cyber::Init(*argv);
std::shared_ptrapollo::cyber::Node client_node =
apollo::cyber::CreateNode(“parameters_client”);
auto param_client = std::make_shared(client_node,“parameters_server”);
AINFO<<“Hi, I’m car’s parameters client!”;
//获取所有参数,用vector获取所有参数
std::vector car_parameters;
param_client->ListParameters(&car_parameters);
//遍历获取的参数,显示参数的名字以及参数的类型
for(auto &¶meter : car_parameters){
AINFO << parameter.Name() <<" is " << parameter.TypeName();
}
//客户端选择一个参数进行修改,比如修改max_speed
param_client->SetParameter(Parameter("max_speed",180));
//获取修改后的max_speed
Parameter car_parameter;
param_client->GetParameter("max_speed", &car_parameter);
AINFO << "now max_speed " << car_parameter.AsInt64();
apollo::cyber::WaitForShutdown();
return 0;
}
编写编译文件(编写在cyber/examples/cyber_test/BUILD中)。
deps = [
"//cyber",
"//cyber/parameter",
],
linkstatic = True,
)
cc_binary(
name = “param_server”,
srcs = [“param_server.cc”],
deps = [
“//cyber”,
“//cyber/parameter”,
],
linkstatic = True,
)
install(
name = “install”,
runtime_dest = “test/bin”,
targets = [
“:talker”,
“:listener”,
“:server”,
“:client”,
“:param_client”,
“:param_server”
],
)
install_src_files(
name = “install_src”,
src_dir = [“.”],
dest = “test/src/cyberatest”,
filter = “*”,
)
编译:
还是打开两个终端,进入apollo的docker环境后,先进行编译。
buildtool build test/communication/
运行:
./bazel-bin/test/communication/server/param_server
./bazel-bin/test/communication/server/param_client
好啦,至此通过“小车”案例已经介绍完了Cyber三种通信方式的简单用法,快动起手来试试吧~