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 positiveHysteresisValue = 0.0;
49     auto negativeHysteresisValue = 0.0;
50     if (positiveHysteresis != p.end())
51     {
52         p.at("positiveHysteresis").get_to(positiveHysteresisValue);
53     }
54     if (negativeHysteresis != p.end())
55     {
56         p.at("negativeHysteresis").get_to(negativeHysteresisValue);
57     }
58 
59     if (c.type != "stepwise")
60     {
61         p.at("samplePeriod").get_to(c.pidInfo.ts);
62         p.at("proportionalCoeff").get_to(c.pidInfo.proportionalCoeff);
63         p.at("integralCoeff").get_to(c.pidInfo.integralCoeff);
64         p.at("derivativeCoeff").get_to(c.pidInfo.derivativeCoeff);
65         p.at("feedFwdOffsetCoeff").get_to(c.pidInfo.feedFwdOffset);
66         p.at("feedFwdGainCoeff").get_to(c.pidInfo.feedFwdGain);
67         p.at("integralLimit_min").get_to(c.pidInfo.integralLimit.min);
68         p.at("integralLimit_max").get_to(c.pidInfo.integralLimit.max);
69         p.at("outLim_min").get_to(c.pidInfo.outLim.min);
70         p.at("outLim_max").get_to(c.pidInfo.outLim.max);
71         p.at("slewNeg").get_to(c.pidInfo.slewNeg);
72         p.at("slewPos").get_to(c.pidInfo.slewPos);
73 
74         c.pidInfo.positiveHysteresis = positiveHysteresisValue;
75         c.pidInfo.negativeHysteresis = negativeHysteresisValue;
76     }
77     else
78     {
79         p.at("samplePeriod").get_to(c.stepwiseInfo.ts);
80         p.at("isCeiling").get_to(c.stepwiseInfo.isCeiling);
81 
82         for (size_t i = 0; i < ec::maxStepwisePoints; i++)
83         {
84             c.stepwiseInfo.reading[i] =
85                 std::numeric_limits<double>::quiet_NaN();
86             c.stepwiseInfo.output[i] = std::numeric_limits<double>::quiet_NaN();
87         }
88 
89         auto reading = p.find("reading");
90         if (reading != p.end())
91         {
92             auto r = p.at("reading");
93             for (size_t i = 0; i < ec::maxStepwisePoints; i++)
94             {
95                 auto n = r.find(std::to_string(i));
96                 if (n != r.end())
97                 {
98                     r.at(std::to_string(i)).get_to(c.stepwiseInfo.reading[i]);
99                 }
100             }
101         }
102 
103         auto output = p.find("output");
104         if (output != p.end())
105         {
106             auto o = p.at("output");
107             for (size_t i = 0; i < ec::maxStepwisePoints; i++)
108             {
109                 auto n = o.find(std::to_string(i));
110                 if (n != o.end())
111                 {
112                     o.at(std::to_string(i)).get_to(c.stepwiseInfo.output[i]);
113                 }
114             }
115         }
116 
117         c.stepwiseInfo.positiveHysteresis = positiveHysteresisValue;
118         c.stepwiseInfo.negativeHysteresis = negativeHysteresisValue;
119     }
120 }
121 } // namespace conf
122 
123 std::pair<std::map<int64_t, conf::PIDConf>, std::map<int64_t, conf::ZoneConfig>>
124     buildPIDsFromJson(const json& data)
125 {
126     // zone -> pids
127     std::map<int64_t, conf::PIDConf> pidConfig;
128     // zone -> configs
129     std::map<int64_t, conf::ZoneConfig> zoneConfig;
130 
131     /* TODO: if zones is empty, that's invalid. */
132     auto zones = data["zones"];
133     for (const auto& zone : zones)
134     {
135         int64_t id;
136         conf::PIDConf thisZone;
137         conf::ZoneConfig thisZoneConfig;
138 
139         /* TODO: using at() throws a specific exception we can catch */
140         id = zone["id"];
141         thisZoneConfig.minThermalOutput = zone["minThermalOutput"];
142         thisZoneConfig.failsafePercent = zone["failsafePercent"];
143 
144         auto findTimeInterval = zone.find("cycleIntervalTimeMS");
145         if (findTimeInterval != zone.end())
146         {
147             uint64_t tmp;
148             findTimeInterval->get_to(tmp);
149             if (tmp != 0)
150             {
151                 thisZoneConfig.cycleTime.cycleIntervalTimeMS = tmp;
152             }
153             else
154             {
155                 std::cerr << "cycleIntervalTimeMS cannot be 0. Use default "
156                           << thisZoneConfig.cycleTime.cycleIntervalTimeMS
157                           << " ms\n";
158             }
159         }
160 
161         auto findUpdateThermalsTime = zone.find("updateThermalsTimeMS");
162         if (findUpdateThermalsTime != zone.end())
163         {
164             uint64_t tmp;
165             findUpdateThermalsTime->get_to(tmp);
166             if (tmp != 0)
167             {
168                 thisZoneConfig.cycleTime.updateThermalsTimeMS = tmp;
169             }
170             else
171             {
172                 std::cerr << "updateThermalsTimeMS cannot be 0. Use default "
173                           << thisZoneConfig.cycleTime.updateThermalsTimeMS
174                           << " ms\n";
175             }
176         }
177 
178         double updateCount =
179             double(thisZoneConfig.cycleTime.updateThermalsTimeMS) /
180             double(thisZoneConfig.cycleTime.cycleIntervalTimeMS);
181 
182         /* Check if updateThermalsTimeMS could be divided by cycleIntervalTimeMS
183          * without leaving a remainder */
184         if (updateCount != std::ceil(updateCount))
185         {
186             std::cerr
187                 << "updateThermalsTimeMS cannot be divided by "
188                    "cycleIntervalTimeMS without leaving a remainder. Using the "
189                    "smallest integer that is not less than the result.\n";
190             updateCount = std::ceil(updateCount);
191         }
192         thisZoneConfig.cycleTime.updateThermalsTimeMS = updateCount;
193 
194         auto pids = zone["pids"];
195         for (const auto& pid : pids)
196         {
197             auto name = pid["name"];
198             auto item = pid.get<conf::ControllerInfo>();
199 
200             thisZone[name] = item;
201         }
202 
203         pidConfig[id] = thisZone;
204         zoneConfig[id] = thisZoneConfig;
205     }
206 
207     return std::make_pair(pidConfig, zoneConfig);
208 }
209 
210 } // namespace pid_control
211