Skip to content

Commit 1c0dd12

Browse files
committed
fix: improve control safety
Optimize error handling; when an error occurs, the calculation must be skipped.
1 parent d3ba72c commit 1c0dd12

File tree

5 files changed

+202
-143
lines changed

5 files changed

+202
-143
lines changed

modules/control/conf/control_conf.pb.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,9 @@ lat_controller_conf {
103103
}
104104
reverse_leadlag_conf {
105105
innerstate_saturation_level: 3000
106-
alpha: 1.0
107-
beta: 1.0
108-
tau: 0.0
106+
alpha: 0.7
107+
beta: 0.6
108+
tau: 0.01
109109
}
110110
steer_mrac_conf {
111111
mrac_model_order: 1

modules/control/control_component.cc

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,12 @@ Status ControlComponent::ProduceControlCommand(
140140

141141
// 2. Safety Pre-Check (Input Validation)
142142
// Checks timestamps, sensor health, and trajectory integrity.
143-
bool critical_failure = safety_manager_->CheckInput(local_view_);
143+
SafetyResult input_res = safety_manager_->PreCheck(local_view_);
144144

145145
Status status = Status::OK();
146+
bool use_previous_cmd = false;
146147

147-
if (!critical_failure) {
148+
if (!input_res.must_bypass) {
148149
// 3. Core Control Computation
149150
// Only run algorithms if system is relatively healthy.
150151
status = controller_agent_.ComputeControlCommand(
@@ -154,21 +155,32 @@ Status ControlComponent::ProduceControlCommand(
154155
if (status.ok()) {
155156
// 4. Safety Post-Check (Output Validation)
156157
// Sanity check on computed commands (e.g., jerk, steering rate).
157-
safety_manager_->CheckOutput(*control_command, previous_cmd_);
158+
SafetyResult output_res =
159+
safety_manager_->PostCheck(*control_command, previous_cmd_);
160+
if (output_res.need_freeze) {
161+
use_previous_cmd = true;
162+
status = Status(ErrorCode::CONTROL_COMPUTE_ERROR,
163+
"Output Limits Violated (Freeze)");
164+
}
158165
} else {
159-
AERROR_EVERY(100) << "Controller Agent Compute failed: "
160-
<< status.error_message();
161166
// Logic failure (e.g., solver error) is treated as an internal fault.
162-
controller_agent_.Reset();
167+
status = Status(ErrorCode::CONTROL_COMPUTE_ERROR,
168+
"Controller Agent Compute failed");
169+
use_previous_cmd = true;
163170
}
164171
} else {
165-
// skip control algorithm on input failure.
166-
// Set status to error, but proceed to Safety Policy to generate Estop cmd.
167-
status =
168-
Status(ErrorCode::CONTROL_COMPUTE_ERROR, "Safety Input Check Failed");
172+
use_previous_cmd = true;
173+
status = Status(ErrorCode::CONTROL_COMPUTE_ERROR,
174+
"Input Physics Missing (Bypass)");
169175
}
170176

171-
// 5. Apply Safety Policy (The Override)
177+
// 5. Freeze Strategy
178+
if (use_previous_cmd) {
179+
*control_command = previous_cmd_;
180+
controller_agent_.Reset();
181+
}
182+
183+
// 6. Apply Safety Policy (The Override)
172184
// This is the final authority. It overrides the command based on the FSM
173185
// state (Normal, SoftStop, HardEstop).
174186
safety_manager_->ApplySafetyPolicy(control_command);
@@ -240,6 +252,10 @@ bool ControlComponent::Proc() {
240252
// but the Reset command ensures no actuator conflict.
241253
}
242254

255+
if (pad_msg != nullptr) {
256+
control_command.mutable_pad_msg()->CopyFrom(pad_msg_);
257+
}
258+
243259
// 4. Header & Diagnostics
244260
if (!status.ok()) {
245261
AERROR_EVERY(100) << "Control Error: " << status.error_message();

modules/control/safety/safety_manager.cc

Lines changed: 54 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -35,60 +35,71 @@ bool SafetyManager::Init(const ControlConf& conf) {
3535
active_faults_.clear();
3636
}
3737

38-
trajectory_loss_debouncer_ = std::make_unique<CounterDebouncer>(3);
38+
// Control logic runs at 100Hz (10ms/cycle).
39+
// Planning logic runs at 10Hz (100ms/cycle).
40+
// Setting debouncer to 30 allows for a maximum of 300ms (3 missing planning
41+
// frames) before triggering a trajectory loss fault.
42+
trajectory_loss_debouncer_ = std::make_unique<CounterDebouncer>(30);
43+
output_fault_debouncer_ = std::make_unique<CounterDebouncer>(3);
3944
return true;
4045
}
4146

42-
bool SafetyManager::CheckInput(const LocalView& view) {
47+
SafetyResult SafetyManager::PreCheck(const LocalView& view) {
4348
std::lock_guard<std::mutex> lk(mutex_);
44-
// Clear transient faults from the previous cycle
4549
active_faults_.clear();
50+
SafetyResult result;
4651

47-
CheckPlanningTrajectory(view);
48-
CheckKinematics(view);
52+
CheckPlanningTrajectory(view, &result);
4953

50-
// Determine if state transition is needed based on inputs
51-
Arbitrate();
54+
CheckKinematics(view, &result);
5255

53-
// Return true if the system is in a critical state where
54-
// running the control algorithm is futile or dangerous.
55-
return (current_state_ == SafetyState::kHardEstop ||
56-
current_state_ == SafetyState::kFatal);
56+
return result;
5757
}
5858

59-
void SafetyManager::CheckOutput(const ControlCommand& cmd,
60-
const ControlCommand& prev_cmd) {
59+
SafetyResult SafetyManager::PostCheck(const ControlCommand& cmd,
60+
const ControlCommand& prev_cmd) {
6161
std::lock_guard<std::mutex> lk(mutex_);
62-
CheckControlOutputDynamic(cmd, prev_cmd);
62+
SafetyResult result;
6363

64-
// Re-arbitrate with output faults included
65-
Arbitrate();
64+
CheckControlOutputDynamic(cmd, prev_cmd, &result);
65+
66+
return result;
6667
}
6768

68-
void SafetyManager::CheckPlanningTrajectory(const LocalView& view) {
69-
// Check if trajectory is empty
70-
bool is_traj_empty = view.trajectory().trajectory_point().empty();
71-
if (trajectory_loss_debouncer_->Update(is_traj_empty)) {
72-
ReportFault(CONTROL_FAULT_ID(0x02, 0x03), FaultLevel::LEVEL_SOFT_STOP,
73-
FaultSource::SOURCE_INPUT);
69+
void SafetyManager::CheckPlanningTrajectory(const LocalView& view,
70+
SafetyResult* result) {
71+
bool traj_invalid = false;
72+
73+
if (view.trajectory().trajectory_point().empty()) {
74+
traj_invalid = true;
75+
} else {
76+
for (const auto& pt : view.trajectory().trajectory_point()) {
77+
if (std::isnan(pt.v()) || std::isnan(pt.a()) || std::isinf(pt.v()) ||
78+
std::isinf(pt.a())) {
79+
traj_invalid = true;
80+
break;
81+
}
82+
}
7483
}
7584

76-
if (is_traj_empty) return;
85+
if (traj_invalid) {
86+
result->must_bypass = true;
87+
}
7788

78-
// Physical Sanity Check (NaN/Inf protection)
79-
// This prevents the PID/MPC solvers from crashing or outputting garbage
80-
const auto& pt = view.trajectory().trajectory_point(0);
81-
if (std::isnan(pt.v()) || std::isnan(pt.a()) || std::isinf(pt.v())) {
82-
ReportFault(CONTROL_FAULT_ID(0x02, 0x04), FaultLevel::LEVEL_HARD_ESTOP,
83-
FaultSource::SOURCE_INPUT);
89+
if (trajectory_loss_debouncer_->Update(traj_invalid)) {
90+
ReportFault(0x0203, FaultLevel::LEVEL_SOFT_STOP, FaultSource::SOURCE_INPUT);
8491
}
8592
}
8693

87-
void SafetyManager::CheckKinematics(const LocalView& view) {
94+
void SafetyManager::CheckKinematics(const LocalView& view,
95+
SafetyResult* result) {
8896
if (view.trajectory().trajectory_point().empty()) return;
8997

9098
const double kEpsilon = 0.001;
9199
auto first_pt = view.trajectory().trajectory_point(0);
100+
101+
// Check if the gear selection conflicts with the intended speed (e.g., a
102+
// forward gear is planned for reverse speed).
92103
if (view.chassis().gear_location() == canbus::Chassis::GEAR_DRIVE &&
93104
first_pt.v() < -kEpsilon) {
94105
ReportFault(0x0205, FaultLevel::LEVEL_HARD_ESTOP,
@@ -97,30 +108,21 @@ void SafetyManager::CheckKinematics(const LocalView& view) {
97108
}
98109

99110
void SafetyManager::CheckControlOutputDynamic(const ControlCommand& cmd,
100-
const ControlCommand& prev_cmd) {
101-
// 1. Steering Rate Protection
102-
// Prevents violent steering movements that could destabilize the vehicle
103-
double dt = conf_.control_period();
104-
if (dt <= 0.0) {
105-
dt = 0.01; // Avoid division by zero
106-
}
107-
108-
double steer_diff =
109-
std::abs(cmd.steering_target() - prev_cmd.steering_target());
110-
double steer_rate = steer_diff / dt;
111-
111+
const ControlCommand& prev_cmd,
112+
SafetyResult* result) {
112113
const auto& vehicle_param =
113114
common::VehicleConfigHelper::GetConfig().vehicle_param();
114115

115-
if (steer_rate > vehicle_param.max_steer_angle_rate()) {
116-
ReportFault(CONTROL_FAULT_ID(0x03, 0x01), FaultLevel::LEVEL_HARD_ESTOP,
117-
FaultSource::SOURCE_OUTPUT);
118-
AERROR << "Steer rate limit violation: " << steer_rate;
116+
// 1. Physical inspection logic: Acceleration over-limit check
117+
bool is_accel_insane =
118+
std::abs(cmd.acceleration()) > vehicle_param.max_acceleration();
119+
120+
if (is_accel_insane) {
121+
result->need_freeze = true; // Intercept this frame
119122
}
120123

121-
// 2. Acceleration Sanity Check
122-
if (std::abs(cmd.acceleration()) > vehicle_param.max_acceleration()) {
123-
ReportFault(CONTROL_FAULT_ID(0x03, 0x02), FaultLevel::LEVEL_HARD_ESTOP,
124+
if (output_fault_debouncer_->Update(is_accel_insane)) {
125+
ReportFault(0x0302, FaultLevel::LEVEL_HARD_ESTOP,
124126
FaultSource::SOURCE_OUTPUT);
125127
}
126128
}
@@ -146,6 +148,7 @@ void SafetyManager::Arbitrate() {
146148

147149
// Find the highest severity level among all active faults
148150
for (const auto& event : active_faults_) {
151+
AINFO << event.DebugString();
149152
if (event.has_level() && event.level() > max_level) {
150153
max_level = event.level();
151154
}
@@ -192,6 +195,8 @@ void SafetyManager::ExecuteHardEstop(ControlCommand* cmd) {
192195
}
193196

194197
void SafetyManager::ApplySafetyPolicy(ControlCommand* cmd) {
198+
Arbitrate();
199+
195200
if (current_state_ == SafetyState::kNormal) return;
196201

197202
switch (current_state_) {

modules/control/safety/safety_manager.h

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@
3434
namespace apollo {
3535
namespace control {
3636

37+
struct SafetyResult {
38+
bool must_bypass = false;
39+
bool need_freeze = false;
40+
};
41+
3742
class SafetyManager {
3843
public:
3944
SafetyManager() = default;
@@ -43,11 +48,12 @@ class SafetyManager {
4348

4449
// Phase 1: Pre-Computation Check
4550
// Returns true if critical failure detected (Skip Compute)
46-
bool CheckInput(const LocalView& view);
51+
SafetyResult PreCheck(const LocalView& view);
4752

4853
// Phase 2: Post-Computation Check
4954
// Validates the calculated command
50-
void CheckOutput(const ControlCommand& cmd, const ControlCommand& prev_cmd);
55+
SafetyResult PostCheck(const ControlCommand& cmd,
56+
const ControlCommand& prev_cmd);
5157

5258
// Phase 3: Policy Application
5359
// Overrides the command based on current SafetyState
@@ -59,10 +65,11 @@ class SafetyManager {
5965
SafetyState GetState() const { return current_state_; }
6066

6167
private:
62-
void CheckPlanningTrajectory(const LocalView& view);
63-
void CheckKinematics(const LocalView& view);
68+
void CheckPlanningTrajectory(const LocalView& view, SafetyResult* result);
69+
void CheckKinematics(const LocalView& view, SafetyResult* result);
6470
void CheckControlOutputDynamic(const ControlCommand& cmd,
65-
const ControlCommand& prev_cmd);
71+
const ControlCommand& prev_cmd,
72+
SafetyResult* result);
6673
void Arbitrate();
6774

6875
void ExecuteSoftStop(ControlCommand* cmd);
@@ -82,6 +89,7 @@ class SafetyManager {
8289

8390
// Debouncers for signal stability
8491
std::unique_ptr<CounterDebouncer> trajectory_loss_debouncer_;
92+
std::unique_ptr<CounterDebouncer> output_fault_debouncer_;
8593
};
8694

8795
} // namespace control

0 commit comments

Comments
 (0)