| #include "tlbmc/thermal/controller/second_order_adrc_controller.h" |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #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 "absl/types/span.h" |
| #include <nlohmann/json.hpp> |
| #include "tlbmc/thermal/zone_manager.h" |
| |
| namespace milotic_tlbmc { |
| namespace thermal { |
| |
| absl::StatusOr<std::unique_ptr<SecondOrderAdrcController>> |
| SecondOrderAdrcController::Create(SecondOrderAdrcControllerParameters params) { |
| if (params.input_sensors.empty()) { |
| return absl::InvalidArgumentError(absl::StrFormat( |
| "No input sensor is assigned to SecondOrderAdrc controller `%s`!", |
| params.id)); |
| } |
| |
| auto second_order_adrc_controller = |
| absl::WrapUnique(new SecondOrderAdrcController( |
| params.id, params.zone_manager, |
| std::move(params.second_order_adrc_loop), params.setpoint, |
| std::move(params.input_sensors), |
| std::move(params.disturbance_sensor_lists))); |
| |
| return second_order_adrc_controller; |
| } |
| |
| void SecondOrderAdrcController::ProcessThermalLoop() { |
| double input = ProcessInput(); |
| double setpoint = GetSetpoint(); |
| ProcessInputDisturbances(); |
| |
| ProcessOutput(GetSecondOrderAdrcLoop()->ExecuteSecondOrderAdrcLoop( |
| setpoint, input, input_disturbances_, zone_owner_->GetDebugEnabled())); |
| } |
| |
| double SecondOrderAdrcController::ProcessInput() { |
| std::optional<double> input_value = |
| ProcessSensorInputs(input_sensors_, zone_owner_); |
| |
| // If no input is acceptable, return `setpoint_` to indicate the invalidness. |
| // This will make the ADRC error to be zero. |
| if (!input_value.has_value()) { |
| input_value = GetSetpoint(); |
| zone_owner_->TryLogThermalDebugMessage(absl::StrFormat( |
| "SecondOrderAdrc controller %s chose the setpoint %lf as the input " |
| "value, as there is no valid input sensor reading\n", |
| GetId(), GetSetpoint())); |
| } else { |
| zone_owner_->TryLogThermalDebugMessage(absl::StrFormat( |
| "SecondOrderAdrc controller %s chose the input value %lf\n", GetId(), |
| *input_value)); |
| } |
| |
| return *input_value; |
| } |
| |
| void SecondOrderAdrcController::ProcessInputDisturbances() { |
| // Set `input_disturbances_` to 0 by default, in case there is no valid sensor |
| // reading, which will result in zero disturbance errors. |
| std::fill(input_disturbances_.begin(), input_disturbances_.end(), 0.0); |
| for (int i = 0; i < disturbance_sensor_lists_.size(); ++i) { |
| std::optional<double> disturbance_value = |
| ProcessSensorInputs(disturbance_sensor_lists_[i], zone_owner_); |
| |
| if (!disturbance_value.has_value()) { |
| continue; |
| } |
| |
| input_disturbances_[i] = *disturbance_value; |
| } |
| } |
| |
| void SecondOrderAdrcController::ProcessOutput(double output) { |
| zone_owner_->AddSetpoint(GetId(), output); |
| zone_owner_->TryLogThermalDebugMessage( |
| absl::StrFormat("SecondOrderAdrc controller %s outputs setpoint: %lf\n", |
| GetId(), output)); |
| if (zone_owner_->GetDebugEnabled()) { |
| zone_owner_->AppendSampledData(absl::StrCat(output, ",")); |
| } |
| } |
| |
| std::optional<double> SecondOrderAdrcController::ProcessSensorInputs( |
| absl::Span<const std::string> sensors, ZoneManager* zone_manager) { |
| std::optional<double> input_value = std::nullopt; |
| |
| for (const std::string& sensor : sensors) { |
| double sensor_value = zone_manager->GetSensorReadings(sensor).scaled_value; |
| |
| // Sensor readings must not be NaN. |
| if (!std::isfinite(sensor_value)) { |
| continue; |
| } |
| |
| input_value = input_value.has_value() ? std::max(*input_value, sensor_value) |
| : sensor_value; |
| } |
| |
| return input_value; |
| } |
| |
| nlohmann::json SecondOrderAdrcController::ToJson() const { |
| nlohmann::json json; |
| json["SampleTimeSec"] = second_order_adrc_loop_->GetSampleTimeSec(); |
| json["SettleTimeSec"] = second_order_adrc_loop_->GetSettleTimeSec(); |
| json["B0"] = second_order_adrc_loop_->GetB0(); |
| json["ObserverBandwidthFactor"] = |
| second_order_adrc_loop_->GetObserverBandwidthFactor(); |
| json["OutLimitMax"] = second_order_adrc_loop_->GetOutputLimitMax(); |
| json["OutLimitMin"] = second_order_adrc_loop_->GetOutputLimitMin(); |
| json["SetPoint"] = GetSetpoint(); |
| json["Inputs"] = nlohmann::json::array(); |
| for (const std::string& input : input_sensors_) { |
| json["Inputs"].push_back(input); |
| } |
| json["Disturbances"] = nlohmann::json::array(); |
| for (const std::vector<std::string>& disturbance_sensor_list : |
| disturbance_sensor_lists_) { |
| nlohmann::json disturbance_json; |
| disturbance_json["InputSensors"] = nlohmann::json::array(); |
| for (const std::string& input_sensor : disturbance_sensor_list) { |
| disturbance_json["InputSensors"].push_back(input_sensor); |
| } |
| json["Disturbances"].push_back(disturbance_json); |
| } |
| return json; |
| } |
| |
| } // namespace thermal |
| } // namespace milotic_tlbmc |