blob: f0db686537ac0bcf2ab75fcb3f571880cf0077fc [file]
#ifndef THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_THERMAL_PID_CONTROLLER_H_
#define THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_THERMAL_PID_CONTROLLER_H_
#include <algorithm>
#include <limits>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "absl/base/attributes.h"
#include "absl/base/nullability.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include <nlohmann/json_fwd.hpp>
#include "thermal_config.pb.h"
#include "thermal_controller_specification.pb.h"
#include "tlbmc/thermal/controller/algorithm/pid.h"
#include "tlbmc/thermal/controller/controller.h"
#include "tlbmc/thermal/controller/optimizer/pid/pid_optimizer.h"
#include "tlbmc/thermal/zone_manager.h"
namespace milotic_tlbmc {
namespace thermal {
constexpr double kDefaultPidSetpointValue = 0.0;
struct PidOptimizerInfo {
optimizer::PidOptimizerStates states;
std::unique_ptr<optimizer::PidOptimizer> optimizer = nullptr;
};
struct PidControllerParameters {
std::string id;
ZoneManager* absl_nonnull zone_manager ABSL_REQUIRE_EXPLICIT_INIT;
std::unique_ptr<PidThermalLoop> absl_nonnull pid_loop
ABSL_REQUIRE_EXPLICIT_INIT;
double setpoint = kDefaultPidSetpointValue;
PidControllerInputProcessType input_process_type;
std::vector<PidControllerSensorInfo> input_sensors;
std::optional<PidOptimizerInfo> pid_optimizer_info = std::nullopt;
};
/**
* `PidController` provides a connection between the thermal zone manager and
* the PID (i.e., proportional, integral, and derivative) thermal loop. It will
* handle the sensor reading & fan output process, and the PID thermal loop
* calculations.
*
* There will be, and should be, exactly one configured PID thermal loop
* assigned to each PID controller object, which may be shared, however, by
* multiple controllers.
*
* This class is not thread-safe.
*
* This class is created based on `pidcontroller.*` and
* `thermalcontroller.*` files at
* https://source.corp.google.com/piper///depot/google3/third_party/openbmc_phosphor_pid_control/pid/
*/
class PidController : public ThermalController {
public:
// These are the coefficients that are allowed to be tuned for PID
// Controllers.
struct PidControllerCoefficients {
std::optional<double> coeff_proportional;
std::optional<double> coeff_integral;
std::optional<double> coeff_derivative;
std::optional<double> feed_forward_offset;
std::optional<double> feed_forward_gain;
std::optional<double> setpoint;
bool operator==(const PidControllerCoefficients& other) const = default;
};
~PidController() final = default;
static absl::StatusOr<std::unique_ptr<PidController>> Create(
PidControllerParameters params);
void ProcessThermalLoop() final;
double ProcessInput() final;
void ProcessOutput(double output) final;
std::string GetId() const final { return id_; }
bool IsFanController() const final { return false; }
bool IsSupportiveController() const final { return false; }
const std::vector<std::string>& GetAffectedControllerIds() const final {
return empty_affected_controller_ids_;
}
void AdjustSetpoint(double offset) final { setpoint_ += offset; }
double GetSetpoint() const { return setpoint_; }
ZoneManager* GetZoneManager() const { return zone_owner_; }
PidThermalLoop* GetPidLoop() const { return pid_loop_.get(); }
static bool IsPidThermalType(PidControllerInputProcessType type) {
return type == PidControllerInputProcessType::MARGIN_PID ||
type == PidControllerInputProcessType::TEMP_PID ||
type == PidControllerInputProcessType::POWER_PID ||
type == PidControllerInputProcessType::POWERSUM_PID;
}
absl::Status SetInputProcessType(PidControllerInputProcessType type) {
if (!IsPidThermalType(type)) {
return absl::InvalidArgumentError(absl::StrFormat(
"Invalid PID thermal type for PID controller `%s`!", id_));
}
if (type == PidControllerInputProcessType::MARGIN_PID) {
input_process_type_ = PidThermalType::MARGIN;
}
if ((type == PidControllerInputProcessType::TEMP_PID) ||
(type == PidControllerInputProcessType::POWER_PID)) {
input_process_type_ = PidThermalType::ABSOLUTE;
}
if (type == PidControllerInputProcessType::POWERSUM_PID) {
input_process_type_ = PidThermalType::SUMMATION;
}
return absl::OkStatus();
}
PidThermalType GetInputProcessType() const { return input_process_type_; }
void AddInputSensor(const PidControllerSensorInfo& input_sensor) {
input_sensors_.push_back(input_sensor);
}
const std::vector<PidControllerSensorInfo>& GetInputSensors() const {
return input_sensors_;
}
double InitializeInputBasedOnPidThermalType() const {
switch (GetInputProcessType()) {
case PidThermalType::MARGIN:
// If the input process type is `MARGIN`, we will use the minimal value
// of all input sensors. Therefore, return the maximum double value as
// the initial value.
return std::numeric_limits<double>::max();
case PidThermalType::ABSOLUTE:
// If the input process type is `ABSOLUTE`, we will use the maximum
// value of all input sensors. Therefore, return the minimum double
// value as the initial value.
return std::numeric_limits<double>::lowest();
default:
// The type is `SUMMATION`; so return 0.0 as the initial value for the
// later sum.
return 0.0;
}
}
double ProcessSensorReadingForMarginPid(
double sensor_value, const PidControllerSensorInfo& sensor) const {
if (GetInputProcessType() != PidThermalType::MARGIN ||
!sensor.convert_temperature_to_margin()) {
return sensor_value;
}
// Return the margin value.
return sensor.max_junction_temperature() - sensor_value;
}
double IntegrateSensorReadingBasedOnPidThermalType(
double current_input, double sensor_value) const {
switch (GetInputProcessType()) {
case PidThermalType::MARGIN:
return std::min(current_input, sensor_value);
case PidThermalType::ABSOLUTE:
return std::max(current_input, sensor_value);
default:
// The type is `PidThermalType::SUMMATION`.
return current_input + sensor_value;
}
}
nlohmann::json ToJson() const;
// Setters of PID coefficients for tuning purposes.
void SetCoefficients(const PidControllerCoefficients& coefficients);
protected:
explicit PidController(absl::string_view id, ZoneManager* zone_manager,
std::unique_ptr<PidThermalLoop> absl_nonnull pid_loop,
double setpoint,
std::vector<PidControllerSensorInfo> input_sensors)
: ThermalController(),
id_(id),
zone_owner_(zone_manager),
pid_loop_(std::move(pid_loop)),
setpoint_(setpoint),
input_sensors_(std::move(input_sensors)) {}
explicit PidController(absl::string_view id, ZoneManager* zone_manager,
std::unique_ptr<PidThermalLoop> absl_nonnull pid_loop,
double setpoint,
std::vector<PidControllerSensorInfo> input_sensors,
PidOptimizerInfo pid_optimizer_info)
: ThermalController(),
id_(id),
zone_owner_(zone_manager),
pid_loop_(std::move(pid_loop)),
setpoint_(setpoint),
input_sensors_(std::move(input_sensors)) {
if (pid_optimizer_info.optimizer != nullptr) {
pid_optimizer_info_ = std::move(pid_optimizer_info);
PreparePidOptimizer();
}
}
// `PreparePidOptimizer` sets up the controller environment for PID optimizer.
// It should only be called when `pid_optimizer_info_` is present.
void PreparePidOptimizer();
private:
const std::string id_;
// Use a simple pointer here to avoid circular dependency, as `ZoneManager`
// depends on `PidController` with `std::unique_ptr`.
// Every `ZoneManager` will be owned by the thermal control collector of
// tlBMC, and will live as long as the thermal control service is on.
ZoneManager* zone_owner_ = nullptr;
// Every `PidThermalLoop` will be owned by its thermal controller, and will
// live as long as the thermal control service is on.
std::unique_ptr<PidThermalLoop> pid_loop_;
double setpoint_ = kDefaultPidSetpointValue;
PidThermalType input_process_type_;
std::vector<PidControllerSensorInfo> input_sensors_;
// An empty vector to return by reference for `GetAffectedControllerIds`.
const std::vector<std::string> empty_affected_controller_ids_;
PidOptimizerInfo pid_optimizer_info_;
};
} // namespace thermal
} // namespace milotic_tlbmc
#endif // THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_THERMAL_PID_CONTROLLER_H_