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 = spliceInputs(inputNames, inputTempToMargin, 61 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 positiveHysteresis = p.find("positiveHysteresis"); 70 auto negativeHysteresis = p.find("negativeHysteresis"); 71 auto derivativeCoeff = p.find("derivativeCoeff"); 72 auto positiveHysteresisValue = 0.0; 73 auto negativeHysteresisValue = 0.0; 74 auto derivativeCoeffValue = 0.0; 75 if (positiveHysteresis != p.end()) 76 { 77 positiveHysteresis->get_to(positiveHysteresisValue); 78 } 79 if (negativeHysteresis != p.end()) 80 { 81 negativeHysteresis->get_to(negativeHysteresisValue); 82 } 83 if (derivativeCoeff != p.end()) 84 { 85 derivativeCoeff->get_to(derivativeCoeffValue); 86 } 87 88 auto failSafePercent = j.find("FailSafePercent"); 89 auto failSafePercentValue = 0; 90 if (failSafePercent != j.end()) 91 { 92 failSafePercent->get_to(failSafePercentValue); 93 } 94 c.failSafePercent = failSafePercentValue; 95 96 if (c.type != "stepwise") 97 { 98 p.at("samplePeriod").get_to(c.pidInfo.ts); 99 p.at("proportionalCoeff").get_to(c.pidInfo.proportionalCoeff); 100 p.at("integralCoeff").get_to(c.pidInfo.integralCoeff); 101 p.at("feedFwdOffsetCoeff").get_to(c.pidInfo.feedFwdOffset); 102 p.at("feedFwdGainCoeff").get_to(c.pidInfo.feedFwdGain); 103 p.at("integralLimit_min").get_to(c.pidInfo.integralLimit.min); 104 p.at("integralLimit_max").get_to(c.pidInfo.integralLimit.max); 105 p.at("outLim_min").get_to(c.pidInfo.outLim.min); 106 p.at("outLim_max").get_to(c.pidInfo.outLim.max); 107 p.at("slewNeg").get_to(c.pidInfo.slewNeg); 108 p.at("slewPos").get_to(c.pidInfo.slewPos); 109 110 // Unlike other coefficients, treat derivativeCoeff as an optional 111 // parameter, as support for it is fairly new, to avoid breaking 112 // existing configurations in the field that predate it. 113 c.pidInfo.positiveHysteresis = positiveHysteresisValue; 114 c.pidInfo.negativeHysteresis = negativeHysteresisValue; 115 c.pidInfo.derivativeCoeff = derivativeCoeffValue; 116 } 117 else 118 { 119 p.at("samplePeriod").get_to(c.stepwiseInfo.ts); 120 p.at("isCeiling").get_to(c.stepwiseInfo.isCeiling); 121 122 for (size_t i = 0; i < ec::maxStepwisePoints; i++) 123 { 124 c.stepwiseInfo.reading[i] = 125 std::numeric_limits<double>::quiet_NaN(); 126 c.stepwiseInfo.output[i] = std::numeric_limits<double>::quiet_NaN(); 127 } 128 129 auto reading = p.find("reading"); 130 if (reading != p.end()) 131 { 132 auto r = p.at("reading"); 133 for (size_t i = 0; i < ec::maxStepwisePoints; i++) 134 { 135 auto n = r.find(std::to_string(i)); 136 if (n != r.end()) 137 { 138 r.at(std::to_string(i)).get_to(c.stepwiseInfo.reading[i]); 139 } 140 } 141 } 142 143 auto output = p.find("output"); 144 if (output != p.end()) 145 { 146 auto o = p.at("output"); 147 for (size_t i = 0; i < ec::maxStepwisePoints; i++) 148 { 149 auto n = o.find(std::to_string(i)); 150 if (n != o.end()) 151 { 152 o.at(std::to_string(i)).get_to(c.stepwiseInfo.output[i]); 153 } 154 } 155 } 156 157 c.stepwiseInfo.positiveHysteresis = positiveHysteresisValue; 158 c.stepwiseInfo.negativeHysteresis = negativeHysteresisValue; 159 } 160 } 161 162 } // namespace conf 163 164 inline void getCycleTimeSetting(const auto& zone, const int id, 165 const std::string& attributeName, 166 uint64_t& value) 167 { 168 auto findAttributeName = zone.find(attributeName); 169 if (findAttributeName != zone.end()) 170 { 171 uint64_t tmpAttributeValue = 0; 172 findAttributeName->get_to(tmpAttributeValue); 173 if (tmpAttributeValue >= 1) 174 { 175 value = tmpAttributeValue; 176 } 177 else 178 { 179 std::cerr << "Zone " << id << ": " << attributeName 180 << " is invalid. Use default " << value << " ms\n"; 181 } 182 } 183 else 184 { 185 std::cerr << "Zone " << id << ": " << attributeName 186 << " cannot find setting. Use default " << value << " ms\n"; 187 } 188 } 189 190 std::pair<std::map<int64_t, conf::PIDConf>, std::map<int64_t, conf::ZoneConfig>> 191 buildPIDsFromJson(const json& data) 192 { 193 // zone -> pids 194 std::map<int64_t, conf::PIDConf> pidConfig; 195 // zone -> configs 196 std::map<int64_t, conf::ZoneConfig> zoneConfig; 197 198 /* TODO: if zones is empty, that's invalid. */ 199 auto zones = data["zones"]; 200 for (const auto& zone : zones) 201 { 202 int64_t id; 203 conf::PIDConf thisZone; 204 conf::ZoneConfig thisZoneConfig; 205 206 /* TODO: using at() throws a specific exception we can catch */ 207 id = zone["id"]; 208 thisZoneConfig.minThermalOutput = zone["minThermalOutput"]; 209 thisZoneConfig.failsafePercent = zone["failsafePercent"]; 210 211 getCycleTimeSetting(zone, id, "cycleIntervalTimeMS", 212 thisZoneConfig.cycleTime.cycleIntervalTimeMS); 213 getCycleTimeSetting(zone, id, "updateThermalsTimeMS", 214 thisZoneConfig.cycleTime.updateThermalsTimeMS); 215 216 auto pids = zone["pids"]; 217 for (const auto& pid : pids) 218 { 219 auto name = pid["name"]; 220 auto item = pid.get<conf::ControllerInfo>(); 221 222 if (thisZone.find(name) != thisZone.end()) 223 { 224 std::cerr << "Warning: zone " << id 225 << " have the same pid name " << name << std::endl; 226 } 227 228 thisZone[name] = item; 229 } 230 231 pidConfig[id] = thisZone; 232 zoneConfig[id] = thisZoneConfig; 233 } 234 235 return std::make_pair(pidConfig, zoneConfig); 236 } 237 238 } // namespace pid_control 239