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 std::pair<std::map<int64_t, conf::PIDConf>, std::map<int64_t, conf::ZoneConfig>> 133 buildPIDsFromJson(const json& data) 134 { 135 // zone -> pids 136 std::map<int64_t, conf::PIDConf> pidConfig; 137 // zone -> configs 138 std::map<int64_t, conf::ZoneConfig> zoneConfig; 139 140 /* TODO: if zones is empty, that's invalid. */ 141 auto zones = data["zones"]; 142 for (const auto& zone : zones) 143 { 144 int64_t id; 145 conf::PIDConf thisZone; 146 conf::ZoneConfig thisZoneConfig; 147 148 /* TODO: using at() throws a specific exception we can catch */ 149 id = zone["id"]; 150 thisZoneConfig.minThermalOutput = zone["minThermalOutput"]; 151 thisZoneConfig.failsafePercent = zone["failsafePercent"]; 152 153 auto findTimeInterval = zone.find("cycleIntervalTimeMS"); 154 if (findTimeInterval != zone.end()) 155 { 156 uint64_t tmp; 157 findTimeInterval->get_to(tmp); 158 if (tmp != 0) 159 { 160 thisZoneConfig.cycleTime.cycleIntervalTimeMS = tmp; 161 } 162 else 163 { 164 std::cerr << "cycleIntervalTimeMS cannot be 0. Use default " 165 << thisZoneConfig.cycleTime.cycleIntervalTimeMS 166 << " ms\n"; 167 } 168 } 169 170 auto findUpdateThermalsTime = zone.find("updateThermalsTimeMS"); 171 if (findUpdateThermalsTime != zone.end()) 172 { 173 uint64_t tmp; 174 findUpdateThermalsTime->get_to(tmp); 175 if (tmp != 0) 176 { 177 thisZoneConfig.cycleTime.updateThermalsTimeMS = tmp; 178 } 179 else 180 { 181 std::cerr << "updateThermalsTimeMS cannot be 0. Use default " 182 << thisZoneConfig.cycleTime.updateThermalsTimeMS 183 << " ms\n"; 184 } 185 } 186 187 double updateCount = 188 double(thisZoneConfig.cycleTime.updateThermalsTimeMS) / 189 double(thisZoneConfig.cycleTime.cycleIntervalTimeMS); 190 191 /* Check if updateThermalsTimeMS could be divided by cycleIntervalTimeMS 192 * without leaving a remainder */ 193 if (updateCount != std::ceil(updateCount)) 194 { 195 std::cerr 196 << "updateThermalsTimeMS cannot be divided by " 197 "cycleIntervalTimeMS without leaving a remainder. Using the " 198 "smallest integer that is not less than the result.\n"; 199 updateCount = std::ceil(updateCount); 200 } 201 thisZoneConfig.cycleTime.updateThermalsTimeMS = updateCount; 202 203 auto pids = zone["pids"]; 204 for (const auto& pid : pids) 205 { 206 auto name = pid["name"]; 207 auto item = pid.get<conf::ControllerInfo>(); 208 209 thisZone[name] = item; 210 } 211 212 pidConfig[id] = thisZone; 213 zoneConfig[id] = thisZoneConfig; 214 } 215 216 return std::make_pair(pidConfig, zoneConfig); 217 } 218 219 } // namespace pid_control 220