百度开源自动驾驶apollo源码分析
Posted MBWorld
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了百度开源自动驾驶apollo源码分析相关的知识,希望对你有一定的参考价值。
我们先来看一下源码的结构
一级目录结构
docker文件夹放的是和docker容器有关的文件
docs文件夹放的是说明文档
modules放的是apollo中各个模块的源代码,也是之后我们将要详细说明的部分
scripts放的是一些百度写的部署和运行apollo的脚本
third_party存放了第三方的依赖库
tools文件夹和其他文件都是和apollo构建有关的配置文件
进入modules文件夹
modules目录结构
可以看到每个模块都分得很清楚,而且模块的功能通过文件夹的名称也能很直观的看懂。
Apollo 整体上由13个模块构成,分别是
canbus -汽车CAN总线控制模块
common - 公有的源码模块
control -控制模块
decision -决策模块
dreamview -可视化模块
drivers -驱动模块
hmi -人机交互模块
localization -定位模块
monitor -监控模块
perception -感知模块
planning -运动规划模块
prediction -预测模块
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:
#define DISALLOW_COPY_AND_ASSIGN(classname) \
private: \
classname(const classname &); \
classname &operator=(const classname &);
用于在C++中禁止class的拷贝构造函数和赋值构造函数,良好的c++代码应该主动管理这2个操作符。
在caffe和cartographer或者其他的著名库中均有类似的操作。
宏定义 DISALLOW_IMPLICIT_CONSTRUCTORS:
#define DISALLOW_IMPLICIT_CONSTRUCTORS(classname) \
private: \
classname(); \
DISALLOW_COPY_AND_ASSIGN(classname);
禁止class的无参构造函数。
宏定义 DECLARE_SINGLETON:
#define DECLARE_SINGLETON(classname) \
public: \
static classname *instance() { \
static classname instance; \
return &instance; \
} \
DISALLOW_IMPLICIT_CONSTRUCTORS(classname) \
单例类定义,instance() 返回指向同一个class对象的指针。禁止拷贝/赋值运算符。
modules/common/log.h
apollo内部使用谷歌的glog作为日志库。
有5个日志级别,分别是DEBUG,INFO,WARNING,ERROR,FATAL。
#include "glog/logging.h"
#define ADEBUG VLOG(4) << "[DEBUG] "
#define AINFO VLOG(3) << "[INFO] "
#define AWARN LOG(WARNING)
#define AERROR LOG(ERROR)
#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)
将纳秒ns转换为以PrecisionDuration为精度单位计的int64整数。
int64_t AsInt64(const Timestamp ×tamp) :
将Timestamp(时间点)转换为64位整数表示。
double ToSecond(const Duration &duration) :
将纳秒ns转换为秒s。
inline double ToSecond(const Timestamp ×tamp) :
将以纳秒表示的时间点ns转换为秒s
Timestamp FromInt64(int64_t timestamp_value) :
将以PrecisionDuration为精度计的int64整数转换为Timestamp(时间点)。
Timestamp From(double timestamp_value):
将秒s转换为时间点Timestamp。即FromInt64的特化版本:先将时间转换为ns计的64位整数nanos_value,再转换为Timestamp。
Clock 类:
Clock是封装c++ 11 chrono后抽象的时钟计时类class。
是线程安全的单例模式(c++ 11的语法可确保)。
数据成员:
bool is_system_clock_; true表示系统时间,false表示模拟时间
Timestamp mock_now_; 模拟时间的当前值。
为啥要标记模拟时间?:为了仿真训练。多次仿真,反复训练。
Clock类没有公有的构造函数。私有构造函数初始化为使用cpu的系统时间。
Clock提供4个static 公有函数:
static Timestamp Now()
返回当前时间的最新值。
static void UseSystemClock(bool is_system_clock)
设置是否使用模拟时间。
static bool IsSystemClock()
返回是否使用系统时间。
static void SetNow(const Duration &duration)
当Clock使用mock时间时,将系统时间设定为给定值。
否则抛出运行时错误。(只有模拟时间才可调整值,系统时间不可调整)
modules/common/time/time_test.cc
Duration duration = std::chrono::milliseconds(12); //12ms
EXPECT_EQ(12000, AsInt64<micros>(duration)); //==121000us
Duration duration = std::chrono::microseconds(1234567);//1234567us
EXPECT_EQ(1234, AsInt64<millis>(duration)); //==1234ms
Duration duration = std::chrono::microseconds(123 456 789 012);//123...us
EXPECT_FLOAT_EQ(123456.789012, ToSecond(duration)); //123456(s)
...略.
EXPECT_TRUE(Clock::IsSystemClock()); //默认是cpu系统时间
Clock::UseSystemClock(false); //修改。
EXPECT_FALSE(Clock::IsSystemClock());
EXPECT_EQ(0, AsInt64<micros>(Clock::Now())); //模拟时间的初值是0.
Clock::SetNow(micros(123)); //修改为123 (us)
EXPECT_EQ(123, AsInt64<micros>(Clock::Now()));
modules/common/util/util.h
函数原型:
bool EndWith(const std::string &original, const std::string &pattern);
当original字符串以 pattern 结尾时,返回true
测试代码,util_test.cc:
EXPECT_TRUE(EndWith("abc.def", "def"));
EXPECT_TRUE(EndWith("abc.def", ".def"));
EXPECT_FALSE(EndWith("abc.def", "abc"));
EXPECT_FALSE(EndWith("abc.def", "de"));
modules/common/util/file.h
file.h提供了多个关于操作文件(关于protobuf文件的读,写,删)的函数。
都是模板类函数:
template <typename MessageType>
(1). 将message存储到filename中,以ascii格式存储.
bool SetProtoToASCIIFile(const MessageType &message,
const std::string &file_name)
bool GetProtoFromASCIIFile(const std::string &file_name, MessageType *message)
(3) . 解析二进制filename文件的内容,并存储到message中。
bool GetProtoFromBinaryFile(const std::string &file_name,
MessageType *message)
(4) 从filename中解析文件内容。filename可以是二进制或者ascii格式。
bool GetProtoFromFile(const std::string &file_name, MessageType *message)
(5) . 给定的目录路径是否存在。
bool DirectoryExists(const std::string &directory_path);
(6) . 按照directory_path创建文件夹。类似于mkdir -p选项。
bool EnsureDirectory(const std::string &directory_path);
(7) . 删除给定目录下所有的文件。(文件夹不删除)
bool RemoveAllFiles(const std::string &directory_path);
modules/common/util/string_tokenizer.h
StringTokenizer类主要目的是使用特定的分割符将字符串分割成多个部分。
作用与python的字符串分割函数split()类似。默认分割符是" "
1个成员函数:
std::string Next();
分割得到的下一个子串。
1个静态成员函数,可以指定切分标记delims:
static std::vector<std::string> Split(const std::string &str, const std::string &delims);
使用分隔符delims对给定的字符串str进行分割。分割结果可以用 Next()成员函数访问;
测试代码:
std::string str("aa,bbb,c");
std::string delim(",");
EXPECT_THAT(StringTokenizer::Split(str, delim),
ElementsAre("aa", "bbb", "c"));
std::string str(" aa, bbb , c ");
std::string delim(", ");
StringTokenizer stn(str, delim);
auto t0 = stn.Next();
auto t1 = stn.Next();
auto t2 = stn.Next();
auto t3 = stn.Next();
auto t4 = stn.Next();
EXPECT_EQ(t0, "aa");
EXPECT_EQ(t1, "bbb");
EXPECT_EQ(t2, "c");
EXPECT_EQ(t3, "");
EXPECT_EQ(t4, "");
modules/common/util/factory.h
创建对象的工厂模式。所有需要创建的对象使用ProductCreator函数指针创建。
默认使用unordered_map管理元素。
IdentifierType:唯一标识符,如string字符串。
AbstractProduct: 如具有相同接口的纯虚基类class。
ProductCreator= AbstractProduct *(*)():函数指针。形参是空。返回值是指针,指向抽象class。
MapContainer = std::map<IdentifierType, ProductCreator>:按照IdentifierType存储需要创建的对象的方法。创建方法是ProductCreator
3个公有的函数:
Register():注册某class的信息到unordered_map中,但是没有创建该class的实例对象。
Unregister():删除已经注册的信息。
CreateObject(): 创建某class的实例对象。
测试代码:
Factory<std::string, Base> factory;
//注册信息。
EXPECT_TRUE(factory.Register("derived_class",
[]() -> Base* { return new Derived(); }));
//创建实例对象
auto derived_ptr = factory.CreateObject("derived_class");
EXPECT_TRUE(derived_ptr != nullptr);
EXPECT_EQ("derived", derived_ptr->Name());
auto non_exist_ptr = factory.CreateObject("non_exist_class");
EXPECT_TRUE(non_exist_ptr == nullptr);
Factory<std::string, Base> factory;
//注册信息
EXPECT_TRUE(factory.Register("derived_class",
[]() -> Base* { return new Derived(); }));
//没有fake_class,则提示删除失败。
EXPECT_FALSE(factory.Unregister("fake_class"));
auto derived_ptr = factory.CreateObject("derived_class");
EXPECT_TRUE(derived_ptr != nullptr);
EXPECT_TRUE(factory.Unregister("derived_class"));
auto non_exist_ptr = factory.CreateObject("derived_class");
EXPECT_TRUE(non_exist_ptr == nullptr);
modules/common/status/status.h
apollo内部定义了一系列的状态码用于标识各个模块的工作状态。
状态码的含义可以在modules/common/proto/error_code.proto文件查看,以下是该文件内容:
// Error codes enum for API's categorized by modules.
enum ErrorCode {
// No error, reutrns on success.
OK = 0;
// Control module error codes start from here.
CONTROL_ERROR = 1000;
CONTROL_INIT_ERROR = 1001;
CONTROL_COMPUTE_ERROR = 1002;
// 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;
// Localization module error codes start from here.
LOCALIZATION_ERROR = 3000;
// Perception module error codes start from here.
PERCEPTION_ERROR = 4000;
PERCEPTION_ERROR_TF = 4001;
PERCEPTION_ERROR_PROCESS = 4002;
// Prediction module error codes start from here.
PREDICTION_ERROR = 5000;
}
message StatusPb {
optional ErrorCode error_code = 1 [default = OK];
optional string msg = 2;
}
Status类用于标记各个API模块的返回值。
构造函数:
Status() : code_(ErrorCode::OK), msg_() {}
默认构造函数为ErrorCode::OK(调用成功)。
Status(ErrorCode code, const std::string &msg) : code_(code), msg_(msg) {}
指定错误码和错误message。
成员函数:
1) ok() 是否调用成功。
2) code() 错误码
3) string &error_message() 错误信息。
4) Save(StatusPb *status_pb) 序列化到文件。
5) string ToString() 状态信息转换为字符串格式。
测试代码:
TEST(Status, OK) {
EXPECT_EQ(Status::OK().code(), ErrorCode::OK);
EXPECT_EQ(Status::OK().error_message(), "");
EXPECT_EQ(Status::OK(), Status::OK());
EXPECT_EQ(Status::OK(), Status());
Status s;
EXPECT_TRUE(s.ok());
}
TEST(Status, Set) {
Status status;
status = Status(ErrorCode::CONTROL_ERROR, "Error message");
EXPECT_EQ(status.code(), ErrorCode::CONTROL_ERROR);
EXPECT_EQ(status.error_message(), "Error message");
}
TEST(Status, ToString) {
EXPECT_EQ("OK", Status().ToString());
const Status a(ErrorCode::CONTROL_ERROR, "Error message");
EXPECT_EQ("CONTROL_ERROR: Error message", a.ToString());
}
common/adapters/adapter_gflags.h
用gflags的宏解析命令行参数。
声明:DECLARE_xxx(变量名)
定义:DEFINE_xxxxx(变量名,默认值,help-string)。
DEFINE_string(adapter_config_path, "", "the file path of adapter config file");
DEFINE_bool(enable_adapter_dump, false,
"Whether enable dumping the messages to "
"/tmp/adapters/<topic_name>/<seq_num>.txt for debugging purposes.");
DEFINE_string(gps_topic, "/apollo/sensor/gnss/odometry", "GPS topic name");
DEFINE_string(imu_topic, "/apollo/sensor/gnss/corrected_imu", "IMU topic name");
DEFINE_string(chassis_topic, "/apollo/canbus/chassis", "chassis topic name");
...
common/adapters/adapter.h
Adapter是来自传感器的底层数据和Apollo各个模块交互的统一接口。(c++ 适配器模式) 使得Apollo各个模块不强依赖于ROS框架:将数据IO抽象。上层是Apollo框架,底层是data IO,二者松耦合。Adapter持有一段时间内的所有历史数据,因此所有的模块可获取这些数据而无需使用ROS通信机制进行数据交换(提高通信效率)。模板参数可以是message消息类型,不一定是传感器数据。
数据成员
/// 需要监听的ROS的话题名称,message的来源topic
std::string topic_name_;
/// 数据队列的最大容量
size_t message_num_ = 0;
// 接收的数据队列,原始数据。该部分数据尚未进行观测
std::list<std::shared_ptr<D>> data_queue_;
/// Observe() is called.
/// 备份已经观测过的数据。
std::list<std::shared_ptr<D>> observed_queue_;
/// 消息到达的回调函数(用于处理到达的消息)
Callback receive_callback_ = nullptr;
///互斥锁保证线程安全
mutable std::mutex mutex_;
/// 是否输出到dump路径
bool enable_dump_ = false;
/// dump输出路径
std::string dump_path_;
/// 消息的序列号,单调递增。此值随着消息的增加而单调增加。
uint32_t seq_num_ = 0;
构造函数初始化成员变量:
Adapter(const std::string &adapter_name, const std::string &topic_name,size_t message_num, const std::string &dump_dir = "/tmp")
构造函数参数:
1,Adapter标识自己的名称,
2,监听的ros_topic,
3,保存历史消息的最大数量,
4,dump路径
void FeedProtoFile(const std::string &message_file)
从proto文件读取data。
OnReceive(const D &message)
接收原始数据data。并调用相关回调函数。
void Observe()
进行一次观察/测量。只有调用了Observe(),接收的数据才能被有效处理。
const D &GetLatestObserved()
获取观测集中的最新值
begin(),end(),迭代器模式指向观测数据集。支持for循环。
void SetCallback(Callback callback) 设置消息到达时的回调函数。
测试代码:
TEST(AdapterTest, Empty) {
//创建一个IntegerAdapter对象,监听integer_topic话题,保存历史消息数量是10
IntegerAdapter对象, adapter("Integer", "integer_topic", 10);
EXPECT_TRUE(adapter.Empty());
}
TEST(AdapterTest, Observe) {
IntegerAdapter adapter("Integer", "integer_topic", 10);
adapter.OnReceive(173);//接收到了一个数据,但是尚未处理数据。
// Before calling Observe.
EXPECT_TRUE(adapter.Empty());
// After calling Observe.
//进行一次观测,即接收待处理数据。
adapter.Observe();
EXPECT_FALSE(adapter.Empty());
}
TEST(AdapterTest, Callback) {
IntegerAdapter adapter("Integer", "integer_topic", 3);
// Set the callback to act as a counter of messages.
int count = 0;
adapter.SetCallback([&count](int x) { count += x; });
adapter.OnReceive(11);
adapter.OnReceive(41);
adapter.OnReceive(31);
EXPECT_EQ(11 + 41 + 31, count);//接收一次消息就回调一次函数。
}
TEST(AdapterTest, Dump) {
FLAGS_enable_adapter_dump = true;
std::string temp_dir = std::getenv("TEST_TMPDIR");//dump输出路径
MyLocalizationAdapter adapter("local", "local_topic", 3, temp_dir);
localization::LocalizationEstimate loaded;
localization::LocalizationEstimate msg;
msg.mutable_header()->set_sequence_num(17);
adapter.OnReceive(msg);//接收时,即已经写入dump文件。
//从dump文件读取信息到loaded中。
apollo::common::util::GetProtoFromASCIIFile(temp_dir + "/local/17.pb.txt",
&loaded);
EXPECT_EQ(17, loaded.header().sequence_num());
msg.mutable_header()->set_sequence_num(23);
adapter.OnReceive(msg);
//同上。
apollo::common::util::GetProtoFromASCIIFile(temp_dir + "/local/23.pb.txt",
&loaded);
EXPECT_EQ(23, loaded.header().sequence_num());
}
common/adapters/adapter_manager.h
AdapterManager 类用于管理多个适配器,单例模式。所有的message/IO适配器均需要通过REGISTER_ADAPTER(name)在这里注册。所有的数据交互也通过AdapterManager来进行统一的管理。
由于是单例模式,AdapterManager只有静态成员函数。
加载默认config:
Init()
加载给定filename的config:
static void Init(const std::string &adapter_config_filename)
加载proto格式的配置,创建 Adapter对象:
static void Init(const AdapterManagerConfig &configs);
例如imu:
case AdapterConfig::IMU:
EnableImu(FLAGS_imu_topic, config.mode(),
config.message_history_limit());
break;
对所有的adapter对象执行observe()函数,调用每个Adapter自身的函数Observe()。observers_的元素由宏定义产生。:
static void Observe();
ROS节点回调函数:
static ros::Timer CreateTimer
(ros::Duration period,
void (T::*callback)(const ros::TimerEvent &),
T *obj, bool oneshot = false,
bool autostart = true)
common/vehicle_state/vehicle_state.h
VehicleState类是标识车辆状态信息的class。
主要包含线速度.角速度.加速度.齿轮状态.车辆坐标x,y,z
构造函数:
explicit VehicleState(const localization::LocalizationEstimate &localization);
只根据定位来初始化车辆信息。
VehicleState(const localization::LocalizationEstimate *localization,const canbus::Chassis *chassis);
根据定位信息和车辆底盘信息初始化。
成员函数:
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_**()
状态估计
根据当前信息估计t时刻后的车辆位置。注意,Apollo的状态估计比较简单,很多因素都没有考虑。所以1.0版本只能在简单道路上行驶。
Eigen::Vector2d EstimateFuturePosition(const double t) const;
Eigen::Vector2d ComputeCOMPosition(const double rear_to_com_distance) const;
给定后轮到质心的距离。 估计质心的位置(x,y)
测试代码:
TEST_F(VehicleStateTest, Accessors) {
VehicleState vehicle_state(&localization_, &chassis_);
//都是proto文件的内容。
EXPECT_DOUBLE_EQ(vehicle_state.x(), 357.51331791372041);
EXPECT_DOUBLE_EQ(vehicle_state.y(), 96.165912376788725);
EXPECT_DOUBLE_EQ(vehicle_state.heading(), -1.8388082455104939);
EXPECT_DOUBLE_EQ(vehicle_state.pitch(), -0.010712737572581465);
EXPECT_DOUBLE_EQ(vehicle_state.linear_velocity(), 3.0);
EXPECT_DOUBLE_EQ(vehicle_state.angular_velocity(), -0.0079623083093763921);
EXPECT_DOUBLE_EQ(vehicle_state.linear_acceleration(), -0.079383290718229638);
EXPECT_DOUBLE_EQ(vehicle_state.gear(), canbus::Chassis::GEAR_DRIVE);
}
TEST_F(VehicleStateTest, EstimateFuturePosition) {
VehicleState vehicle_state(&localization_, &chassis_);
//估计1s后的车辆位置
Eigen::Vector2d future_position = vehicle_state.EstimateFuturePosition(1.0);
EXPECT_NEAR(future_position[0], 356.707, 1e-3);
EXPECT_NEAR(future_position[1], 93.276, 1e-3);
//估计2s后的车辆位置
future_position = vehicle_state.EstimateFuturePosition(2.0);
EXPECT_NEAR(future_position[0], 355.879, 1e-3);
EXPECT_NEAR(future_position[1], 90.393, 1e-3);
}
common/monitor/monitor_buffer.h
MonitorBuffer主要用于缓存多条log的消息日志。将多个相同level的log组合到一起。避免冗余信息。 不同level的log,将不会被组合。但是相同level的log日志将被组合到一起。
构造函数
explicit MonitorBuffer(Monitor *monitor);
成员函数:
void PrintLog();
将monitor_msg_items_的尾部消息输出。
REG_MSG_TYPE(INFO);
注册INFO级别的log。
REG_MSG_TYPE(WARN);
REG_MSG_TYPE(ERROR);
REG_MSG_TYPE(FATAL);
void AddMonitorMsgItem(const MonitorMessageItem::LogLevel log_level,const std::string &msg);
添加一个msgitem:level+msg.
MonitorBuffer &operator<<(const T &msg)
重载<<运算符,将msg写入MonitorBuffer。level是当前的level_
void Publish();
调用monitor的成员函数Publish(),发布消息。
测试代码:
TEST_F(MonitorBufferTest, PrintLog) {
FLAGS_logtostderr = true;
FLAGS_v = 4;
{
testing::internal::CaptureStderr();
buffer_->PrintLog();
EXPECT_TRUE(testing::internal::GetCapturedStderr().empty());
//没有log日志,所以为空。
}
{
buffer_->INFO("INFO_msg");//写入"INFO_msg".
testing::internal::CaptureStderr();
buffer_->PrintLog();
EXPECT_NE(std::string::npos,//找到string,不为npos。
testing::internal::GetCapturedStderr().find("[INFO] INFO_msg"));
}
{
buffer_->ERROR("ERROR_msg");//写入"ERROR_msg"
testing::internal::CaptureStderr();
buffer_->PrintLog();
EXPECT_NE(std::string::npos,
testing::internal::GetCapturedStderr().find("ERROR_msg"));
}
{
buffer_->WARN("WARN_msg");
testing::internal::CaptureStderr();
buffer_->PrintLog();
EXPECT_NE(std::string::npos,
testing::internal::GetCapturedStderr().find("WARN_msg"));
}
{
buffer_->FATAL("FATAL_msg");
EXPECT_DEATH(buffer_->PrintLog(), "");
}
}
TEST_F(MonitorBufferTest, Operator) {
buffer_->ERROR() << "Hi";//使用<<运算符写入ERROR级别的"Hi"
EXPECT_EQ(MonitorMessageItem::ERROR, buffer_->level_);
ASSERT_EQ(1, buffer_->monitor_msg_items_.size());
auto &item = buffer_->monitor_msg_items_.back();
EXPECT_EQ(MonitorMessageItem::ERROR, item.first);
EXPECT_EQ("Hi", item.second);
//level_是最近一次调用的level。
(*buffer_) << " How"
<< " are"
<< " you";
EXPECT_EQ(MonitorMessageItem::ERROR, buffer_->level_);
ASSERT_EQ(1, buffer_->monitor_msg_items_.size());
EXPECT_EQ(MonitorMessageItem::ERROR, item.first);
EXPECT_EQ("Hi How are you", item.second);
buffer_->INFO() << 3 << "pieces";
EXPECT_EQ(MonitorMessageItem::INFO, buffer_->level_);
ASSERT_EQ(2, buffer_->monitor_msg_items_.size());
item = buffer_->monitor_msg_items_.back();
EXPECT_EQ(MonitorMessageItem::INFO, item.first);
EXPECT_EQ("3pieces", item.second);
const char *fake_input = nullptr;
EXPECT_TRUE(&(buffer_->INFO() << fake_input) == buffer_);
}
common/monitor/monitor.h
Monitor类主要用于收集各个模块的message信息,并发布到相关的topic中。
构造函数
explicit Monitor(const MonitorMessageItem::MessageSource &source)
: source_(source) {}
创建一个消息项,指定message来源哪一个模块。
成员函数:
virtual void Publish(const std::vector<MessageItem> &messages) const;
组合多条消息,并发布到相关的topic。
测试代码:
MonitorTest monitor(MonitorMessageItem::CONTROL);
std::vector<std::pair<MonitorMessageItem::LogLevel, std::string>> items{
{MonitorMessageItem::INFO, "info message"},
{MonitorMessageItem::WARN, "warn message"},
{MonitorMessageItem::ERROR, "error message"},
};
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是纯虚函数。这意味着每个模块需要重写多个纯虚函数接口才能实例化本模块。而重写的正是每个模块不同的地方。
虚函数接口:
virtual std::string Name() const = 0;
模块名称,进程名。
virtual int Spin();
初始化模块app的信息,并持续运行直到shutdown
virtual apollo::common::Status Init() = 0;
执行初始化加载配置文件和传感器数据等任务。
virtual apollo::common::Status Start() = 0;
开始执行模块任务。若由上层message到来触发,则执行与message相关的回调函数。
若由时间time触发,则调用时间处理回调函数。
virtual void Stop() = 0;
模块停止运行。在ros::shutdown()执行完毕后的清理工作。
virtual void ReportModuleStatus();
向HMI人机交互界面发送状态status码。
最后本文件还有一个宏定义方便各个模块运行。
每个模块是一个进程。
#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; \
}
其他文件夹的内容还没看,今天就先写这么多吧
总结
common的代码已经分析完毕。该目录下主要提供了各个模块公用的函数和class以及一些数学API还有公共的宏定义。
在Apollo 1.0中,common是整个框架的基础。
configs是配置文件加载。
adapters是数据交互的抽象接口。
math提供了数学几何api接口。
monitor提供监控log信息。
status提供各个模块工作状态。
time提供计时类。
util提供文件io管理功能。
vehicle_state提供车辆状态信息与预期状态估计。
以上是关于百度开源自动驾驶apollo源码分析的主要内容,如果未能解决你的问题,请参考以下文章
自动驾驶Apollo源码分析系统,CyberRT篇:简述CyberRT框架基础概念
自动驾驶Apollo源码分析系统,CyberRT篇:简述CyberRT框架基础概念
自动驾驶Apollo源码分析系统,CyberRT篇:简述CyberRT框架基础概念
自动驾驶 Apollo 源码分析系列,感知篇:感知融合代码的基本流程