無人駕駛筆記系列(三):深入理解Apollo Cyber RT

無人駕駛筆記系列(三):深入理解 Apollo Cyber RT之訂閱、發佈

apollo 在3.5版本之前採用ros的框架,因此,CyberRT 中同樣有大量的ROS的影子。Writer、Reader就是最典型的例子。下面我將會採用從node(節點)、Writer(發佈者)、Reader(訂閱者)三個方面,和ROS逐一比較分析說明兩者間的異同。

節點

在CyberRT框架下,node是最基礎的單元。在創建writer,reader等均需要連接到一個node上。
在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

發佈者

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 valuestd::shared_ptr<Writer<MessageT>>

訂閱者

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 valuestd::shared_ptr<Reader<MessageT>>

舉個栗子

proto(cyber/examples/proto/examples.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;
};

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[]) {
  // cyber初始化
  apollo::cyber::Init(argv[0]);
  // 創建 node
  std::shared_ptr<apollo::cyber::Node> talker_node(apollo::cyber::CreateNode("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[]) {
  // 初始化cyber
  apollo::cyber::Init(argv[0]);  
  // 創建node
  auto listener_node = apollo::cyber::CreateNode("listener");
  // 創建 listener
  auto listener =listener_node->CreateReader<apollo::cyber::proto::Chatter>("channel/chatter", MessageCallback);
  //阻塞
  apollo::cyber::WaitForShutdown();
  return 0;
}

Bazel BUILD file(cyber/samples/BUILD)

# 編譯生成bin文件
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",
    ],
)

編譯會生成二進制的可執行文件

Bazel BUILD file(cyber/samples/proto/BUILD)

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

cc_proto_library(
    name = "examples_cc_proto",
    deps = [
        ":examples_proto",
    ],
)

proto_library(
    name = "examples_proto",
    srcs = [
        "examples.proto",
    ],
)

消息類型也必須編譯生成c++可用的庫文件,上式中均調用了該庫

運行一下

  • 編譯: bazel build cyber/examples/…
  • 運行 在不同的兩個終端運行talker/listener:
    • ./bazel-bin/cyber/examples/talker
    • ./bazel-bin/cyber/examples/listener

總結

未完待續

以上分析只是從使用的角度來分析CyberRT架構,後續在時間允許的情況下,計劃用10篇左右的篇幅詳解一下通信機制,apollo主要採用了fastrtps,這是個非常複雜的話題,先立個flag吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章