blob: 3ee585e444f7a7f94563e94088513ffe43f16b73 [file] [log] [blame]
Pete O_o765a6d82025-07-23 21:44:14 -07001#include "config.h"
Patrick Rudolph93660892023-10-13 14:18:46 +02002
Patrick Venture566a1512018-06-12 14:51:07 -07003#include "pid/ec/pid.hpp"
Ed Tanousf8b6e552025-06-27 13:27:50 -07004#include "pid/fan.hpp"
Patrick Ventureda4a5dd2018-08-31 09:42:48 -07005#include "pid/fancontroller.hpp"
Ed Tanousf8b6e552025-06-27 13:27:50 -07006#include "pid/pidcontroller.hpp"
7#include "sensors/sensor.hpp"
Patrick Venture566a1512018-06-12 14:51:07 -07008#include "test/sensor_mock.hpp"
9#include "test/zone_mock.hpp"
10
Ed Tanousf8b6e552025-06-27 13:27:50 -070011#include <cstdint>
12#include <memory>
Patrick Ventureda4a5dd2018-08-31 09:42:48 -070013#include <string>
14#include <vector>
15
16#include <gmock/gmock.h>
17#include <gtest/gtest.h>
18
Patrick Venturea0764872020-08-08 07:48:43 -070019namespace pid_control
20{
21namespace
22{
23
Patrick Ventureda4a5dd2018-08-31 09:42:48 -070024using ::testing::_;
Patrick Venture566a1512018-06-12 14:51:07 -070025using ::testing::Return;
26using ::testing::StrEq;
Patrick Venture566a1512018-06-12 14:51:07 -070027
Patrick Ventureda4a5dd2018-08-31 09:42:48 -070028TEST(FanControllerTest, BoringFactoryTest)
29{
Patrick Venture566a1512018-06-12 14:51:07 -070030 // 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 =
Patrick Venture563a3562018-10-30 09:31:26 -070038 FanController::createFanPid(&z, "fan1", inputs, initial);
Patrick Venture566a1512018-06-12 14:51:07 -070039 // Success
40 EXPECT_FALSE(p == nullptr);
41}
42
Patrick Ventureda4a5dd2018-08-31 09:42:48 -070043TEST(FanControllerTest, VerifyFactoryFailsWithZeroInputs)
44{
Patrick Venture566a1512018-06-12 14:51:07 -070045 // 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 =
Patrick Venture563a3562018-10-30 09:31:26 -070053 FanController::createFanPid(&z, "fan1", inputs, initial);
Patrick Venture566a1512018-06-12 14:51:07 -070054 EXPECT_TRUE(p == nullptr);
55}
56
Patrick Ventureda4a5dd2018-08-31 09:42:48 -070057TEST(FanControllerTest, InputProc_AllSensorsReturnZero)
58{
Patrick Venture566a1512018-06-12 14:51:07 -070059 // 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 =
Patrick Venture563a3562018-10-30 09:31:26 -070067 FanController::createFanPid(&z, "fan1", inputs, initial);
Patrick Venture566a1512018-06-12 14:51:07 -070068 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
Patrick Venture563a3562018-10-30 09:31:26 -070073 EXPECT_EQ(0.0, p->inputProc());
Patrick Venture566a1512018-06-12 14:51:07 -070074}
75
Patrick Ventureda4a5dd2018-08-31 09:42:48 -070076TEST(FanControllerTest, InputProc_IfSensorNegativeIsIgnored)
77{
Patrick Venture566a1512018-06-12 14:51:07 -070078 // 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 =
Patrick Venture563a3562018-10-30 09:31:26 -070085 FanController::createFanPid(&z, "fan1", inputs, initial);
Patrick Venture566a1512018-06-12 14:51:07 -070086 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
Patrick Venture563a3562018-10-30 09:31:26 -070091 EXPECT_EQ(0.0, p->inputProc());
Patrick Venture566a1512018-06-12 14:51:07 -070092}
93
Patrick Ventureda4a5dd2018-08-31 09:42:48 -070094TEST(FanControllerTest, InputProc_ChoosesMinimumValue)
95{
Patrick Venture566a1512018-06-12 14:51:07 -070096 // 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 =
Patrick Venture563a3562018-10-30 09:31:26 -0700104 FanController::createFanPid(&z, "fan1", inputs, initial);
Patrick Venture566a1512018-06-12 14:51:07 -0700105 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
Patrick Venture563a3562018-10-30 09:31:26 -0700111 EXPECT_EQ(5.0, p->inputProc());
Patrick Venture566a1512018-06-12 14:51:07 -0700112}
113
114// The direction is unused presently, but these tests validate the logic.
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700115TEST(FanControllerTest, SetPtProc_SpeedChanges_VerifyDirection)
116{
Patrick Venture566a1512018-06-12 14:51:07 -0700117 // 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 =
Patrick Venture563a3562018-10-30 09:31:26 -0700127 FanController::createFanPid(&z, "fan1", inputs, initial);
Patrick Venture566a1512018-06-12 14:51:07 -0700128 EXPECT_FALSE(p == nullptr);
129 // Grab pointer for mocking.
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700130 FanController* fp = reinterpret_cast<FanController*>(p.get());
Patrick Venture566a1512018-06-12 14:51:07 -0700131
132 // Fanspeed starts are Neutral.
133 EXPECT_EQ(FanSpeedDirection::NEUTRAL, fp->getFanDirection());
134
Patrick Venturef7a2dd52019-07-16 14:31:13 -0700135 // 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));
Patrick Venture563a3562018-10-30 09:31:26 -0700138 EXPECT_EQ(10.0, p->setptProc());
Patrick Venture566a1512018-06-12 14:51:07 -0700139 EXPECT_EQ(FanSpeedDirection::UP, fp->getFanDirection());
140
Patrick Venturef7a2dd52019-07-16 14:31:13 -0700141 // 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));
Patrick Venture563a3562018-10-30 09:31:26 -0700144 EXPECT_EQ(5.0, p->setptProc());
Patrick Venture566a1512018-06-12 14:51:07 -0700145 EXPECT_EQ(FanSpeedDirection::DOWN, fp->getFanDirection());
146
Patrick Venturef7a2dd52019-07-16 14:31:13 -0700147 // getMaxSetPointRequest returns the same value, so the fans should be
148 // marked as neutral.
149 EXPECT_CALL(z, getMaxSetPointRequest()).WillOnce(Return(5.0));
Patrick Venture563a3562018-10-30 09:31:26 -0700150 EXPECT_EQ(5.0, p->setptProc());
Patrick Venture566a1512018-06-12 14:51:07 -0700151 EXPECT_EQ(FanSpeedDirection::NEUTRAL, fp->getFanDirection());
152}
153
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700154TEST(FanControllerTest, OutputProc_VerifiesIfFailsafeEnabledInputIsIgnored)
155{
Patrick Venture566a1512018-06-12 14:51:07 -0700156 // 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
Brandon Kimbcdeb832022-08-15 23:27:36 +0000158 // are driven at failsafe RPM (this assumes STRICT_FAILSAFE_PWM is not set)
Patrick Venture566a1512018-06-12 14:51:07 -0700159
160 ZoneMock z;
161
162 std::vector<std::string> inputs = {"fan0", "fan1"};
163 ec::pidinfo initial;
164
165 std::unique_ptr<PIDController> p =
Patrick Venture563a3562018-10-30 09:31:26 -0700166 FanController::createFanPid(&z, "fan1", inputs, initial);
Patrick Venture566a1512018-06-12 14:51:07 -0700167 EXPECT_FALSE(p == nullptr);
168
169 EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(true));
Brandon Kimbcdeb832022-08-15 23:27:36 +0000170 EXPECT_CALL(z, getFailSafePercent()).WillOnce(Return(75.0));
Patrick Venture566a1512018-06-12 14:51:07 -0700171
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.
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700176 SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get());
177 SensorMock* sm2 = reinterpret_cast<SensorMock*>(s2.get());
Patrick Venture566a1512018-06-12 14:51:07 -0700178
Josh Lehana4146eb2020-10-01 11:49:09 -0700179 EXPECT_CALL(z, getRedundantWrite())
180 .WillOnce(Return(false))
181 .WillOnce(Return(false));
Patrick Venture566a1512018-06-12 14:51:07 -0700182 EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
Josh Lehana4146eb2020-10-01 11:49:09 -0700183 EXPECT_CALL(*sm1, write(0.75, false, _));
Patrick Venture566a1512018-06-12 14:51:07 -0700184 EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get()));
Josh Lehana4146eb2020-10-01 11:49:09 -0700185 EXPECT_CALL(*sm2, write(0.75, false, _));
Patrick Venture566a1512018-06-12 14:51:07 -0700186
Patrick Venture563a3562018-10-30 09:31:26 -0700187 // This is a fan PID, so calling outputProc will try to write this value
Patrick Venture566a1512018-06-12 14:51:07 -0700188 // to the sensors.
189
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700190 // Setting 50%, will end up being 75% because the sensors are in failsafe
191 // mode.
Patrick Venture563a3562018-10-30 09:31:26 -0700192 p->outputProc(50.0);
Patrick Venture566a1512018-06-12 14:51:07 -0700193}
194
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700195TEST(FanControllerTest, OutputProc_BehavesAsExpected)
196{
Patrick Venture566a1512018-06-12 14:51:07 -0700197 // Verifies that when the system is not in failsafe mode, the input value
Patrick Venture563a3562018-10-30 09:31:26 -0700198 // to outputProc is used to drive the sensors (fans).
Patrick Venture566a1512018-06-12 14:51:07 -0700199
200 ZoneMock z;
201
202 std::vector<std::string> inputs = {"fan0", "fan1"};
203 ec::pidinfo initial;
204
205 std::unique_ptr<PIDController> p =
Patrick Venture563a3562018-10-30 09:31:26 -0700206 FanController::createFanPid(&z, "fan1", inputs, initial);
Patrick Venture566a1512018-06-12 14:51:07 -0700207 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.
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700215 SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get());
216 SensorMock* sm2 = reinterpret_cast<SensorMock*>(s2.get());
Patrick Venture566a1512018-06-12 14:51:07 -0700217
Josh Lehana4146eb2020-10-01 11:49:09 -0700218 EXPECT_CALL(z, getRedundantWrite())
219 .WillOnce(Return(false))
220 .WillOnce(Return(false));
Patrick Venture566a1512018-06-12 14:51:07 -0700221 EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
Josh Lehana4146eb2020-10-01 11:49:09 -0700222 EXPECT_CALL(*sm1, write(0.5, false, _));
Patrick Venture566a1512018-06-12 14:51:07 -0700223 EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get()));
Josh Lehana4146eb2020-10-01 11:49:09 -0700224 EXPECT_CALL(*sm2, write(0.5, false, _));
Patrick Venture566a1512018-06-12 14:51:07 -0700225
Patrick Venture563a3562018-10-30 09:31:26 -0700226 // This is a fan PID, so calling outputProc will try to write this value
Patrick Venture566a1512018-06-12 14:51:07 -0700227 // to the sensors.
Patrick Venture563a3562018-10-30 09:31:26 -0700228 p->outputProc(50.0);
Patrick Venture566a1512018-06-12 14:51:07 -0700229}
230
Brandon Kimbcdeb832022-08-15 23:27:36 +0000231TEST(FanControllerTest, OutputProc_VerifyFailSafeWhenInputHigher)
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700232{
Brandon Kimbcdeb832022-08-15 23:27:36 +0000233 // 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.
Patrick Venture566a1512018-06-12 14:51:07 -0700238
239 ZoneMock z;
240
241 std::vector<std::string> inputs = {"fan0"};
242 ec::pidinfo initial;
Brandon Kimbcdeb832022-08-15 23:27:36 +0000243 const double failsafePWM = 75.0;
Patrick Venture566a1512018-06-12 14:51:07 -0700244
245 std::unique_ptr<PIDController> p =
Patrick Venture563a3562018-10-30 09:31:26 -0700246 FanController::createFanPid(&z, "fan1", inputs, initial);
Patrick Venture566a1512018-06-12 14:51:07 -0700247 EXPECT_FALSE(p == nullptr);
248
249 EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(true));
Brandon Kimbcdeb832022-08-15 23:27:36 +0000250 EXPECT_CALL(z, getFailSafePercent()).WillOnce(Return(failsafePWM));
Patrick Venture566a1512018-06-12 14:51:07 -0700251
252 int64_t timeout = 0;
253 std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout);
254 // Grab pointer for mocking.
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700255 SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get());
Patrick Venture566a1512018-06-12 14:51:07 -0700256
Patrick Venture5f59c0f2018-11-11 12:55:14 -0800257 double percent = 80;
Patrick Venture566a1512018-06-12 14:51:07 -0700258
Josh Lehana4146eb2020-10-01 11:49:09 -0700259 EXPECT_CALL(z, getRedundantWrite()).WillOnce(Return(false));
Patrick Venture566a1512018-06-12 14:51:07 -0700260 EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
Brandon Kimbcdeb832022-08-15 23:27:36 +0000261#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;
Josh Lehana4146eb2020-10-01 11:49:09 -0700267 EXPECT_CALL(*sm1, write(value, false, _));
Brandon Kimbcdeb832022-08-15 23:27:36 +0000268#endif
Patrick Venture566a1512018-06-12 14:51:07 -0700269
Patrick Venture563a3562018-10-30 09:31:26 -0700270 // This is a fan PID, so calling outputProc will try to write this value
Patrick Venture566a1512018-06-12 14:51:07 -0700271 // to the sensors.
Patrick Venture563a3562018-10-30 09:31:26 -0700272 p->outputProc(percent);
Patrick Venture566a1512018-06-12 14:51:07 -0700273}
Patrick Venturea0764872020-08-08 07:48:43 -0700274
Josh Lehana4146eb2020-10-01 11:49:09 -0700275TEST(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
Patrick Venturea0764872020-08-08 07:48:43 -0700311} // namespace
312} // namespace pid_control