使用CyberRT写第一个代码

0. 简介

计算框架是自动驾驶系统中的重中之重,也是整个系统得以高效稳定运行的基础。为了实时地完成感知、决策和执行,系统需要一系列的模块相互紧密配合,高效地执行任务流。由于各种原因,这些模块可能位于不同进程,也可能位于不同机器。这就要求计算框架中具有灵活的、高性能的通信机制。Apollo在3.5版本中推出了Cyber RT替代了原先的ROS。

和ROS & ROS2中类似,Cyber RT中支持两种数据交换模式:一种是Publish-Subscriber模式,常用于数据流处理中节点间通信。即发布者(Publisher)在channel(ROS中对应地称为topic)上发布消息,订阅该channel的订阅者(Subscriber)便会收到消息数据;另一种就是常见的Service-Client模式,常用于客户端与服务端的请求与响应。Node是整个数据拓扑网络中的基本单元。一个Node中可以创建多个读者/写者,服务端/客户端。读者和写者分别对应Reader和Writer,用于Publish-Subscribe模式。服务端和客户端分别对应Service和Client,用于Service-Client模式。

1. CyberRT BUILD文件

Apollo 是优秀的自动驾驶开发框架,出自百度之手,目前的Apollo是基于Cyber RT通信键实现的。Apollo的具体安装可以看Apollo 6.0 安装完全指南。Apollo (或者说CyberRT)使用 Bazel 进行代码构建,Bazel 是由 Google 开源的一款高效的软件构建工具。使用 Bazel 时,我们需要为每个参与构建的目录创建一个 BUILD 文件来定义一些构建规则,BUILD 文件使用类似 Python 的语法。其优点如下:

  • Bazel 仅重建必要的内容。借助高级的本地和分布式缓存,优化的依赖关系分析和并行执行,可以获得快速而增量的构建。
  • 构建和测试 Java、C++、Android、iOS、和其他各种语言平台。Bazel 可以在 Windows、macOS 和 Linux 上运行。
  • Bazel 帮助你扩展你的组织、代码库和持续集成系统。它可以处理任何规模的代码库。

1.1 Bazel项目结构

在构建项目之前,您需要设置其工作区。工作区是一个保存项目源文件和 Bazel 构建输出的目录。它还包含 Bazel 识别为特殊的文件:

  • 该WORKSPACE文件将目录及其内容标识为 Bazel 工作区并位于项目目录结构的根部,
  • 一个或多个BUILD文件,告诉 Bazel 如何构建项目的不同部分。
    其目录结构大致如下:
project
|-- pkg
|   |-- BUILD
|   |-- src.cc
|-- WORKSPACE

1.2 了解BUILD文件

一个BUILD文件包含多种不同类型的 Bazel 指令。最重要的类型是构建规则,它告诉 Bazel 如何构建所需的输出,例如可执行二进制文件或库。文件中构建规则的每个实例BUILD称为目标,并指向一组特定的源文件和依赖项。一个目标也可以指向其他目标。例如:

cc_binary(
    name = "hello_world",
    srcs = ["hello_world.cc"],
)

在示例中,hello-world目标实例化 Bazel 的内置 cc_binary规则。该规则告诉 Bazel 从源文件构建一个独立的可执行二进制文件。

BUILD文件中常见的两种规则:

  • cc_binary:表示要构建对应文件变成二进制文件。
    • name:表示构建完成后的文件名字。
    • srcs:表示要构建的源文件。
    • deps:表示构建该文件所依赖相关的库。
  • cc_library:表示要构建对应文件变成相关依赖库。
    • hdrs:表示的是源文件对应的头文件路径。
    • package(default_visibility = [“//visibility:public”])这段代码则表示该文件是公开的,能被所有对象找到并依赖。
load("//tools:cpplint.bzl", "cpplint")

package(default_visibility = ["//visibility:public"])

cc_binary(
    name = "libcommon_component_example.so",
    deps = [":common_component_example_lib"],
    linkopts = ["-shared"],
    linkstatic = False,
)

cc_library(
    name = "common_component_example_lib",
    srcs = [
        "common_component_example.cc",
    ],
    hdrs = [
        "common_component_example.h",
    ],
    deps = [
        "//cyber",
        "//cyber/examples/proto:examples_cc_proto",
    ],
)

cpplint()

2. 算法组件创建(内容引用文档)

Apollo Cyber 运行时框架 (Apollo Cyber RT Framework) 是基于组件概念来构建的。每个组件都是 Cyber 框架的一个构建块,它包括一个特定的算法模块, 此算法模块处理一组输入数椐并产生一组输出数椐。要创建并启动一个算法组件,需要通过以下 4 个步骤:

2.1 初如化组件的文件结构

例如组件的根目录为/apollo/cyber/examples/common_component_example/需要创建以下文件:

Header file: common_component_example.h
Source file: common_component_example.cc
Build file: BUILD
DAG dependency file: common.dag
Launch file: common.launch

2.2 实现组件类

2.2.1 实现组件头文件

如何实现common_component_example.h:

继承 Component 类
定义自己的 Init 和 Proc 函数。Proc 需要指定输入数椐类型。
使用CYBER_REGISTER_COMPONENT宏定义把组件类注册成全局可用。

#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 CommonComponentSample : 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(CommonComponentSample)
2.2.2 实现组件源文件

对于源文件 common_component_example.ccInit 和 Proc 这两个函数需要实现。

#include "cyber/examples/common_component_example/common_component_example.h"
#include "cyber/class_loader/class_loader.h"
#include "cyber/component/component.h"

bool CommonComponentSample::Init() {
  AINFO << "Commontest component init";
  return true;
}

bool CommonComponentSample::Proc(const std::shared_ptr<Driver>& msg0,
                               const std::shared_ptr<Driver>& msg1) {
  AINFO << "Start common component Proc [" << msg0->msg_id() << "] ["
        << msg1->msg_id() << "]";
  return true;
}

2.3 设置配置文件

2.3.1 配置 DAG 依赖文件

在 DAG 依赖配置文件 (例如 common.dag) 中配置下面的项:

  • Channel names: 输入输出数椐的 Channel 名字
  • Library path: 此组件最终编译出的库的名字
  • Class name: 此组件的入口类的名字
# Define all components in DAG streaming.
component_config {
    component_library : "/apollo/bazel-bin/cyber/examples/common_component_example/libcommon_component_example.so"
    components {
        class_name : "CommonComponentSample"
        config {
            name : "common"
            readers {
                channel: "/apollo/prediction"
            }
            readers {
                channel: "/apollo/test"
            }
        }
    }
}
2.3.2 配置 launch 启动文件

在 launch 启动文件中 (common.launch), 配置下面的项:

  • 组件的名字
  • 上一步创建的 dag 配置的名字。
  • 组件运行时所在的进程目录。
<cyber>
    <component>
        <name>common</name>
        <dag_conf>/apollo/cyber/examples/common_component_example/common.dag</dag_conf>
        <process_name>common</process_name>
    </component>
</cyber>

2.4 启动组件

通过下面的命令来编译组件:

bash /apollo/apollo.sh build

然后配置环境:

cd /apollo/cyber
source setup.bash

有两种方法来启动组件:

  • 使用 launch 文件来启动 (推荐这种方式)
cyber_launch start /apollo/cyber/examples/common_component_example/common.launch
  • 使用 dag 文件来启动
mainboard -d /apollo/cyber/examples/common_component_example/common.dag

3. 订阅发布与服务客户

3.1 节点 Node

Node 是整个数据拓扑网络中的基本单元。Node 对象会根据需要创建和管理 Writer,Reader,Service 和 Client 对象。Reader 和 Writer,用于发布—订阅模式。Service 和 Client,用于服务—客户模式。

在cyberRT中创建方法如下:

std::unique_ptr<Node> apollo::cyber::CreateNode(const std::string& node_name, const std::string& name_space = "");

参数:

  • node_name:node 的名称,必须保证全局是唯一的,不能重名
  • name_space:node 所在的命名空间。主要用来防止node_name发生冲突。
  • name_space默认为空,加入命名空间后,node的name 变成 /namespace/node_name
  • return value:node的独占智能指针

在cyber::Init()还未执行前,系统还处于未初始化状态,没法创建node,会直接返回nullptr

3.2 发布者

writer 是CyberRT中,用来发布消息最基本的方法。每个writer对应一个特定消息类型的channel。writer可以通过Node类中的 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:字面意思,就是该writer对应的channel_name
  • MessageT:写入的消息类型
  • return value:std::shared_ptr<writer></writer

3.3 订阅者

reader是接收消息最基础的方法。reader必须绑定一个回调函数。当channel中的消息到达后,回调会被调用。
reader 可以通过Node类中 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>>;

参数:

  • channel_name:字面意思,就是该reader对应的channel_name
  • MessageT:读取的消息类型
  • reader_func:回调函数
  • return value:std::shared_ptr<reader></reader

3.4 服务端&客户端

除 Reader/Writer 外,Cyber RT 还提供了用于模块通信的 Service/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;
}

3.5 参数服务

参数服务被用于节点之间共享的数据,并提供了诸如基本操作setgetlist。参数服务基于Service实现,并包含服务(service)和客户端(client)。

通过 cyber 传递的所有参数都是apollo::cyber::Parameter对象,下表列出了5种受支持的参数类型。

参数类型C++数据类型protobuf数据类型
apollo::cyber::proto::ParamType::INTint64_tint64
apollo::cyber::proto::ParamType::DOUBLEdoubledouble
apollo::cyber::proto::ParamType::BOOLboolbool
apollo::cyber::proto::ParamType::STRINGstd::stringstring
apollo::cyber::proto::ParamType::PROTOBUFstd::stringstring
apollo::cyber::proto::ParamType::NOT_SET--

除了上述5种类型外,Parameter还支持使用protobuf对象作为传入参数的接口。执行序列化后处理该对象,并将其转换为STRING类型以进行传输。具体示例:

#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", &parameter);
  AINFO << "int: " << parameter.AsInt64();
  param_client->SetParameter(Parameter("string", "test"));
  param_client->GetParameter("string", &parameter);
  AINFO << "string: " << parameter.AsString();
  param_client->GetParameter("int", &parameter);
  AINFO << "int: " << parameter.AsInt64();
  return 0;
}

3.6 Log API(日志)

3.6.1 日志库

cyber 日志库建立在glog之上。需要包括以下头文件:

#include "cyber/common/log.h"
#include "cyber/init.h"
3.6.2 日志配置

默认全局配置路径:cyber / setup.bash

以下配置可以由devloper修改:

export GLOG_log_dir=/apollo/data/log
export GLOG_alsologtostderr=0
export GLOG_colorlogtostderr=1
export GLOG_minloglevel=0

 点击使用CyberRT写第一个代码 - 古月居可查看全文

  • 28
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值