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 
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 = spliceInputs(inputNames, inputTempToMargin,
61                             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 positiveHysteresis = p.find("positiveHysteresis");
70     auto negativeHysteresis = p.find("negativeHysteresis");
71     auto derivativeCoeff = p.find("derivativeCoeff");
72     auto positiveHysteresisValue = 0.0;
73     auto negativeHysteresisValue = 0.0;
74     auto derivativeCoeffValue = 0.0;
75     if (positiveHysteresis != p.end())
76     {
77         positiveHysteresis->get_to(positiveHysteresisValue);
78     }
79     if (negativeHysteresis != p.end())
80     {
81         negativeHysteresis->get_to(negativeHysteresisValue);
82     }
83     if (derivativeCoeff != p.end())
84     {
85         derivativeCoeff->get_to(derivativeCoeffValue);
86     }
87 
88     auto failSafePercent = j.find("FailSafePercent");
89     auto failSafePercentValue = 0;
90     if (failSafePercent != j.end())
91     {
92         failSafePercent->get_to(failSafePercentValue);
93     }
94     c.failSafePercent = failSafePercentValue;
95 
96     if (c.type != "stepwise")
97     {
98         p.at("samplePeriod").get_to(c.pidInfo.ts);
99         p.at("proportionalCoeff").get_to(c.pidInfo.proportionalCoeff);
100         p.at("integralCoeff").get_to(c.pidInfo.integralCoeff);
101         p.at("feedFwdOffsetCoeff").get_to(c.pidInfo.feedFwdOffset);
102         p.at("feedFwdGainCoeff").get_to(c.pidInfo.feedFwdGain);
103         p.at("integralLimit_min").get_to(c.pidInfo.integralLimit.min);
104         p.at("integralLimit_max").get_to(c.pidInfo.integralLimit.max);
105         p.at("outLim_min").get_to(c.pidInfo.outLim.min);
106         p.at("outLim_max").get_to(c.pidInfo.outLim.max);
107         p.at("slewNeg").get_to(c.pidInfo.slewNeg);
108         p.at("slewPos").get_to(c.pidInfo.slewPos);
109 
110         // Unlike other coefficients, treat derivativeCoeff as an optional
111         // parameter, as support for it is fairly new, to avoid breaking
112         // existing configurations in the field that predate it.
113         c.pidInfo.positiveHysteresis = positiveHysteresisValue;
114         c.pidInfo.negativeHysteresis = negativeHysteresisValue;
115         c.pidInfo.derivativeCoeff = derivativeCoeffValue;
116     }
117     else
118     {
119         p.at("samplePeriod").get_to(c.stepwiseInfo.ts);
120         p.at("isCeiling").get_to(c.stepwiseInfo.isCeiling);
121 
122         for (size_t i = 0; i < ec::maxStepwisePoints; i++)
123         {
124             c.stepwiseInfo.reading[i] =
125                 std::numeric_limits<double>::quiet_NaN();
126             c.stepwiseInfo.output[i] = std::numeric_limits<double>::quiet_NaN();
127         }
128 
129         auto reading = p.find("reading");
130         if (reading != p.end())
131         {
132             auto r = p.at("reading");
133             for (size_t i = 0; i < ec::maxStepwisePoints; i++)
134             {
135                 auto n = r.find(std::to_string(i));
136                 if (n != r.end())
137                 {
138                     r.at(std::to_string(i)).get_to(c.stepwiseInfo.reading[i]);
139                 }
140             }
141         }
142 
143         auto output = p.find("output");
144         if (output != p.end())
145         {
146             auto o = p.at("output");
147             for (size_t i = 0; i < ec::maxStepwisePoints; i++)
148             {
149                 auto n = o.find(std::to_string(i));
150                 if (n != o.end())
151                 {
152                     o.at(std::to_string(i)).get_to(c.stepwiseInfo.output[i]);
153                 }
154             }
155         }
156 
157         c.stepwiseInfo.positiveHysteresis = positiveHysteresisValue;
158         c.stepwiseInfo.negativeHysteresis = negativeHysteresisValue;
159     }
160 }
161 
162 } // namespace conf
163 
164 inline void getCycleTimeSetting(const auto& zone, const int id,
165                                 const std::string& attributeName,
166                                 uint64_t& value)
167 {
168     auto findAttributeName = zone.find(attributeName);
169     if (findAttributeName != zone.end())
170     {
171         uint64_t tmpAttributeValue = 0;
172         findAttributeName->get_to(tmpAttributeValue);
173         if (tmpAttributeValue >= 1)
174         {
175             value = tmpAttributeValue;
176         }
177         else
178         {
179             std::cerr << "Zone " << id << ": " << attributeName
180                       << " is invalid. Use default " << value << " ms\n";
181         }
182     }
183     else
184     {
185         std::cerr << "Zone " << id << ": " << attributeName
186                   << " cannot find setting. Use default " << value << " ms\n";
187     }
188 }
189 
190 std::pair<std::map<int64_t, conf::PIDConf>, std::map<int64_t, conf::ZoneConfig>>
191     buildPIDsFromJson(const json& data)
192 {
193     // zone -> pids
194     std::map<int64_t, conf::PIDConf> pidConfig;
195     // zone -> configs
196     std::map<int64_t, conf::ZoneConfig> zoneConfig;
197 
198     /* TODO: if zones is empty, that's invalid. */
199     auto zones = data["zones"];
200     for (const auto& zone : zones)
201     {
202         int64_t id;
203         conf::PIDConf thisZone;
204         conf::ZoneConfig thisZoneConfig;
205 
206         /* TODO: using at() throws a specific exception we can catch */
207         id = zone["id"];
208         thisZoneConfig.minThermalOutput = zone["minThermalOutput"];
209         thisZoneConfig.failsafePercent = zone["failsafePercent"];
210 
211         getCycleTimeSetting(zone, id, "cycleIntervalTimeMS",
212                             thisZoneConfig.cycleTime.cycleIntervalTimeMS);
213         getCycleTimeSetting(zone, id, "updateThermalsTimeMS",
214                             thisZoneConfig.cycleTime.updateThermalsTimeMS);
215 
216         auto pids = zone["pids"];
217         for (const auto& pid : pids)
218         {
219             auto name = pid["name"];
220             auto item = pid.get<conf::ControllerInfo>();
221 
222             if (thisZone.find(name) != thisZone.end())
223             {
224                 std::cerr << "Warning: zone " << id
225                           << " have the same pid name " << name << std::endl;
226             }
227 
228             thisZone[name] = item;
229         }
230 
231         pidConfig[id] = thisZone;
232         zoneConfig[id] = thisZoneConfig;
233     }
234 
235     return std::make_pair(pidConfig, zoneConfig);
236 }
237 
238 } // namespace pid_control
239