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