| #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_ |