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