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