Support threshold hysteresis

Add support for a hysteresis value on the warning high/low and critical
high/low sensor thresholds.  On the high thresholds, this requires the
sensor value to dip below the (threshold - hysteresis) value before the
alarm clears, and on the low thresholds it has to go above (threshold +
hysteresis).

These are optional fields in the configuration JSON and will default to
zero if not present, which results in the previous behavior of not
having a hysteresis at all.

Tested: with IBM's Ambient_Virtual_Temp sensor. When changing the value
above a high threshold and then back below the threshold, we only see
the alarm deasserted when the temp is below (threshold - hysteresis).

Change-Id: Ied1d40def94e1b66cf4ec8826799bada4e6236ab
Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Signed-off-by: Rashmica Gupta <rashmica.g@gmail.com>
diff --git a/virtualSensor.cpp b/virtualSensor.cpp
index 4e9f50a..0f27019 100644
--- a/virtualSensor.cpp
+++ b/virtualSensor.cpp
@@ -18,6 +18,7 @@
 static constexpr auto vsThresholdsIfaceSuffix = ".Thresholds";
 static constexpr std::array<const char*, 1> calculationIfaces = {
     "xyz.openbmc_project.Configuration.ModifiedMedian"};
+static constexpr auto defaultHysteresis = 0;
 
 using namespace phosphor::logging;
 
@@ -185,6 +186,13 @@
 
     auto threshold = getThresholdType(direction, severity);
     thresholds[threshold] = value;
+
+    auto hysteresis =
+        getNumberFromConfig<double>(propertyMap, "Hysteresis", false);
+    if (hysteresis != std::numeric_limits<double>::quiet_NaN())
+    {
+        thresholds[threshold + "Hysteresis"] = hysteresis;
+    }
 }
 
 void VirtualSensor::parseConfigInterface(const PropertyMap& propertyMap,
@@ -507,6 +515,10 @@
             "CriticalHigh", std::numeric_limits<double>::quiet_NaN()));
         criticalIface->criticalLow(threshold.value(
             "CriticalLow", std::numeric_limits<double>::quiet_NaN()));
+        criticalIface->setHighHysteresis(
+            threshold.value("CriticalHighHysteresis", defaultHysteresis));
+        criticalIface->setLowHysteresis(
+            threshold.value("CriticalLowHysteresis", defaultHysteresis));
     }
 
     if (threshold.contains("WarningHigh") || threshold.contains("WarningLow"))
@@ -518,6 +530,10 @@
             "WarningHigh", std::numeric_limits<double>::quiet_NaN()));
         warningIface->warningLow(threshold.value(
             "WarningLow", std::numeric_limits<double>::quiet_NaN()));
+        warningIface->setHighHysteresis(
+            threshold.value("WarningHighHysteresis", defaultHysteresis));
+        warningIface->setLowHysteresis(
+            threshold.value("WarningLowHysteresis", defaultHysteresis));
     }
 
     if (threshold.contains("HardShutdownHigh") ||
@@ -530,6 +546,10 @@
             "HardShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
         hardShutdownIface->hardShutdownLow(threshold.value(
             "HardShutdownLow", std::numeric_limits<double>::quiet_NaN()));
+        hardShutdownIface->setHighHysteresis(
+            threshold.value("HardShutdownHighHysteresis", defaultHysteresis));
+        hardShutdownIface->setLowHysteresis(
+            threshold.value("HardShutdownLowHysteresis", defaultHysteresis));
     }
 
     if (threshold.contains("SoftShutdownHigh") ||
@@ -542,6 +562,10 @@
             "SoftShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
         softShutdownIface->softShutdownLow(threshold.value(
             "SoftShutdownLow", std::numeric_limits<double>::quiet_NaN()));
+        softShutdownIface->setHighHysteresis(
+            threshold.value("SoftShutdownHighHysteresis", defaultHysteresis));
+        softShutdownIface->setLowHysteresis(
+            threshold.value("SoftShutdownLowHysteresis", defaultHysteresis));
     }
 
     if (threshold.contains("PerformanceLossHigh") ||
@@ -554,6 +578,10 @@
             "PerformanceLossHigh", std::numeric_limits<double>::quiet_NaN()));
         perfLossIface->performanceLossLow(threshold.value(
             "PerformanceLossLow", std::numeric_limits<double>::quiet_NaN()));
+        perfLossIface->setHighHysteresis(threshold.value(
+            "PerformanceLossHighHysteresis", defaultHysteresis));
+        perfLossIface->setLowHysteresis(
+            threshold.value("PerformanceLossLowHysteresis", defaultHysteresis));
     }
 }