【Apollo源码分析】系列的第一部分【common】

【Apollo源码分析】系列的第一部分【common】

源码分析

Apollo 整体上由13个模块构成,分别是

   
   
   
   
  1. canbus -汽车CAN总线控制模块
  2. common - 公有的源码模块
  3. control -控制模块
  4. decision -决策模块
  5. dreamview -可视化模块
  6. drivers -驱动模块
  7. hmi -人机交互模块
  8. localization-定位模块
  9. monitor -监控模块
  10. perception -感知模块
  11. planning -运动规划模块
  12. prediction -预测模块
  13. tools -通用监控与可视化工具

Apollo采用bazel 作为代码编译构建工具。 
每个源码文件夹下有一个BUILD文件,作用是按照bazel的格式来编译代码。 
关于如何使用bazel编译c++代码,可以查看以下网址: 
【1】https://docs.bazel.build/versions/master/tutorial/cpp.html 
【2】https://docs.bazel.build/versions/master/tutorial/cpp-use-cases.html


ok。现在开始分析源码。系列文章的第一篇,首先分析modules/common目录下面的源码。

modules/common/macro.h


宏定义 DISALLOW_COPY_AND_ASSIGN:

   
   
   
   
  1. #define DISALLOW_COPY_AND_ASSIGN(classname) \
  2. private: \
  3. classname(const classname &); \
  4. classname &operator=(const classname &);
用于在C++中禁止class的拷贝构造函数和赋值构造函数,良好的c++代码应该主动管理这2个操作符。
在caffe和cartographer或者其他的著名库中均有类似的操作。

宏定义 DISALLOW_IMPLICIT_CONSTRUCTORS:

   
   
   
   
  1. #define DISALLOW_IMPLICIT_CONSTRUCTORS(classname) \
  2. private: \
  3. classname(); \
  4. DISALLOW_COPY_AND_ASSIGN(classname);
禁止class的无参构造函数。

宏定义 DECLARE_SINGLETON:

   
   
   
   
  1. #define DECLARE_SINGLETON(classname) \
  2. public: \
  3. static classname *instance() { \
  4. static classname instance; \
  5. return &instance; \
  6. } \
  7. DISALLOW_IMPLICIT_CONSTRUCTORS(classname) \
单例类定义,instance() 返回指向同一个class对象的指针。禁止拷贝/赋值运算符。

modules/common/log.h

apollo内部使用谷歌的glog作为日志库。 
有5个日志级别,分别是DEBUG,INFO,WARNING,ERROR,FATAL。

   
   
   
   
  1. #include "glog/logging.h"
  2. #define ADEBUG VLOG(4) << "[DEBUG] "
  3. #define AINFO VLOG(3) << "[INFO] "
  4. #define AWARN LOG(WARNING)
  5. #define AERROR LOG(ERROR)
  6. #define AFATAL LOG(FATAL)

modules/common/time/time.h

apollo内部使用c++ 11的 chrono库作为时间管理工具。默认精度是纳秒(1e-9). 
std::chrono::duration 表示时间间隔大小。 
std::chrono::time_point 表示时间中的一个点。

定义2个别名:

  • Duration,1纳秒,1e-9s。
  • Timestamp,以纳秒ns为单位的时间点。

全局函数:

  • int64_t AsInt64(const Duration &duration)
   
   
   
   
  1. 将纳秒ns转换为以PrecisionDuration为精度单位计的int64整数。
  • int64_t AsInt64(const Timestamp ×tamp) :
   
   
   
   
  1. Timestamp(时间点)转换为64位整数表示。
  • double ToSecond(const Duration &duration) :
   
   
   
   
  1. 将纳秒ns转换为秒s
  • inline double ToSecond(const Timestamp ×tamp) :
   
   
   
   
  1. 将以纳秒表示的时间点ns转换为秒s
  • Timestamp FromInt64(int64_t timestamp_value) :
   
   
   
   
  1. 将以PrecisionDuration为精度计的int64整数转换为Timestamp(时间点)。
  • Timestamp From(double timestamp_value):
   
   
   
   
  1. 将秒s转换为时间点Timestamp。即FromInt64的特化版本:先将时间转换为ns计的64位整数nanos_value,再转换为Timestamp

Clock 类:

Clock是封装c++ 11 chrono后抽象的时钟计时类class。

是线程安全的单例模式(c++ 11的语法可确保)。

数据成员:

   
   
   
   
  1. bool is_system_clock_; true表示系统时间,false表示模拟时间
  2. Timestamp mock_now_; 模拟时间的当前值。

为啥要标记模拟时间?:为了仿真训练。多次仿真,反复训练。 
Clock类没有公有的构造函数。私有构造函数初始化为使用cpu的系统时间。

Clock提供4个static 公有函数:

   
   
   
   
  1. static Timestamp Now()
  2. 返回当前时间的最新值。
  3. static void UseSystemClock(bool is_system_clock)
  4. 设置是否使用模拟时间。
  5. static bool IsSystemClock()
  6. 返回是否使用系统时间。
  7. static void SetNow(const Duration &duration)
  8. Clock使用mock时间时,将系统时间设定为给定值。
  9. 否则抛出运行时错误。(只有模拟时间才可调整值,系统时间不可调整)

modules/common/time/time_test.cc

   
   
   
   
  1. Duration duration = std::chrono::milliseconds(12); //12ms
  2. EXPECT_EQ(12000, AsInt64(duration)); //==121000us
   
   
   
   
  1. Duration duration = std::chrono::microseconds(1234567);//1234567us
  2. EXPECT_EQ(1234, AsInt64(duration)); //==1234ms
   
   
   
   
  1. Duration duration = std::chrono::microseconds(123 456 789 012);//123...us
  2. EXPECT_FLOAT_EQ(123456.789012, ToSecond(duration)); //123456(s)
  3. ...略.
   
   
   
   
  1. EXPECT_TRUE(Clock::IsSystemClock()); //默认是cpu系统时间
  2. Clock::UseSystemClock(false); //修改。
  3. EXPECT_FALSE(Clock::IsSystemClock());
  4. EXPECT_EQ(0, AsInt64(Clock::Now())); //模拟时间的初值是0.
  5. Clock::SetNow(micros(123)); //修改为123 (us)
  6. EXPECT_EQ(123, AsInt64(Clock::Now()));

modules/common/util/util.h

函数原型:

   
   
   
   
  1. bool EndWith(const std::string &original, const std::string &pattern);
  • 当original字符串以 pattern 结尾时,返回true

  •  测试代码,util_test.cc:

   
   
   
   
  1. EXPECT_TRUE(EndWith("abc.def", "def"));
  2. EXPECT_TRUE(EndWith("abc.def", ".def"));
  3. EXPECT_FALSE(EndWith("abc.def", "abc"));
  4. EXPECT_FALSE(EndWith("abc.def", "de"));

modules/common/util/file.h

file.h提供了多个关于操作文件(关于protobuf文件的读,写,删)的函数。 
都是模板类函数:

   
   
   
   
  1. template <typename MessageType>
  •  (1). 将message存储到filename中,以ascii格式存储.
   
   
   
   
  1. bool SetProtoToASCIIFile(const MessageType &message,
  2. const std::string &file_name)
  •  (2). 从ascii格式的filename中读取message信息,并存储到指针指向的地址空间中.
   
   
   
   
  1. bool GetProtoFromASCIIFile(const std::string &file_name, MessageType *message)
  •  (3) . 解析二进制filename文件的内容,并存储到message中。
   
   
   
   
  1. bool GetProtoFromBinaryFile(const std::string &file_name,
  2. MessageType *message)
  •  (4) 从filename中解析文件内容。filename可以是二进制或者ascii格式。
   
   
   
   
  1. bool GetProtoFromFile(const std::string &file_name, MessageType *message)
  •  (5) . 给定的目录路径是否存在。
   
   
   
   
  1. bool DirectoryExists(const std::string &directory_path);
  •  (6) . 按照directory_path创建文件夹。类似于mkdir -p选项。
   
   
   
   
  1. bool EnsureDirectory(const std::string &directory_path);
  •  (7) . 删除给定目录下所有的文件。(文件夹不删除)
   
   
   
   
  1. bool RemoveAllFiles(const std::string &directory_path);

modules/common/util/string_tokenizer.h

StringTokenizer类主要目的是使用特定的分割符将字符串分割成多个部分。 
作用与python的字符串分割函数split()类似。默认分割符是" " 
1个成员函数:

   
   
   
   
  1. std::string Next();
  2. 分割得到的下一个子串。

1个静态成员函数,可以指定切分标记delims:

   
   
   
   
  1. static std::vector<std::string> Split(const std::string &str, const std::string &delims);
  2. 使用分隔符delims对给定的字符串str进行分割。分割结果可以用 Next()成员函数访问;

测试代码:

   
   
   
   
  1. std::string str("aa,bbb,c");
  2. std::string delim(",");
  3. EXPECT_THAT(StringTokenizer::Split(str, delim),
  4. ElementsAre("aa", "bbb", "c"));
   
   
   
   
  1. std::string str(" aa, bbb , c ");
  2. std::string delim(", ");
  3. StringTokenizer stn(str, delim);
  4. auto t0 = stn.Next();
  5. auto t1 = stn.Next();
  6. auto t2 = stn.Next();
  7. auto t3 = stn.Next();
  8. auto t4 = stn.Next();
  9. EXPECT_EQ(t0, "aa");
  10. EXPECT_EQ(t1, "bbb");
  11. EXPECT_EQ(t2, "c");
  12. EXPECT_EQ(t3, "");
  13. EXPECT_EQ(t4, "");

modules/common/util/factory.h

创建对象的工厂模式。所有需要创建的对象使用ProductCreator函数指针创建。 
默认使用unordered_map管理元素。

   
   
   
   
  1. IdentifierType:唯一标识符,如string字符串。
  2. AbstractProduct 如具有相同接口的纯虚基类class
  3. ProductCreator= AbstractProduct *(*)():函数指针。形参是空。返回值是指针,指向抽象class
  4. MapContainer = std::map<IdentifierType, ProductCreator>:按照IdentifierType存储需要创建的对象的方法。创建方法是ProductCreator

3个公有的函数:

   
   
   
   
  1. Register():注册某class的信息到unordered_map中,但是没有创建该class的实例对象。
  2. Unregister():删除已经注册的信息。
  3. CreateObject(): 创建某class的实例对象。

测试代码:

   
   
   
   
  1. Factory<std::string, Base> factory;
  2. //注册信息。
  3. EXPECT_TRUE(factory.Register("derived_class",
  4. []() -> Base* { return new Derived(); }));
  5. //创建实例对象
  6. auto derived_ptr = factory.CreateObject("derived_class");
  7. EXPECT_TRUE(derived_ptr != nullptr);
  8. EXPECT_EQ("derived", derived_ptr->Name());
  9. auto non_exist_ptr = factory.CreateObject("non_exist_class");
  10. EXPECT_TRUE(non_exist_ptr == nullptr);
   
   
   
   
  1. Factory<std::string, Base> factory;
  2. //注册信息
  3. EXPECT_TRUE(factory.Register("derived_class",
  4. []() -> Base* { return new Derived(); }));
  5. //没有fake_class,则提示删除失败。
  6. EXPECT_FALSE(factory.Unregister("fake_class"));
  7. auto derived_ptr = factory.CreateObject("derived_class");
  8. EXPECT_TRUE(derived_ptr != nullptr);
  9. EXPECT_TRUE(factory.Unregister("derived_class"));
  10. auto non_exist_ptr = factory.CreateObject("derived_class");
  11. EXPECT_TRUE(non_exist_ptr == nullptr);

modules/common/status/status.h

apollo内部定义了一系列的状态码用于标识各个模块的工作状态。 
状态码的含义可以在modules/common/proto/error_code.proto文件查看,以下是该文件内容:

   
   
   
   
  1. // Error codes enum for API's categorized by modules.
  2. enum ErrorCode {
  3. // No error, reutrns on success.
  4. OK = 0;
  5. // Control module error codes start from here.
  6. CONTROL_ERROR = 1000;
  7. CONTROL_INIT_ERROR = 1001;
  8. CONTROL_COMPUTE_ERROR = 1002;
  9. // Canbus module error codes start from here.
  10. CANBUS_ERROR = 2000;
  11. CAN_CLIENT_ERROR_BASE = 2100;
  12. CAN_CLIENT_ERROR_OPEN_DEVICE_FAILED = 2101;
  13. CAN_CLIENT_ERROR_FRAME_NUM = 2102;
  14. CAN_CLIENT_ERROR_SEND_FAILED = 2103;
  15. CAN_CLIENT_ERROR_RECV_FAILED = 2104;
  16. // Localization module error codes start from here.
  17. LOCALIZATION_ERROR = 3000;
  18. // Perception module error codes start from here.
  19. PERCEPTION_ERROR = 4000;
  20. PERCEPTION_ERROR_TF = 4001;
  21. PERCEPTION_ERROR_PROCESS = 4002;
  22. // Prediction module error codes start from here.
  23. PREDICTION_ERROR = 5000;
  24. }
  25. message StatusPb {
  26. optional ErrorCode error_code = 1 [default = OK];
  27. optional string msg = 2;
  28. }

Status类用于标记各个API模块的返回值。

  • 构造函数:
   
   
   
   
  1. Status() : code_(ErrorCode::OK), msg_() {}
  2. 默认构造函数为ErrorCode::OK(调用成功)。
   
   
   
   
  1. Status(ErrorCode code, const std::string &msg) : code_(code), msg_(msg) {}
  2. 指定错误码和错误message
  • 成员函数:
   
   
   
   
  1. 1) ok() 是否调用成功。
  2. 2) code() 错误码
  3. 3 string &error_message() 错误信息。
  4. 4 Save(StatusPb *status_pb) 序列化到文件。
  5. 5) string ToString() 状态信息转换为字符串格式。

测试代码:

   
   
   
   
  1. TEST(Status, OK) {
  2. EXPECT_EQ(Status::OK().code(), ErrorCode::OK);
  3. EXPECT_EQ(Status::OK().error_message(), "");
  4. EXPECT_EQ(Status::OK(), Status::OK());
  5. EXPECT_EQ(Status::OK(), Status());
  6. Status s;
  7. EXPECT_TRUE(s.ok());
  8. }
   
   
   
   
  1. TEST(Status, Set) {
  2. Status status;
  3. status = Status(ErrorCode::CONTROL_ERROR, "Error message");
  4. EXPECT_EQ(status.code(), ErrorCode::CONTROL_ERROR);
  5. EXPECT_EQ(status.error_message(), "Error message");
  6. }
   
   
   
   
  1. TEST(Status, ToString) {
  2. EXPECT_EQ("OK", Status().ToString());
  3. const Status a(ErrorCode::CONTROL_ERROR, "Error message");
  4. EXPECT_EQ("CONTROL_ERROR: Error message", a.ToString());
  5. }

common/adapters/adapter_gflags.h

用gflags的宏解析命令行参数。 
声明:DECLARE_xxx(变量名) 
定义:DEFINE_xxxxx(变量名,默认值,help-string)。

adapter_gflags.cc定义了在ROS环境下的多个topic的发布地址路径。包括gps,imu,localization,planning等.... 
几乎每个模块都有*_gflags.cc用于定义本模块用到的变量。

   
   
   
   
  1. DEFINE_string(adapter_config_path, "", "the file path of adapter config file");
  2. DEFINE_bool(enable_adapter_dump, false,
  3. "Whether enable dumping the messages to "
  4. "/tmp/adapters//.txt for debugging purposes.");
  5. DEFINE_string(gps_topic, "/apollo/sensor/gnss/odometry", "GPS topic name");
  6. DEFINE_string(imu_topic, "/apollo/sensor/gnss/corrected_imu", "IMU topic name");
  7. DEFINE_string(chassis_topic, "/apollo/canbus/chassis", "chassis topic name");
  8. ...

common/adapters/adapter.h

Adapter是来自传感器的底层数据和Apollo各个模块交互的统一接口。(c++ 适配器模式) 使得Apollo各个模块不强依赖于ROS框架:将数据IO抽象。上层是Apollo框架,底层是data IO,二者松耦合。Adapter持有一段时间内的所有历史数据,因此所有的模块可获取这些数据而无需使用ROS通信机制进行数据交换(提高通信效率)。模板参数可以是message消息类型,不一定是传感器数据。

  • 数据成员
   
   
   
   
  1. /// 需要监听的ROS的话题名称,message的来源topic
  2. std::string topic_name_;
  3. /// 数据队列的最大容量
  4. size_t message_num_ = 0;
  5. // 接收的数据队列,原始数据。该部分数据尚未进行观测
  6. std::list<std::shared_ptr<D>> data_queue_;
  7. /// Observe() is called.
  8. /// 备份已经观测过的数据。
  9. std::list<std::shared_ptr<D>> observed_queue_;
  10. /// 消息到达的回调函数(用于处理到达的消息)
  11. Callback receive_callback_ = nullptr;
  12. ///互斥锁保证线程安全
  13. mutable std::mutex mutex_;
  14. /// 是否输出到dump路径
  15. bool enable_dump_ = false;
  16. /// dump输出路径
  17. std::string dump_path_;
  18. /// 消息的序列号,单调递增。此值随着消息的增加而单调增加。
  19. uint32_t seq_num_ = 0;
   
   
   
   
  1. 构造函数初始化成员变量:
  2. Adapter(const std::string &adapter_name, const std::string &topic_name,size_t message_num, const std::string &dump_dir = "/tmp")
  3. 构造函数参数:
  4. 1Adapter标识自己的名称,
  5. 2,监听的ros_topic
  6. 3,保存历史消息的最大数量,
  7. 4dump路径
   
   
   
   
  1. void FeedProtoFile(const std::string &message_file)
  2. proto文件读取data
   
   
   
   
  1. OnReceive(const D &message)
  2. 接收原始数据data。并调用相关回调函数。
   
   
   
   
  1. void Observe()
  2. 进行一次观察/测量。只有调用了Observe(),接收的数据才能被有效处理。
   
   
   
   
  1. const D &GetLatestObserved()
  2. 获取观测集中的最新值
   
   
   
   
  1. begin(),end(),迭代器模式指向观测数据集。支持for循环。
   
   
   
   
  1. void SetCallback(Callback callback) 设置消息到达时的回调函数。

测试代码:

   
   
   
   
  1. TEST(AdapterTest, Empty) {
  2. //创建一个IntegerAdapter对象,监听integer_topic话题,保存历史消息数量是10
  3. IntegerAdapter对象, adapter("Integer", "integer_topic", 10);
  4. EXPECT_TRUE(adapter.Empty());
  5. }
  6. TEST(AdapterTest, Observe) {
  7. IntegerAdapter adapter("Integer", "integer_topic", 10);
  8. adapter.OnReceive(173);//接收到了一个数据,但是尚未处理数据。
  9. // Before calling Observe.
  10. EXPECT_TRUE(adapter.Empty());
  11. // After calling Observe.
  12. //进行一次观测,即接收待处理数据。
  13. adapter.Observe();
  14. EXPECT_FALSE(adapter.Empty());
  15. }
   
   
   
   
  1. TEST(AdapterTest, Callback) {
  2. IntegerAdapter adapter("Integer", "integer_topic", 3);
  3. // Set the callback to act as a counter of messages.
  4. int count = 0;
  5. adapter.SetCallback([&count](int x) { count += x; });
  6. adapter.OnReceive(11);
  7. adapter.OnReceive(41);
  8. adapter.OnReceive(31);
  9. EXPECT_EQ(11 + 41 + 31, count);//接收一次消息就回调一次函数。
  10. }
   
   
   
   
  1. TEST(AdapterTest, Dump) {
  2. FLAGS_enable_adapter_dump = true;
  3. std::string temp_dir = std::getenv("TEST_TMPDIR");//dump输出路径
  4. MyLocalizationAdapter adapter("local", "local_topic", 3, temp_dir);
  5. localization::LocalizationEstimate loaded;
  6. localization::LocalizationEstimate msg;
  7. msg.mutable_header()->set_sequence_num(17);
  8. adapter.OnReceive(msg);//接收时,即已经写入dump文件。
  9. //从dump文件读取信息到loaded中。
  10. apollo::common::util::GetProtoFromASCIIFile(temp_dir + "/local/17.pb.txt",
  11. &loaded);
  12. EXPECT_EQ(17, loaded.header().sequence_num());
  13. msg.mutable_header()->set_sequence_num(23);
  14. adapter.OnReceive(msg);
  15. //同上。
  16. apollo::common::util::GetProtoFromASCIIFile(temp_dir + "/local/23.pb.txt",
  17. &loaded);
  18. EXPECT_EQ(23, loaded.header().sequence_num());
  19. }

common/adapters/adapter_manager.h

AdapterManager 类用于管理多个适配器,单例模式。所有的message/IO适配器均需要通过REGISTER_ADAPTER(name)在这里注册。所有的数据交互也通过AdapterManager来进行统一的管理。

由于是单例模式,AdapterManager只有静态成员函数。

加载默认config:

   
   
   
   
  1. Init()

加载给定filename的config:

   
   
   
   
  1. static void Init(const std::string &adapter_config_filename)

加载proto格式的配置,创建 Adapter对象:

   
   
   
   
  1. static void Init(const AdapterManagerConfig &configs);

例如imu:

   
   
   
   
  1. case AdapterConfig::IMU:
  2. EnableImu(FLAGS_imu_topic, config.mode(),
  3. config.message_history_limit());
  4. break;

对所有的adapter对象执行observe()函数,调用每个Adapter自身的函数Observe()。observers_的元素由宏定义产生。:

   
   
   
   
  1. static void Observe();

ROS节点回调函数:

   
   
   
   
  1. static ros::Timer CreateTimer
  2. (ros::Duration period,
  3. void (T::*callback)(const ros::TimerEvent &),
  4. T *obj, bool oneshot = false,
  5. bool autostart = true)

common/vehicle_state/vehicle_state.h

VehicleState类是标识车辆状态信息的class。 
主要包含线速度.角速度.加速度.齿轮状态.车辆坐标x,y,z

  •  构造函数:
   
   
   
   
  1. explicit VehicleState(const localization::LocalizationEstimate &localization);
  2. 只根据定位来初始化车辆信息。
   
   
   
   
  1. VehicleState(const localization::LocalizationEstimate *localization,const canbus::Chassis *chassis);
  2. 根据定位信息和车辆底盘信息初始化。

  • 成员函数: 
    • x坐标 
      double x() const;
    • y坐标 
      double y() const;
    • z坐标。 
      double z() const;
    • pitch角,绕y轴。(车身左右倾斜角度)。 
      double pitch() const;
    • yaw角,绕z轴。表征车头转向角度。0°与x轴重合,90°与y轴重合 
      double heading() const;
    • 线速度 
      double linear_velocity() const;
    • 角速度 
      double angular_velocity() const;
    • y方向线性加速度 
      double linear_acceleration() const;
    • 齿轮状态 
      GearPosition gear() const;
    • 设置上述值的函数set_**()

  •  状态估计
   
   
   
   
  1. 根据当前信息估计t时刻后的车辆位置。注意,Apollo的状态估计比较简单,很多因素都没有考虑。所以1.0版本只能在简单道路上行驶。
  2. Eigen::Vector2d EstimateFuturePosition(const double t) const;
   
   
   
   
  1. Eigen::Vector2d ComputeCOMPosition(const double rear_to_com_distance) const;
  2. 给定后轮到质心的距离。 估计质心的位置(x,y)

测试代码:

   
   
   
   
  1. TEST_F(VehicleStateTest, Accessors) {
  2. VehicleState vehicle_state(&localization_, &chassis_);
  3. //都是proto文件的内容。
  4. EXPECT_DOUBLE_EQ(vehicle_state.x(), 357.51331791372041);
  5. EXPECT_DOUBLE_EQ(vehicle_state.y(), 96.165912376788725);
  6. EXPECT_DOUBLE_EQ(vehicle_state.heading(), -1.8388082455104939);
  7. EXPECT_DOUBLE_EQ(vehicle_state.pitch(), -0.010712737572581465);
  8. EXPECT_DOUBLE_EQ(vehicle_state.linear_velocity(), 3.0);
  9. EXPECT_DOUBLE_EQ(vehicle_state.angular_velocity(), -0.0079623083093763921);
  10. EXPECT_DOUBLE_EQ(vehicle_state.linear_acceleration(), -0.079383290718229638);
  11. EXPECT_DOUBLE_EQ(vehicle_state.gear(), canbus::Chassis::GEAR_DRIVE);
  12. }
   
   
   
   
  1. TEST_F(VehicleStateTest, EstimateFuturePosition) {
  2. VehicleState vehicle_state(&localization_, &chassis_);
  3. //估计1s后的车辆位置
  4. Eigen::Vector2d future_position = vehicle_state.EstimateFuturePosition(1.0);
  5. EXPECT_NEAR(future_position[0], 356.707, 1e-3);
  6. EXPECT_NEAR(future_position[1], 93.276, 1e-3);
  7. //估计2s后的车辆位置
  8. future_position = vehicle_state.EstimateFuturePosition(2.0);
  9. EXPECT_NEAR(future_position[0], 355.879, 1e-3);
  10. EXPECT_NEAR(future_position[1], 90.393, 1e-3);
  11. }

common/monitor/monitor_buffer.h

MonitorBuffer主要用于缓存多条log的消息日志。将多个相同level的log组合到一起。避免冗余信息。 不同level的log,将不会被组合。但是相同level的log日志将被组合到一起。

  • 构造函数 
    explicit MonitorBuffer(Monitor *monitor);

成员函数:

   
   
   
   
  1. void PrintLog();
  2. monitor_msg_items_的尾部消息输出。
   
   
   
   
  1. REG_MSG_TYPE(INFO);
  2. 注册INFO级别的log
  3. REG_MSG_TYPE(WARN);
  4. REG_MSG_TYPE(ERROR);
  5. REG_MSG_TYPE(FATAL);
   
   
   
   
  1. void AddMonitorMsgItem(const MonitorMessageItem::LogLevel log_level,const std::string &msg);
  2. 添加一个msgitemlevel+msg.
   
   
   
   
  1. MonitorBuffer &operator<<(const T &msg)
  2. 重载<<运算符,将msg写入MonitorBufferlevel是当前的level_
   
   
   
   
  1. void Publish();
  2. 调用monitor的成员函数Publish(),发布消息。

测试代码:

   
   
   
   
  1. TEST_F(MonitorBufferTest, PrintLog) {
  2. FLAGS_logtostderr = true;
  3. FLAGS_v = 4;
  4. {
  5. testing::internal::CaptureStderr();
  6. buffer_->PrintLog();
  7. EXPECT_TRUE(testing::internal::GetCapturedStderr().empty());
  8. //没有log日志,所以为空。
  9. }
  10. {
  11. buffer_->INFO("INFO_msg");//写入"INFO_msg".
  12. testing::internal::CaptureStderr();
  13. buffer_->PrintLog();
  14. EXPECT_NE(std::string::npos,//找到string,不为npos。
  15. testing::internal::GetCapturedStderr().find("[INFO] INFO_msg"));
  16. }
  17. {
  18. buffer_->ERROR("ERROR_msg");//写入"ERROR_msg"
  19. testing::internal::CaptureStderr();
  20. buffer_->PrintLog();
  21. EXPECT_NE(std::string::npos,
  22. testing::internal::GetCapturedStderr().find("ERROR_msg"));
  23. }
  24. {
  25. buffer_->WARN("WARN_msg");
  26. testing::internal::CaptureStderr();
  27. buffer_->PrintLog();
  28. EXPECT_NE(std::string::npos,
  29. testing::internal::GetCapturedStderr().find("WARN_msg"));
  30. }
  31. {
  32. buffer_->FATAL("FATAL_msg");
  33. EXPECT_DEATH(buffer_->PrintLog(), "");
  34. }
  35. }
   
   
   
   
  1. TEST_F(MonitorBufferTest, Operator) {
  2. buffer_->ERROR() << "Hi";//使用<<运算符写入ERROR级别的"Hi"
  3. EXPECT_EQ(MonitorMessageItem::ERROR, buffer_->level_);
  4. ASSERT_EQ(1, buffer_->monitor_msg_items_.size());
  5. auto &item = buffer_->monitor_msg_items_.back();
  6. EXPECT_EQ(MonitorMessageItem::ERROR, item.first);
  7. EXPECT_EQ("Hi", item.second);
  8. //level_是最近一次调用的level。
  9. (*buffer_) << " How"
  10. << " are"
  11. << " you";
  12. EXPECT_EQ(MonitorMessageItem::ERROR, buffer_->level_);
  13. ASSERT_EQ(1, buffer_->monitor_msg_items_.size());
  14. EXPECT_EQ(MonitorMessageItem::ERROR, item.first);
  15. EXPECT_EQ("Hi How are you", item.second);
  16. buffer_->INFO() << 3 << "pieces";
  17. EXPECT_EQ(MonitorMessageItem::INFO, buffer_->level_);
  18. ASSERT_EQ(2, buffer_->monitor_msg_items_.size());
  19. item = buffer_->monitor_msg_items_.back();
  20. EXPECT_EQ(MonitorMessageItem::INFO, item.first);
  21. EXPECT_EQ("3pieces", item.second);
  22. const char *fake_input = nullptr;
  23. EXPECT_TRUE(&(buffer_->INFO() << fake_input) == buffer_);
  24. }

common/monitor/monitor.h

Monitor类主要用于收集各个模块的message信息,并发布到相关的topic中。

  • 构造函数
   
   
   
   
  1. explicit Monitor(const MonitorMessageItem::MessageSource &source)
  2. : source_(source) {}
  3. 创建一个消息项,指定message来源哪一个模块。
  • 成员函数:
   
   
   
   
  1. virtual void Publish(const std::vector<MessageItem> &messages) const;
  2. 组合多条消息,并发布到相关的topic

测试代码:

   
   
   
   
  1. MonitorTest monitor(MonitorMessageItem::CONTROL);
  2. std::vector<std::pair<MonitorMessageItem::LogLevel, std::string>> items{
  3. {MonitorMessageItem::INFO, "info message"},
  4. {MonitorMessageItem::WARN, "warn message"},
  5. {MonitorMessageItem::ERROR, "error message"},
  6. };
  7. monitor.Publish(items);//一次性发布多条消息。

总的来说Monitor类就是收集各个模块的工作log日志并发布到相应的topic用于监控。其中用到了MonitorBuffer,目的是合并相同level的log消息日志并一次性发布——减少冗余信息。


common/math

math目录下面的函数都是一些数学class封装。只做粗略的介绍。细致探究还是要看源码。

  • vec2d.h 2D的向量, 与Eigen::Vector2d相似。
  • aabox2d.h 2D区域
  • aaboxkdtree2d.h kd树组成的2D区域集
  • sin_table.h 查表法,快速计算sin值。
  • angle.h 角度弧度转换
  • box2d.h 2D框
  • euler_angles_zxy.h 欧拉角
  • integral.h 求GaussLegendre高斯-勒让德积分结果
  • kalman_filter.h 卡尔曼滤波,没有ekf,没有ukf。只有最简单的kf。
  • linear_interpolation.h 插值函数
  • polygon2d.h 2D多边形
  • search.h 在给定的区间内寻找极小值。 
    math目录下都是常规代码。快速了解其API,建议阅读对应的测试代码。

common/apollo_app.h

ApolloApp类用于各个模块注册信息。各个模块每调用一次APOLLO_MAIN(APP),即创建一个module模块进程, ApolloApp类规范了每个模块APP类的公有接口。 正常情况下,该进程将持续运行。ApolloApp是纯虚函数。这意味着每个模块需要重写多个纯虚函数接口才能实例化本模块。而重写的正是每个模块不同的地方。

虚函数接口:

   
   
   
   
  1. virtual std::string Name() const = 0;
  2. 模块名称,进程名。
   
   
   
   
  1. virtual int Spin();
  2. 初始化模块app的信息,并持续运行直到shutdown
   
   
   
   
  1. virtual apollo::common::Status Init() = 0;
  2. 执行初始化加载配置文件和传感器数据等任务。
   
   
   
   
  1. virtual apollo::common::Status Start() = 0;
  2. 开始执行模块任务。若由上层message到来触发,则执行与message相关的回调函数。
  3. 若由时间time触发,则调用时间处理回调函数。
   
   
   
   
  1. virtual void Stop() = 0;
  2. 模块停止运行。在ros::shutdown()执行完毕后的清理工作。
   
   
   
   
  1. virtual void ReportModuleStatus();
  2. HMI人机交互界面发送状态status码。

最后本文件还有一个宏定义方便各个模块运行。

   
   
   
   
  1. 每个模块是一个进程。
  2. #define APOLLO_MAIN(APP) \
  3. int main(int argc, char **argv) { \
  4. google::InitGoogleLogging(argv[0]); \
  5. google::ParseCommandLineFlags(&argc, &argv, true); \
  6. signal(SIGINT, apollo::common::apollo_app_sigint_handler); \
  7. APP apollo_app_; \
  8. ros::init(argc, argv, apollo_app_.Name()); \
  9. apollo_app_.Spin(); \
  10. return 0; \
  11. }

总结

common的代码已经分析完毕。该目录下主要提供了各个模块公用的函数和class以及一些数学API还有公共的宏定义。

  • 在Apollo 1.0中,common是整个框架的基础。
  • configs是配置文件加载。
  • adapters是数据交互的抽象接口。
  • math提供了数学几何api接口。
  • monitor提供监控log信息。
  • status提供各个模块工作状态。
  • time提供计时类。
  • util提供文件io管理功能。
  • vehicle_state提供车辆状态信息与预期状态估计。

本文首发于微信公众号slamcode。 
在blog.csdn.net/learnmoreonce亦有连载。

注释版源码:点击打开链接

【Apollo源码分析】系列的第一部分【common】_第1张图片

你可能感兴趣的:(Apollo)