每次遇見複雜的事情總是在先尋找一種簡單明了的方式進行研究,用一種淺顯易懂的方式來表達。
今天繼續apollo代碼中control子產品的總結。
好了,還是先看下總架構圖吧:

回憶下上次的代碼總結。如下:
int ApolloApp::Spin() {
ros::AsyncSpinner spinner(callback_thread_num_); ///開消息線程
auto status = Init(); ///子產品初始化(由子類具體重寫的)
if (!status.ok()) {
AERROR << Name() << " Init failed: " << status;
return -1;
}
status = Start(); ///子產品開啟(由子類具體重寫的)
if (!status.ok()) {
AERROR << Name() << " Start failed: " << status;
return -2;
}
ExportFlags(); ///輸出一些flag參數
spinner.start(); ///消息線程開啟
ros::waitForShutdown(); ///消息循環處理并檢測關閉
Stop(); ///退出(由子類具體重寫的)
AINFO << Name() << " exited.";
return 0;
}
可以說百度的apollo代碼架構非正常範,從上面的代碼結構來看,control子產品就是包括:開消息線程---->初始化子產品---->開啟---->輸出flag參數---->消息處理開啟---->循環處理并檢測關閉---->關閉。但是有些步驟是比較複雜的,上次總結僅僅針對初始化,那麼今天,開始總結下開始子產品。
開始子產品主要的流程在檔案control.cc中。下面來看。
///control子產品開啟
Status Control::Start() {
// set initial vehicle state by cmd
// need to sleep, because advertised channel is not ready immediately
// simple test shows a short delay of 80 ms or so
AINFO << "Control resetting vehicle state, sleeping for 1000 ms ...";
usleep(1000 * 1000); ///先休眠一會
// should init_vehicle first, let car enter work status, then use status msg
// trigger control
AINFO << "Control default driving action is "
<< DrivingAction_Name(control_conf_.action()); ///檢視驅動模式
pad_msg_.set_action(control_conf_.action()); ///設定踏闆驅動模式
timer_ = AdapterManager::CreateTimer(
ros::Duration(control_conf_.control_period()), &Control::OnTimer, this); ///啟用定時器做消息處理-間隔為0.01s
AINFO << "Control init done!";
common::monitor::MonitorBuffer buffer(&monitor_); ///日志緩存
buffer.INFO("control started");
return Status::OK();
}
上面的start()函數中主要講一下開啟事件定時器, AdapterManager::CreateTimer(ros::Duration(control_conf_.control_period()), &Control::OnTimer, this);
定時器時間間隔為control_period=0.01s,OnTimer将指向具體事件。
void Control::OnTimer(const ros::TimerEvent &) {
double start_timestamp = Clock::NowInSeconds(); ///擷取目前開始時刻
ControlCommand control_command; ///聲明一個指令類
Status status = ProduceControlCommand(&control_command); ///産生指令
AERROR_IF(!status.ok()) << "Failed to produce control command:"
<< status.error_message();
double end_timestamp = Clock::NowInSeconds(); ///擷取結束時刻
if (pad_received_) {
control_command.mutable_pad_msg()->CopyFrom(pad_msg_);
pad_received_ = false;
} ///将産生的新指令移送至緩存
const double time_diff_ms = (end_timestamp - start_timestamp) * 1000; ///計算産生指令所用的時間
control_command.mutable_latency_stats()->set_total_time_ms(time_diff_ms);
ADEBUG << "control cycle time is: " << time_diff_ms << " ms.";
status.Save(control_command.mutable_header()->mutable_status());
SendCmd(&control_command); ///發送指令
}
相關注釋已經寫在了代碼中,就不一行一行解釋了。上面的代碼段中主要包含産生控制指令接口。
ProduceControlCommand(&control_command);這個函數稍微長一點,但還是一些檢查處理資訊。如下:
///一下為control子產品計算控制指令
Status Control::ProduceControlCommand(ControlCommand *control_command) {
Status status = CheckInput(); ///檢查所需輸入信号是否正常
// check data
if (!status.ok()) {
AERROR_EVERY(100) << "Control input data failed: "
<< status.error_message();
estop_ = true;
} else {
Status status_ts = CheckTimestamp(); ///檢查時間撮
if (!status_ts.ok()) {
AERROR << "Input messages timeout";
estop_ = true;
status = status_ts;
}
}
// check estop
estop_ = estop_ || trajectory_.estop().is_estop();
// if planning set estop, then no control process triggered
if (!estop_) {
if (chassis_.driving_mode() == Chassis::COMPLETE_MANUAL) {
controller_agent_.Reset(); ///控制器進行複位。
AINFO_EVERY(100) << "Reset Controllers in Manual Mode";
}
auto debug = control_command->mutable_debug()->mutable_input_debug();
debug->mutable_localization_header()->CopyFrom(localization_.header());
debug->mutable_canbus_header()->CopyFrom(chassis_.header());
debug->mutable_trajectory_header()->CopyFrom(trajectory_.header());
///控制子產品的具體算法在controller_agent_中,這裡相當于調用一個控制器接口。
Status status_compute = controller_agent_.ComputeControlCommand(
&localization_, &chassis_, &trajectory_, control_command);
if (!status_compute.ok()) {
AERROR << "Control main function failed"
<< " with localization: " << localization_.ShortDebugString()
<< " with chassis: " << chassis_.ShortDebugString()
<< " with trajectory: " << trajectory_.ShortDebugString()
<< " with cmd: " << control_command->ShortDebugString()
<< " status:" << status_compute.error_message();
estop_ = true;
status = status_compute;
}
}
/**
* 以下代碼可以解釋為程式運作異常,estop_為false,程式強制将控制輸出量置為0,即讓車輛原地停止。
*/
if (estop_) {
AWARN_EVERY(100) << "Estop triggered! No control core method executed!";
// set Estop command
control_command->set_speed(0);
control_command->set_throttle(0);
control_command->set_brake(control_conf_.soft_estop_brake());
control_command->set_gear_location(Chassis::GEAR_DRIVE);
}
// check signal
///将車輛控制信号中的軌迹決策存儲起來。
if (trajectory_.decision().has_vehicle_signal()) {
control_command->mutable_signal()->CopyFrom(
trajectory_.decision().vehicle_signal());
}
return status;
}
ProduceControlCommand代碼段先要檢查下資料通道是否正常輸入資料,再檢查資料幀的時間搓。這裡需要注意的是,代碼段中會檢查兩個資訊資料接受的狀态,不僅僅是control自身資料接收情況,還包括上遊子產品中軌迹輸出的狀态。通過estop_參數來判斷車輛的駕駛模式(手動還是自動)。當然正常情況下肯定是自動喽,前面的程式隻要沒有異常都會很自然設定成為自動駕駛,後面就開始自動駕駛的代碼,但是假若車輛在運作當中出現異常,代碼中也進行了相關設定,将控制指令中的目标參數均置0。
if (estop_) {
AWARN_EVERY(100) << "Estop triggered! No control core method executed!";
// set Estop command
control_command->set_speed(0);
control_command->set_throttle(0);
control_command->set_brake(control_conf_.soft_estop_brake());
control_command->set_gear_location(Chassis::GEAR_DRIVE);
}
其實上面這段代碼我們主要看的是下面的代碼
if (!estop_) {
.........
}
這部分主要是對控制器進行操作,當設定好驅動模式後,就調用Status status_compute = controller_agent_.ComputeControlCommand( &localization_, &chassis_, &trajectory_, control_command );這個接口進行計算。這個函數調用的是算法層面的代碼,具展現在先不介紹了,後續補充。
到此, Status status = ProduceControlCommand(&control_command);的流程就算結束了,我們繼續回到定時器函數往下看代碼。
計算完控制指令,會将pad_received_資訊儲存起來。最後将計算資料釋出出去。
SendCmd(&control_command);
具體如下:
void Control::SendCmd(ControlCommand *control_command) {
// set header
AdapterManager::FillControlCommandHeader(Name(), control_command); ///添加控制指令幀的頭資訊
ADEBUG << control_command->ShortDebugString(); ///列印資訊
if (FLAGS_is_control_test_mode) {
ADEBUG << "Skip publish control command in test mode";
return;
} ///若是在測試模式下,将不釋出控制指令
AdapterManager::PublishControlCommand(*control_command); ///釋出控制指令
}
這段代碼也很容易了解了。好了,今天就先總結到這裡,其實開始步驟還沒有總結完,下一次再接着總結。
本文僅僅針對子產品start()步驟進行了簡單梳理,下一篇會接着總結。歡迎關注。
剛剛學習apollo,難免寫的不準确,歡迎大家指正。