blob: aa162f6bfd3b681179b25561e91bea1d5253a308 [file]
#include "tlbmc/thermal/controller/fan_pid_controller.h"
#include <algorithm>
#include <cmath>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "absl/log/log.h"
#include "absl/memory/memory.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include <nlohmann/json.hpp>
#include "tlbmc/thermal/controller/algorithm/pid.h"
#include "tlbmc/thermal/zone_manager.h"
namespace milotic_tlbmc {
namespace thermal {
absl::StatusOr<std::unique_ptr<FanPidController>> FanPidController::Create(
FanPidControllerParameters params) {
if (params.input_fans.empty()) {
return absl::InvalidArgumentError(absl::StrFormat(
"No input fan is assigned to fan PID controller `%s`!", params.id));
}
if (params.output_fans.empty()) {
return absl::InvalidArgumentError(absl::StrFormat(
"No output fan is assigned to fan PID controller `%s`!", params.id));
}
auto fan_pid_controller = absl::WrapUnique(new FanPidController(
params.id, params.zone_manager, std::move(params.pid_loop),
std::move(params.input_fans), std::move(params.output_fans)));
return fan_pid_controller;
}
void FanPidController::ProcessThermalLoop() {
double input = ProcessInput();
double setpoint = GetSetpoint();
zone_owner_->TryLogThermalDebugMessage(
absl::StrFormat("Fan PID controller `%s` in zone `%d` takes "
"input RPM `%f` and setpoint `%f`",
GetId(), zone_owner_->GetId(), input, setpoint));
ProcessOutput(GetPidLoop()->ExecutePidLoop(
input, setpoint, zone_owner_->GetDebugPidEnabled()));
}
double FanPidController::ProcessInput() {
std::optional<double> min_rpm;
for (const std::string& fan_name : GetInputFans()) {
double value = zone_owner_->GetSensorReadings(fan_name).scaled_value;
if (!std::isfinite(value) || value < 1.0) {
continue;
}
min_rpm = min_rpm ? std::min(value, *min_rpm) : value;
}
return min_rpm.value_or(kDefaultFanRpmValue);
}
void FanPidController::ProcessOutput(double pwm) {
bool failsafe_transition = false;
// Failsafe mode is only for non-tuning modes: In tuning mode, just print the
// calculation results, instead of checking whether to enter the failsafe
// mode.
if (zone_owner_->GetTuningEnabled()) {
ZoneManager::LogThermalMessage(
absl::StrFormat("Fan PID controller `%s` in zone `%d` is in tuning "
"mode with output PWM `%f` by `%s`",
GetId(), zone_owner_->GetId(), pwm, GetId()));
} else {
bool current_failsafe_state = zone_owner_->GetFailsafeMode();
if (previous_failsafe_state_ != current_failsafe_state) {
previous_failsafe_state_ = current_failsafe_state;
failsafe_transition = true;
}
if (current_failsafe_state) {
double failsafe_percent = zone_owner_->GetFailsafePercent();
// Ensure PWM is never lower than the failsafe PWM under failsafe mode.
pwm = std::max(pwm, failsafe_percent);
}
if (zone_owner_->GetDebugEnabled()) {
zone_owner_->TryLogThermalDebugMessage(
absl::StrFormat("Fan PID controller `%s` in zone `%d` is in %s mode "
"with output PWM `%f`",
GetId(), zone_owner_->GetId(),
current_failsafe_state ? "failsafe" : "normal", pwm));
} else if (failsafe_transition) {
// Output failsafe log as long as the failsafe state changes, once only.
ZoneManager::LogThermalMessage(
absl::StrFormat("Fan PID controller `%s` in zone `%d` is in failsafe "
"mode with output PWM `%f`",
GetId(), zone_owner_->GetId(), pwm));
}
}
// Update the real fan speeds.
for (const std::string& fan_name : GetOutputFans()) {
zone_owner_->UpdateLocallyRecordedFanPwmAndCheckFailsafe(fan_name, pwm);
}
if (zone_owner_->GetDebugEnabled()) {
zone_owner_->AppendSampledData(absl::StrCat(pwm, ","));
}
}
void FanPidController::SetCoefficients(
const FanPidControllerCoefficients& coefficients) {
if (coefficients.coeff_proportional.has_value()) {
pid_loop_->SetCoeffProportional(*coefficients.coeff_proportional);
}
if (coefficients.coeff_integral.has_value()) {
pid_loop_->SetCoeffIntegral(*coefficients.coeff_integral);
}
if (coefficients.coeff_derivative.has_value()) {
pid_loop_->SetCoeffDerivative(*coefficients.coeff_derivative);
}
if (coefficients.feed_forward_offset.has_value()) {
pid_loop_->SetFeedForwardOffset(*coefficients.feed_forward_offset);
}
if (coefficients.feed_forward_gain.has_value()) {
pid_loop_->SetFeedForwardGain(*coefficients.feed_forward_gain);
}
}
nlohmann::json FanPidController::ToJson() const {
nlohmann::json json;
json["DCoefficient"] = pid_loop_->GetCoeffDerivative();
json["FFGainCoefficient"] = pid_loop_->GetFeedForwardGain();
json["FFOffCoefficient"] = pid_loop_->GetFeedForwardOffset();
json["ICoefficient"] = pid_loop_->GetCoeffIntegral();
json["ILimitMax"] = pid_loop_->GetIntegralLimitMax();
json["ILimitMin"] = pid_loop_->GetIntegralLimitMin();
json["OutLimitMax"] = pid_loop_->GetOutputLimitMax();
json["OutLimitMin"] = pid_loop_->GetOutputLimitMin();
json["PCoefficient"] = pid_loop_->GetCoeffProportional();
json["SlewNeg"] = pid_loop_->GetSlewNeg();
json["SlewPos"] = pid_loop_->GetSlewPos();
json["Inputs"] = nlohmann::json::array();
for (const auto& input : input_fans_) {
json["Inputs"].push_back(input);
}
return json;
}
} // namespace thermal
} // namespace milotic_tlbmc