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