blob: 315931f0fc3a7cc7972b80fe488e1b5efbbe1114 [file] [log] [blame]
Patrick Rudolph93660892023-10-13 14:18:46 +02001
Patrick Venture566a1512018-06-12 14:51:07 -07002#include "pid/ec/pid.hpp"
Ed Tanousf8b6e552025-06-27 13:27:50 -07003#include "pid/fan.hpp"
Patrick Ventureda4a5dd2018-08-31 09:42:48 -07004#include "pid/fancontroller.hpp"
Ed Tanousf8b6e552025-06-27 13:27:50 -07005#include "pid/pidcontroller.hpp"
6#include "sensors/sensor.hpp"
Patrick Venture566a1512018-06-12 14:51:07 -07007#include "test/sensor_mock.hpp"
8#include "test/zone_mock.hpp"
9
Ed Tanousf8b6e552025-06-27 13:27:50 -070010#include <cstdint>
11#include <memory>
Patrick Ventureda4a5dd2018-08-31 09:42:48 -070012#include <string>
13#include <vector>
14
15#include <gmock/gmock.h>
16#include <gtest/gtest.h>
17
Patrick Venturea0764872020-08-08 07:48:43 -070018namespace pid_control
19{
20namespace
21{
22
Patrick Ventureda4a5dd2018-08-31 09:42:48 -070023using ::testing::_;
Patrick Venture566a1512018-06-12 14:51:07 -070024using ::testing::Return;
25using ::testing::StrEq;
Patrick Venture566a1512018-06-12 14:51:07 -070026
Patrick Ventureda4a5dd2018-08-31 09:42:48 -070027TEST(FanControllerTest, BoringFactoryTest)
28{
Patrick Venture566a1512018-06-12 14:51:07 -070029 // Verify the factory will properly build the FanPIDController in the
30 // boring (uninteresting) case.
31 ZoneMock z;
32
33 std::vector<std::string> inputs = {"fan0"};
34 ec::pidinfo initial;
35
36 std::unique_ptr<PIDController> p =
Patrick Venture563a3562018-10-30 09:31:26 -070037 FanController::createFanPid(&z, "fan1", inputs, initial);
Patrick Venture566a1512018-06-12 14:51:07 -070038 // Success
39 EXPECT_FALSE(p == nullptr);
40}
41
Patrick Ventureda4a5dd2018-08-31 09:42:48 -070042TEST(FanControllerTest, VerifyFactoryFailsWithZeroInputs)
43{
Patrick Venture566a1512018-06-12 14:51:07 -070044 // A fan controller needs at least one input.
45
46 ZoneMock z;
47
48 std::vector<std::string> inputs = {};
49 ec::pidinfo initial;
50
51 std::unique_ptr<PIDController> p =
Patrick Venture563a3562018-10-30 09:31:26 -070052 FanController::createFanPid(&z, "fan1", inputs, initial);
Patrick Venture566a1512018-06-12 14:51:07 -070053 EXPECT_TRUE(p == nullptr);
54}
55
Patrick Ventureda4a5dd2018-08-31 09:42:48 -070056TEST(FanControllerTest, InputProc_AllSensorsReturnZero)
57{
Patrick Venture566a1512018-06-12 14:51:07 -070058 // If all your inputs are 0, return 0.
59
60 ZoneMock z;
61
62 std::vector<std::string> inputs = {"fan0", "fan1"};
63 ec::pidinfo initial;
64
65 std::unique_ptr<PIDController> p =
Patrick Venture563a3562018-10-30 09:31:26 -070066 FanController::createFanPid(&z, "fan1", inputs, initial);
Patrick Venture566a1512018-06-12 14:51:07 -070067 EXPECT_FALSE(p == nullptr);
68
69 EXPECT_CALL(z, getCachedValue(StrEq("fan0"))).WillOnce(Return(0));
70 EXPECT_CALL(z, getCachedValue(StrEq("fan1"))).WillOnce(Return(0));
71
Patrick Venture563a3562018-10-30 09:31:26 -070072 EXPECT_EQ(0.0, p->inputProc());
Patrick Venture566a1512018-06-12 14:51:07 -070073}
74
Patrick Ventureda4a5dd2018-08-31 09:42:48 -070075TEST(FanControllerTest, InputProc_IfSensorNegativeIsIgnored)
76{
Patrick Venture566a1512018-06-12 14:51:07 -070077 // A sensor value returning sub-zero is ignored as an error.
78 ZoneMock z;
79
80 std::vector<std::string> inputs = {"fan0", "fan1"};
81 ec::pidinfo initial;
82
83 std::unique_ptr<PIDController> p =
Patrick Venture563a3562018-10-30 09:31:26 -070084 FanController::createFanPid(&z, "fan1", inputs, initial);
Patrick Venture566a1512018-06-12 14:51:07 -070085 EXPECT_FALSE(p == nullptr);
86
87 EXPECT_CALL(z, getCachedValue(StrEq("fan0"))).WillOnce(Return(-1));
88 EXPECT_CALL(z, getCachedValue(StrEq("fan1"))).WillOnce(Return(-1));
89
Patrick Venture563a3562018-10-30 09:31:26 -070090 EXPECT_EQ(0.0, p->inputProc());
Patrick Venture566a1512018-06-12 14:51:07 -070091}
92
Patrick Ventureda4a5dd2018-08-31 09:42:48 -070093TEST(FanControllerTest, InputProc_ChoosesMinimumValue)
94{
Patrick Venture566a1512018-06-12 14:51:07 -070095 // Verify it selects the minimum value from its inputs.
96
97 ZoneMock z;
98
99 std::vector<std::string> inputs = {"fan0", "fan1", "fan2"};
100 ec::pidinfo initial;
101
102 std::unique_ptr<PIDController> p =
Patrick Venture563a3562018-10-30 09:31:26 -0700103 FanController::createFanPid(&z, "fan1", inputs, initial);
Patrick Venture566a1512018-06-12 14:51:07 -0700104 EXPECT_FALSE(p == nullptr);
105
106 EXPECT_CALL(z, getCachedValue(StrEq("fan0"))).WillOnce(Return(10.0));
107 EXPECT_CALL(z, getCachedValue(StrEq("fan1"))).WillOnce(Return(30.0));
108 EXPECT_CALL(z, getCachedValue(StrEq("fan2"))).WillOnce(Return(5.0));
109
Patrick Venture563a3562018-10-30 09:31:26 -0700110 EXPECT_EQ(5.0, p->inputProc());
Patrick Venture566a1512018-06-12 14:51:07 -0700111}
112
113// The direction is unused presently, but these tests validate the logic.
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700114TEST(FanControllerTest, SetPtProc_SpeedChanges_VerifyDirection)
115{
Patrick Venture566a1512018-06-12 14:51:07 -0700116 // The fan direction defaults to neutral, because we have no data. Verify
117 // that after this point it appropriately will indicate speeding up or
118 // slowing down based on the RPM values specified.
119
120 ZoneMock z;
121
122 std::vector<std::string> inputs = {"fan0", "fan1"};
123 ec::pidinfo initial;
124
125 std::unique_ptr<PIDController> p =
Patrick Venture563a3562018-10-30 09:31:26 -0700126 FanController::createFanPid(&z, "fan1", inputs, initial);
Patrick Venture566a1512018-06-12 14:51:07 -0700127 EXPECT_FALSE(p == nullptr);
128 // Grab pointer for mocking.
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700129 FanController* fp = reinterpret_cast<FanController*>(p.get());
Patrick Venture566a1512018-06-12 14:51:07 -0700130
131 // Fanspeed starts are Neutral.
132 EXPECT_EQ(FanSpeedDirection::NEUTRAL, fp->getFanDirection());
133
Patrick Venturef7a2dd52019-07-16 14:31:13 -0700134 // getMaxSetPointRequest returns a higher value than 0, so the fans should
135 // be marked as speeding up.
136 EXPECT_CALL(z, getMaxSetPointRequest()).WillOnce(Return(10.0));
Patrick Venture563a3562018-10-30 09:31:26 -0700137 EXPECT_EQ(10.0, p->setptProc());
Patrick Venture566a1512018-06-12 14:51:07 -0700138 EXPECT_EQ(FanSpeedDirection::UP, fp->getFanDirection());
139
Patrick Venturef7a2dd52019-07-16 14:31:13 -0700140 // getMaxSetPointRequest returns a lower value than 10, so the fans should
141 // be marked as slowing down.
142 EXPECT_CALL(z, getMaxSetPointRequest()).WillOnce(Return(5.0));
Patrick Venture563a3562018-10-30 09:31:26 -0700143 EXPECT_EQ(5.0, p->setptProc());
Patrick Venture566a1512018-06-12 14:51:07 -0700144 EXPECT_EQ(FanSpeedDirection::DOWN, fp->getFanDirection());
145
Patrick Venturef7a2dd52019-07-16 14:31:13 -0700146 // getMaxSetPointRequest returns the same value, so the fans should be
147 // marked as neutral.
148 EXPECT_CALL(z, getMaxSetPointRequest()).WillOnce(Return(5.0));
Patrick Venture563a3562018-10-30 09:31:26 -0700149 EXPECT_EQ(5.0, p->setptProc());
Patrick Venture566a1512018-06-12 14:51:07 -0700150 EXPECT_EQ(FanSpeedDirection::NEUTRAL, fp->getFanDirection());
151}
152
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700153TEST(FanControllerTest, OutputProc_VerifiesIfFailsafeEnabledInputIsIgnored)
154{
Patrick Venture566a1512018-06-12 14:51:07 -0700155 // Verify that if failsafe mode is enabled and the input value for the fans
156 // is below the failsafe minimum value, the input is not used and the fans
Brandon Kimbcdeb832022-08-15 23:27:36 +0000157 // are driven at failsafe RPM (this assumes STRICT_FAILSAFE_PWM is not set)
Patrick Venture566a1512018-06-12 14:51:07 -0700158
159 ZoneMock z;
160
161 std::vector<std::string> inputs = {"fan0", "fan1"};
162 ec::pidinfo initial;
163
164 std::unique_ptr<PIDController> p =
Patrick Venture563a3562018-10-30 09:31:26 -0700165 FanController::createFanPid(&z, "fan1", inputs, initial);
Patrick Venture566a1512018-06-12 14:51:07 -0700166 EXPECT_FALSE(p == nullptr);
167
168 EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(true));
Brandon Kimbcdeb832022-08-15 23:27:36 +0000169 EXPECT_CALL(z, getFailSafePercent()).WillOnce(Return(75.0));
Patrick Venture566a1512018-06-12 14:51:07 -0700170
171 int64_t timeout = 0;
172 std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout);
173 std::unique_ptr<Sensor> s2 = std::make_unique<SensorMock>("fan1", timeout);
174 // Grab pointers for mocking.
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700175 SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get());
176 SensorMock* sm2 = reinterpret_cast<SensorMock*>(s2.get());
Patrick Venture566a1512018-06-12 14:51:07 -0700177
Josh Lehana4146eb2020-10-01 11:49:09 -0700178 EXPECT_CALL(z, getRedundantWrite())
179 .WillOnce(Return(false))
180 .WillOnce(Return(false));
Patrick Venture566a1512018-06-12 14:51:07 -0700181 EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
Josh Lehana4146eb2020-10-01 11:49:09 -0700182 EXPECT_CALL(*sm1, write(0.75, false, _));
Patrick Venture566a1512018-06-12 14:51:07 -0700183 EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get()));
Josh Lehana4146eb2020-10-01 11:49:09 -0700184 EXPECT_CALL(*sm2, write(0.75, false, _));
Patrick Venture566a1512018-06-12 14:51:07 -0700185
Patrick Venture563a3562018-10-30 09:31:26 -0700186 // This is a fan PID, so calling outputProc will try to write this value
Patrick Venture566a1512018-06-12 14:51:07 -0700187 // to the sensors.
188
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700189 // Setting 50%, will end up being 75% because the sensors are in failsafe
190 // mode.
Patrick Venture563a3562018-10-30 09:31:26 -0700191 p->outputProc(50.0);
Patrick Venture566a1512018-06-12 14:51:07 -0700192}
193
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700194TEST(FanControllerTest, OutputProc_BehavesAsExpected)
195{
Patrick Venture566a1512018-06-12 14:51:07 -0700196 // Verifies that when the system is not in failsafe mode, the input value
Patrick Venture563a3562018-10-30 09:31:26 -0700197 // to outputProc is used to drive the sensors (fans).
Patrick Venture566a1512018-06-12 14:51:07 -0700198
199 ZoneMock z;
200
201 std::vector<std::string> inputs = {"fan0", "fan1"};
202 ec::pidinfo initial;
203
204 std::unique_ptr<PIDController> p =
Patrick Venture563a3562018-10-30 09:31:26 -0700205 FanController::createFanPid(&z, "fan1", inputs, initial);
Patrick Venture566a1512018-06-12 14:51:07 -0700206 EXPECT_FALSE(p == nullptr);
207
208 EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(false));
209
210 int64_t timeout = 0;
211 std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout);
212 std::unique_ptr<Sensor> s2 = std::make_unique<SensorMock>("fan1", timeout);
213 // Grab pointers for mocking.
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700214 SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get());
215 SensorMock* sm2 = reinterpret_cast<SensorMock*>(s2.get());
Patrick Venture566a1512018-06-12 14:51:07 -0700216
Josh Lehana4146eb2020-10-01 11:49:09 -0700217 EXPECT_CALL(z, getRedundantWrite())
218 .WillOnce(Return(false))
219 .WillOnce(Return(false));
Patrick Venture566a1512018-06-12 14:51:07 -0700220 EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
Josh Lehana4146eb2020-10-01 11:49:09 -0700221 EXPECT_CALL(*sm1, write(0.5, false, _));
Patrick Venture566a1512018-06-12 14:51:07 -0700222 EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get()));
Josh Lehana4146eb2020-10-01 11:49:09 -0700223 EXPECT_CALL(*sm2, write(0.5, false, _));
Patrick Venture566a1512018-06-12 14:51:07 -0700224
Patrick Venture563a3562018-10-30 09:31:26 -0700225 // This is a fan PID, so calling outputProc will try to write this value
Patrick Venture566a1512018-06-12 14:51:07 -0700226 // to the sensors.
Patrick Venture563a3562018-10-30 09:31:26 -0700227 p->outputProc(50.0);
Patrick Venture566a1512018-06-12 14:51:07 -0700228}
229
Brandon Kimbcdeb832022-08-15 23:27:36 +0000230TEST(FanControllerTest, OutputProc_VerifyFailSafeWhenInputHigher)
Patrick Ventureda4a5dd2018-08-31 09:42:48 -0700231{
Brandon Kimbcdeb832022-08-15 23:27:36 +0000232 // If STRICT_FAILSAFE_PWM flag is NOT defined and the requested output is
233 // higher than the failsafe value, then use the value provided to outputProc
234 //
235 // If STRICT_FAILSAFE_PWM is defined, we expect the FailSafe PWM to be
236 // capped to the failsafe PWM, and not go higher than that.
Patrick Venture566a1512018-06-12 14:51:07 -0700237
238 ZoneMock z;
239
240 std::vector<std::string> inputs = {"fan0"};
241 ec::pidinfo initial;
Brandon Kimbcdeb832022-08-15 23:27:36 +0000242 const double failsafePWM = 75.0;
Patrick Venture566a1512018-06-12 14:51:07 -0700243
244 std::unique_ptr<PIDController> p =
Patrick Venture563a3562018-10-30 09:31:26 -0700245 FanController::createFanPid(&z, "fan1", inputs, initial);
Patrick Venture566a1512018-06-12 14:51:07 -0700246 EXPECT_FALSE(p == nullptr);
247
248 EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(true));
Brandon Kimbcdeb832022-08-15 23:27:36 +0000249 EXPECT_CALL(z, getFailSafePercent()).WillOnce(Return(failsafePWM));
Patrick Venture566a1512018-06-12 14:51:07 -0700250
251 int64_t timeout = 0;
252 std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout);
253 // Grab pointer for mocking.
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700254 SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get());
Patrick Venture566a1512018-06-12 14:51:07 -0700255
Patrick Venture5f59c0f2018-11-11 12:55:14 -0800256 double percent = 80;
Patrick Venture566a1512018-06-12 14:51:07 -0700257
Josh Lehana4146eb2020-10-01 11:49:09 -0700258 EXPECT_CALL(z, getRedundantWrite()).WillOnce(Return(false));
Patrick Venture566a1512018-06-12 14:51:07 -0700259 EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
Brandon Kimbcdeb832022-08-15 23:27:36 +0000260#ifdef STRICT_FAILSAFE_PWM
261 double failsafeValue = failsafePWM / 100;
262 EXPECT_CALL(*sm1, write(failsafeValue, false, _));
263#else
264 // Converting from double to double for expectation.
265 double value = percent / 100;
Josh Lehana4146eb2020-10-01 11:49:09 -0700266 EXPECT_CALL(*sm1, write(value, false, _));
Brandon Kimbcdeb832022-08-15 23:27:36 +0000267#endif
Patrick Venture566a1512018-06-12 14:51:07 -0700268
Patrick Venture563a3562018-10-30 09:31:26 -0700269 // This is a fan PID, so calling outputProc will try to write this value
Patrick Venture566a1512018-06-12 14:51:07 -0700270 // to the sensors.
Patrick Venture563a3562018-10-30 09:31:26 -0700271 p->outputProc(percent);
Patrick Venture566a1512018-06-12 14:51:07 -0700272}
Patrick Venturea0764872020-08-08 07:48:43 -0700273
Josh Lehana4146eb2020-10-01 11:49:09 -0700274TEST(FanControllerTest, OutputProc_VerifyRedundantWrites)
275{
276 /* when a zone indicates that redundant writes are enabled
277 * make sure the fan controller honors this by forcing a sensor write
278 */
279 ZoneMock z;
280
281 std::vector<std::string> inputs = {"fan0", "fan1"};
282 ec::pidinfo initial;
283
284 std::unique_ptr<PIDController> p =
285 FanController::createFanPid(&z, "fan1", inputs, initial);
286 EXPECT_FALSE(p == nullptr);
287
288 EXPECT_CALL(z, getFailSafeMode()).WillOnce(Return(false));
289
290 int64_t timeout = 0;
291 std::unique_ptr<Sensor> s1 = std::make_unique<SensorMock>("fan0", timeout);
292 std::unique_ptr<Sensor> s2 = std::make_unique<SensorMock>("fan1", timeout);
293 // Grab pointers for mocking.
294 SensorMock* sm1 = reinterpret_cast<SensorMock*>(s1.get());
295 SensorMock* sm2 = reinterpret_cast<SensorMock*>(s2.get());
296
297 EXPECT_CALL(z, getRedundantWrite())
298 .WillOnce(Return(true))
299 .WillOnce(Return(true));
300 EXPECT_CALL(z, getSensor(StrEq("fan0"))).WillOnce(Return(s1.get()));
301 EXPECT_CALL(*sm1, write(0.5, true, _));
302 EXPECT_CALL(z, getSensor(StrEq("fan1"))).WillOnce(Return(s2.get()));
303 EXPECT_CALL(*sm2, write(0.5, true, _));
304
305 // This is a fan PID, so calling outputProc will try to write this value
306 // to the sensors.
307 p->outputProc(50.0);
308}
309
Patrick Venturea0764872020-08-08 07:48:43 -0700310} // namespace
311} // namespace pid_control