Allow setting setpoint based on dynamic thresholds

As we allow dynamic thresholds in dbus-sensors to track
the t-control of dimms etc, we want to be able to set a
setpoint based on a offset from a threshold. This adds
the ability to create a "SetPointOffset" that is a
threshold of the given sensor. For instance a "SetPointOffset"
of "WarningHigh" would get the sensors "WarningHigh" value
then add the "SetPoint" value to it (commonly negative for
WarningHigh/CriticalHigh).

Tested: Turned on debug print and saw correct setpoint being
loaded into config

Change-Id: Idb9760ea5a66347f24573fb26937f8f278834a19
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/dbus/dbusconfiguration.cpp b/dbus/dbusconfiguration.cpp
index 7641eaf..e68c852 100644
--- a/dbus/dbusconfiguration.cpp
+++ b/dbus/dbusconfiguration.cpp
@@ -50,9 +50,24 @@
 constexpr const char* sensorInterface = "xyz.openbmc_project.Sensor.Value";
 constexpr const char* pwmInterface = "xyz.openbmc_project.Control.FanPwm";
 
+namespace thresholds
+{
+constexpr const char* warningInterface =
+    "xyz.openbmc_project.Sensor.Threshold.Warning";
+constexpr const char* criticalInterface =
+    "xyz.openbmc_project.Sensor.Threshold.Critical";
+const std::array<const char*, 4> types = {"CriticalLow", "CriticalHigh",
+                                          "WarningLow", "WarningHigh"};
+
+} // namespace thresholds
+
 namespace dbus_configuration
 {
 
+using DbusVariantType =
+    std::variant<uint64_t, int64_t, double, std::string,
+                 std::vector<std::string>, std::vector<double>>;
+
 bool findSensors(const std::unordered_map<std::string, std::string>& sensors,
                  const std::string& search,
                  std::vector<std::pair<std::string, std::string>>& matches)
@@ -276,6 +291,97 @@
         eventHandler, &timer);
 }
 
+void populatePidInfo(
+    sdbusplus::bus::bus& bus,
+    const std::unordered_map<std::string, DbusVariantType>& base,
+    struct conf::ControllerInfo& info, const std::string* thresholdProperty)
+{
+
+    info.type = std::get<std::string>(base.at("Class"));
+
+    if (info.type == "fan")
+    {
+        info.setpoint = 0;
+    }
+    else
+    {
+        info.setpoint =
+            std::visit(VariantToDoubleVisitor(), base.at("SetPoint"));
+    }
+
+    if (thresholdProperty != nullptr)
+    {
+        std::string interface;
+        if (*thresholdProperty == "WarningHigh" ||
+            *thresholdProperty == "WarningLow")
+        {
+            interface = thresholds::warningInterface;
+        }
+        else
+        {
+            interface = thresholds::criticalInterface;
+        }
+        const std::string& path = sensorConfig[info.inputs.front()].readPath;
+
+        DbusHelper helper;
+        std::string service = helper.getService(bus, interface, path);
+        double reading = 0;
+        try
+        {
+            helper.getProperty(bus, service, path, interface,
+                               *thresholdProperty, reading);
+        }
+        catch (const sdbusplus::exception::SdBusError& ex)
+        {
+            // unsupported threshold, leaving reading at 0
+        }
+
+        info.setpoint += reading;
+    }
+
+    info.pidInfo.ts = 1.0; // currently unused
+    info.pidInfo.proportionalCoeff =
+        std::visit(VariantToDoubleVisitor(), base.at("PCoefficient"));
+    info.pidInfo.integralCoeff =
+        std::visit(VariantToDoubleVisitor(), base.at("ICoefficient"));
+    info.pidInfo.feedFwdOffset =
+        std::visit(VariantToDoubleVisitor(), base.at("FFOffCoefficient"));
+    info.pidInfo.feedFwdGain =
+        std::visit(VariantToDoubleVisitor(), base.at("FFGainCoefficient"));
+    info.pidInfo.integralLimit.max =
+        std::visit(VariantToDoubleVisitor(), base.at("ILimitMax"));
+    info.pidInfo.integralLimit.min =
+        std::visit(VariantToDoubleVisitor(), base.at("ILimitMin"));
+    info.pidInfo.outLim.max =
+        std::visit(VariantToDoubleVisitor(), base.at("OutLimitMax"));
+    info.pidInfo.outLim.min =
+        std::visit(VariantToDoubleVisitor(), base.at("OutLimitMin"));
+    info.pidInfo.slewNeg =
+        std::visit(VariantToDoubleVisitor(), base.at("SlewNeg"));
+    info.pidInfo.slewPos =
+        std::visit(VariantToDoubleVisitor(), base.at("SlewPos"));
+    double negativeHysteresis = 0;
+    double positiveHysteresis = 0;
+
+    auto findNeg = base.find("NegativeHysteresis");
+    auto findPos = base.find("PositiveHysteresis");
+
+    if (findNeg != base.end())
+    {
+        negativeHysteresis =
+            std::visit(VariantToDoubleVisitor(), findNeg->second);
+    }
+
+    if (findPos != base.end())
+    {
+        positiveHysteresis =
+            std::visit(VariantToDoubleVisitor(), findPos->second);
+    }
+
+    info.pidInfo.negativeHysteresis = negativeHysteresis;
+    info.pidInfo.positiveHysteresis = positiveHysteresis;
+}
+
 bool init(sdbusplus::bus::bus& bus, boost::asio::steady_timer& timer)
 {
 
@@ -558,61 +664,43 @@
                     continue;
                 }
 
-                struct conf::ControllerInfo& info =
-                    conf[std::get<std::string>(base.at("Name"))];
-                info.inputs = std::move(inputs);
+                std::string offsetType;
 
-                info.type = std::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")
+                // SetPointOffset is a threshold value to pull from the sensor
+                // to apply an offset. For upper thresholds this means the
+                // setpoint is usually negative.
+                auto findSetpointOffset = base.find("SetPointOffset");
+                if (findSetpointOffset != base.end())
                 {
-                    info.setpoint = 0;
+                    offsetType =
+                        std::get<std::string>(findSetpointOffset->second);
+                    if (std::find(thresholds::types.begin(),
+                                  thresholds::types.end(),
+                                  offsetType) == thresholds::types.end())
+                    {
+                        throw std::runtime_error("Unsupported type: " +
+                                                 offsetType);
+                    }
+                }
+
+                if (offsetType.empty())
+                {
+                    struct conf::ControllerInfo& info =
+                        conf[std::get<std::string>(base.at("Name"))];
+                    info.inputs = std::move(inputs);
+                    populatePidInfo(bus, base, info, nullptr);
                 }
                 else
                 {
-                    info.setpoint = std::visit(VariantToDoubleVisitor(),
-                                               base.at("SetPoint"));
+                    // we have to split up the inputs, as in practice t-control
+                    // values will differ, making setpoints differ
+                    for (const std::string& input : inputs)
+                    {
+                        struct conf::ControllerInfo& info = conf[input];
+                        info.inputs.emplace_back(input);
+                        populatePidInfo(bus, base, info, &offsetType);
+                    }
                 }
-                info.pidInfo.ts = 1.0; // currently unused
-                info.pidInfo.proportionalCoeff = std::visit(
-                    VariantToDoubleVisitor(), base.at("PCoefficient"));
-                info.pidInfo.integralCoeff = std::visit(
-                    VariantToDoubleVisitor(), base.at("ICoefficient"));
-                info.pidInfo.feedFwdOffset = std::visit(
-                    VariantToDoubleVisitor(), base.at("FFOffCoefficient"));
-                info.pidInfo.feedFwdGain = std::visit(
-                    VariantToDoubleVisitor(), base.at("FFGainCoefficient"));
-                info.pidInfo.integralLimit.max =
-                    std::visit(VariantToDoubleVisitor(), base.at("ILimitMax"));
-                info.pidInfo.integralLimit.min =
-                    std::visit(VariantToDoubleVisitor(), base.at("ILimitMin"));
-                info.pidInfo.outLim.max = std::visit(VariantToDoubleVisitor(),
-                                                     base.at("OutLimitMax"));
-                info.pidInfo.outLim.min = std::visit(VariantToDoubleVisitor(),
-                                                     base.at("OutLimitMin"));
-                info.pidInfo.slewNeg =
-                    std::visit(VariantToDoubleVisitor(), base.at("SlewNeg"));
-                info.pidInfo.slewPos =
-                    std::visit(VariantToDoubleVisitor(), base.at("SlewPos"));
-                double negativeHysteresis = 0;
-                double positiveHysteresis = 0;
-
-                auto findNeg = base.find("NegativeHysteresis");
-                auto findPos = base.find("PositiveHysteresis");
-                if (findNeg != base.end())
-                {
-                    negativeHysteresis =
-                        std::visit(VariantToDoubleVisitor(), findNeg->second);
-                }
-
-                if (findPos != base.end())
-                {
-                    positiveHysteresis =
-                        std::visit(VariantToDoubleVisitor(), findPos->second);
-                }
-                info.pidInfo.negativeHysteresis = negativeHysteresis;
-                info.pidInfo.positiveHysteresis = positiveHysteresis;
             }
         }
         auto findStepwise =