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 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 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 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 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 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. 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 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 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 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 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