Add stepwise controller
This adds the ability to use stepwise curves alongside
pid control. This creates a base controller class that
pidcontroller and stepwise controller inherit from.
Note: Hysteresis to come in follow-on patch
Tested-by:
Created a stepwise controller and noticed that when it
crossed a threshold that it contributed to the pwm setting.
Change-Id: I6cf842f80eaccafc905d620970afe91e2092d568
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/dbus/dbusconfiguration.cpp b/dbus/dbusconfiguration.cpp
index 7f721bf..b912e62 100644
--- a/dbus/dbusconfiguration.cpp
+++ b/dbus/dbusconfiguration.cpp
@@ -21,6 +21,7 @@
#include <iostream>
#include <sdbusplus/bus.hpp>
#include <sdbusplus/bus/match.hpp>
+#include <sdbusplus/exception.hpp>
#include <set>
#include <thread>
#include <unordered_map>
@@ -37,6 +38,8 @@
"org.freedesktop.DBus.ObjectManager";
constexpr const char* pidZoneConfigurationInterface =
"xyz.openbmc_project.Configuration.Pid.Zone";
+constexpr const char* stepwiseConfigurationInterface =
+ "xyz.openbmc_project.Configuration.Stepwise";
constexpr const char* sensorInterface = "xyz.openbmc_project.Sensor.Value";
constexpr const char* pwmInterface = "xyz.openbmc_project.Control.FanPwm";
@@ -103,17 +106,17 @@
}
std::cout << "\t\t\t}\n";
std::cout << "\t\t\t" << pidconf.second.setpoint << ",\n";
- std::cout << "\t\t\t{" << pidconf.second.info.ts << ",\n";
- std::cout << "\t\t\t" << pidconf.second.info.p_c << ",\n";
- std::cout << "\t\t\t" << pidconf.second.info.i_c << ",\n";
- std::cout << "\t\t\t" << pidconf.second.info.ff_off << ",\n";
- std::cout << "\t\t\t" << pidconf.second.info.ff_gain << ",\n";
- std::cout << "\t\t\t{" << pidconf.second.info.i_lim.min << ","
- << pidconf.second.info.i_lim.max << "},\n";
- std::cout << "\t\t\t{" << pidconf.second.info.out_lim.min << ","
- << pidconf.second.info.out_lim.max << "},\n";
- std::cout << "\t\t\t" << pidconf.second.info.slew_neg << ",\n";
- std::cout << "\t\t\t" << pidconf.second.info.slew_pos << ",\n";
+ std::cout << "\t\t\t{" << pidconf.second.pidInfo.ts << ",\n";
+ std::cout << "\t\t\t" << pidconf.second.pidInfo.p_c << ",\n";
+ std::cout << "\t\t\t" << pidconf.second.pidInfo.i_c << ",\n";
+ std::cout << "\t\t\t" << pidconf.second.pidInfo.ff_off << ",\n";
+ std::cout << "\t\t\t" << pidconf.second.pidInfo.ff_gain << ",\n";
+ std::cout << "\t\t\t{" << pidconf.second.pidInfo.i_lim.min << ","
+ << pidconf.second.pidInfo.i_lim.max << "},\n";
+ std::cout << "\t\t\t{" << pidconf.second.pidInfo.out_lim.min << ","
+ << pidconf.second.pidInfo.out_lim.max << "},\n";
+ std::cout << "\t\t\t" << pidconf.second.pidInfo.slew_neg << ",\n";
+ std::cout << "\t\t\t" << pidconf.second.pidInfo.slew_pos << ",\n";
std::cout << "\t\t\t}\n\t\t}\n";
}
std::cout << "\t},\n";
@@ -123,14 +126,15 @@
void init(sdbusplus::bus::bus& bus)
{
+ using DbusVariantType =
+ sdbusplus::message::variant<uint64_t, int64_t, double, std::string,
+ std::vector<std::string>,
+ std::vector<double>>;
+
using ManagedObjectType = std::unordered_map<
sdbusplus::message::object_path,
- std::unordered_map<
- std::string,
- std::unordered_map<std::string,
- sdbusplus::message::variant<
- uint64_t, int64_t, double, std::string,
- std::vector<std::string>>>>>;
+ std::unordered_map<std::string,
+ std::unordered_map<std::string, DbusVariantType>>>;
// install watch for properties changed
std::function<void(sdbusplus::message::message & message)> eventHandler =
@@ -153,22 +157,32 @@
"/xyz/openbmc_project/object_mapper",
"xyz.openbmc_project.ObjectMapper", "GetSubTree");
mapper.append("", 0,
- std::array<const char*, 5>{objectManagerInterface,
+ std::array<const char*, 6>{objectManagerInterface,
pidConfigurationInterface,
pidZoneConfigurationInterface,
+ stepwiseConfigurationInterface,
sensorInterface, pwmInterface});
- auto resp = bus.call(mapper);
- if (resp.is_method_error())
- {
- throw std::runtime_error("ObjectMapper Call Failure");
- }
std::unordered_map<
std::string, std::unordered_map<std::string, std::vector<std::string>>>
respData;
+ try
+ {
+ auto resp = bus.call(mapper);
+ if (resp.is_method_error())
+ {
+ throw std::runtime_error("ObjectMapper Call Failure");
+ }
+ resp.read(respData);
+ }
+ catch (sdbusplus::exception_t&)
+ {
+ // can't do anything without mapper call data
+ throw std::runtime_error("ObjectMapper Call Failure");
+ }
- resp.read(respData);
if (respData.empty())
{
+ // can't do anything without mapper call data
throw std::runtime_error("No configuration data available from Mapper");
}
// create a map of pair of <has pid configuration, ObjectManager path>
@@ -188,7 +202,8 @@
owner.second = objectPair.first;
}
if (interface == pidConfigurationInterface ||
- interface == pidZoneConfigurationInterface)
+ interface == pidZoneConfigurationInterface ||
+ interface == stepwiseConfigurationInterface)
{
owner.first = true;
}
@@ -216,19 +231,31 @@
auto endpoint = bus.new_method_call(
owner.first.c_str(), owner.second.second.c_str(),
"org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
- auto responce = bus.call(endpoint);
- if (responce.is_method_error())
+ ManagedObjectType configuration;
+ try
{
+ auto responce = bus.call(endpoint);
+ if (responce.is_method_error())
+ {
+ throw std::runtime_error("Error getting managed objects from " +
+ owner.first);
+ }
+ responce.read(configuration);
+ }
+ catch (sdbusplus::exception_t&)
+ {
+ // this shouldn't happen, probably means daemon crashed
throw std::runtime_error("Error getting managed objects from " +
owner.first);
}
- ManagedObjectType configuration;
- responce.read(configuration);
+
for (auto& pathPair : configuration)
{
if (pathPair.second.find(pidConfigurationInterface) !=
pathPair.second.end() ||
pathPair.second.find(pidZoneConfigurationInterface) !=
+ pathPair.second.end() ||
+ pathPair.second.find(stepwiseConfigurationInterface) !=
pathPair.second.end())
{
configurations.emplace(pathPair);
@@ -268,125 +295,217 @@
VariantToFloatVisitor(), zone.at("FailSafePercent"));
}
auto findBase = configuration.second.find(pidConfigurationInterface);
- if (findBase == configuration.second.end())
+ if (findBase != configuration.second.end())
{
- continue;
- }
- const auto& base = configuration.second.at(pidConfigurationInterface);
- const std::vector<std::string>& zones =
- sdbusplus::message::variant_ns::get<std::vector<std::string>>(
- base.at("Zones"));
- for (const std::string& zone : zones)
- {
- auto it = std::find(zoneIndex.begin(), zoneIndex.end(), zone);
- size_t index = 1;
- if (it == zoneIndex.end())
- {
- zoneIndex.emplace_back(zone);
- index = zoneIndex.size();
- }
- else
- {
- index = zoneIndex.end() - it;
- }
- PIDConf& conf = ZoneConfig[index];
- struct controller_info& info =
- conf[sdbusplus::message::variant_ns::get<std::string>(
- base.at("Name"))];
- info.type = sdbusplus::message::variant_ns::get<std::string>(
- base.at("Class"));
- // todo: auto generation yaml -> c script seems to discard this
- // value for fans, verify this is okay
- if (info.type == "fan")
- {
- info.setpoint = 0;
- }
- else
- {
- info.setpoint = mapbox::util::apply_visitor(
- VariantToFloatVisitor(), base.at("SetPoint"));
- }
- info.info.ts = 1.0; // currently unused
- info.info.p_c = mapbox::util::apply_visitor(
- VariantToFloatVisitor(), base.at("PCoefficient"));
- info.info.i_c = mapbox::util::apply_visitor(
- VariantToFloatVisitor(), base.at("ICoefficient"));
- info.info.ff_off = mapbox::util::apply_visitor(
- VariantToFloatVisitor(), base.at("FFOffCoefficient"));
- info.info.ff_gain = mapbox::util::apply_visitor(
- VariantToFloatVisitor(), base.at("FFGainCoefficient"));
- info.info.i_lim.max = mapbox::util::apply_visitor(
- VariantToFloatVisitor(), base.at("ILimitMax"));
- info.info.i_lim.min = mapbox::util::apply_visitor(
- VariantToFloatVisitor(), base.at("ILimitMin"));
- info.info.out_lim.max = mapbox::util::apply_visitor(
- VariantToFloatVisitor(), base.at("OutLimitMax"));
- info.info.out_lim.min = mapbox::util::apply_visitor(
- VariantToFloatVisitor(), base.at("OutLimitMin"));
- info.info.slew_neg = mapbox::util::apply_visitor(
- VariantToFloatVisitor(), base.at("SlewNeg"));
- info.info.slew_pos = mapbox::util::apply_visitor(
- VariantToFloatVisitor(), base.at("SlewPos"));
-
- std::vector<std::string> sensorNames =
+ const auto& base =
+ configuration.second.at(pidConfigurationInterface);
+ const std::vector<std::string>& zones =
sdbusplus::message::variant_ns::get<std::vector<std::string>>(
- base.at("Inputs"));
- auto findOutputs =
- base.find("Outputs"); // currently only fans have outputs
- if (findOutputs != base.end())
+ base.at("Zones"));
+ for (const std::string& zone : zones)
{
- std::vector<std::string> outputs =
- sdbusplus::message::variant_ns::get<
- std::vector<std::string>>(findOutputs->second);
- sensorNames.insert(sensorNames.end(), outputs.begin(),
- outputs.end());
- }
- for (const std::string& sensorName : sensorNames)
- {
- std::string name = sensorName;
- // replace spaces with underscores to be legal on dbus
- std::replace(name.begin(), name.end(), ' ', '_');
- std::pair<std::string, std::string> sensorPathIfacePair;
-
- if (!findSensor(sensors, name, sensorPathIfacePair))
+ auto it = std::find(zoneIndex.begin(), zoneIndex.end(), zone);
+ size_t index = 1;
+ if (it == zoneIndex.end())
{
- throw std::runtime_error(
- "Could not map configuration to sensor " + name);
+ zoneIndex.emplace_back(zone);
+ index = zoneIndex.size();
}
- if (sensorPathIfacePair.second == sensorInterface)
+ else
{
+ index = zoneIndex.end() - it;
+ }
+ PIDConf& conf = ZoneConfig[index];
+ struct controller_info& info =
+ conf[sdbusplus::message::variant_ns::get<std::string>(
+ base.at("Name"))];
+ info.type = sdbusplus::message::variant_ns::get<std::string>(
+ base.at("Class"));
+ // todo: auto generation yaml -> c script seems to discard this
+ // value for fans, verify this is okay
+ if (info.type == "fan")
+ {
+ info.setpoint = 0;
+ }
+ else
+ {
+ info.setpoint = mapbox::util::apply_visitor(
+ VariantToFloatVisitor(), base.at("SetPoint"));
+ }
+ info.pidInfo.ts = 1.0; // currently unused
+ info.pidInfo.p_c = mapbox::util::apply_visitor(
+ VariantToFloatVisitor(), base.at("PCoefficient"));
+ info.pidInfo.i_c = mapbox::util::apply_visitor(
+ VariantToFloatVisitor(), base.at("ICoefficient"));
+ info.pidInfo.ff_off = mapbox::util::apply_visitor(
+ VariantToFloatVisitor(), base.at("FFOffCoefficient"));
+ info.pidInfo.ff_gain = mapbox::util::apply_visitor(
+ VariantToFloatVisitor(), base.at("FFGainCoefficient"));
+ info.pidInfo.i_lim.max = mapbox::util::apply_visitor(
+ VariantToFloatVisitor(), base.at("ILimitMax"));
+ info.pidInfo.i_lim.min = mapbox::util::apply_visitor(
+ VariantToFloatVisitor(), base.at("ILimitMin"));
+ info.pidInfo.out_lim.max = mapbox::util::apply_visitor(
+ VariantToFloatVisitor(), base.at("OutLimitMax"));
+ info.pidInfo.out_lim.min = mapbox::util::apply_visitor(
+ VariantToFloatVisitor(), base.at("OutLimitMin"));
+ info.pidInfo.slew_neg = mapbox::util::apply_visitor(
+ VariantToFloatVisitor(), base.at("SlewNeg"));
+ info.pidInfo.slew_pos = mapbox::util::apply_visitor(
+ VariantToFloatVisitor(), base.at("SlewPos"));
+
+ std::vector<std::string> sensorNames =
+ sdbusplus::message::variant_ns::get<
+ std::vector<std::string>>(base.at("Inputs"));
+ auto findOutputs =
+ base.find("Outputs"); // currently only fans have outputs
+ if (findOutputs != base.end())
+ {
+ std::vector<std::string> outputs =
+ sdbusplus::message::variant_ns::get<
+ std::vector<std::string>>(findOutputs->second);
+ sensorNames.insert(sensorNames.end(), outputs.begin(),
+ outputs.end());
+ }
+ for (const std::string& sensorName : sensorNames)
+ {
+ std::string name = sensorName;
+ // replace spaces with underscores to be legal on dbus
+ std::replace(name.begin(), name.end(), ' ', '_');
+ std::pair<std::string, std::string> sensorPathIfacePair;
+
+ if (!findSensor(sensors, name, sensorPathIfacePair))
+ {
+ throw std::runtime_error(
+ "Could not map configuration to sensor " + name);
+ }
+ if (sensorPathIfacePair.second == sensorInterface)
+ {
+ info.inputs.push_back(name);
+ auto& config = SensorConfig[name];
+ config.type =
+ sdbusplus::message::variant_ns::get<std::string>(
+ base.at("Class"));
+ config.readpath = sensorPathIfacePair.first;
+ // todo: maybe un-hardcode this if we run into slower
+ // timeouts with sensors
+ if (config.type == "temp")
+ {
+ config.timeout = 500;
+ }
+ }
+ if (sensorPathIfacePair.second == pwmInterface)
+ {
+ // copy so we can modify it
+ for (std::string otherSensor : sensorNames)
+ {
+ if (otherSensor == sensorName)
+ {
+ continue;
+ }
+ std::replace(otherSensor.begin(), otherSensor.end(),
+ ' ', '_');
+ auto& config = SensorConfig[otherSensor];
+ config.writepath = sensorPathIfacePair.first;
+ // todo: un-hardcode this if there are fans with
+ // different ranges
+ config.max = 255;
+ config.min = 0;
+ }
+ }
+ }
+ }
+ }
+ auto findStepwise =
+ configuration.second.find(stepwiseConfigurationInterface);
+ if (findStepwise != configuration.second.end())
+ {
+ const auto& base = findStepwise->second;
+ const std::vector<std::string>& zones =
+ sdbusplus::message::variant_ns::get<std::vector<std::string>>(
+ base.at("Zones"));
+ for (const std::string& zone : zones)
+ {
+ auto it = std::find(zoneIndex.begin(), zoneIndex.end(), zone);
+ size_t index = 1;
+ if (it == zoneIndex.end())
+ {
+ zoneIndex.emplace_back(zone);
+ index = zoneIndex.size();
+ }
+ else
+ {
+ index = zoneIndex.end() - it;
+ }
+ PIDConf& conf = ZoneConfig[index];
+ struct controller_info& info =
+ conf[sdbusplus::message::variant_ns::get<std::string>(
+ base.at("Name"))];
+ info.type = "stepwise";
+ info.stepwiseInfo.ts = 1.0; // currently unused
+ std::vector<double> readings =
+ sdbusplus::message::variant_ns::get<std::vector<double>>(
+ base.at("Reading"));
+ if (readings.size() > ec::maxStepwisePoints)
+ {
+ throw std::invalid_argument("Too many stepwise points.");
+ }
+ if (readings.empty())
+ {
+ throw std::invalid_argument(
+ "Must have one stepwise point.");
+ }
+ std::copy(readings.begin(), readings.end(),
+ info.stepwiseInfo.reading);
+ if (readings.size() < ec::maxStepwisePoints)
+ {
+ info.stepwiseInfo.reading[readings.size()] =
+ std::numeric_limits<float>::quiet_NaN();
+ }
+ std::vector<double> outputs =
+ sdbusplus::message::variant_ns::get<std::vector<double>>(
+ base.at("Output"));
+ if (readings.size() != outputs.size())
+ {
+ throw std::invalid_argument(
+ "Outputs size must match readings");
+ }
+ std::copy(outputs.begin(), outputs.end(),
+ info.stepwiseInfo.output);
+ if (outputs.size() < ec::maxStepwisePoints)
+ {
+ info.stepwiseInfo.output[outputs.size()] =
+ std::numeric_limits<float>::quiet_NaN();
+ }
+
+ std::vector<std::string> sensorNames =
+ sdbusplus::message::variant_ns::get<
+ std::vector<std::string>>(base.at("Inputs"));
+
+ for (const std::string& sensorName : sensorNames)
+ {
+ std::string name = sensorName;
+ // replace spaces with underscores to be legal on dbus
+ std::replace(name.begin(), name.end(), ' ', '_');
+ std::pair<std::string, std::string> sensorPathIfacePair;
+
+ if (!findSensor(sensors, name, sensorPathIfacePair))
+ {
+ // todo(james): if we can't find a sensor, delete it
+ // from config, we'll find it on rescan
+ throw std::runtime_error(
+ "Could not map configuration to sensor " + name);
+ }
+
info.inputs.push_back(name);
auto& config = SensorConfig[name];
- config.type =
- sdbusplus::message::variant_ns::get<std::string>(
- base.at("Class"));
config.readpath = sensorPathIfacePair.first;
+ config.type = "temp";
// todo: maybe un-hardcode this if we run into slower
// timeouts with sensors
- if (config.type == "temp")
- {
- config.timeout = 500;
- }
- }
- if (sensorPathIfacePair.second == pwmInterface)
- {
- // copy so we can modify it
- for (std::string otherSensor : sensorNames)
- {
- if (otherSensor == sensorName)
- {
- continue;
- }
- std::replace(otherSensor.begin(), otherSensor.end(),
- ' ', '_');
- auto& config = SensorConfig[otherSensor];
- config.writepath = sensorPathIfacePair.first;
- // todo: un-hardcode this if there are fans with
- // different ranges
- config.max = 255;
- config.min = 0;
- }
+
+ config.timeout = 500;
}
}
}