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