自动驾驶系列:从Apollo-1.0探索Apollo系统架构和代码设计
- 《目录》
- 零. 前言
- 壹. Apollo-1.0 整体结构组成
- 贰. 从"门"开始
- 叁. Common
- 肆. Apollo APP
- 伍. apollo模块注册和运行
- 陆. 总结
零. 前言
近期在阅读Apollo
的源码,感到Apollo
的代码设计和架构搭建都十分的优雅,因此打算系统的对Apollo
进行梳理,并在此进行记录,也希望能给有需要的人带来一点点的启发。本人目前还是小白,错误之处难免,还望不吝指出~
Apollo 3.5
开始彻底摒弃ROS
,改用自研的CyberRT
作为底层通讯的中间件。由于目前使用ROS仍然是主流,我们的项目中也在使用ROS作为底层通讯的中间件,且Apollo 1.0
模块较少,具有更加清晰的结构,因此从Apollo 1.0
入手,探索Apollo
的设计理念,系统架构搭建和代码设计是最方便的。
本文将从Apollo 1.0
入手,首先宏观把握Apollo 1.0
的整体结构,继而分析其各个模块的功能组成和架构设计,最后对Apollo
的运行机制进行总结。
Apollo3.0
使用与Apollo1.0
相同的架构模式,因此在本篇梳理完Apollo的架构后,下篇将重点和详细的梳理Apollo3.0
。
尤其感谢slamcode同学,本文的许多内容都是基于其工作所进行的重新整理.
壹. Apollo-1.0 整体结构组成
Apollo-1.0
主要由以下六大部分组成:
从上图中我们可以看出,我们最感兴趣的部分都在modules中。modules中的结构目录如下:
因此,后续我们的分析都集中在modules
部分.
贰. 从"门"开始
任何程序都有个"入口", Apollo的入口(最常用入口)就是${APOLLO_HOME}/scripts/hmi.sh
. hmi.sh
主要做了两个工作
1. source一堆环境变量, 为程序的运行做准备
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "${DIR}/.."
source "${DIR}/apollo_base.sh"
source "${DIR}/apollo_init.sh"
2. 定义start()和stop()函数start()
函数启动了三个进程:
1) roscore 管理所有ROS结点的通信
2) ros_node_service 使用gRPC框架的server,连接HMI和系统,监控和管理模块/车辆的运行状态
3) hmi.py 使用flask实现的人机交互界面
与之对应,stop()
按hmi->ros_node_service->roscore
实现各个进程的关闭.
后续,将从controlcontrol
control和Localization入手,其他perception
等模块由于在1.0版本中只有框架没有实现,因此只简单的将其框架梳理一下.
叁. Common
Common
是Apollo中很重要的一个文件夹,里面承载了构建Apollo架构的许多部件。
几个额外需要关注的部分为:
macro.h
factory.h
adapter.h
adapter_manager.h
apollo_app.h
1. macro.h
macro.h是Apollo的宏定义文件夹,定义了以下三个宏定义
(1) DISALLOW_COPY_AND_ASSIGN(classname)
代码示例:
#define DISALLOW_COPY_AND_ASSIGN(classname) \
private: \
classname(const classname &); \ // 禁止拷贝构造函数
classname &operator=(const classname &); // 禁止赋值构造函数
如上所述,通过将以上两个操作符设置为private
,禁止class
的拷贝构造函数和赋值构造函数.
注:
1. 良好的代码设计中,程序员应该主动管理这两个操作符,以避免程序中类的意外复制.
2. 使用宏定义进行该类操作,只需要在使用时的末尾加上该宏定义即可,非常方便.
(2) DISALLOW_IMPLICIT_CONSTRUCTORS(classname)
通过将classname()
设置为private,来禁止类的无参构造. 同时使用了(1)中的宏定义,禁止类的拷贝构造函数和赋值构造函数.
(3) DECLARE_SINGLETON(classname)
代码示例:
#define DECLARE_SINGLETON(classname) \
public: \
static classname *instance() { \
static classname instance; \
return &instance; \
} \
DISALLOW_IMPLICIT_CONSTRUCTORS(classname) \
单例类宏定义
在定义单例类的同时,禁止掉类的无参构造/拷贝构造/赋值构造.
Apollo中使用了很多单例类,比如消息管理适配器AdapterManager
,时间管理类Clock
等都是static实现的单例类模式,其都使用了DECLARE_SINGLETON
宏方便的进行单例类的定义.
另外,很多程序中出现的xx->instance()
均是相应单例类的实例, 指针instance
出自此处.
2. log.h
同样是log相关的一些宏定义.
Apollo使用google的glog作为日志库,log.h
定义了5个日志级别. 由低到高分别为AINFO -> ADEBUG -> AWARN -> AERROR -> AFATAL
宏定义代码示例:
#include "glog/logging.h"
#define AINFO VLOG(3) << "[INFO] "
#define ADEBUG VLOG(4) << "[DEBUG] "
#define AWARN LOG(WARNING)
#define AERROR LOG(ERROR)
#define AFATAL LOG(FATAL)
使用示例:
AERROR << "imu_msg should NOT be nullptr.";
3. time
apollo使用c++ 11内部的chrono作为时间管理工具,而不是ros::Time
.
两个最基本的时间表示:
using Duration = std::chrono::nanoseconds;
--时间间隔大小,单位纳秒
using Timestamp = std::chrono::time_point<std::chrono::system_clock, Duration>;
--时刻点表示
除了进行了Duration/Timestamp/nanos/micros/millis/seconds/minutes/hours
等的别名表示, time.h
还设置一系列的时间转换函数, 具体可查看代码.
单例模式的时间管理类Clock()
最重要的,time.h
中进行了时间管理单例类Clock()
的定义.Clock是封装c++ 11chrono
后抽象的时钟计时类class, 是线程安全的单例模式(c++ 11的语法可确保), 因此所有与时间相关的模块都直接引入Clock()
即可使用.
Clock()的构造函数为私有,初始时使用系统时间.
Clock中主要的属性/方法
1. bool is_system_clock
用以区分当前是使用系统时间,还是仿真时间,并由相应的get/set
方法进行获取和设置.
2. Now()
, 根据is_system_clock
返回当前的时间.
static Timestamp Now() // 返回当前时间的最新值。
static void UseSystemClock(bool is_system_clock) // 设置是否使用模拟时间。
static bool IsSystemClock() // 返回是否使用系统时间。
static void SetNow(const Duration &duration) // 重置时间
// 只有模拟时间才可调整值,系统时间不可调整
使用方法:
#include "modules/common/time/time.h"
using ::apollo::common::time::Clock;
// 代码中直接使用Clock::Now()获取时间
4. util
util
中包含多个工具:文件操作file/字符串操作string_tokenize
(1). 文件操作相关file.h (主要为protobuf)
都是模板类函数.可分为两类:
Protobuf文件读写
template <typename MessageType>
bool SetProtoToASCIIFile(const MessageType &message, const std::string &file_name) // 将message存储到filename中,格式为ascii
bool GetProtoFromASCIIFile(const std::string &file_name, MessageType *message) // 从ascii格式的文件中恢复message
bool GetProtoFromBinaryFile(const std::string &file_name, MessageType *message) // 将message存储到filename中,格式为二进制
bool GetProtoFromFile(const std::string &file_name, MessageType *message) // 从文件中恢复message, 兼容ascii格式和二进制格式, 最常用
文件夹操作相关
bool DirectoryExists(const std::string &directory_path); // 判断指定文件夹是否存在
bool EnsureDirectory(const std::string &directory_path); // 确认文件夹, 无则新建
bool RemoveAllFiles(const std::string &directory_path); // 清空目录下的文件
(2). 字符串操作相关string_tokenize.h
定义一个StringTokenizer()
类,带有Split(const std::string &str,const std::string &delims);)
方法进行字符串的分割(等效于Python中的split()函数)
(3). util.h
只有一个EndWith(const std::string &original, const std::string &pattern)
方法,实现Python中字符串endswith()
方法.
(4). factory.h
创建对象的抽象工厂模式。所有需要创建的对象使用ProductCreator函数指针创建。 默认使用unordered_map管理元素。
工厂模式是在Apollo是使用非常广泛的一个设计模式,所有具体算法模块均使用工厂模式进行创建。
具体例子可以参考本文最后RTKLocalization的创建。
template <typename IdentifierType, class AbstractProduct,
class ProductCreator = AbstractProduct *(*)(),
class MapContainer = std::map<IdentifierType, ProductCreator>>
包含Register Unregister CreateObject
三个主要的方法。
5. status
一系列的状态码用于标识各个模块的工作状态,状态码的含义可以在error_code.proto文件查看。Status类
用于标记各个API模块的返回值。 例如:
// Canbus module error codes start from here.
CANBUS_ERROR = 2000;
CAN_CLIENT_ERROR_BASE = 2100;
CAN_CLIENT_ERROR_OPEN_DEVICE_FAILED = 2101;
CAN_CLIENT_ERROR_FRAME_NUM = 2102;
CAN_CLIENT_ERROR_SEND_FAILED = 2103;
CAN_CLIENT_ERROR_RECV_FAILED = 2104;
6. ( )adapter 和adapter_manager
adapter
以及adapter_manager
是apollo-1.0中相当重要的概念,
(1)adapter
用于管理某个具体模块和底层ROS的通信,是来自传感器的底层数据和Apollo各个模块交互的统一接口。
adapter的构造函数:
Adapter(const std::string &adapter_name,
const std::string &topic_name,
size_t message_num,
const std::string &dump_dir = "/tmp")
共有四个变量:
1. adapter_name 指示adapter唯一的name id
2. topic_name 监听的消息
3. message_num 保存消息的队列大小
4. dump_dir 保存路径
adapter中的几个重要方法函数和属性:
1. SetCallBack()
用来设置回调函数,最终实际调用的是ros里的createTimer
设置回调函数的。所加入的可以是一个函数,也可以是一个类方法。在Apollo中,一般情况下设置的是具体算法模块的OnTimer()
类方法。
2. OnReceived()
当接收到新消息时,OnReceived()会调用回调函数对数据进行处理。
3. Observe()
每个adapter
都有自己的Observe()
类方法,实际这是一个“观察者”, 被观察者则是AdapterManager
,AdapterManager
维护一个数组observers_
用来保存所有adapter
的Observe
。当有消息到来时,AdapterManager
会遍历一遍所有的观察者Observe()
。
而Observe()
的作用就一个:将当前消息做一个拷贝,即从date_queue_
拷贝到observe_queue_
,本次需要请求的消息则从observe_queue_
中取,即后期的Getxxx等获取传感器数据的操作都是在这个数组上进行的。
--注1:这是为了数据有效性而进行了,因为所有的通信都通过AdapterManager进行,AdapterManager的消息缓存区会变化的很快,因此某个模块要使用AdapterManager记录的消息时,需要先获得所有消息的一份拷贝,然后再在这份拷贝的数据上读取自己需要的数据。
--注2:是的,当某个模块想要读取数据的时候,实际上所有的模块Adapter都会调用自身的Observe函数实现数据的保存;
data_queue 和 observe_queue_
作用前面已经讲过了,data_queue_
用来保存实时到来的消息,而observe_qeue_
则在有模块需要使用消息时,从data_queue_
做一个拷贝。
(2)adapter_manager则用来管理所有的adapter。
adapter_manager
中使用了一个非常长的宏定义REGISTER_ADAPTER(name)
, 用来以静态的方式对各个adapter进行注册和定义。*这个宏定义非常重要*,重要到我想直接把它贴到这里:
#define REGISTER_ADAPTER(name) \
public: \
static void Enable##name(const std::string &topic_name, \
AdapterConfig::Mode mode, \
int message_history_limit) \
{ \
CHECK(message_history_limit > 0) \
<< "Message history limit must be greater than 0"; \
instance()->InternalEnable##name(topic_name, mode, message_history_limit); \
} \
static name##Adapter *Get##name() \
{ \
return instance()->InternalGet##name(); \
} \
static void Feed##name##ProtoFile(const std::string &proto_file) \
{ \
CHECK(instance()->name##_) \
<< "Initialize adapter before feeding protobuf"; \
Get##name()->FeedProtoFile(proto_file); \
} \
static void Publish##name(const name##Adapter::DataType &data) \
{ \
instance()->InternalPublish##name(data); \
} \
static void Fill##name##Header(const std::string &module_name, \
apollo::common::Header *header) \
{ \
instance()->name##_->FillHeader(module_name, header); \
} \
static void Set##name##Callback(name##Adapter::Callback callback) \
{ \
CHECK(instance()->name##_) \
<< "Initialize adapter before setting callback"; \
instance()->name##_->SetCallback(callback); \
// name##_是个Adapter
} \
template <class T> \
static void Set##name##Callback( \
void (T::*fp)(const name##Adapter::DataType &data), T *obj) \
{ \
Set##name##Callback(std::bind(fp, obj, std::placeholders::_1)); \
} \
\
private: \
// name##_声明一个name##_的新unique_ptr, 后面会使用reset将其实例化,以后使用name##_使用Adapter
std::unique_ptr<name##Adapter> name##_; // 此处定义的智能指针——name##_;,
ros::Publisher name##publisher_;
ros::Subscriber name##subscriber_;
// 此处正式启用相应的Adapter, 包括
void InternalEnable##name(const std::string &topic_name,
AdapterConfig::Mode mode,
int message_history_limit)
{
// 智能指针包含了 reset() 方法,如果不传递参数(或者传递 NULL),则智能指针会释放当前管理的内存。
// 如果传递一个对象,则智能指针会释放当前对象,来管理新传入的对象。
// 注:所有的xxxAdapter都在message_adapter.h中进行了定义
name##_.reset(new name##Adapter(#name, topic_name, message_history_limit));
if (mode != AdapterConfig::PUBLISH_ONLY && node_handle_)
{
name##subscriber_ =
node_handle_->subscribe(topic_name, message_history_limit,
&name##Adapter::OnReceive, name##_.get());
}
if (mode != AdapterConfig::RECEIVE_ONLY && node_handle_)
{
name##publisher_ = node_handle_->advertise<name##Adapter::DataType>(
topic_name, message_history_limit);
}
// TODO::什么语法???
observers_.push_back([this]() { name##_->Observe(); });
}
name##Adapter *InternalGet##name() { return name##_.get(); }
void InternalPublish##name(const name##Adapter::DataType &data)
{
name##publisher_.publish(data);
}
几个很重要的方法宏定义我已经加粗了,多加注意一下。后面也会对其中几个有所讲解。
adapter_manager
本身只定义了一个handler
供所有的 adapter
使用,并定义了一个Observe()
用来调用所有adapter
的observe()
函数。adapter_manager
使用了单例模式,因此每个需要和ROS通信的模块都需要引入adapter_manager
。
例如RTK_Localization中:
using ::apollo::common::Status; // 直接使用Status()
using ::apollo::common::adapter::AdapterManager; // 直接使用AdapterManager的单例
using ::apollo::common::adapter::ImuAdapter; // 使用ImuAdapter
using ::apollo::common::monitor::MonitorMessageItem;
using ::apollo::common::time::Clock; using ::Eigen::Vector3d;
注1:topic_name管理和参数管理
1. AdapterManager
管理了所有的topic_name
和adapter
.
topic_name
用来和ROS进行通信;
adapter
则在注册后由各个实际模块(比如localization等)直接引入,进行消息的收发。
2. AdapterManager
对topic_name
的管理是通过adapter_gflags
来进行的,
使用google:gflag
的DECLEAR
和DEFINE
功能进行topic的注册管理。\
实际上,在所有的实际模块中都有**_gflags.h
文件,但这些文件不管理topic_name
,而是用来管理如发布频率等其他参数的。
注2:gflags的使用
gflags是google发布的用于处理命令行参数的项目。
通常在.h
文件中使用DECLEAER()
进行参数的声明
在.cpp
文件中使用DEFINE()
进行参数的定义
当其他文件需要使用别处文件中定义的参数时,在其中使用DECLEAR()
即可,作用相当于extern xxx
(3) adapter_manager的注册和运行机制
adapter_manager
通过宏定义REGISTER_ADAPTER(name)
注册了一大推adapter,以及声明其对应的三个私有变量:
private:
std::unique_ptr<name##Adapter> name##_; // 此处定义智能指针——name##_;,
ros::Publisher name##publisher_; // 定义ros通信的Sub/Pub
ros::Subscriber name##subscriber_;
但是这些都在启动adapter_manager
之后实际都是空的,当新的模块加入的时候,新的模块会引入adapter_manager
using ::apollo::common::adapter_manager;
并通过调用AdapterManager::Init(FLAGS_rtk_adapter_config_file);
(以rtk为例)通过载入adapter配置文件来使用adapter_manager
的Init()
函数正式启用该adapter
。
adapter_manager
的Init()
函数通过配置文件,完成以下功能:
void InternalEnable##name(const std::string &topic_name,
AdapterConfig::Mode mode,
int message_history_limit)
{
// 智能指针包含了 reset() 方法,如果不传递参数(或者传递 NULL),则智能指针会释放当前管理的内存。
// 如果传递一个对象,则智能指针会释放当前对象,来管理新传入的对象。
// 注:所有的xxxAdapter都在message_adapter.h中进行了定义
// 1. 将前期声明的智能指针std::unique_ptr<name##Adapter> name##_; 置为新的对应的对象
name##_.reset(new name##Adapter(#name, topic_name, message_history_limit));
// 2. 根据配置文件中mode的不同,选择该模块是收/发还是收发一体,启用相应的Sub/Pub
if (mode != AdapterConfig::PUBLISH_ONLY && node_handle_)
{
name##subscriber_ =
node_handle_->subscribe(topic_name, message_history_limit,
&name##Adapter::OnReceive, name##_.get());
}
if (mode != AdapterConfig::RECEIVE_ONLY && node_handle_)
{
name##publisher_ = node_handle_->advertise<name##Adapter::DataType>(
topic_name, message_history_limit);
}
// TODO::这是什么语法?有知道的小伙伴烦请告知一下。
// 3. 在adapter_manager中加入该adapter的Observe()方法,方便后期adapter_manager统一调用Observe()对所有adapter遍历
observers_.push_back([this]() { name##_->Observe(); });
}
如此,对应模块使用自己的Adapter中相应的函数即可进行与底层ROS的通信,且对应的模块只知道自己的name##Adapter
,而不知道底层ROS的存在,这样后期将ROS替换成CyberRT之后就可以保持高层代码不变。
7. vehicle_state
VehicleState类是标识车辆状态信息的class。 主要包含线速度.角速度.加速度.齿轮状态.车辆坐标x,y,z
8. Monitor类
总的来说Monitor类就是收集各个模块的工作log日志并发布到相应的topic用于监控。其中用到了MonitorBuffer,目的是合并相同level的log消息日志并一次性发布——减少冗余信息。
9. math
math文件夹下对很多数学公式进行了封装。
肆 . Apollo APP
- Apollo app全部都是 纯虚函数 ,定了了所有模块的公有结构,所有模块在继承该class后对公有接口进行实现;
- 各个模块每调用一次
APOLLO_MAIN(APP)
,即创建一个module模块进程,正常情况下该进程将持续运行。
apollo app
的结构: 也即所有模块的公有对外接口
class ApolloAPP{
public:
virtual std::string Name() const = 0; // 定义模块名称,也是进程名
virtual int Spin(); // 程序实际运行的部分,初始化模块信息并持续运行直到结束
virtual ~ApolloAPP() = default; // 默认调用子类析构函数
protected:
virtual apollo::common::Status Init() = 0; // 初始化部分,加载参数,加载传感器adapter等
virtual apollo::common::Status Start() = 0; // 程序运行
virtual void Stop() = 0; // 程序结束,在ros::shutdown()执行完毕后的清理工作。
// 向HMI发送状态信息
virtual void ReportModuleStatus(const apollo::hmi::ModuleStatus::Status status);
}
最后还有一个宏定义,每个模块通过#define APOLLO_MAIN(APP)
开始程序进程的运行
每个模块是一个进程。
#define APOLLO_MAIN(APP) \
int main(int argc, char **argv) { \
google::InitGoogleLogging(argv[0]); \
google::ParseCommandLineFlags(&argc, &argv, true); \
signal(SIGINT, apollo::common::apollo_app_sigint_handler); \
APP apollo_app_; \
ros::init(argc, argv, apollo_app_.Name()); \
apollo_app_.Spin(); \
return 0; \
}
伍. apollo模块注册和运行
Apollo_app
是所有模块的父类,Localization/Prediction/Perception等等都要继承Apollo_app,并复写Apollo_app的Init()/Start()/Stop()
等实现自己的功能,实际运行时会调用Apollo_app
的Spin()
开始改模块的正式运行 。 --这部分是在各个大模块的main.cpp
中进行的
(apollo_app的)Spin()
内部主要做了以下的工作:
1) 调用Init()注册该工厂,每个工厂都是一类算法的集合,比如定位软法,感知算法等等;
2) 调用Start()正式开始程序的执行
3) 调用spiner(),实现程序的自旋
4) ros::waitForShutdown()之后调用Stop()做清理工作
以Localization_APP为例(缩进代表运行的层级):
1. Localizaiton 继承了Apollo_app并实现了其所有接口函数Init()/Start()/Stop()等;
2. Localization 调用自身的Spin()函数实现程序的运行:
Spin()函数运行:
①调用自身的Localizaion::Init():
Init()调用RTKLocalization_app的RegisterLocalizationMethods()注册定位模块的工厂,并注册RTKLocalization
检查Localization_config_file的合理性
②调用自身的Localication::Start():
从Localization的localization_factory_工厂中实例化一个Localization(或者为RTKLocalization,或者为CameraLocalization,所有定位方法均在该工厂中注册)
调用实例化后的RTKLocalization的Start()函数,实现定位**算法**的运行
1> 调用AdapterManager::Init(config), 在AdapterManager中正式注册并启用相应的Adapter,实现消息收发
2> 调用AdapterManager::CreateTimer(ros::Duration(duration),RTKLocalization::OnTimer, this)实现回调函数的注册,设置频率,创建定时执行器
OnTimer()作为回调函数基于事件触发,实现消息的处理,并发布最终的定位结果
获取GPS
获取IMU
计算定位数据
发布定位数据
检查时间delay合理性(RunWatchDog())
③调用自身的spiner,实现程序的自循环,定位模块运行
ros::waitForShutdown()
④调用自身的Stop()实现进程的清理所有算法单元都有自己的父类,比如LocalizationBase,该父类声明了所有的对外接口,子类继承父类并实现所有对外接口作为对外通信的唯一接口,其他算法定义均为Private
(1)具体模块的运行机制
所有的模块(Localization/Perception...)都有自己的xxxBase,用以被具体算法模块继承。
使用父类xxxBase也是为了便于在模块App中的工厂中进行注册,使用统一的父类和接口,实现算法的可替换性。
还是以Localization_app为例: Localization_Base.h
public:
Start();
Stop();
即,所有的定位算法module实际都只有两个开关,一个Start和一个Stop,其他所有的具体实现都在private空间中进行实现。
看一下具体定位算法的Start()
////////////// Init等运行,已获得具体的定位算法module
Localization_app调用具体定位module(此处为RTKLocalization)的Start()
1> 调用AdapterManager::Init(config), 在AdapterManager中正式注册并启用相应的Adapter,实现消息收发
2> 调用AdapterManager::CreateTimer(ros::Duration(duration),RTKLocalization::OnTimer, this)实现回调函数的注册,设置频率,创建定时执行器
(1) OnTimer()作为回调函数基于事件触发,实现消息的处理,并发布最终的定位结果
获取GPS
获取IMU
计算定位数据
发布定位数据
检查时间delay合理性(RunWatchDog())
(2)AdapterManager::CreateTimer()
这里需要再看以下AdapterManager的CreateTimer
函数,这是个很重要的函数: 该函数定义在AdapterManager中,是一个static静态函数
static ros::Timer CreateTimer(ros::Duration period, // 时间频率
void (T::*callback)(const ros::TimerEvent &), // 回调函数
T *obj, bool oneshot = false, // 实例的类
bool autostart = true)
{
CHECK(instance()->node_handle_)
<< "ROS node is only available in ROS mode, "
"check your adapter config file!";
return instance()->node_handle_->createTimer(period, callback, obj, oneshot, autostart);
}
可以看到其最终还是调用了ROS的nodehandle.createTimer()
来执行的,通过定义一个回调函数(类方法等都可以)和一个固定的频率,实现该方法的定时触发。关于ROS的createTimer具体可以参考https://www.ncnynl.com/archives/201702/1296.html
(3)AdapterManager::Observe()
AdapterManager
维护一个observers_
的vector,实现观察者模式。
所有算法在调用AdapterManager::Init(config)
正式注册和启用自身的Adapter的时候,实际上也将自己的observe()函数加入到了AdapterManager的观察者数组observers_
,后期在OnTimer执行的时候,会调用一次AdapterManager的Observe函数,获得一个当前记录的数据的快照snapshot
AdapterManager中的定义
void AdapterManager::Observe()
{
for (const auto observe : instance()->observers_)
{
// std::vector<std::function<void()>> observers_;
observe(); //调用Adapter自身的函数Observe()。observers_的元素由宏定义产生。// 观察者模式
}
}
关于Adapter的Observe()
的定义在adapter.h中,因为其是一个通用的函数 adapter.h
void Observe() {
std::lock_guard<std::mutex> lock(mutex_);
observed_queue_ = data_queue_;//标记data_queue中的数据将要进行处理。
}
date_queue
和observed_queue
都是list格式,每个Adapter私有这两个变量;
1. data_queue
保存最新的数据,当新消息到来时,OnReceived会自动将消息放入这个队列,并执行一次callback操作;
2. observed_queue
是data_queue的拷贝,在Adapter::Observe()函数执行的时候保存一次,后期的Getxxx等获取传感器数据的操作都是在这个数组上进行的。
注:AdapterManager管理nodehandle,通过绑定具体消息和具体Adapter的OnReceived()函数,实现消息的保存和处理 (OnReceived()函数首先会将数据保存在自己的data_queue_数组,然后会调用自身Adapter的callback函数,实现函数的回调处理,这种是基于消息触发的,还有另外一种是基于Timer定时触发的)
陆. 总结
以上即为关于Apollo 1.0
架构和代码设计分析的所有内容。可以看出,Apollo
用了多种设计方式,整体结构比较合理,扩展性和可维护性都很好。
Apollo 3.0
同时使用了GPS/Imu/Lidar/Radar/Camera等传感器,已具有可量产的雏形,关于Apollo 3.0
的介绍可以参考apollo3.0介绍,我觉得这篇官方的文章就写的很好了。
同时Apollo 3.0
采用了和Apollo 1.0
非常相似的设计,是在Apollo 1.0
上进行的扩展和升级,因此我们后期将在本篇内容基础上继续对Apollo 3.0
进行详细的分析,同时对其进行适当改造,以使用超低成本对其进行实现。
评论(0)
您还未登录,请登录后发表或查看评论