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