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