/** * Copyright 2017 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "config.h" #include "fancontroller.hpp" #include "tuning.hpp" #include "util.hpp" #include "zone.hpp" #include #include #include namespace pid_control { std::unique_ptr FanController::createFanPid(ZoneInterface* owner, const std::string& id, const std::vector& inputs, const ec::pidinfo& initial) { if (inputs.size() == 0) { return nullptr; } auto fan = std::make_unique(id, inputs, owner); ec::pid_info_t* info = fan->getPIDInfo(); initializePIDStruct(info, initial); return fan; } double FanController::inputProc(void) { double value = 0.0; std::vector values; std::vector::iterator result; try { for (const auto& name : _inputs) { // Read the unscaled value, to correctly recover the RPM value = _owner->getCachedValues(name).unscaled; /* If we have a fan we can't read, its value will be 0 for at least * some boards, while others... the fan will drop off dbus (if * that's how it's being read and in that case its value will never * be updated anymore, which is relatively harmless, except, when * something tries to read its value through IPMI, and can't, they * sort of have to guess -- all the other fans are reporting, why * not this one? Maybe it's unable to be read, so it's "bad." */ if (!(std::isfinite(value))) { continue; } if (value <= 0.0) { continue; } values.push_back(value); } } catch (const std::exception& e) { std::cerr << "exception on inputProc.\n"; throw; } /* Reset the value from the above loop. */ value = 0.0; if (values.size() > 0) { /* the fan PID algorithm was unstable with average, and seemed to work * better with minimum. I had considered making this choice a variable * in the configuration, and it's a nice-to-have.. */ result = std::min_element(values.begin(), values.end()); value = *result; } return value; } double FanController::setptProc(void) { double maxRPM = _owner->getMaxSetPointRequest(); // store for reference, and check if more or less. double prev = getSetpoint(); if (maxRPM > prev) { setFanDirection(FanSpeedDirection::UP); } else if (prev > maxRPM) { setFanDirection(FanSpeedDirection::DOWN); } else { setFanDirection(FanSpeedDirection::NEUTRAL); } setSetpoint(maxRPM); return (maxRPM); } void FanController::outputProc(double value) { double percent = value; /* If doing tuning, don't go into failsafe mode. */ if (!tuningEnabled) { bool failsafeCurrState = _owner->getFailSafeMode(); // Note when failsafe state transitions happen if (failsafePrevState != failsafeCurrState) { failsafePrevState = failsafeCurrState; failsafeTransition = true; } if (failsafeCurrState) { double failsafePercent = _owner->getFailSafePercent(); #ifdef STRICT_FAILSAFE_PWM // Unconditionally replace the computed PWM with the // failsafe PWM if STRICT_FAILSAFE_PWM is defined. percent = failsafePercent; #else // Ensure PWM is never lower than the failsafe PWM. // The computed PWM is still allowed to rise higher than // failsafe PWM if STRICT_FAILSAFE_PWM is NOT defined. // This is the default behavior. if (percent < failsafePercent) { percent = failsafePercent; } #endif } // Always print if debug enabled if (debugEnabled) { std::cerr << "Zone " << _owner->getZoneID() << " fans, " << (failsafeCurrState ? "failsafe" : "normal") << " mode, output pwm: " << percent << "\n"; } else { // Only print once per transition when not debugging if (failsafeTransition) { failsafeTransition = false; std::cerr << "Zone " << _owner->getZoneID() << " fans, " << (failsafeCurrState ? "entering failsafe" : "returning to normal") << " mode, output pwm: " << percent << "\n"; } } } else { if (debugEnabled) { std::cerr << "Zone " << _owner->getZoneID() << " fans, tuning mode, bypassing failsafe, output pwm: " << percent << "\n"; } } // value and kFanFailSafeDutyCycle are 10 for 10% so let's fix that. percent /= 100.0; // PidSensorMap for writing. for (const auto& it : _inputs) { auto sensor = _owner->getSensor(it); auto redundantWrite = _owner->getRedundantWrite(); int64_t rawWritten = -1; sensor->write(percent, redundantWrite, &rawWritten); // The outputCache will be used later, // to store a record of the PWM commanded, // so that this information can be included during logging. auto unscaledWritten = static_cast(rawWritten); _owner->setOutputCache(sensor->getName(), {percent, unscaledWritten}); } return; } FanController::~FanController() { #ifdef OFFLINE_FAILSAFE_PWM double percent = _owner->getFailSafePercent(); if (debugEnabled) { std::cerr << "Zone " << _owner->getZoneID() << " offline fans output pwm: " << percent << "\n"; } // value and kFanFailSafeDutyCycle are 10 for 10% so let's fix that. percent /= 100.0; // PidSensorMap for writing. for (const auto& it : _inputs) { auto sensor = _owner->getSensor(it); auto redundantWrite = _owner->getRedundantWrite(); int64_t rawWritten; sensor->write(percent, redundantWrite, &rawWritten); // The outputCache will be used later, // to store a record of the PWM commanded, // so that this information can be included during logging. auto unscaledWritten = static_cast(rawWritten); _owner->setOutputCache(sensor->getName(), {percent, unscaledWritten}); } #endif } } // namespace pid_control