無人駕駛筆記系列(三):深入理解 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 value:
std::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 value:
std::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吧。