1 #include "config.h"
2
3 #include "pid/ec/pid.hpp"
4 #include "pid/fan.hpp"
5 #include "pid/fancontroller.hpp"
6 #include "pid/pidcontroller.hpp"
7 #include "sensors/sensor.hpp"
8 #include "test/sensor_mock.hpp"
9 #include "test/zone_mock.hpp"
10
11 #include <cstdint>
12 #include <memory>
13 #include <string>
14 #include <vector>
15
16 #include <gmock/gmock.h>
17 #include <gtest/gtest.h>
18
19 namespace pid_control
20 {
21 namespace
22 {
23
24 using ::testing::_;
25 using ::testing::Return;
26 using ::testing::StrEq;
27
TEST(FanControllerTest,BoringFactoryTest)28 TEST(FanControllerTest, BoringFactoryTest)
29 {
30 // Verify the factory will properly build the FanPIDController in the
31 // boring (uninteresting) case.
32 ZoneMock z;
33
34 std::vector<std::string> inputs = {"fan0"};
35 ec::pidinfo initial;
36
37 std::unique_ptr<PIDController> p =
38 FanController::createFanPid(&z, "fan1", inputs, initial);
39 // Success
40 EXPECT_FALSE(p == nullptr);
41 }
42
TEST(FanControllerTest,VerifyFactoryFailsWithZeroInputs)43 TEST(FanControllerTest, VerifyFactoryFailsWithZeroInputs)
44 {
45 // A fan controller needs at least one input.
46
47 ZoneMock z;
48
49 std::vector<std::string> inputs = {};
50 ec::pidinfo initial;
51
52 std::unique_ptr<PIDController> p =
53 FanController::createFanPid(&z, "fan1", inputs, initial);
54 EXPECT_TRUE(p == nullptr);
55 }
56
TEST(FanControllerTest,InputProc_AllSensorsReturnZero)57 TEST(FanControllerTest, InputProc_AllSensorsReturnZero)
58 {
59 // If all your inputs are 0, return 0.
60
61 ZoneMock z;
62
63 std::vector<std::string> inputs = {"fan0", "fan1"};
64 ec::pidinfo initial;
65
66 std::unique_ptr<PIDController> p =
67 FanController::createFanPid(&z, "fan1", inputs, initial);
68 EXPECT_FALSE(p == nullptr);
69
70 EXPECT_CALL(z, getCachedValue(StrEq("fan0"))).WillOnce(Return(0));
71 EXPECT_CALL(z, getCachedValue(StrEq("fan1"))).WillOnce(Return(0));
72
73 EXPECT_EQ(0.0, p->inputProc());
74 }
75
TEST(FanControllerTest,InputProc_IfSensorNegativeIsIgnored)76 TEST(FanControllerTest, InputProc_IfSensorNegativeIsIgnored)
77 {
78 // A sensor value returning sub-zero is ignored as an error.
79 ZoneMock z;
80
81 std::vector<std::string> inputs = {"fan0", "fan1"};
82 ec::pidinfo initial;
83
84 std::unique_ptr<PIDController> p =
85 FanController::createFanPid(&z, "fan1", inputs, initial);
86 EXPECT_FALSE(p == nullptr);
87
88 EXPECT_CALL(z, getCachedValue(StrEq("fan0"))).WillOnce(Return(-1));
89 EXPECT_CALL(z, getCachedValue(StrEq("fan1"))).WillOnce(Return(-1));
90
91 EXPECT_EQ(0.0, p->inputProc());
92 }
93
TEST(FanControllerTest,InputProc_ChoosesMinimumValue)94 TEST(FanControllerTest, InputProc_ChoosesMinimumValue)
95 {
96 // Verify it selects the minimum value from its inputs.
97
98 ZoneMock z;
99
100 std::vector<std::string> inputs = {"fan0", "fan1", "fan2"};
101 ec::pidinfo initial;
102
103 std::unique_ptr<PIDController> p =
104 FanController::createFanPid(&z, "fan1", inputs, initial);
105 EXPECT_FALSE(p == nullptr);
106
107 EXPECT_CALL(z, getCachedValue(StrEq("fan0"))).WillOnce(Return(10.0));
108 EXPECT_CALL(z, getCachedValue(StrEq("fan1"))).WillOnce(Return(30.0));
109 EXPECT_CALL(z, getCachedValue(StrEq("fan2"))).WillOnce(Return(5.0));
110
111 EXPECT_EQ(5.0, p->inputProc());
112 }
113
114 // The direction is unused presently, but these tests validate the logic.
TEST(FanControllerTest,SetPtProc_SpeedChanges_VerifyDirection)115 TEST(FanControllerTest, SetPtProc_SpeedChanges_VerifyDirection)
116 {
117 // The fan direction defaults to neutral, because we have no data. Verify
118 // that after this point it appropriately will indicate speeding up or
119 // slowing down based on the RPM values specified.
120
121 ZoneMock z;
122
123 std::vector<std::string> inputs = {"fan0", "fan1"};
124 ec::pidinfo initial;
125
126 std::unique_ptr<PIDController> p =
127 FanController::createFanPid(&z, "fan1", inputs, initial);
128 EXPECT_FALSE(p == nullptr);
129 // Grab pointer for mocking.
130 FanController* fp = reinterpret_cast<FanController*>(p.get());
131
132 // Fanspeed starts are Neutral.
133 EXPECT_EQ(FanSpeedDirection::NEUTRAL, fp->getFanDirection());
134
135 // getMaxSetPointRequest returns a higher value than 0, so the fans should
136 // be marked as speeding up.
137 EXPECT_CALL(z, getMaxSetPointRequest()).WillOnce(Return(10.0));
138 EXPECT_EQ(10.0, p->setptProc());
139 EXPECT_EQ(FanSpeedDirection::UP, fp->getFanDirection());
140
141 // getMaxSetPointRequest returns a lower value than 10, so the fans should
142 // be marked as slowing down.
143 EXPECT_CALL(z, getMaxSetPointRequest()).WillOnce(Return(5.0));
144 EXPECT_EQ(5.0, p->setptProc());
145 EXPECT_EQ(FanSpeedDirection::DOWN, fp->getFanDirection());
146
147 // getMaxSetPointRequest returns the same value, so the fans should be
148 // marked as neutral.
149 EXPECT_CALL(z, getMaxSetPointRequest()).WillOnce(Return(5.0));
150 EXPECT_EQ(5.0, p->setptProc());
151 EXPECT_EQ(FanSpeedDirection::NEUTRAL, fp->getFanDirection());
152 }
153
TEST(FanControllerTest,OutputProc_VerifiesIfFailsafeEnabledInputIsIgnored)154 TEST(FanControllerTest, OutputProc_VerifiesIfFailsafeEnabledInputIsIgnored)
155 {
156 // Verify that if failsafe mode is enabled and the input value for the fans
157 // is below the failsafe minimum value, the input is not used and the fans
158 // are driven at failsafe RPM (this assumes STRICT_FAILSAFE_PWM is not set)
159
160 ZoneMock z;
161
162 std::vector<std::string> inputs = {"fan0", "fan1"};
163 ec::pidinfo initial;
164
165 std::unique_ptr<PIDController> p =
166 FanController::createFanPid(&z, "fan1", inputs, initial);
167 EXPECT_FALSE(p == nullptr);
168
169 EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(true));
170 EXPECT_CALL(z, getFailSafePercent()).WillOnce(Return(75.0));
171
172 int64_t timeout = 0;
173 std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout);
174 std::unique_ptr<Sensor> s2 = std::make_unique<SensorMock>("fan1", timeout);
175 // Grab pointers for mocking.
176 SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get());
177 SensorMock* sm2 = reinterpret_cast<SensorMock*>(s2.get());
178
179 EXPECT_CALL(z, getRedundantWrite())
180 .WillOnce(Return(false))
181 .WillOnce(Return(false));
182 EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
183 EXPECT_CALL(*sm1, write(0.75, false, _));
184 EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get()));
185 EXPECT_CALL(*sm2, write(0.75, false, _));
186
187 // This is a fan PID, so calling outputProc will try to write this value
188 // to the sensors.
189
190 // Setting 50%, will end up being 75% because the sensors are in failsafe
191 // mode.
192 p->outputProc(50.0);
193 }
194
TEST(FanControllerTest,OutputProc_BehavesAsExpected)195 TEST(FanControllerTest, OutputProc_BehavesAsExpected)
196 {
197 // Verifies that when the system is not in failsafe mode, the input value
198 // to outputProc is used to drive the sensors (fans).
199
200 ZoneMock z;
201
202 std::vector<std::string> inputs = {"fan0", "fan1"};
203 ec::pidinfo initial;
204
205 std::unique_ptr<PIDController> p =
206 FanController::createFanPid(&z, "fan1", inputs, initial);
207 EXPECT_FALSE(p == nullptr);
208
209 EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(false));
210
211 int64_t timeout = 0;
212 std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout);
213 std::unique_ptr<Sensor> s2 = std::make_unique<SensorMock>("fan1", timeout);
214 // Grab pointers for mocking.
215 SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get());
216 SensorMock* sm2 = reinterpret_cast<SensorMock*>(s2.get());
217
218 EXPECT_CALL(z, getRedundantWrite())
219 .WillOnce(Return(false))
220 .WillOnce(Return(false));
221 EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
222 EXPECT_CALL(*sm1, write(0.5, false, _));
223 EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get()));
224 EXPECT_CALL(*sm2, write(0.5, false, _));
225
226 // This is a fan PID, so calling outputProc will try to write this value
227 // to the sensors.
228 p->outputProc(50.0);
229 }
230
TEST(FanControllerTest,OutputProc_VerifyFailSafeWhenInputHigher)231 TEST(FanControllerTest, OutputProc_VerifyFailSafeWhenInputHigher)
232 {
233 // If STRICT_FAILSAFE_PWM flag is NOT defined and the requested output is
234 // higher than the failsafe value, then use the value provided to outputProc
235 //
236 // If STRICT_FAILSAFE_PWM is defined, we expect the FailSafe PWM to be
237 // capped to the failsafe PWM, and not go higher than that.
238
239 ZoneMock z;
240
241 std::vector<std::string> inputs = {"fan0"};
242 ec::pidinfo initial;
243 const double failsafePWM = 75.0;
244
245 std::unique_ptr<PIDController> p =
246 FanController::createFanPid(&z, "fan1", inputs, initial);
247 EXPECT_FALSE(p == nullptr);
248
249 EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(true));
250 EXPECT_CALL(z, getFailSafePercent()).WillOnce(Return(failsafePWM));
251
252 int64_t timeout = 0;
253 std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout);
254 // Grab pointer for mocking.
255 SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get());
256
257 double percent = 80;
258
259 EXPECT_CALL(z, getRedundantWrite()).WillOnce(Return(false));
260 EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
261 #ifdef STRICT_FAILSAFE_PWM
262 double failsafeValue = failsafePWM / 100;
263 EXPECT_CALL(*sm1, write(failsafeValue, false, _));
264 #else
265 // Converting from double to double for expectation.
266 double value = percent / 100;
267 EXPECT_CALL(*sm1, write(value, false, _));
268 #endif
269
270 // This is a fan PID, so calling outputProc will try to write this value
271 // to the sensors.
272 p->outputProc(percent);
273 }
274
TEST(FanControllerTest,OutputProc_VerifyRedundantWrites)275 TEST(FanControllerTest, OutputProc_VerifyRedundantWrites)
276 {
277 /* when a zone indicates that redundant writes are enabled
278 * make sure the fan controller honors this by forcing a sensor write
279 */
280 ZoneMock z;
281
282 std::vector<std::string> inputs = {"fan0", "fan1"};
283 ec::pidinfo initial;
284
285 std::unique_ptr<PIDController> p =
286 FanController::createFanPid(&z, "fan1", inputs, initial);
287 EXPECT_FALSE(p == nullptr);
288
289 EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(false));
290
291 int64_t timeout = 0;
292 std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout);
293 std::unique_ptr<Sensor> s2 = std::make_unique<SensorMock>("fan1", timeout);
294 // Grab pointers for mocking.
295 SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get());
296 SensorMock* sm2 = reinterpret_cast<SensorMock*>(s2.get());
297
298 EXPECT_CALL(z, getRedundantWrite())
299 .WillOnce(Return(true))
300 .WillOnce(Return(true));
301 EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
302 EXPECT_CALL(*sm1, write(0.5, true, _));
303 EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get()));
304 EXPECT_CALL(*sm2, write(0.5, true, _));
305
306 // This is a fan PID, so calling outputProc will try to write this value
307 // to the sensors.
308 p->outputProc(50.0);
309 }
310
311 } // namespace
312 } // namespace pid_control
313