@@ -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
99110void 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
194197void SafetyManager::ApplySafetyPolicy (ControlCommand* cmd) {
198+ Arbitrate ();
199+
195200 if (current_state_ == SafetyState::kNormal ) return ;
196201
197202 switch (current_state_) {
0 commit comments