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/Makefile.am b/Makefile.am
index a46ffd0..23f39bb 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -54,9 +54,11 @@
sensors/builderconfig.cpp \
sensors/manager.cpp \
pid/ec/pid.cpp \
- pid/controller.cpp \
+ pid/ec/stepwise.cpp \
pid/fancontroller.cpp \
pid/thermalcontroller.cpp \
+ pid/pidcontroller.cpp \
+ pid/stepwisecontroller.cpp \
pid/builder.cpp \
pid/builderconfig.cpp \
pid/zone.cpp \
diff --git a/conf.hpp b/conf.hpp
index 1586187..01968cb 100644
--- a/conf.hpp
+++ b/conf.hpp
@@ -1,6 +1,7 @@
#pragma once
#include "pid/ec/pid.hpp"
+#include "pid/ec/stepwise.hpp"
#include <map>
#include <string>
@@ -30,7 +31,11 @@
std::string type; // fan or margin or temp?
std::vector<std::string> inputs; // one or more sensors.
float setpoint; // initial setpoint for thermal.
- ec::pidinfo info; // pid details
+ union
+ {
+ ec::pidinfo pidInfo; // pid details
+ ec::StepwiseInfo stepwiseInfo;
+ };
};
/*
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;
}
}
}
diff --git a/pid/builder.cpp b/pid/builder.cpp
index 8ffa77c..e98ba22 100644
--- a/pid/builder.cpp
+++ b/pid/builder.cpp
@@ -17,7 +17,9 @@
#include "pid/builder.hpp"
#include "conf.hpp"
+#include "pid/controller.hpp"
#include "pid/fancontroller.hpp"
+#include "pid/stepwisecontroller.hpp"
#include "pid/thermalcontroller.hpp"
#include <iostream>
@@ -89,7 +91,7 @@
}
auto pid = FanController::CreateFanPid(zone.get(), name, inputs,
- info->info);
+ info->pidInfo);
zone->addFanPID(std::move(pid));
}
else if (info->type == "temp" || info->type == "margin")
@@ -101,9 +103,21 @@
}
auto pid = ThermalController::CreateThermalPid(
- zone.get(), name, inputs, info->setpoint, info->info);
+ zone.get(), name, inputs, info->setpoint, info->pidInfo);
+
zone->addThermalPID(std::move(pid));
}
+ else if (info->type == "stepwise")
+ {
+ for (auto& i : info->inputs)
+ {
+ inputs.push_back(i);
+ zone->addThermalInput(i);
+ }
+ auto stepwise = StepwiseController::CreateStepwiseController(
+ zone.get(), name, inputs, info->stepwiseInfo);
+ zone->addThermalPID(std::move(stepwise));
+ }
std::cerr << "inputs: ";
for (auto& i : inputs)
diff --git a/pid/builderconfig.cpp b/pid/builderconfig.cpp
index 0106057..c72ef9e 100644
--- a/pid/builderconfig.cpp
+++ b/pid/builderconfig.cpp
@@ -102,20 +102,22 @@
/* set-point is only required to be set for thermal. */
/* TODO(venture): Verify this works optionally here. */
info.setpoint = pid.lookup("set-point");
- info.info.ts = pid.lookup("pid.sampleperiod");
- info.info.p_c = pid.lookup("pid.p_coefficient");
- info.info.i_c = pid.lookup("pid.i_coefficient");
- info.info.ff_off = pid.lookup("pid.ff_off_coefficient");
- info.info.ff_gain = pid.lookup("pid.ff_gain_coefficient");
- info.info.i_lim.min = pid.lookup("pid.i_limit.min");
- info.info.i_lim.max = pid.lookup("pid.i_limit.max");
- info.info.out_lim.min = pid.lookup("pid.out_limit.min");
- info.info.out_lim.max = pid.lookup("pid.out_limit.max");
- info.info.slew_neg = pid.lookup("pid.slew_neg");
- info.info.slew_pos = pid.lookup("pid.slew_pos");
+ info.pidInfo.ts = pid.lookup("pid.sampleperiod");
+ info.pidInfo.p_c = pid.lookup("pid.p_coefficient");
+ info.pidInfo.i_c = pid.lookup("pid.i_coefficient");
+ info.pidInfo.ff_off = pid.lookup("pid.ff_off_coefficient");
+ info.pidInfo.ff_gain = pid.lookup("pid.ff_gain_coefficient");
+ info.pidInfo.i_lim.min = pid.lookup("pid.i_limit.min");
+ info.pidInfo.i_lim.max = pid.lookup("pid.i_limit.max");
+ info.pidInfo.out_lim.min = pid.lookup("pid.out_limit.min");
+ info.pidInfo.out_lim.max = pid.lookup("pid.out_limit.max");
+ info.pidInfo.slew_neg = pid.lookup("pid.slew_neg");
+ info.pidInfo.slew_pos = pid.lookup("pid.slew_pos");
- std::cerr << "out_lim.min: " << info.info.out_lim.min << "\n";
- std::cerr << "out_lim.max: " << info.info.out_lim.max << "\n";
+ std::cerr << "out_lim.min: " << info.pidInfo.out_lim.min
+ << "\n";
+ std::cerr << "out_lim.max: " << info.pidInfo.out_lim.max
+ << "\n";
const Setting& inputs = pid["inputs"];
int icount = inputs.getLength();
diff --git a/pid/controller.hpp b/pid/controller.hpp
index 57ee43f..f52c412 100644
--- a/pid/controller.hpp
+++ b/pid/controller.hpp
@@ -3,57 +3,23 @@
#include "ec/pid.hpp"
#include "fan.hpp"
-#include <memory>
-#include <vector>
-
-class ZoneInterface;
+#include <string>
/*
- * Base class for PID controllers. Each PID that implements this needs to
- * provide an input_proc, setpt_proc, and output_proc.
+ * Base class for controllers. Each controller that implements this needs to
+ * provide an input_proc, process, and output_proc.
*/
-class PIDController
-{
- public:
- PIDController(const std::string& id, ZoneInterface* owner) :
- _owner(owner), _setpoint(0), _id(id)
- {
- }
+class ZoneInterface;
- virtual ~PIDController()
- {
- }
+struct Controller
+{
+ virtual ~Controller() = default;
virtual float input_proc(void) = 0;
- virtual float setpt_proc(void) = 0;
+
virtual void output_proc(float value) = 0;
- void pid_process(void);
+ virtual void process(void) = 0;
- std::string get_id(void)
- {
- return _id;
- }
- float get_setpoint(void)
- {
- return _setpoint;
- }
- void set_setpoint(float setpoint)
- {
- _setpoint = setpoint;
- }
-
- ec::pid_info_t* get_pid_info(void)
- {
- return &_pid_info;
- }
-
- protected:
- ZoneInterface* _owner;
-
- private:
- // parameters
- ec::pid_info_t _pid_info;
- float _setpoint;
- std::string _id;
+ virtual std::string get_id(void) = 0;
};
diff --git a/pid/ec/stepwise.cpp b/pid/ec/stepwise.cpp
new file mode 100644
index 0000000..4246fb3
--- /dev/null
+++ b/pid/ec/stepwise.cpp
@@ -0,0 +1,45 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// 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 "stepwise.hpp"
+
+#include <cstddef>
+#include <limits>
+
+namespace ec
+{
+float stepwise(const ec::StepwiseInfo& info, float input)
+{
+ float value = info.output[0]; // if we are below the lowest
+ // point, we set the lowest value
+
+ for (size_t ii = 1; ii < ec::maxStepwisePoints; ii++)
+ {
+
+ if (info.reading[ii] == std::numeric_limits<float>::quiet_NaN())
+ {
+ break;
+ }
+ if (info.reading[ii] > input)
+ {
+ break;
+ }
+ value = info.output[ii];
+ }
+
+ return value;
+}
+} // namespace ec
\ No newline at end of file
diff --git a/pid/ec/stepwise.hpp b/pid/ec/stepwise.hpp
new file mode 100644
index 0000000..ed07b44
--- /dev/null
+++ b/pid/ec/stepwise.hpp
@@ -0,0 +1,36 @@
+
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// 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.
+*/
+
+#pragma once
+
+#include <cstddef>
+#include <vector>
+
+namespace ec
+{
+constexpr size_t maxStepwisePoints = 20;
+
+struct StepwiseInfo
+{
+ float ts; // sample time in seconds
+ float reading[maxStepwisePoints];
+ float output[maxStepwisePoints];
+};
+
+float stepwise(const ec::StepwiseInfo& info, float value);
+
+} // namespace ec
\ No newline at end of file
diff --git a/pid/fancontroller.hpp b/pid/fancontroller.hpp
index aaccd4b..521d638 100644
--- a/pid/fancontroller.hpp
+++ b/pid/fancontroller.hpp
@@ -1,8 +1,8 @@
#pragma once
-#include "controller.hpp"
#include "ec/pid.hpp"
#include "fan.hpp"
+#include "pidcontroller.hpp"
#include <memory>
#include <string>
diff --git a/pid/controller.cpp b/pid/pidcontroller.cpp
similarity index 94%
rename from pid/controller.cpp
rename to pid/pidcontroller.cpp
index 138268b..524b0ef 100644
--- a/pid/controller.cpp
+++ b/pid/pidcontroller.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "controller.hpp"
+#include "pidcontroller.hpp"
#include "ec/pid.hpp"
@@ -26,7 +26,7 @@
#include <thread>
#include <vector>
-void PIDController::pid_process(void)
+void PIDController::process(void)
{
float input;
float setpt;
diff --git a/pid/pidcontroller.hpp b/pid/pidcontroller.hpp
new file mode 100644
index 0000000..18a448e
--- /dev/null
+++ b/pid/pidcontroller.hpp
@@ -0,0 +1,60 @@
+#pragma once
+
+#include "controller.hpp"
+#include "ec/pid.hpp"
+#include "fan.hpp"
+
+#include <memory>
+#include <vector>
+
+class ZoneInterface;
+
+/*
+ * Base class for PID controllers. Each PID that implements this needs to
+ * provide an input_proc, setpt_proc, and output_proc.
+ */
+class PIDController : public Controller
+{
+ public:
+ PIDController(const std::string& id, ZoneInterface* owner) :
+ Controller(), _owner(owner), _setpoint(0), _id(id)
+ {
+ }
+
+ virtual ~PIDController()
+ {
+ }
+
+ virtual float input_proc(void) = 0;
+ virtual float setpt_proc(void) = 0;
+ virtual void output_proc(float value) = 0;
+
+ void process(void);
+
+ std::string get_id(void)
+ {
+ return _id;
+ }
+ float get_setpoint(void)
+ {
+ return _setpoint;
+ }
+ void set_setpoint(float setpoint)
+ {
+ _setpoint = setpoint;
+ }
+
+ ec::pid_info_t* get_pid_info(void)
+ {
+ return &_pid_info;
+ }
+
+ protected:
+ ZoneInterface* _owner;
+
+ private:
+ // parameters
+ ec::pid_info_t _pid_info;
+ float _setpoint;
+ std::string _id;
+};
diff --git a/pid/pidthread.cpp b/pid/pidthread.cpp
index 490b70e..4dfc22a 100644
--- a/pid/pidthread.cpp
+++ b/pid/pidthread.cpp
@@ -16,7 +16,7 @@
#include "pidthread.hpp"
-#include "pid/controller.hpp"
+#include "pid/pidcontroller.hpp"
#include "sensors/sensor.hpp"
#include <chrono>
diff --git a/pid/stepwisecontroller.cpp b/pid/stepwisecontroller.cpp
new file mode 100644
index 0000000..cea21e5
--- /dev/null
+++ b/pid/stepwisecontroller.cpp
@@ -0,0 +1,75 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// 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 "stepwisecontroller.hpp"
+
+#include "ec/stepwise.hpp"
+#include "util.hpp"
+#include "zone.hpp"
+
+#include <algorithm>
+#include <chrono>
+#include <iostream>
+#include <map>
+#include <memory>
+#include <thread>
+#include <vector>
+
+void StepwiseController::process(void)
+{
+ // Get input value
+ float input = input_proc();
+
+ // Calculate new output
+ float output = ec::stepwise(get_stepwise_info(), input);
+
+ // Output new value
+ output_proc(output);
+
+ return;
+}
+
+std::unique_ptr<Controller> StepwiseController::CreateStepwiseController(
+ ZoneInterface* owner, const std::string& id,
+ const std::vector<std::string>& inputs, const ec::StepwiseInfo& initial)
+{
+ // StepwiseController currently only supports precisely one input.
+ if (inputs.size() != 1)
+ {
+ return nullptr;
+ }
+
+ auto thermal = std::make_unique<StepwiseController>(id, inputs, owner);
+
+ ec::StepwiseInfo& info = thermal->get_stepwise_info();
+
+ info = initial;
+
+ return thermal;
+}
+
+float StepwiseController::input_proc(void)
+{
+ double value = _owner->getCachedValue(_inputs.at(0));
+ return static_cast<float>(value);
+}
+
+void StepwiseController::output_proc(float value)
+{
+ _owner->addRPMSetPoint(value);
+
+ return;
+}
diff --git a/pid/stepwisecontroller.hpp b/pid/stepwisecontroller.hpp
new file mode 100644
index 0000000..c3af1a1
--- /dev/null
+++ b/pid/stepwisecontroller.hpp
@@ -0,0 +1,52 @@
+#pragma once
+
+#include "controller.hpp"
+#include "ec/stepwise.hpp"
+#include "fan.hpp"
+
+#include <memory>
+#include <vector>
+
+class ZoneInterface;
+
+class StepwiseController : public Controller
+{
+ public:
+ static std::unique_ptr<Controller>
+ CreateStepwiseController(ZoneInterface* owner, const std::string& id,
+ const std::vector<std::string>& inputs,
+ const ec::StepwiseInfo& initial);
+
+ StepwiseController(const std::string& id,
+ const std::vector<std::string>& inputs,
+ ZoneInterface* owner) :
+ Controller(),
+ _owner(owner), _id(id), _inputs(inputs)
+ {
+ }
+
+ float input_proc(void) override;
+
+ void output_proc(float value) override;
+
+ void process(void) override;
+
+ std::string get_id(void)
+ {
+ return _id;
+ }
+
+ ec::StepwiseInfo& get_stepwise_info(void)
+ {
+ return _stepwise_info;
+ }
+
+ protected:
+ ZoneInterface* _owner;
+
+ private:
+ // parameters
+ ec::StepwiseInfo _stepwise_info;
+ std::string _id;
+ std::vector<std::string> _inputs;
+};
diff --git a/pid/thermalcontroller.hpp b/pid/thermalcontroller.hpp
index ed94acb..85c3a2b 100644
--- a/pid/thermalcontroller.hpp
+++ b/pid/thermalcontroller.hpp
@@ -1,7 +1,7 @@
#pragma once
-#include "controller.hpp"
#include "ec/pid.hpp"
+#include "pidcontroller.hpp"
#include <memory>
#include <string>
diff --git a/pid/zone.cpp b/pid/zone.cpp
index 9e948f0..92a332e 100644
--- a/pid/zone.cpp
+++ b/pid/zone.cpp
@@ -21,6 +21,7 @@
#include "pid/controller.hpp"
#include "pid/ec/pid.hpp"
#include "pid/fancontroller.hpp"
+#include "pid/stepwisecontroller.hpp"
#include "pid/thermalcontroller.hpp"
#include <algorithm>
@@ -80,12 +81,12 @@
return _minThermalRpmSetPt;
}
-void PIDZone::addFanPID(std::unique_ptr<PIDController> pid)
+void PIDZone::addFanPID(std::unique_ptr<Controller> pid)
{
_fans.push_back(std::move(pid));
}
-void PIDZone::addThermalPID(std::unique_ptr<PIDController> pid)
+void PIDZone::addThermalPID(std::unique_ptr<Controller> pid)
{
_thermals.push_back(std::move(pid));
}
@@ -305,7 +306,7 @@
{
for (auto& p : _fans)
{
- p->pid_process();
+ p->process();
}
}
@@ -313,7 +314,7 @@
{
for (auto& p : _thermals)
{
- p->pid_process();
+ p->process();
}
}
diff --git a/pid/zone.hpp b/pid/zone.hpp
index 99a8f14..6d5f6cc 100644
--- a/pid/zone.hpp
+++ b/pid/zone.hpp
@@ -2,6 +2,7 @@
#include "conf.hpp"
#include "controller.hpp"
+#include "pidcontroller.hpp"
#include "sensors/manager.hpp"
#include "sensors/sensor.hpp"
#include "xyz/openbmc_project/Control/Mode/server.hpp"
@@ -76,8 +77,8 @@
void process_fans(void);
void process_thermals(void);
- void addFanPID(std::unique_ptr<PIDController> pid);
- void addThermalPID(std::unique_ptr<PIDController> pid);
+ void addFanPID(std::unique_ptr<Controller> pid);
+ void addThermalPID(std::unique_ptr<Controller> pid);
double getCachedValue(const std::string& name) override;
void addFanInput(std::string fan);
void addThermalInput(std::string therm);
@@ -111,6 +112,6 @@
std::map<std::string, double> _cachedValuesByName;
const SensorManager& _mgr;
- std::vector<std::unique_ptr<PIDController>> _fans;
- std::vector<std::unique_ptr<PIDController>> _thermals;
+ std::vector<std::unique_ptr<Controller>> _fans;
+ std::vector<std::unique_ptr<Controller>> _thermals;
};
diff --git a/test/Makefile.am b/test/Makefile.am
index 6622169..4a01994 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -32,17 +32,17 @@
pid_zone_unittest_SOURCES = pid_zone_unittest.cpp
pid_zone_unittest_LDADD = $(top_builddir)/pid/ec/pid.o \
- $(top_builddir)/sensors/manager.o $(top_builddir)/pid/controller.o \
+ $(top_builddir)/sensors/manager.o $(top_builddir)/pid/pidcontroller.o \
$(top_builddir)/pid/zone.o
pid_thermalcontroller_unittest_SOURCES = pid_thermalcontroller_unittest.cpp
pid_thermalcontroller_unittest_LDADD = $(top_builddir)/pid/ec/pid.o \
- $(top_builddir)/pid/util.o $(top_builddir)/pid/controller.o \
+ $(top_builddir)/pid/util.o $(top_builddir)/pid/pidcontroller.o \
$(top_builddir)/pid/thermalcontroller.o
pid_fancontroller_unittest_SOURCES = pid_fancontroller_unittest.cpp
pid_fancontroller_unittest_LDADD = $(top_builddir)/pid/ec/pid.o \
- $(top_builddir)/pid/util.o $(top_builddir)/pid/controller.o \
+ $(top_builddir)/pid/util.o $(top_builddir)/pid/pidcontroller.o \
$(top_builddir)/pid/fancontroller.o
dbus_passive_unittest_SOURCES = dbus_passive_unittest.cpp