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