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 <map>
24 #include <tuple>
25 
26 using json = nlohmann::json;
27 
28 namespace conf
29 {
30 void from_json(const json& j, conf::ControllerInfo& c)
31 {
32     j.at("type").get_to(c.type);
33     j.at("inputs").get_to(c.inputs);
34     j.at("setpoint").get_to(c.setpoint);
35 
36     /* TODO: We need to handle parsing other PID controller configurations.
37      * We can do that by checking for different keys and making the decision
38      * accordingly.
39      */
40     auto p = j.at("pid");
41 
42     auto positiveHysteresis = p.find("positiveHysteresis");
43     auto negativeHysteresis = p.find("negativeHysteresis");
44     auto positiveHysteresisValue = 0.0;
45     auto negativeHysteresisValue = 0.0;
46     if (positiveHysteresis != p.end())
47     {
48         p.at("positiveHysteresis").get_to(positiveHysteresisValue);
49     }
50     if (negativeHysteresis != p.end())
51     {
52         p.at("negativeHysteresis").get_to(negativeHysteresisValue);
53     }
54 
55     if (c.type != "stepwise")
56     {
57         p.at("samplePeriod").get_to(c.pidInfo.ts);
58         p.at("proportionalCoeff").get_to(c.pidInfo.proportionalCoeff);
59         p.at("integralCoeff").get_to(c.pidInfo.integralCoeff);
60         p.at("feedFwdOffsetCoeff").get_to(c.pidInfo.feedFwdOffset);
61         p.at("feedFwdGainCoeff").get_to(c.pidInfo.feedFwdGain);
62         p.at("integralLimit_min").get_to(c.pidInfo.integralLimit.min);
63         p.at("integralLimit_max").get_to(c.pidInfo.integralLimit.max);
64         p.at("outLim_min").get_to(c.pidInfo.outLim.min);
65         p.at("outLim_max").get_to(c.pidInfo.outLim.max);
66         p.at("slewNeg").get_to(c.pidInfo.slewNeg);
67         p.at("slewPos").get_to(c.pidInfo.slewPos);
68 
69         c.pidInfo.positiveHysteresis = positiveHysteresisValue;
70         c.pidInfo.negativeHysteresis = negativeHysteresisValue;
71     }
72     else
73     {
74         p.at("samplePeriod").get_to(c.stepwiseInfo.ts);
75         p.at("isCeiling").get_to(c.stepwiseInfo.isCeiling);
76 
77         for (size_t i = 0; i < ec::maxStepwisePoints; i++)
78         {
79             c.stepwiseInfo.reading[i] =
80                 std::numeric_limits<double>::quiet_NaN();
81             c.stepwiseInfo.output[i] = std::numeric_limits<double>::quiet_NaN();
82         }
83 
84         auto reading = p.find("reading");
85         if (reading != p.end())
86         {
87             auto r = p.at("reading");
88             for (size_t i = 0; i < ec::maxStepwisePoints; i++)
89             {
90                 auto n = r.find(std::to_string(i));
91                 if (n != r.end())
92                 {
93                     r.at(std::to_string(i)).get_to(c.stepwiseInfo.reading[i]);
94                 }
95             }
96         }
97 
98         auto output = p.find("output");
99         if (output != p.end())
100         {
101             auto o = p.at("output");
102             for (size_t i = 0; i < ec::maxStepwisePoints; i++)
103             {
104                 auto n = o.find(std::to_string(i));
105                 if (n != o.end())
106                 {
107                     o.at(std::to_string(i)).get_to(c.stepwiseInfo.output[i]);
108                 }
109             }
110         }
111 
112         c.stepwiseInfo.positiveHysteresis = positiveHysteresisValue;
113         c.stepwiseInfo.negativeHysteresis = negativeHysteresisValue;
114     }
115 }
116 } // namespace conf
117 
118 std::pair<std::map<int64_t, conf::PIDConf>,
119           std::map<int64_t, struct conf::ZoneConfig>>
120     buildPIDsFromJson(const json& data)
121 {
122     // zone -> pids
123     std::map<int64_t, conf::PIDConf> pidConfig;
124     // zone -> configs
125     std::map<int64_t, struct conf::ZoneConfig> zoneConfig;
126 
127     /* TODO: if zones is empty, that's invalid. */
128     auto zones = data["zones"];
129     for (const auto& zone : zones)
130     {
131         int64_t id;
132         conf::PIDConf thisZone;
133         struct conf::ZoneConfig thisZoneConfig;
134 
135         /* TODO: using at() throws a specific exception we can catch */
136         id = zone["id"];
137         thisZoneConfig.minThermalOutput = zone["minThermalOutput"];
138         thisZoneConfig.failsafePercent = zone["failsafePercent"];
139 
140         auto pids = zone["pids"];
141         for (const auto& pid : pids)
142         {
143             auto name = pid["name"];
144             auto item = pid.get<conf::ControllerInfo>();
145 
146             thisZone[name] = item;
147         }
148 
149         pidConfig[id] = thisZone;
150         zoneConfig[id] = thisZoneConfig;
151     }
152 
153     return std::make_pair(pidConfig, zoneConfig);
154 }
155