xref: /openbmc/phosphor-pid-control/test/pid_fancontroller_unittest.cpp (revision 6df8bb5086b29c43217596b194dda7fbc4e3ec4a)
19366089aSPatrick Rudolph #include "config.h"
29366089aSPatrick Rudolph 
3*6df8bb50SJames Zheng #include "failsafeloggers/builder.hpp"
4*6df8bb50SJames Zheng #include "failsafeloggers/failsafe_logger.hpp"
5*6df8bb50SJames Zheng #include "failsafeloggers/failsafe_logger_utility.hpp"
6de74542cSJosh Lehan #include "pid/ec/logging.hpp"
7566a1518SPatrick Venture #include "pid/ec/pid.hpp"
8da4a5dd1SPatrick Venture #include "pid/fancontroller.hpp"
9566a1518SPatrick Venture #include "test/sensor_mock.hpp"
10566a1518SPatrick Venture #include "test/zone_mock.hpp"
11566a1518SPatrick Venture 
12da4a5dd1SPatrick Venture #include <string>
13da4a5dd1SPatrick Venture #include <vector>
14da4a5dd1SPatrick Venture 
15da4a5dd1SPatrick Venture #include <gmock/gmock.h>
16da4a5dd1SPatrick Venture #include <gtest/gtest.h>
17da4a5dd1SPatrick Venture 
18a076487aSPatrick Venture namespace pid_control
19a076487aSPatrick Venture {
20a076487aSPatrick Venture namespace
21a076487aSPatrick Venture {
22a076487aSPatrick Venture 
23da4a5dd1SPatrick Venture using ::testing::_;
24566a1518SPatrick Venture using ::testing::DoubleEq;
25566a1518SPatrick Venture using ::testing::Invoke;
26566a1518SPatrick Venture using ::testing::Return;
27566a1518SPatrick Venture using ::testing::StrEq;
28566a1518SPatrick Venture 
TEST(FanControllerTest,BoringFactoryTest)29da4a5dd1SPatrick Venture TEST(FanControllerTest, BoringFactoryTest)
30da4a5dd1SPatrick Venture {
31566a1518SPatrick Venture     // Verify the factory will properly build the FanPIDController in the
32566a1518SPatrick Venture     // boring (uninteresting) case.
33566a1518SPatrick Venture     ZoneMock z;
34566a1518SPatrick Venture 
35566a1518SPatrick Venture     std::vector<std::string> inputs = {"fan0"};
36566a1518SPatrick Venture     ec::pidinfo initial;
37566a1518SPatrick Venture 
38566a1518SPatrick Venture     std::unique_ptr<PIDController> p =
39563a356fSPatrick Venture         FanController::createFanPid(&z, "fan1", inputs, initial);
40566a1518SPatrick Venture     // Success
41566a1518SPatrick Venture     EXPECT_FALSE(p == nullptr);
42566a1518SPatrick Venture }
43566a1518SPatrick Venture 
TEST(FanControllerTest,VerifyFactoryFailsWithZeroInputs)44da4a5dd1SPatrick Venture TEST(FanControllerTest, VerifyFactoryFailsWithZeroInputs)
45da4a5dd1SPatrick Venture {
46566a1518SPatrick Venture     // A fan controller needs at least one input.
47566a1518SPatrick Venture 
48566a1518SPatrick Venture     ZoneMock z;
49566a1518SPatrick Venture 
50566a1518SPatrick Venture     std::vector<std::string> inputs = {};
51566a1518SPatrick Venture     ec::pidinfo initial;
52566a1518SPatrick Venture 
53566a1518SPatrick Venture     std::unique_ptr<PIDController> p =
54563a356fSPatrick Venture         FanController::createFanPid(&z, "fan1", inputs, initial);
55566a1518SPatrick Venture     EXPECT_TRUE(p == nullptr);
56566a1518SPatrick Venture }
57566a1518SPatrick Venture 
TEST(FanControllerTest,InputProc_AllSensorsReturnZero)58da4a5dd1SPatrick Venture TEST(FanControllerTest, InputProc_AllSensorsReturnZero)
59da4a5dd1SPatrick Venture {
60566a1518SPatrick Venture     // If all your inputs are 0, return 0.
61566a1518SPatrick Venture 
62566a1518SPatrick Venture     ZoneMock z;
63566a1518SPatrick Venture 
64566a1518SPatrick Venture     std::vector<std::string> inputs = {"fan0", "fan1"};
65566a1518SPatrick Venture     ec::pidinfo initial;
66566a1518SPatrick Venture 
67566a1518SPatrick Venture     std::unique_ptr<PIDController> p =
68563a356fSPatrick Venture         FanController::createFanPid(&z, "fan1", inputs, initial);
69566a1518SPatrick Venture     EXPECT_FALSE(p == nullptr);
70566a1518SPatrick Venture 
71566a1518SPatrick Venture     EXPECT_CALL(z, getCachedValue(StrEq("fan0"))).WillOnce(Return(0));
72566a1518SPatrick Venture     EXPECT_CALL(z, getCachedValue(StrEq("fan1"))).WillOnce(Return(0));
73566a1518SPatrick Venture 
74563a356fSPatrick Venture     EXPECT_EQ(0.0, p->inputProc());
75566a1518SPatrick Venture }
76566a1518SPatrick Venture 
TEST(FanControllerTest,InputProc_IfSensorNegativeIsIgnored)77da4a5dd1SPatrick Venture TEST(FanControllerTest, InputProc_IfSensorNegativeIsIgnored)
78da4a5dd1SPatrick Venture {
79566a1518SPatrick Venture     // A sensor value returning sub-zero is ignored as an error.
80566a1518SPatrick Venture     ZoneMock z;
81566a1518SPatrick Venture 
82566a1518SPatrick Venture     std::vector<std::string> inputs = {"fan0", "fan1"};
83566a1518SPatrick Venture     ec::pidinfo initial;
84566a1518SPatrick Venture 
85566a1518SPatrick Venture     std::unique_ptr<PIDController> p =
86563a356fSPatrick Venture         FanController::createFanPid(&z, "fan1", inputs, initial);
87566a1518SPatrick Venture     EXPECT_FALSE(p == nullptr);
88566a1518SPatrick Venture 
89566a1518SPatrick Venture     EXPECT_CALL(z, getCachedValue(StrEq("fan0"))).WillOnce(Return(-1));
90566a1518SPatrick Venture     EXPECT_CALL(z, getCachedValue(StrEq("fan1"))).WillOnce(Return(-1));
91566a1518SPatrick Venture 
92563a356fSPatrick Venture     EXPECT_EQ(0.0, p->inputProc());
93566a1518SPatrick Venture }
94566a1518SPatrick Venture 
TEST(FanControllerTest,InputProc_ChoosesMinimumValue)95da4a5dd1SPatrick Venture TEST(FanControllerTest, InputProc_ChoosesMinimumValue)
96da4a5dd1SPatrick Venture {
97566a1518SPatrick Venture     // Verify it selects the minimum value from its inputs.
98566a1518SPatrick Venture 
99566a1518SPatrick Venture     ZoneMock z;
100566a1518SPatrick Venture 
101566a1518SPatrick Venture     std::vector<std::string> inputs = {"fan0", "fan1", "fan2"};
102566a1518SPatrick Venture     ec::pidinfo initial;
103566a1518SPatrick Venture 
104566a1518SPatrick Venture     std::unique_ptr<PIDController> p =
105563a356fSPatrick Venture         FanController::createFanPid(&z, "fan1", inputs, initial);
106566a1518SPatrick Venture     EXPECT_FALSE(p == nullptr);
107566a1518SPatrick Venture 
108566a1518SPatrick Venture     EXPECT_CALL(z, getCachedValue(StrEq("fan0"))).WillOnce(Return(10.0));
109566a1518SPatrick Venture     EXPECT_CALL(z, getCachedValue(StrEq("fan1"))).WillOnce(Return(30.0));
110566a1518SPatrick Venture     EXPECT_CALL(z, getCachedValue(StrEq("fan2"))).WillOnce(Return(5.0));
111566a1518SPatrick Venture 
112563a356fSPatrick Venture     EXPECT_EQ(5.0, p->inputProc());
113566a1518SPatrick Venture }
114566a1518SPatrick Venture 
115566a1518SPatrick Venture // The direction is unused presently, but these tests validate the logic.
TEST(FanControllerTest,SetPtProc_SpeedChanges_VerifyDirection)116da4a5dd1SPatrick Venture TEST(FanControllerTest, SetPtProc_SpeedChanges_VerifyDirection)
117da4a5dd1SPatrick Venture {
118566a1518SPatrick Venture     // The fan direction defaults to neutral, because we have no data.  Verify
119566a1518SPatrick Venture     // that after this point it appropriately will indicate speeding up or
120566a1518SPatrick Venture     // slowing down based on the RPM values specified.
121566a1518SPatrick Venture 
122566a1518SPatrick Venture     ZoneMock z;
123566a1518SPatrick Venture 
124566a1518SPatrick Venture     std::vector<std::string> inputs = {"fan0", "fan1"};
125566a1518SPatrick Venture     ec::pidinfo initial;
126566a1518SPatrick Venture 
127566a1518SPatrick Venture     std::unique_ptr<PIDController> p =
128563a356fSPatrick Venture         FanController::createFanPid(&z, "fan1", inputs, initial);
129566a1518SPatrick Venture     EXPECT_FALSE(p == nullptr);
130566a1518SPatrick Venture     // Grab pointer for mocking.
131566a1518SPatrick Venture     FanController* fp = reinterpret_cast<FanController*>(p.get());
132566a1518SPatrick Venture 
133566a1518SPatrick Venture     // Fanspeed starts are Neutral.
134566a1518SPatrick Venture     EXPECT_EQ(FanSpeedDirection::NEUTRAL, fp->getFanDirection());
135566a1518SPatrick Venture 
136f7a2dd5cSPatrick Venture     // getMaxSetPointRequest returns a higher value than 0, so the fans should
137f7a2dd5cSPatrick Venture     // be marked as speeding up.
138f7a2dd5cSPatrick Venture     EXPECT_CALL(z, getMaxSetPointRequest()).WillOnce(Return(10.0));
139563a356fSPatrick Venture     EXPECT_EQ(10.0, p->setptProc());
140566a1518SPatrick Venture     EXPECT_EQ(FanSpeedDirection::UP, fp->getFanDirection());
141566a1518SPatrick Venture 
142f7a2dd5cSPatrick Venture     // getMaxSetPointRequest returns a lower value than 10, so the fans should
143f7a2dd5cSPatrick Venture     // be marked as slowing down.
144f7a2dd5cSPatrick Venture     EXPECT_CALL(z, getMaxSetPointRequest()).WillOnce(Return(5.0));
145563a356fSPatrick Venture     EXPECT_EQ(5.0, p->setptProc());
146566a1518SPatrick Venture     EXPECT_EQ(FanSpeedDirection::DOWN, fp->getFanDirection());
147566a1518SPatrick Venture 
148f7a2dd5cSPatrick Venture     // getMaxSetPointRequest returns the same value, so the fans should be
149f7a2dd5cSPatrick Venture     // marked as neutral.
150f7a2dd5cSPatrick Venture     EXPECT_CALL(z, getMaxSetPointRequest()).WillOnce(Return(5.0));
151563a356fSPatrick Venture     EXPECT_EQ(5.0, p->setptProc());
152566a1518SPatrick Venture     EXPECT_EQ(FanSpeedDirection::NEUTRAL, fp->getFanDirection());
153566a1518SPatrick Venture }
154566a1518SPatrick Venture 
TEST(FanControllerTest,OutputProc_VerifiesIfFailsafeEnabledInputIsIgnored)155da4a5dd1SPatrick Venture TEST(FanControllerTest, OutputProc_VerifiesIfFailsafeEnabledInputIsIgnored)
156da4a5dd1SPatrick Venture {
157566a1518SPatrick Venture     // Verify that if failsafe mode is enabled and the input value for the fans
158566a1518SPatrick Venture     // is below the failsafe minimum value, the input is not used and the fans
159bcdeb83cSBrandon Kim     // are driven at failsafe RPM (this assumes STRICT_FAILSAFE_PWM is not set)
160566a1518SPatrick Venture 
161566a1518SPatrick Venture     ZoneMock z;
162566a1518SPatrick Venture 
163566a1518SPatrick Venture     std::vector<std::string> inputs = {"fan0", "fan1"};
164566a1518SPatrick Venture     ec::pidinfo initial;
165566a1518SPatrick Venture 
166566a1518SPatrick Venture     std::unique_ptr<PIDController> p =
167563a356fSPatrick Venture         FanController::createFanPid(&z, "fan1", inputs, initial);
168566a1518SPatrick Venture     EXPECT_FALSE(p == nullptr);
169566a1518SPatrick Venture 
170566a1518SPatrick Venture     EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(true));
171bcdeb83cSBrandon Kim     EXPECT_CALL(z, getFailSafePercent()).WillOnce(Return(75.0));
172566a1518SPatrick Venture 
173566a1518SPatrick Venture     int64_t timeout = 0;
174566a1518SPatrick Venture     std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout);
175566a1518SPatrick Venture     std::unique_ptr<Sensor> s2 = std::make_unique<SensorMock>("fan1", timeout);
176566a1518SPatrick Venture     // Grab pointers for mocking.
177566a1518SPatrick Venture     SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get());
178566a1518SPatrick Venture     SensorMock* sm2 = reinterpret_cast<SensorMock*>(s2.get());
179566a1518SPatrick Venture 
180a4146eb1SJosh Lehan     EXPECT_CALL(z, getRedundantWrite())
181a4146eb1SJosh Lehan         .WillOnce(Return(false))
182a4146eb1SJosh Lehan         .WillOnce(Return(false));
183566a1518SPatrick Venture     EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
184a4146eb1SJosh Lehan     EXPECT_CALL(*sm1, write(0.75, false, _));
185566a1518SPatrick Venture     EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get()));
186a4146eb1SJosh Lehan     EXPECT_CALL(*sm2, write(0.75, false, _));
187566a1518SPatrick Venture 
188563a356fSPatrick Venture     // This is a fan PID, so calling outputProc will try to write this value
189566a1518SPatrick Venture     // to the sensors.
190566a1518SPatrick Venture 
191da4a5dd1SPatrick Venture     // Setting 50%, will end up being 75% because the sensors are in failsafe
192da4a5dd1SPatrick Venture     // mode.
193563a356fSPatrick Venture     p->outputProc(50.0);
194566a1518SPatrick Venture }
195566a1518SPatrick Venture 
TEST(FanControllerTest,OutputProc_BehavesAsExpected)196da4a5dd1SPatrick Venture TEST(FanControllerTest, OutputProc_BehavesAsExpected)
197da4a5dd1SPatrick Venture {
198566a1518SPatrick Venture     // Verifies that when the system is not in failsafe mode, the input value
199563a356fSPatrick Venture     // to outputProc is used to drive the sensors (fans).
200566a1518SPatrick Venture 
201566a1518SPatrick Venture     ZoneMock z;
202566a1518SPatrick Venture 
203566a1518SPatrick Venture     std::vector<std::string> inputs = {"fan0", "fan1"};
204566a1518SPatrick Venture     ec::pidinfo initial;
205566a1518SPatrick Venture 
206566a1518SPatrick Venture     std::unique_ptr<PIDController> p =
207563a356fSPatrick Venture         FanController::createFanPid(&z, "fan1", inputs, initial);
208566a1518SPatrick Venture     EXPECT_FALSE(p == nullptr);
209566a1518SPatrick Venture 
210566a1518SPatrick Venture     EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(false));
211566a1518SPatrick Venture 
212566a1518SPatrick Venture     int64_t timeout = 0;
213566a1518SPatrick Venture     std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout);
214566a1518SPatrick Venture     std::unique_ptr<Sensor> s2 = std::make_unique<SensorMock>("fan1", timeout);
215566a1518SPatrick Venture     // Grab pointers for mocking.
216566a1518SPatrick Venture     SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get());
217566a1518SPatrick Venture     SensorMock* sm2 = reinterpret_cast<SensorMock*>(s2.get());
218566a1518SPatrick Venture 
219a4146eb1SJosh Lehan     EXPECT_CALL(z, getRedundantWrite())
220a4146eb1SJosh Lehan         .WillOnce(Return(false))
221a4146eb1SJosh Lehan         .WillOnce(Return(false));
222566a1518SPatrick Venture     EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
223a4146eb1SJosh Lehan     EXPECT_CALL(*sm1, write(0.5, false, _));
224566a1518SPatrick Venture     EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get()));
225a4146eb1SJosh Lehan     EXPECT_CALL(*sm2, write(0.5, false, _));
226566a1518SPatrick Venture 
227563a356fSPatrick Venture     // This is a fan PID, so calling outputProc will try to write this value
228566a1518SPatrick Venture     // to the sensors.
229563a356fSPatrick Venture     p->outputProc(50.0);
230566a1518SPatrick Venture }
231566a1518SPatrick Venture 
TEST(FanControllerTest,OutputProc_VerifyFailSafeWhenInputHigher)232bcdeb83cSBrandon Kim TEST(FanControllerTest, OutputProc_VerifyFailSafeWhenInputHigher)
233da4a5dd1SPatrick Venture {
234bcdeb83cSBrandon Kim     // If STRICT_FAILSAFE_PWM flag is NOT defined and the requested output is
235bcdeb83cSBrandon Kim     // higher than the failsafe value, then use the value provided to outputProc
236bcdeb83cSBrandon Kim     //
237bcdeb83cSBrandon Kim     // If STRICT_FAILSAFE_PWM is defined, we expect the FailSafe PWM to be
238bcdeb83cSBrandon Kim     // capped to the failsafe PWM, and not go higher than that.
239566a1518SPatrick Venture 
240566a1518SPatrick Venture     ZoneMock z;
241566a1518SPatrick Venture 
242566a1518SPatrick Venture     std::vector<std::string> inputs = {"fan0"};
243566a1518SPatrick Venture     ec::pidinfo initial;
244bcdeb83cSBrandon Kim     const double failsafePWM = 75.0;
245566a1518SPatrick Venture 
246566a1518SPatrick Venture     std::unique_ptr<PIDController> p =
247563a356fSPatrick Venture         FanController::createFanPid(&z, "fan1", inputs, initial);
248566a1518SPatrick Venture     EXPECT_FALSE(p == nullptr);
249566a1518SPatrick Venture 
250566a1518SPatrick Venture     EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(true));
251bcdeb83cSBrandon Kim     EXPECT_CALL(z, getFailSafePercent()).WillOnce(Return(failsafePWM));
252566a1518SPatrick Venture 
253566a1518SPatrick Venture     int64_t timeout = 0;
254566a1518SPatrick Venture     std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout);
255566a1518SPatrick Venture     // Grab pointer for mocking.
256566a1518SPatrick Venture     SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get());
257566a1518SPatrick Venture 
2585f59c0fdSPatrick Venture     double percent = 80;
259566a1518SPatrick Venture 
260a4146eb1SJosh Lehan     EXPECT_CALL(z, getRedundantWrite()).WillOnce(Return(false));
261566a1518SPatrick Venture     EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
262bcdeb83cSBrandon Kim #ifdef STRICT_FAILSAFE_PWM
263bcdeb83cSBrandon Kim     double failsafeValue = failsafePWM / 100;
264bcdeb83cSBrandon Kim     EXPECT_CALL(*sm1, write(failsafeValue, false, _));
265bcdeb83cSBrandon Kim #else
266bcdeb83cSBrandon Kim     // Converting from double to double for expectation.
267bcdeb83cSBrandon Kim     double value = percent / 100;
268a4146eb1SJosh Lehan     EXPECT_CALL(*sm1, write(value, false, _));
269bcdeb83cSBrandon Kim #endif
270566a1518SPatrick Venture 
271563a356fSPatrick Venture     // This is a fan PID, so calling outputProc will try to write this value
272566a1518SPatrick Venture     // to the sensors.
273563a356fSPatrick Venture     p->outputProc(percent);
274566a1518SPatrick Venture }
275a076487aSPatrick Venture 
TEST(FanControllerTest,OutputProc_VerifyRedundantWrites)276a4146eb1SJosh Lehan TEST(FanControllerTest, OutputProc_VerifyRedundantWrites)
277a4146eb1SJosh Lehan {
278a4146eb1SJosh Lehan     /* when a zone indicates that redundant writes are enabled
279a4146eb1SJosh Lehan      * make sure the fan controller honors this by forcing a sensor write
280a4146eb1SJosh Lehan      */
281a4146eb1SJosh Lehan     ZoneMock z;
282a4146eb1SJosh Lehan 
283a4146eb1SJosh Lehan     std::vector<std::string> inputs = {"fan0", "fan1"};
284a4146eb1SJosh Lehan     ec::pidinfo initial;
285a4146eb1SJosh Lehan 
286a4146eb1SJosh Lehan     std::unique_ptr<PIDController> p =
287a4146eb1SJosh Lehan         FanController::createFanPid(&z, "fan1", inputs, initial);
288a4146eb1SJosh Lehan     EXPECT_FALSE(p == nullptr);
289a4146eb1SJosh Lehan 
290a4146eb1SJosh Lehan     EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(false));
291a4146eb1SJosh Lehan 
292a4146eb1SJosh Lehan     int64_t timeout = 0;
293a4146eb1SJosh Lehan     std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout);
294a4146eb1SJosh Lehan     std::unique_ptr<Sensor> s2 = std::make_unique<SensorMock>("fan1", timeout);
295a4146eb1SJosh Lehan     // Grab pointers for mocking.
296a4146eb1SJosh Lehan     SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get());
297a4146eb1SJosh Lehan     SensorMock* sm2 = reinterpret_cast<SensorMock*>(s2.get());
298a4146eb1SJosh Lehan 
299a4146eb1SJosh Lehan     EXPECT_CALL(z, getRedundantWrite())
300a4146eb1SJosh Lehan         .WillOnce(Return(true))
301a4146eb1SJosh Lehan         .WillOnce(Return(true));
302a4146eb1SJosh Lehan     EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
303a4146eb1SJosh Lehan     EXPECT_CALL(*sm1, write(0.5, true, _));
304a4146eb1SJosh Lehan     EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get()));
305a4146eb1SJosh Lehan     EXPECT_CALL(*sm2, write(0.5, true, _));
306a4146eb1SJosh Lehan 
307a4146eb1SJosh Lehan     // This is a fan PID, so calling outputProc will try to write this value
308a4146eb1SJosh Lehan     // to the sensors.
309a4146eb1SJosh Lehan     p->outputProc(50.0);
310a4146eb1SJosh Lehan }
311a4146eb1SJosh Lehan 
312a076487aSPatrick Venture } // namespace
313a076487aSPatrick Venture } // namespace pid_control
314