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