xref: /openbmc/phosphor-pid-control/pid/buildjson.cpp (revision f8b6e55147148c3cfb42327ff267197a460b411c)
1 /**
2  * Copyright 2019 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 "pid/buildjson.hpp"
18 
19 #include "conf.hpp"
20 #include "ec/stepwise.hpp"
21 #include "util.hpp"
22 
23 #include <nlohmann/json.hpp>
24 
25 #include <cstddef>
26 #include <cstdint>
27 #include <iostream>
28 #include <limits>
29 #include <map>
30 #include <string>
31 #include <utility>
32 #include <vector>
33 
34 namespace pid_control
35 {
36 
37 using json = nlohmann::json;
38 
39 namespace conf
40 {
41 
from_json(const json & j,conf::ControllerInfo & c)42 void from_json(const json& j, conf::ControllerInfo& c)
43 {
44     std::vector<std::string> inputNames;
45     std::vector<std::string> missingAcceptableNames;
46 
47     j.at("type").get_to(c.type);
48     j.at("inputs").get_to(inputNames);
49     j.at("setpoint").get_to(c.setpoint);
50 
51     std::vector<double> inputTempToMargin;
52 
53     auto findTempToMargin = j.find("tempToMargin");
54     if (findTempToMargin != j.end())
55     {
56         findTempToMargin->get_to(inputTempToMargin);
57     }
58 
59     auto findMissingAcceptable = j.find("missingIsAcceptable");
60     if (findMissingAcceptable != j.end())
61     {
62         findMissingAcceptable->get_to(missingAcceptableNames);
63     }
64 
65     c.inputs =
66         spliceInputs(inputNames, inputTempToMargin, missingAcceptableNames);
67 
68     /* TODO: We need to handle parsing other PID controller configurations.
69      * We can do that by checking for different keys and making the decision
70      * accordingly.
71      */
72     auto p = j.at("pid");
73 
74     auto checkHysterWithSetpt = p.find("checkHysteresisWithSetpoint");
75     auto positiveHysteresis = p.find("positiveHysteresis");
76     auto negativeHysteresis = p.find("negativeHysteresis");
77     auto derivativeCoeff = p.find("derivativeCoeff");
78     auto checkHysterWithSetptValue = false;
79     auto positiveHysteresisValue = 0.0;
80     auto negativeHysteresisValue = 0.0;
81     auto derivativeCoeffValue = 0.0;
82     if (checkHysterWithSetpt != p.end())
83     {
84         checkHysterWithSetpt->get_to(checkHysterWithSetptValue);
85     }
86     if (positiveHysteresis != p.end())
87     {
88         positiveHysteresis->get_to(positiveHysteresisValue);
89     }
90     if (negativeHysteresis != p.end())
91     {
92         negativeHysteresis->get_to(negativeHysteresisValue);
93     }
94     if (derivativeCoeff != p.end())
95     {
96         derivativeCoeff->get_to(derivativeCoeffValue);
97     }
98 
99     auto failSafePercent = j.find("FailSafePercent");
100     auto failSafePercentValue = 0;
101     if (failSafePercent != j.end())
102     {
103         failSafePercent->get_to(failSafePercentValue);
104     }
105     c.failSafePercent = failSafePercentValue;
106 
107     if (c.type != "stepwise")
108     {
109         p.at("samplePeriod").get_to(c.pidInfo.ts);
110         p.at("proportionalCoeff").get_to(c.pidInfo.proportionalCoeff);
111         p.at("integralCoeff").get_to(c.pidInfo.integralCoeff);
112         p.at("feedFwdOffsetCoeff").get_to(c.pidInfo.feedFwdOffset);
113         p.at("feedFwdGainCoeff").get_to(c.pidInfo.feedFwdGain);
114         p.at("integralLimit_min").get_to(c.pidInfo.integralLimit.min);
115         p.at("integralLimit_max").get_to(c.pidInfo.integralLimit.max);
116         p.at("outLim_min").get_to(c.pidInfo.outLim.min);
117         p.at("outLim_max").get_to(c.pidInfo.outLim.max);
118         p.at("slewNeg").get_to(c.pidInfo.slewNeg);
119         p.at("slewPos").get_to(c.pidInfo.slewPos);
120 
121         // Unlike other coefficients, treat derivativeCoeff as an optional
122         // parameter, as support for it is fairly new, to avoid breaking
123         // existing configurations in the field that predate it.
124         c.pidInfo.positiveHysteresis = positiveHysteresisValue;
125         c.pidInfo.negativeHysteresis = negativeHysteresisValue;
126         c.pidInfo.derivativeCoeff = derivativeCoeffValue;
127         c.pidInfo.checkHysterWithSetpt = checkHysterWithSetptValue;
128     }
129     else
130     {
131         p.at("samplePeriod").get_to(c.stepwiseInfo.ts);
132         p.at("isCeiling").get_to(c.stepwiseInfo.isCeiling);
133 
134         for (size_t i = 0; i < ec::maxStepwisePoints; i++)
135         {
136             c.stepwiseInfo.reading[i] =
137                 std::numeric_limits<double>::quiet_NaN();
138             c.stepwiseInfo.output[i] = std::numeric_limits<double>::quiet_NaN();
139         }
140 
141         auto reading = p.find("reading");
142         if (reading != p.end())
143         {
144             auto r = p.at("reading");
145             for (size_t i = 0; i < ec::maxStepwisePoints; i++)
146             {
147                 auto n = r.find(std::to_string(i));
148                 if (n != r.end())
149                 {
150                     r.at(std::to_string(i)).get_to(c.stepwiseInfo.reading[i]);
151                 }
152             }
153         }
154 
155         auto output = p.find("output");
156         if (output != p.end())
157         {
158             auto o = p.at("output");
159             for (size_t i = 0; i < ec::maxStepwisePoints; i++)
160             {
161                 auto n = o.find(std::to_string(i));
162                 if (n != o.end())
163                 {
164                     o.at(std::to_string(i)).get_to(c.stepwiseInfo.output[i]);
165                 }
166             }
167         }
168 
169         c.stepwiseInfo.positiveHysteresis = positiveHysteresisValue;
170         c.stepwiseInfo.negativeHysteresis = negativeHysteresisValue;
171     }
172 }
173 
174 } // namespace conf
175 
getCycleTimeSetting(const auto & zone,const int id,const std::string & attributeName,uint64_t & value)176 inline void getCycleTimeSetting(const auto& zone, const int id,
177                                 const std::string& attributeName,
178                                 uint64_t& value)
179 {
180     auto findAttributeName = zone.find(attributeName);
181     if (findAttributeName != zone.end())
182     {
183         uint64_t tmpAttributeValue = 0;
184         findAttributeName->get_to(tmpAttributeValue);
185         if (tmpAttributeValue >= 1)
186         {
187             value = tmpAttributeValue;
188         }
189         else
190         {
191             std::cerr << "Zone " << id << ": " << attributeName
192                       << " is invalid. Use default " << value << " ms\n";
193         }
194     }
195     else
196     {
197         std::cerr << "Zone " << id << ": " << attributeName
198                   << " cannot find setting. Use default " << value << " ms\n";
199     }
200 }
201 
202 std::pair<std::map<int64_t, conf::PIDConf>, std::map<int64_t, conf::ZoneConfig>>
buildPIDsFromJson(const json & data)203     buildPIDsFromJson(const json& data)
204 {
205     // zone -> pids
206     std::map<int64_t, conf::PIDConf> pidConfig;
207     // zone -> configs
208     std::map<int64_t, conf::ZoneConfig> zoneConfig;
209 
210     /* TODO: if zones is empty, that's invalid. */
211     auto zones = data["zones"];
212     for (const auto& zone : zones)
213     {
214         int64_t id;
215         conf::PIDConf thisZone;
216         conf::ZoneConfig thisZoneConfig;
217 
218         /* TODO: using at() throws a specific exception we can catch */
219         id = zone["id"];
220         thisZoneConfig.minThermalOutput = zone["minThermalOutput"];
221         thisZoneConfig.failsafePercent = zone["failsafePercent"];
222 
223         getCycleTimeSetting(zone, id, "cycleIntervalTimeMS",
224                             thisZoneConfig.cycleTime.cycleIntervalTimeMS);
225         getCycleTimeSetting(zone, id, "updateThermalsTimeMS",
226                             thisZoneConfig.cycleTime.updateThermalsTimeMS);
227 
228         bool accumulateSetPoint = false;
229         auto findAccSetPoint = zone.find("accumulateSetPoint");
230         if (findAccSetPoint != zone.end())
231         {
232             findAccSetPoint->get_to(accumulateSetPoint);
233         }
234         thisZoneConfig.accumulateSetPoint = accumulateSetPoint;
235 
236         auto pids = zone["pids"];
237         for (const auto& pid : pids)
238         {
239             auto name = pid["name"];
240             auto item = pid.get<conf::ControllerInfo>();
241 
242             if (thisZone.find(name) != thisZone.end())
243             {
244                 std::cerr << "Warning: zone " << id
245                           << " have the same pid name " << name << std::endl;
246             }
247 
248             thisZone[name] = item;
249         }
250 
251         pidConfig[id] = thisZone;
252         zoneConfig[id] = thisZoneConfig;
253     }
254 
255     return std::make_pair(pidConfig, zoneConfig);
256 }
257 
258 } // namespace pid_control
259