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 #include "util.hpp" 21 22 #include <nlohmann/json.hpp> 23 24 #include <iostream> 25 #include <limits> 26 #include <map> 27 #include <tuple> 28 29 namespace pid_control 30 { 31 32 using json = nlohmann::json; 33 34 namespace conf 35 { 36 37 void from_json(const json& j, conf::ControllerInfo& c) 38 { 39 std::vector<std::string> inputNames; 40 std::vector<std::string> missingAcceptableNames; 41 42 j.at("type").get_to(c.type); 43 j.at("inputs").get_to(inputNames); 44 j.at("setpoint").get_to(c.setpoint); 45 46 std::vector<double> inputTempToMargin; 47 48 auto findTempToMargin = j.find("tempToMargin"); 49 if (findTempToMargin != j.end()) 50 { 51 findTempToMargin->get_to(inputTempToMargin); 52 } 53 54 auto findMissingAcceptable = j.find("missingIsAcceptable"); 55 if (findMissingAcceptable != j.end()) 56 { 57 findMissingAcceptable->get_to(missingAcceptableNames); 58 } 59 60 c.inputs = 61 spliceInputs(inputNames, inputTempToMargin, missingAcceptableNames); 62 63 /* TODO: We need to handle parsing other PID controller configurations. 64 * We can do that by checking for different keys and making the decision 65 * accordingly. 66 */ 67 auto p = j.at("pid"); 68 69 auto checkHysterWithSetpt = p.find("checkHysteresisWithSetpoint"); 70 auto positiveHysteresis = p.find("positiveHysteresis"); 71 auto negativeHysteresis = p.find("negativeHysteresis"); 72 auto derivativeCoeff = p.find("derivativeCoeff"); 73 auto checkHysterWithSetptValue = false; 74 auto positiveHysteresisValue = 0.0; 75 auto negativeHysteresisValue = 0.0; 76 auto derivativeCoeffValue = 0.0; 77 if (checkHysterWithSetpt != p.end()) 78 { 79 checkHysterWithSetpt->get_to(checkHysterWithSetptValue); 80 } 81 if (positiveHysteresis != p.end()) 82 { 83 positiveHysteresis->get_to(positiveHysteresisValue); 84 } 85 if (negativeHysteresis != p.end()) 86 { 87 negativeHysteresis->get_to(negativeHysteresisValue); 88 } 89 if (derivativeCoeff != p.end()) 90 { 91 derivativeCoeff->get_to(derivativeCoeffValue); 92 } 93 94 auto failSafePercent = j.find("FailSafePercent"); 95 auto failSafePercentValue = 0; 96 if (failSafePercent != j.end()) 97 { 98 failSafePercent->get_to(failSafePercentValue); 99 } 100 c.failSafePercent = failSafePercentValue; 101 102 if (c.type != "stepwise") 103 { 104 p.at("samplePeriod").get_to(c.pidInfo.ts); 105 p.at("proportionalCoeff").get_to(c.pidInfo.proportionalCoeff); 106 p.at("integralCoeff").get_to(c.pidInfo.integralCoeff); 107 p.at("feedFwdOffsetCoeff").get_to(c.pidInfo.feedFwdOffset); 108 p.at("feedFwdGainCoeff").get_to(c.pidInfo.feedFwdGain); 109 p.at("integralLimit_min").get_to(c.pidInfo.integralLimit.min); 110 p.at("integralLimit_max").get_to(c.pidInfo.integralLimit.max); 111 p.at("outLim_min").get_to(c.pidInfo.outLim.min); 112 p.at("outLim_max").get_to(c.pidInfo.outLim.max); 113 p.at("slewNeg").get_to(c.pidInfo.slewNeg); 114 p.at("slewPos").get_to(c.pidInfo.slewPos); 115 116 // Unlike other coefficients, treat derivativeCoeff as an optional 117 // parameter, as support for it is fairly new, to avoid breaking 118 // existing configurations in the field that predate it. 119 c.pidInfo.positiveHysteresis = positiveHysteresisValue; 120 c.pidInfo.negativeHysteresis = negativeHysteresisValue; 121 c.pidInfo.derivativeCoeff = derivativeCoeffValue; 122 c.pidInfo.checkHysterWithSetpt = checkHysterWithSetptValue; 123 } 124 else 125 { 126 p.at("samplePeriod").get_to(c.stepwiseInfo.ts); 127 p.at("isCeiling").get_to(c.stepwiseInfo.isCeiling); 128 129 for (size_t i = 0; i < ec::maxStepwisePoints; i++) 130 { 131 c.stepwiseInfo.reading[i] = 132 std::numeric_limits<double>::quiet_NaN(); 133 c.stepwiseInfo.output[i] = std::numeric_limits<double>::quiet_NaN(); 134 } 135 136 auto reading = p.find("reading"); 137 if (reading != p.end()) 138 { 139 auto r = p.at("reading"); 140 for (size_t i = 0; i < ec::maxStepwisePoints; i++) 141 { 142 auto n = r.find(std::to_string(i)); 143 if (n != r.end()) 144 { 145 r.at(std::to_string(i)).get_to(c.stepwiseInfo.reading[i]); 146 } 147 } 148 } 149 150 auto output = p.find("output"); 151 if (output != p.end()) 152 { 153 auto o = p.at("output"); 154 for (size_t i = 0; i < ec::maxStepwisePoints; i++) 155 { 156 auto n = o.find(std::to_string(i)); 157 if (n != o.end()) 158 { 159 o.at(std::to_string(i)).get_to(c.stepwiseInfo.output[i]); 160 } 161 } 162 } 163 164 c.stepwiseInfo.positiveHysteresis = positiveHysteresisValue; 165 c.stepwiseInfo.negativeHysteresis = negativeHysteresisValue; 166 } 167 } 168 169 } // namespace conf 170 171 inline void getCycleTimeSetting(const auto& zone, const int id, 172 const std::string& attributeName, 173 uint64_t& value) 174 { 175 auto findAttributeName = zone.find(attributeName); 176 if (findAttributeName != zone.end()) 177 { 178 uint64_t tmpAttributeValue = 0; 179 findAttributeName->get_to(tmpAttributeValue); 180 if (tmpAttributeValue >= 1) 181 { 182 value = tmpAttributeValue; 183 } 184 else 185 { 186 std::cerr << "Zone " << id << ": " << attributeName 187 << " is invalid. Use default " << value << " ms\n"; 188 } 189 } 190 else 191 { 192 std::cerr << "Zone " << id << ": " << attributeName 193 << " cannot find setting. Use default " << value << " ms\n"; 194 } 195 } 196 197 std::pair<std::map<int64_t, conf::PIDConf>, std::map<int64_t, conf::ZoneConfig>> 198 buildPIDsFromJson(const json& data) 199 { 200 // zone -> pids 201 std::map<int64_t, conf::PIDConf> pidConfig; 202 // zone -> configs 203 std::map<int64_t, conf::ZoneConfig> zoneConfig; 204 205 /* TODO: if zones is empty, that's invalid. */ 206 auto zones = data["zones"]; 207 for (const auto& zone : zones) 208 { 209 int64_t id; 210 conf::PIDConf thisZone; 211 conf::ZoneConfig thisZoneConfig; 212 213 /* TODO: using at() throws a specific exception we can catch */ 214 id = zone["id"]; 215 thisZoneConfig.minThermalOutput = zone["minThermalOutput"]; 216 thisZoneConfig.failsafePercent = zone["failsafePercent"]; 217 218 getCycleTimeSetting(zone, id, "cycleIntervalTimeMS", 219 thisZoneConfig.cycleTime.cycleIntervalTimeMS); 220 getCycleTimeSetting(zone, id, "updateThermalsTimeMS", 221 thisZoneConfig.cycleTime.updateThermalsTimeMS); 222 223 bool accumulateSetPoint = false; 224 auto findAccSetPoint = zone.find("accumulateSetPoint"); 225 if (findAccSetPoint != zone.end()) 226 { 227 findAccSetPoint->get_to(accumulateSetPoint); 228 } 229 thisZoneConfig.accumulateSetPoint = accumulateSetPoint; 230 231 auto pids = zone["pids"]; 232 for (const auto& pid : pids) 233 { 234 auto name = pid["name"]; 235 auto item = pid.get<conf::ControllerInfo>(); 236 237 if (thisZone.find(name) != thisZone.end()) 238 { 239 std::cerr << "Warning: zone " << id 240 << " have the same pid name " << name << std::endl; 241 } 242 243 thisZone[name] = item; 244 } 245 246 pidConfig[id] = thisZone; 247 zoneConfig[id] = thisZoneConfig; 248 } 249 250 return std::make_pair(pidConfig, zoneConfig); 251 } 252 253 } // namespace pid_control 254