Skip to content

Commit 183f121

Browse files
authored
feat: optimize control module safety management (#114)
1 parent 2508bde commit 183f121

18 files changed

+1126
-519
lines changed

modules/common/util/BUILD

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ cc_library(
6565
]
6666
)
6767

68+
cc_library(
69+
name = "debouncer",
70+
hdrs = ["debouncer.h"]
71+
)
72+
6873
cc_library(
6974
name = "future",
7075
hdrs = ["future.h"],
@@ -292,4 +297,14 @@ cc_test(
292297
],
293298
)
294299

300+
cc_test(
301+
name = "debouncer_test",
302+
size = "small",
303+
srcs = ["debouncer_test.cc"],
304+
deps = [
305+
":debouncer",
306+
"@com_google_googletest//:gtest_main",
307+
],
308+
)
309+
295310
cpplint()

modules/common/util/debouncer.h

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright 2025 WheelOS. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Created Date: 2026-01-03
16+
// Author: daohu527
17+
18+
#pragma once
19+
20+
#include <cstdint>
21+
22+
namespace apollo {
23+
namespace control {
24+
25+
/**
26+
* @class CounterDebouncer
27+
* @brief A counter-based signal debouncer.
28+
* It confirms a fault only when the internal counter reaches a specific
29+
* threshold.
30+
*/
31+
class CounterDebouncer {
32+
public:
33+
/**
34+
* @brief Constructor
35+
* @param threshold The number of consecutive fault detections required to
36+
* confirm a fault.
37+
*/
38+
explicit CounterDebouncer(uint32_t threshold)
39+
: threshold_(threshold), count_(0) {}
40+
41+
/**
42+
* @brief Update the debouncer state with a new sample.
43+
* @param is_fault Current status of the monitored signal.
44+
* @return True if the fault is confirmed (counter >= threshold).
45+
*/
46+
bool Update(bool is_fault) {
47+
if (is_fault) {
48+
if (count_ < threshold_) {
49+
count_++;
50+
}
51+
} else {
52+
// Immediate reset strategy: resets the counter to zero as soon as a
53+
// normal signal is received. This ensures high confidence for fault
54+
// recovery.
55+
count_ = 0;
56+
}
57+
return IsActive();
58+
}
59+
60+
/**
61+
* @brief Reset the internal counter to zero.
62+
*/
63+
void Reset() { count_ = 0; }
64+
65+
/**
66+
* @brief Check if the fault is currently active/confirmed.
67+
* @return True if the counter has reached the threshold.
68+
*/
69+
bool IsActive() const { return count_ >= threshold_ && threshold_ > 0; }
70+
71+
/**
72+
* @brief Get the current counter value (useful for telemetry).
73+
*/
74+
uint32_t count() const { return count_; }
75+
76+
private:
77+
uint32_t threshold_;
78+
uint32_t count_;
79+
};
80+
81+
} // namespace control
82+
} // namespace apollo
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
// Copyright 2025 WheelOS. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Created Date: 2026-01-03
16+
// Author: daohu527
17+
18+
#include "modules/common/util/debouncer.h"
19+
20+
#include <gtest/gtest.h>
21+
22+
namespace apollo {
23+
namespace control {
24+
25+
/**
26+
* @class CounterDebouncerTest
27+
* @brief Test suite class for managing the CounterDebouncer test context.
28+
*/
29+
class CounterDebouncerTest : public ::testing::Test {
30+
protected:
31+
// Setup logic can be added here if needed
32+
virtual void SetUp() {}
33+
virtual void TearDown() {}
34+
};
35+
36+
/**
37+
* @test Verify if the initial state is correct after object construction.
38+
*/
39+
TEST_F(CounterDebouncerTest, Initialization) {
40+
uint32_t threshold = 5;
41+
CounterDebouncer debouncer(threshold);
42+
43+
EXPECT_EQ(debouncer.count(), 0u);
44+
EXPECT_FALSE(debouncer.IsActive());
45+
}
46+
47+
/**
48+
* @test Verify the normal debouncing process until the threshold is reached.
49+
*/
50+
TEST_F(CounterDebouncerTest, NormalDebounceProcess) {
51+
uint32_t threshold = 3;
52+
CounterDebouncer debouncer(threshold);
53+
54+
// 1st fault sample: fault not yet confirmed
55+
EXPECT_FALSE(debouncer.Update(true));
56+
EXPECT_EQ(debouncer.count(), 1u);
57+
58+
// 2nd fault sample: fault not yet confirmed
59+
EXPECT_FALSE(debouncer.Update(true));
60+
EXPECT_EQ(debouncer.count(), 2u);
61+
62+
// 3rd fault sample: threshold reached, fault confirmed
63+
EXPECT_TRUE(debouncer.Update(true));
64+
EXPECT_EQ(debouncer.count(), 3u);
65+
EXPECT_TRUE(debouncer.IsActive());
66+
67+
// 4th fault sample: counter should stay capped at threshold and remain active
68+
EXPECT_TRUE(debouncer.Update(true));
69+
EXPECT_EQ(debouncer.count(), 3u);
70+
}
71+
72+
/**
73+
* @test Verify the Immediate Reset Strategy.
74+
* Even if the fault is close to being confirmed, a single normal signal
75+
* must reset the counter to zero immediately.
76+
*/
77+
TEST_F(CounterDebouncerTest, ImmediateResetOnNormalSignal) {
78+
CounterDebouncer debouncer(10);
79+
80+
// Simulate 9 consecutive fault samples
81+
for (int i = 0; i < 9; ++i) {
82+
debouncer.Update(true);
83+
}
84+
EXPECT_FALSE(debouncer.IsActive());
85+
EXPECT_EQ(debouncer.count(), 9u);
86+
87+
// Receive one normal signal
88+
EXPECT_FALSE(debouncer.Update(false));
89+
90+
// Counter must reset to zero immediately
91+
EXPECT_EQ(debouncer.count(), 0u);
92+
EXPECT_FALSE(debouncer.IsActive());
93+
}
94+
95+
/**
96+
* @test Verify the recovery logic after a fault has been confirmed.
97+
*/
98+
TEST_F(CounterDebouncerTest, RecoveryAfterConfirmation) {
99+
CounterDebouncer debouncer(2);
100+
101+
// Confirm the fault
102+
debouncer.Update(true);
103+
debouncer.Update(true);
104+
ASSERT_TRUE(debouncer.IsActive());
105+
106+
// Receive a normal signal; fault state should recover immediately
107+
EXPECT_FALSE(debouncer.Update(false));
108+
EXPECT_FALSE(debouncer.IsActive());
109+
EXPECT_EQ(debouncer.count(), 0u);
110+
}
111+
112+
/**
113+
* @test Verify the manual Reset function.
114+
*/
115+
TEST_F(CounterDebouncerTest, ManualReset) {
116+
CounterDebouncer debouncer(5);
117+
debouncer.Update(true);
118+
debouncer.Update(true);
119+
120+
debouncer.Reset();
121+
122+
EXPECT_EQ(debouncer.count(), 0u);
123+
EXPECT_FALSE(debouncer.IsActive());
124+
}
125+
126+
/**
127+
* @test Boundary condition: threshold is 0.
128+
* Based on the logic (threshold > 0), IsActive should always be false.
129+
*/
130+
TEST_F(CounterDebouncerTest, ZeroThresholdBoundary) {
131+
CounterDebouncer debouncer(0);
132+
133+
EXPECT_FALSE(debouncer.IsActive());
134+
EXPECT_FALSE(debouncer.Update(true));
135+
EXPECT_FALSE(debouncer.IsActive());
136+
}
137+
138+
/**
139+
* @test Boundary condition: threshold is 1.
140+
* Should trigger/confirm fault on the very first fault sample.
141+
*/
142+
TEST_F(CounterDebouncerTest, SingleSampleThreshold) {
143+
CounterDebouncer debouncer(1);
144+
145+
EXPECT_TRUE(debouncer.Update(true));
146+
EXPECT_TRUE(debouncer.IsActive());
147+
EXPECT_FALSE(debouncer.Update(false));
148+
}
149+
150+
} // namespace control
151+
} // namespace apollo

modules/control/BUILD

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ cc_library(
2828
"//modules/control/submodules:preprocessor_submodule_lib",
2929
"//modules/common_msgs/localization_msgs:localization_cc_proto",
3030
"//modules/common_msgs/planning_msgs:planning_cc_proto",
31+
"//modules/control/safety:safety_manager",
3132
"@com_github_gflags_gflags//:gflags",
3233
],
3334
)
@@ -65,7 +66,6 @@ install(
6566
data_dest = "control",
6667
data = [
6768
":runtime_data",
68-
"control.BUILD",
6969
],
7070
targets = [
7171
":libcontrol_component.so",
@@ -145,6 +145,7 @@ cc_test(
145145
data = ["//modules/control:test_data"],
146146
deps = [
147147
":control_component_lib",
148+
"//modules/common/util:util_lib",
148149
"@com_google_googletest//:gtest_main",
149150
],
150151
linkstatic = True,

modules/control/README_cn.md

Lines changed: 0 additions & 15 deletions
This file was deleted.

modules/control/control.BUILD

Lines changed: 0 additions & 11 deletions
This file was deleted.

modules/control/control.json

Lines changed: 0 additions & 51 deletions
This file was deleted.

0 commit comments

Comments
 (0)