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