/** * Copyright 2019 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "pid/buildjson.hpp" #include "conf.hpp" #include "util.hpp" #include #include #include #include #include namespace pid_control { using json = nlohmann::json; namespace conf { void from_json(const json& j, conf::ControllerInfo& c) { std::vector inputNames; std::vector missingAcceptableNames; j.at("type").get_to(c.type); j.at("inputs").get_to(inputNames); j.at("setpoint").get_to(c.setpoint); std::vector inputTempToMargin; auto findTempToMargin = j.find("tempToMargin"); if (findTempToMargin != j.end()) { findTempToMargin->get_to(inputTempToMargin); } auto findMissingAcceptable = j.find("missingIsAcceptable"); if (findMissingAcceptable != j.end()) { findMissingAcceptable->get_to(missingAcceptableNames); } c.inputs = spliceInputs(inputNames, inputTempToMargin, missingAcceptableNames); /* TODO: We need to handle parsing other PID controller configurations. * We can do that by checking for different keys and making the decision * accordingly. */ auto p = j.at("pid"); auto positiveHysteresis = p.find("positiveHysteresis"); auto negativeHysteresis = p.find("negativeHysteresis"); auto derivativeCoeff = p.find("derivativeCoeff"); auto positiveHysteresisValue = 0.0; auto negativeHysteresisValue = 0.0; auto derivativeCoeffValue = 0.0; if (positiveHysteresis != p.end()) { positiveHysteresis->get_to(positiveHysteresisValue); } if (negativeHysteresis != p.end()) { negativeHysteresis->get_to(negativeHysteresisValue); } if (derivativeCoeff != p.end()) { derivativeCoeff->get_to(derivativeCoeffValue); } auto failSafePercent = j.find("FailSafePercent"); auto failSafePercentValue = 0; if (failSafePercent != j.end()) { failSafePercent->get_to(failSafePercentValue); } c.failSafePercent = failSafePercentValue; if (c.type != "stepwise") { p.at("samplePeriod").get_to(c.pidInfo.ts); p.at("proportionalCoeff").get_to(c.pidInfo.proportionalCoeff); p.at("integralCoeff").get_to(c.pidInfo.integralCoeff); p.at("feedFwdOffsetCoeff").get_to(c.pidInfo.feedFwdOffset); p.at("feedFwdGainCoeff").get_to(c.pidInfo.feedFwdGain); p.at("integralLimit_min").get_to(c.pidInfo.integralLimit.min); p.at("integralLimit_max").get_to(c.pidInfo.integralLimit.max); p.at("outLim_min").get_to(c.pidInfo.outLim.min); p.at("outLim_max").get_to(c.pidInfo.outLim.max); p.at("slewNeg").get_to(c.pidInfo.slewNeg); p.at("slewPos").get_to(c.pidInfo.slewPos); // Unlike other coefficients, treat derivativeCoeff as an optional // parameter, as support for it is fairly new, to avoid breaking // existing configurations in the field that predate it. c.pidInfo.positiveHysteresis = positiveHysteresisValue; c.pidInfo.negativeHysteresis = negativeHysteresisValue; c.pidInfo.derivativeCoeff = derivativeCoeffValue; } else { p.at("samplePeriod").get_to(c.stepwiseInfo.ts); p.at("isCeiling").get_to(c.stepwiseInfo.isCeiling); for (size_t i = 0; i < ec::maxStepwisePoints; i++) { c.stepwiseInfo.reading[i] = std::numeric_limits::quiet_NaN(); c.stepwiseInfo.output[i] = std::numeric_limits::quiet_NaN(); } auto reading = p.find("reading"); if (reading != p.end()) { auto r = p.at("reading"); for (size_t i = 0; i < ec::maxStepwisePoints; i++) { auto n = r.find(std::to_string(i)); if (n != r.end()) { r.at(std::to_string(i)).get_to(c.stepwiseInfo.reading[i]); } } } auto output = p.find("output"); if (output != p.end()) { auto o = p.at("output"); for (size_t i = 0; i < ec::maxStepwisePoints; i++) { auto n = o.find(std::to_string(i)); if (n != o.end()) { o.at(std::to_string(i)).get_to(c.stepwiseInfo.output[i]); } } } c.stepwiseInfo.positiveHysteresis = positiveHysteresisValue; c.stepwiseInfo.negativeHysteresis = negativeHysteresisValue; } } } // namespace conf inline void getCycleTimeSetting(const auto& zone, const int id, const std::string& attributeName, uint64_t& value) { auto findAttributeName = zone.find(attributeName); if (findAttributeName != zone.end()) { uint64_t tmpAttributeValue = 0; findAttributeName->get_to(tmpAttributeValue); if (tmpAttributeValue >= 1) { value = tmpAttributeValue; } else { std::cerr << "Zone " << id << ": " << attributeName << " is invalid. Use default " << value << " ms\n"; } } else { std::cerr << "Zone " << id << ": " << attributeName << " cannot find setting. Use default " << value << " ms\n"; } } std::pair, std::map> buildPIDsFromJson(const json& data) { // zone -> pids std::map pidConfig; // zone -> configs std::map zoneConfig; /* TODO: if zones is empty, that's invalid. */ auto zones = data["zones"]; for (const auto& zone : zones) { int64_t id; conf::PIDConf thisZone; conf::ZoneConfig thisZoneConfig; /* TODO: using at() throws a specific exception we can catch */ id = zone["id"]; thisZoneConfig.minThermalOutput = zone["minThermalOutput"]; thisZoneConfig.failsafePercent = zone["failsafePercent"]; getCycleTimeSetting(zone, id, "cycleIntervalTimeMS", thisZoneConfig.cycleTime.cycleIntervalTimeMS); getCycleTimeSetting(zone, id, "updateThermalsTimeMS", thisZoneConfig.cycleTime.updateThermalsTimeMS); auto pids = zone["pids"]; for (const auto& pid : pids) { auto name = pid["name"]; auto item = pid.get(); if (thisZone.find(name) != thisZone.end()) { std::cerr << "Warning: zone " << id << " have the same pid name " << name << std::endl; } thisZone[name] = item; } pidConfig[id] = thisZone; zoneConfig[id] = thisZoneConfig; } return std::make_pair(pidConfig, zoneConfig); } } // namespace pid_control