blob: 68c4f68210b305757a9c4a3617a04ba936064660 [file] [log] [blame]
/**
* 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 "config.h"
#include "fancontroller.hpp"
#include "tuning.hpp"
#include "util.hpp"
#include "zone.hpp"
#include <algorithm>
#include <cmath>
#include <iostream>
namespace pid_control
{
std::unique_ptr<PIDController>
FanController::createFanPid(ZoneInterface* owner, const std::string& id,
const std::vector<std::string>& inputs,
const ec::pidinfo& initial)
{
if (inputs.size() == 0)
{
return nullptr;
}
auto fan = std::make_unique<FanController>(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<double> values;
std::vector<double>::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<double>(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<double>(rawWritten);
_owner->setOutputCache(sensor->getName(), {percent, unscaledWritten});
}
#endif
}
} // namespace pid_control