自动驾驶系列:从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主要由以下六大部分组成:

Apollo1.0构成部分概览

从上图中我们可以看出,我们最感兴趣的部分都在modules中。modules中的结构目录如下:

Apollo1.0 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实现各个进程的关闭.

后续,将从controlcontrolcontrol和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中相当重要的概念,

apollo消息通信结构图

apollo定位模块消息通信结构图

(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()类方法,实际这是一个“观察者”, 被观察者则是AdapterManagerAdapterManager维护一个数组observers_用来保存所有adapterObserve。当有消息到来时,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()用来调用所有adapterobserve()函数。

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_nameadapter.

topic_name用来和ROS进行通信;

adapter则在注册后由各个实际模块(比如localization等)直接引入,进行消息的收发。


2. AdapterManagertopic_name的管理是通过adapter_gflags来进行的,

使用google:gflagDECLEARDEFINE功能进行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_managerInit()函数正式启用该adapter

adapter_managerInit()函数通过配置文件,完成以下功能:

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

  1. Apollo app全部都是 纯虚函数 ,定了了所有模块的公有结构,所有模块在继承该class后对公有接口进行实现;
  2. 各个模块每调用一次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模块注册和运行

Localization模块使用RTKLocalization算法modele注册运行示意

Apollo_app是所有模块的父类,Localization/Prediction/Perception等等都要继承Apollo_app,并复写Apollo_app的Init()/Start()/Stop()等实现自己的功能,实际运行时会调用Apollo_appSpin()开始改模块的正式运行 。 --这部分是在各个大模块的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具体可以参考ncnynl.com/archives/201

(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_queueobserved_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进行详细的分析,同时对其进行适当改造,以使用超低成本对其进行实现。