Apollo Cyber RT框架文档
TUTORIALS—教程
Cyber RT API教程
1、Talker-Listener
CyberRT API 演示的第一部分是了解Talker / Listener示例。
以下是三个基本概念:
- 示例的节点(基本单元)
- 读取器(读取消息的功能)
- 写入器(写入消息的功能)
create a node(创建节点)
在CyberRT框架中,节点是最基本的单元,类似于的角色handle。创建特定的功能对象(编写器,读取器等)时,需要基于现有节点实例创建它。
节点创建界面如下:
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
返回值
- 指向Node的专有智能指针
错误条件
- cyber::Init()未调用时,系统处于未初始化状态,无法创建节点,返回nullptr
Create a writer
编写器是CyberRT中用于发送消息的基本工具。每个writer
对应于具有特定数据类型的通道。编写器由CreateWriter
节点类中的接口创建。
接口列表如下:
template <typename MessageT>
auto CreateWriter(const std::string& channel_name)
-> std::shared_ptr<Writer<MessageT>>;
template <typename MessageT>
auto CreateWriter(const proto::RoleAttributes& role_attr)
-> std::shared_ptr<Writer<MessageT>>;
参数:
- channel_name:要写入的通道的名称
- MessageT:要写出的消息类型
返回值
- 指向Writer对象的共享指针
Create a reader
这个reader
是网络中用于接收消息的基本工具。创建reader
时,必须将其绑定到回调函数。当新消息到达频道时,将调用回调。reader
是由CreateReader
节点类的接口创建的。
接口列表如下:
template <typename MessageT>
auto CreateReader(const std::string& channel_name, const std::function<void(const std::shared_ptr<MessageT>&)>& reader_func)
-> std::shared_ptr<Reader<MessageT>>;
template <typename MessageT>
auto CreateReader(const ReaderConfig& config,
const CallbackFunc<MessageT>& reader_func = nullptr)
-> std::shared_ptr<cyber::Reader<MessageT>>;
template <typename MessageT>
auto CreateReader(const proto::RoleAttributes& role_attr,
const CallbackFunc<MessageT>& reader_func = nullptr)
-> std::shared_ptr<cyber::Reader<MessageT>>;
参数:
- MessageT:要阅读的消息类型
- channel_name:要从中接收的频道的名称
- reader_func:处理消息的回调函数
返回值
- 指向Reader对象的共享指针
代码示例
Talker(cyber / examples / talker.cc)
#include "cyber/cyber.h"
#include "cyber/proto/chatter.pb.h"
#include "cyber/time/rate.h"
#include "cyber/time/time.h"
using apollo::cyber::Rate;
using apollo::cyber::Time;
using apollo::cyber::proto::Chatter;
int main(int argc, char *argv[]) {
// init cyber framework
apollo::cyber::Init(argv[0]);
// create talker node
std::shared_ptr<apollo::cyber::Node> talker_node(
apollo::cyber::CreateNode("talker"));
// create talker
auto talker = talker_node->CreateWriter<Chatter>("channel/chatter");
Rate rate(1.0);
while (apollo::cyber::OK()) {
static uint64_t seq = 0;
auto msg = std::make_shared<apollo::cyber::proto::Chatter>();
msg->set_timestamp(Time::Now().ToNanosecond());
msg->set_lidar_timestamp(Time::Now().ToNanosecond());
msg->set_seq(seq++);
msg->set_content("Hello, apollo!");
talker->Write(msg);
AINFO << "talker sent a message!";
rate.Sleep();
}
return 0;
}
Listener(cyber / examples / listener.cc)
#include "cyber/cyber.h"
#include "cyber/proto/chatter.pb.h"
void MessageCallback(
const std::shared_ptr<apollo::cyber::proto::Chatter>& msg) {
AINFO << "Received message seq-> " << msg->seq();
AINFO << "msgcontent->" << msg->content();
}
int main(int argc, char *argv[]) {
// init cyber framework
apollo::cyber::Init(argv[0]);
// create listener node
auto listener_node = apollo::cyber::CreateNode("listener");
// create listener
auto listener =
listener_node->CreateReader<apollo::cyber::proto::Chatter>(
"channel/chatter", MessageCallback);
apollo::cyber::WaitForShutdown();
return 0;
}
Bazel BUILD file(cyber/samples/BUILD)
cc_binary(
name = "talker",
srcs = [ "talker.cc", ],
deps = [
"//cyber",
"//cyber/examples/proto:examples_cc_proto",
],
)
cc_binary(
name = "listener",
srcs = [ "listener.cc", ],
deps = [
"//cyber",
"//cyber/examples/proto:examples_cc_proto",
],
)
构建和运行
Build: bazel build cyber/examples/…
- 在不同的终端中运行Talker/LIstener:
./bazel-bin/cyber/examples/talker
./bazel-bin/cyber/examples/listener
- 检查结果:您应该看到消息在Listener上打印出来。
2、Service Creation and Use(服务创建和使用)
简介
在自动驾驶系统中,除了模块发送或接收消息外,还有很多场景需要模块通信。服务是节点之间的另一种通信方式。与信道不同,服务实现two-way
通信,例如,节点通过发送请求获得响应。本节通过示例介绍CyberRT API 模块中的service
。
Demo - Example (演示-示例)
问题:创建来回传递Driver.proto
的客户端-服务器模型。当客户端发送请求时,服务器解析/处理
该请求并返回响应。
该演示的实现主要包括以下步骤。
Define request and response messages (定义请求和响应消息)
cyber
中的所有消息均采用protobuf
格式。任何带有序列化/反序列化
接口的protobuf
消息都可以用作服务请求和响应消息。Driver
在examples.proto
中用作本示例中的服务请求和响应:
// filename: examples.proto
syntax = "proto2";
package apollo.cyber.examples.proto;
message Driver {
optional string content = 1;
optional uint64 msg_id = 2;
optional uint64 timestamp = 3;
};
Create a service and a client (创建服务和客户端)
// filename: cyber/examples/service.cc
#include "cyber/cyber.h"
#include "cyber/examples/proto/examples.pb.h"
using apollo::cyber::examples::proto::Driver;
int main(int argc, char* argv[]) {
apollo::cyber::Init(argv[0]);
std::shared_ptr<apollo::cyber::Node> node(
apollo::cyber::CreateNode("start_node"));
auto server = node->CreateService<Driver, Driver>(
"test_server", [](const std::shared_ptr<Driver>& request,
std::shared_ptr<Driver>& response) {
AINFO << "server: I am driver server";
static uint64_t id = 0;
++id;
response->set_msg_id(id);
response->set_timestamp(0);
});
auto client = node->CreateClient<Driver, Driver>("test_server");
auto driver_msg = std::make_shared<Driver>();
driver_msg->set_msg_id(0);
driver_msg->set_timestamp(0);
while (apollo::cyber::OK()) {
auto res = client->SendRequest(driver_msg);
if (res != nullptr) {
AINFO << "client: response: " << res->ShortDebugString();
} else {
AINFO << "client: service may not ready.";
}
sleep(1);
}
apollo::cyber::WaitForShutdown();
return 0;
}
Bazel build file(构建文件)
cc_binary(
name = "service",
srcs = [ "service.cc", ],
deps = [
"//cyber",
"//cyber/examples/proto:examples_cc_proto",
],
)
编译和运行
-
建立服务/客户:
bazel build cyber/examples/…
-
运行:
./ bazel-bin / cyber / examples / service
-
检查结果:你可以在
apollo / data / log / service.INFO
中看到以下内容
I1124 16:36:44.568845 14965 service.cc:30] [service] server: i am driver server
I1124 16:36:44.569031 14949 service.cc:43] [service] client: response: msg_id: 1 timestamp: 0
I1124 16:36:45.569514 14966 service.cc:30] [service] server: i am driver server
I1124 16:36:45.569932 14949 service.cc:43] [service] client: response: msg_id: 2 timestamp: 0
I1124 16:36:46.570627 14967 service.cc:30] [service] server: i am driver server
I1124 16:36:46.571024 14949 service.cc:43] [service] client: response: msg_id: 3 timestamp: 0
I1124 16:36:47.571566 14968 service.cc:30] [service] server: i am driver server
I1124 16:36:47.571962 14949 service.cc:43] [service] client: response: msg_id: 4 timestamp: 0
I1124 16:36:48.572634 14969 service.cc:30] [service] server: i am driver server
I1124 16:36:48.573030 14949 service.cc:43] [service] client: response: msg_id: 5 timestamp: 0
注意事项
注册服务时,请注意不允许重复的服务名称
注册服务器和客户端时应用的节点名称也不应重复
3、Param parameter service
Parameter Service 参数服务简介
参数服务被用于节点之间共享的数据,并提供了诸如基本操作set
,get
和list
。参数服务基于Service
实现,并包含服务(service)和客户端(client)。
Parameter Object 参数对象
Supported Data types 支持的数据类型
通过 cyber 传递的所有参数都是apollo::cyber::Parameter
对象,下表列出了5种受支持的参数类型。
参数类型| C ++数据类型| protobuf数据类型:———— :————- | :--------–
Parameter type | C++ data type | protobuf data type :————- | :————- | :————–
apollo::cyber::proto::ParamType::INT | int64_t | int64 apollo::cyber::proto::ParamType::DOUBLE |
double | double apollo::cyber::proto::ParamType::BOOL | bool |bool
apollo::cyber::proto::ParamType::STRING | std::string | string
apollo::cyber::proto::ParamType::PROTOBUF | std::string | string
apollo::cyber::proto::ParamType::NOT_SET | - | -
除了上述5种类型外,Parameter
还支持使用protobuf
对象作为传入参数的接口。执行序列化后处理该对象,并将其转换为STRING
类型以进行传输。
创建参数对象
支持的构造函数:
Parameter(); // Name is empty, type is NOT_SET
explicit Parameter(const Parameter& parameter);
explicit Parameter(const std::string& name); // type为NOT_SET
Parameter(const std::string& name, const bool bool_value);
Parameter(const std::string& name, const int int_value);
Parameter(const std::string& name, const int64_t int_value);
Parameter(const std::string& name, const float double_value);
Parameter(const std::string& name, const double double_value);
Parameter(const std::string& name, const std::string& string_value);
Parameter(const std::string& name, const char* string_value);
Parameter(const std::string& name, const std::string& msg_str,
const std::string& full_name, const std::string& proto_desc);
Parameter(const std::string& name, const google::protobuf::Message& msg);
使用Parameter
对象的示例代码:
Parameter a("int", 10);
Parameter b("bool", true);
Parameter c("double", 0.1);
Parameter d("string", "cyber");
Parameter e("string", std::string("cyber"));
// proto message Chatter
Chatter chatter;
Parameter f("chatter", chatter);
std::string msg_str("");
chatter.SerializeToString(&msg_str);
std::string msg_desc("");
ProtobufFactory::GetDescriptorString(chatter, &msg_desc);
Parameter g("chatter", msg_str, Chatter::descriptor()->full_name(), msg_desc);
接口和数据读取
接口列表:
inline ParamType type() const;
inline std::string TypeName() const;
inline std::string Descriptor() const;
inline const std::string Name() const;
inline bool AsBool() const;
inline int64_t AsInt64() const;
inline double AsDouble() const;
inline const std::string AsString() const;
std::string DebugString() const;
template <typename Type>
typename std::enable_if<std::is_base_of<google::protobuf::Message, Type>::value, Type>::type
value() const;
template <typename Type>
typename std::enable_if<std::is_integral<Type>::value && !std::is_same<Type, bool>::value, Type>::type
value() const;
template <typename Type>
typename std::enable_if<std::is_floating_point<Type>::value, Type>::type
value() const;
template <typename Type>
typename std::enable_if<std::is_convertible<Type, std::string>::value, const std::string&>::type
value() const;
template <typename Type>
typename std::enable_if<std::is_same<Type, bool>::value, bool>::type
value() const;
有关如何使用这些接口的示例:
Parameter a("int", 10);
a.Name(); // return int
a.Type(); // return apollo::cyber::proto::ParamType::INT
a.TypeName(); // return string: INT
a.DebugString(); // return string: {name: "int", type: "INT", value: 10}
int x = a.AsInt64(); // x = 10
x = a.value<int64_t>(); // x = 10
x = a.AsString(); // Undefined behavior, error log prompt
f.TypeName(); // return string: chatter
auto chatter = f.value<Chatter>();
参数服务
如果一个节点要向其他节点提供参数服务,则需要创建一个ParameterService
。
/**
* @brief Construct a new ParameterService object
*
* @param node shared_ptr of the node handler
*/
explicit ParameterService(const std::shared_ptr<Node>& node);
由于所有参数都存储在参数服务对象中,因此可以直接在ParameterService
中操纵参数,而无需发送服务请求。
设置参数:
/**
* @brief Set the Parameter object
*
* @param parameter parameter to be set
*/
void SetParameter(const Parameter& parameter);
获取参数:
/**
* @brief Get the Parameter object
*
* @param param_name
* @param parameter the pointer to store
* @return true
* @return false call service fail or timeout
*/
bool GetParameter(const std::string& param_name, Parameter* parameter);
获取参数列表:
/**
* @brief Get all the Parameter objects
*
* @param parameters pointer of vector to store all the parameters
* @return true
* @return false call service fail or timeout
*/
bool ListParameters(std::vector<Parameter>* parameters);
参数客户端
如果一个节点要使用其他节点的参数服务,则需要创建一个ParameterClient
。
/**
* @brief Construct a new ParameterClient object
*
* @param node shared_ptr of the node handler
* @param service_node_name node name which provide a param services
*/
ParameterClient(const std::shared_ptr<Node>& node, const std::string& service_node_name);
您还可以执行SetParameter
,GetParameter
并ListParameters
在“ 参数服务”下进行了提及。
Demo - example(演示-示例)
#include "cyber/cyber.h"
#include "cyber/parameter/parameter_client.h"
#include "cyber/parameter/parameter_server.h"
using apollo::cyber::Parameter;
using apollo::cyber::ParameterServer;
using apollo::cyber::ParameterClient;
int main(int argc, char** argv) {
apollo::cyber::Init(*argv);
std::shared_ptr<apollo::cyber::Node> node =
apollo::cyber::CreateNode("parameter");
auto param_server = std::make_shared<ParameterServer>(node);
auto param_client = std::make_shared<ParameterClient>(node, "parameter");
param_server->SetParameter(Parameter("int", 1));
Parameter parameter;
param_server->GetParameter("int", ¶meter);
AINFO << "int: " << parameter.AsInt64();
param_client->SetParameter(Parameter("string", "test"));
param_client->GetParameter("string", ¶meter);
AINFO << "string: " << parameter.AsString();
param_client->GetParameter("int", ¶meter);
AINFO << "int: " << parameter.AsInt64();
return 0;
}
编译和运行
-
Build(构建):
bazel build cyber/examples/…
-
Run(运行):
./bazel-bin/cyber/examples/paramserver
4、Log API(日志)
日志库
cyber 日志库建立在glog
之上。需要包括以下头文件:
#include "cyber/common/log.h"
#include "cyber/init.h"
日志配置
默认全局配置路径:cyber / setup.bash
以下配置可以由devloper修改:
export GLOG_log_dir=/apollo/data/log
export GLOG_alsologtostderr=0
export GLOG_colorlogtostderr=1
export GLOG_minloglevel=0
登录初始化
在代码条目处调用Init方法以初始化日志:
apollo::cyber::cyber::Init(argv[0]) is initialized.
If no macro definition is made in the previous component, the corresponding log is printed to the binary log.
日志输出宏
日志库封装在日志打印宏中。相关的日志宏的用法如下:
ADEBUG << "hello cyber.";
AINFO << "hello cyber.";
AWARN << "hello cyber.";
AERROR << "hello cyber.";
AFATAL << "hello cyber.";
日志格式
格式为
<MODULE_NAME>.log.<LOG_LEVEL>.<datetime>.<process_id>
关于日志文件
当前,与默认glog
唯一不同的输出行为是模块的不同日志级别将被写入同一日志文件。
5、Building a module based on Component(基于组件构建模块)
关键概念
1.组件
该组件是Cyber RT提供的用于构建应用程序模块的基类。每个特定应用模块可以继承组件类和定义自己Init和Proc功能,因此它可以被加载到 Cyber 框架。
2.二进制VS组件
有两种选择可将Cyber RT框架用于应用程序:
-
基于二进制:将应用程序单独编译成二进制文件,通过创建自己的Reader和Writer与其他 cyber 模块进行通信。
-
基于组件:将应用程序编译到共享库中。通过继承Component类并编写相应的dag描述文件,Cyber
RT框架将动态加载和运行应用程序。
基本组件接口
-
组件的Init()功能类似于执行算法初始化的主要功能。
-
组件的Proc()功能类似于阅读器的回调函数,该函数在消息到达时由框架调用。
使用组件的优点
-
可以通过启动文件将组件加载到不同的进程中,并且部署非常灵活。
-
组件可以通过修改dag文件来更改接收到的通道名称,而无需重新编译。
-
组件支持接收多种类型的数据。
-
组件支持提供多种融合策略。
3.Dag 文件格式
dag文件示例:
# Define all coms in DAG streaming.
module_config {
module_library : "lib/libperception_component.so"
components {
class_name : "PerceptionComponent"
config {
name : "perception"
readers {
channel: "perception/channel_name"
}
}
}
timer_components {
class_name : "DriverComponent"
config {
name : "driver"
interval : 100
}
}
}
-
module_library:如果要加载.so库,则根目录是cyber的工作目录(与的相同目录
setup.bash
) -
components&timer_component:选择需要加载的基本组件类类型。
-
class_name:要加载的组件类的名称
-
name:加载的class_name作为加载示例的标识符
-
readers:当前组件接收的数据,支持1-3个数据通道。
演示-例子
Common_component_example(cyber/examples/common_component_example/*)
标头定义(common_component_example.h)
#include <memory>
#include "cyber/class_loader/class_loader.h"
#include "cyber/component/component.h"
#include "cyber/examples/proto/examples.pb.h"
using apollo::cyber::examples::proto::Driver;
using apollo::cyber::Component;
using apollo::cyber::ComponentBase;
class Commontestcomponent : public Component<Driver, Driver> {
public:
bool Init() override;
bool Proc(const std::shared_ptr<Driver>& msg0,
const std::shared_ptr<Driver>& msg1) override;
};
CYBER_REGISTER_COMPONENT(Commontestcomponent)
Cpp文件实现(common_component_example.cc)
#include "cyber/examples/common_component_smaple/common_component_example.h"
#include "cyber/class_loader/class_loader.h"
#include "cyber/component/component.h"
bool Commontestcomponent::Init() {
AINFO << "Commontest component init";
return true;
}
bool Commontestcomponent::Proc(const std::shared_ptr<Driver>& msg0,
const std::shared_ptr<Driver>& msg1) {
AINFO << "Start commontest component Proc [" << msg0->msg_id() << "] ["
<< msg1->msg_id() << "]";
return true;
}
Timer_component_example(网络/示例/ timer_component_example / *)
标头定义(timer_component_example.h)
Timer_component_example(cyber/examples/timer_component_example/*)
标头定义(timer_component_example.h)
#include <memory>
#include "cyber/class_loader/class_loader.h"
#include "cyber/component/component.h"
#include "cyber/component/timer_component.h"
#include "cyber/examples/proto/examples.pb.h"
using apollo::cyber::examples::proto::Driver;
using apollo::cyber::Component;
using apollo::cyber::ComponentBase;
using apollo::cyber::TimerComponent;
using apollo::cyber::Writer;
class TimertestComponent : public TimerComponent {
public:
bool Init() override;
bool Proc() override;
private:
std::shared_ptr<Writer<Driver>> driver_writer_ = nullptr;
};
CYBER_REGISTER_COMPONENT(TimertestComponent)
Cpp文件实现(timer_component_example.cc)
#include "cyber/examples/timer_component_example/timer_component_example.h"
#include "cyber/class_loader/class_loader.h"
#include "cyber/component/component.h"
#include "cyber/examples/proto/examples.pb.h"
bool TimertestComponent::Init() {
driver_writer_ = node_->CreateWriter<Driver>("/carstatus/channel");
return true;
}
bool TimertestComponent::Proc() {
static int i = 0;
auto out_msg = std::make_shared<Driver>();
out_msg->set_msg_id(i++);
driver_writer_->Write(out_msg);
AINFO << "timertestcomponent: Write drivermsg->"
<< out_msg->ShortDebugString();
return true;
}
编译和运行
使用timertestcomponent
作为示例:
Build(构建): bazel build cyber/examples/timer_component_smaple/…
Run(运行): mainboard -d cyber/examples/timer_component_smaple/timer.dag
注意事项
需要注册组件才能通过SharedLibrary
加载类。注册界面如下所示:
CYBER_REGISTER_COMPONENT(DriverComponent)
如果在注册时使用名称空间,则还需要在dag文件中定义名称空间时添加名称空间。
- Component和TimerComponent的配置文件不同,请注意不要混淆两者。
6、Launch(启动)
cyber_launch是Cyber RT框架的启动器。它根据启动文件启动多个主板,并根据dag文件将不同的组件加载到不同的主板中。cyber_launch支持两种方案,可在子进程中动态加载组件或启动二进制程序。
启动文件格式
<cyber>
<module>
<name>driver</name>
<dag_conf>driver.dag</dag_conf>
<process_name></process_name>
<exception_handler>exit</exception_handler>
</module>
<module>
<name>perception</name>
<dag_conf>perception.dag</dag_conf>
<process_name></process_name>
<exception_handler>respawn</exception_handler>
</module>
<module>
<name>planning</name>
<dag_conf>planning.dag</dag_conf>
<process_name></process_name>
</module>
</cyber>
模块:每个加载的组件或二进制文件都是一个模块
-
name是加载的模块名称
-
dag_conf是组件的相应dag文件的名称
-
process_name是主板进程一旦启动的名称,process_name的相同组件将被加载并在同一进程中运行。
-
exception_handler是流程中发生异常时的处理程序方法。该值可以是下面列出的退出或重新生成。
-
退出,这意味着当当前进程异常退出时,整个进程需要停止运行。
-
重生,异常退出后需要重新启动当前进程。开始这个过程。如果没有空的东西,那就意味着没有治疗。可由用户根据过程的具体条件进行控制
-
计时器