1 /**
2  * Copyright 2017 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "thermalcontroller.hpp"
18 
19 #include "errors/exception.hpp"
20 #include "tuning.hpp"
21 #include "util.hpp"
22 #include "zone.hpp"
23 
24 #include <algorithm>
25 #include <cmath>
26 #include <iostream>
27 
28 namespace pid_control
29 {
30 
getThermalType(const std::string & typeString)31 ThermalType getThermalType(const std::string& typeString)
32 {
33     if (typeString == "margin")
34     {
35         return ThermalType::margin;
36     }
37     if ((typeString == "temp") || (typeString == "power"))
38     {
39         return ThermalType::absolute;
40     }
41     if (typeString == "powersum")
42     {
43         return ThermalType::summation;
44     }
45 
46     throw ControllerBuildException("Unrecognized PID Type/Class string");
47 }
48 
isThermalType(const std::string & typeString)49 bool isThermalType(const std::string& typeString)
50 {
51     static const std::vector<std::string> thermalTypes = {
52         "temp", "margin", "power", "powersum"};
53     return std::count(thermalTypes.begin(), thermalTypes.end(), typeString);
54 }
55 
createThermalPid(ZoneInterface * owner,const std::string & id,const std::vector<pid_control::conf::SensorInput> & inputs,double setpoint,const ec::pidinfo & initial,const ThermalType & type)56 std::unique_ptr<PIDController> ThermalController::createThermalPid(
57     ZoneInterface* owner, const std::string& id,
58     const std::vector<pid_control::conf::SensorInput>& inputs, double setpoint,
59     const ec::pidinfo& initial, const ThermalType& type)
60 {
61     // ThermalController requires at least 1 input
62     if (inputs.empty())
63     {
64         throw ControllerBuildException("Thermal controller missing inputs");
65     }
66 
67     auto thermal = std::make_unique<ThermalController>(id, inputs, type, owner);
68 
69     ec::pid_info_t* info = thermal->getPIDInfo();
70     thermal->setSetpoint(setpoint);
71 
72     initializePIDStruct(info, initial);
73 
74     return thermal;
75 }
76 
77 // bmc_host_sensor_value_double
inputProc(void)78 double ThermalController::inputProc(void)
79 {
80     double value;
81     const double& (*compare)(const double&, const double&);
82     bool doSummation = false;
83 
84     if (type == ThermalType::margin)
85     {
86         value = std::numeric_limits<double>::max();
87         compare = std::min<double>;
88     }
89     else if (type == ThermalType::absolute)
90     {
91         value = std::numeric_limits<double>::lowest();
92         compare = std::max<double>;
93     }
94     else if (type == ThermalType::summation)
95     {
96         doSummation = true;
97         value = 0.0;
98     }
99     else
100     {
101         throw ControllerBuildException("Unrecognized ThermalType");
102     }
103 
104     std::string leaderName = _inputs.begin()->name;
105 
106     bool acceptable = false;
107     for (const auto& in : _inputs)
108     {
109         double cachedValue = _owner->getCachedValue(in.name);
110 
111         // Less than 0 is perfectly OK for temperature, but must not be NAN
112         if (!(std::isfinite(cachedValue)))
113         {
114             continue;
115         }
116 
117         // Perform TempToMargin conversion before further processing
118         if (type == ThermalType::margin)
119         {
120             if (in.convertTempToMargin)
121             {
122                 if (!(std::isfinite(in.convertMarginZero)))
123                 {
124                     throw ControllerBuildException("Unrecognized TempToMargin");
125                 }
126 
127                 double marginValue = in.convertMarginZero - cachedValue;
128 
129                 if (debugEnabled)
130                 {
131                     std::cerr
132                         << "Converting temp to margin: temp " << cachedValue
133                         << ", Tjmax " << in.convertMarginZero << ", margin "
134                         << marginValue << "\n";
135                 }
136 
137                 cachedValue = marginValue;
138             }
139         }
140 
141         double oldValue = value;
142 
143         if (doSummation)
144         {
145             value += cachedValue;
146         }
147         else
148         {
149             value = compare(value, cachedValue);
150         }
151 
152         if (oldValue != value)
153         {
154             leaderName = in.name;
155             _owner->updateThermalPowerDebugInterface(_id, leaderName, value, 0);
156         }
157 
158         acceptable = true;
159     }
160 
161     if (!acceptable)
162     {
163         // If none of the inputs were acceptable, use the setpoint as
164         // the input value. This will continue to run the PID loop, but
165         // make it a no-op, as the error will be zero. This provides safe
166         // behavior until the inputs become acceptable.
167         value = setptProc();
168     }
169 
170     if (debugEnabled)
171     {
172         std::cerr << getID() << " choose the temperature value: " << value
173                   << " " << leaderName << "\n";
174     }
175 
176     return value;
177 }
178 
179 // bmc_get_setpt
setptProc(void)180 double ThermalController::setptProc(void)
181 {
182     double setpoint = getSetpoint();
183 
184     /* TODO(venture): Thermal setpoint invalid? */
185 #if 0
186     if (-1 == setpoint)
187     {
188         return 0.0f;
189     }
190     else
191     {
192         return setpoint;
193     }
194 #endif
195     return setpoint;
196 }
197 
198 // bmc_set_pid_output
outputProc(double value)199 void ThermalController::outputProc(double value)
200 {
201     _owner->addSetPoint(value, _id);
202     _owner->updateThermalPowerDebugInterface(_id, "", 0, value);
203 
204     if (debugEnabled)
205     {
206         std::cerr << getID() << " pid output pwm: " << value << "\n";
207     }
208 
209     return;
210 }
211 
212 } // namespace pid_control
213