Add Hysteresis to pid controllers

Add hysteresis to pid controllers to lower pwm changes.
It is defaulted to 0 so it should be transparent
to any controller that choses not to implement it.
This is the same pattern used by the stepwise controller.

Tested-by: Unit tests passed

Change-Id: Ib47114285b0017258b7f77eaf067d310f95a0c60
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/pid/ec/pid.hpp b/pid/ec/pid.hpp
index 779ced5..3ab64cd 100644
--- a/pid/ec/pid.hpp
+++ b/pid/ec/pid.hpp
@@ -31,6 +31,8 @@
     limits_t out_lim; // clamp of output
     double slew_neg;
     double slew_pos;
+    double positiveHysteresis;
+    double negativeHysteresis;
 } pid_info_t;
 
 double pid(pid_info_t* pidinfoptr, double input, double setpoint);
@@ -47,6 +49,8 @@
     ec::limits_t out_lim; // clamp of output
     double slew_neg;
     double slew_pos;
+    double positiveHysteresis;
+    double negativeHysteresis;
 };
 
 } // namespace ec
diff --git a/pid/pidcontroller.cpp b/pid/pidcontroller.cpp
index 7be6ceb..e3eaaff 100644
--- a/pid/pidcontroller.cpp
+++ b/pid/pidcontroller.cpp
@@ -20,6 +20,7 @@
 
 #include <algorithm>
 #include <chrono>
+#include <cmath>
 #include <iostream>
 #include <map>
 #include <memory>
@@ -38,8 +39,39 @@
     // Get input value
     input = inputProc();
 
-    // Calculate new output
-    output = ec::pid(getPIDInfo(), input, setpt);
+    auto info = getPIDInfo();
+
+    // if no hysteresis, maintain previous behavior
+    if (info->positiveHysteresis == 0 && info->negativeHysteresis == 0)
+    {
+        // Calculate new output
+        output = ec::pid(info, input, setpt);
+
+        // this variable isn't actually used in this context, but we're setting
+        // it here incase somebody uses it later it's the correct value
+        lastInput = input;
+    }
+    else
+    {
+        // initialize if not set yet
+        if (std::isnan(lastInput))
+        {
+            lastInput = input;
+        }
+
+        // if reading is outside of hysteresis bounds, use it for reading,
+        // otherwise use last reading without updating it first
+        else if ((input - lastInput) > info->positiveHysteresis)
+        {
+            lastInput = input;
+        }
+        else if ((lastInput - input) > info->negativeHysteresis)
+        {
+            lastInput = input;
+        }
+
+        output = ec::pid(info, lastInput, setpt);
+    }
 
     // Output new value
     outputProc(output);
diff --git a/pid/pidcontroller.hpp b/pid/pidcontroller.hpp
index 9ed3be2..3d38c2a 100644
--- a/pid/pidcontroller.hpp
+++ b/pid/pidcontroller.hpp
@@ -49,6 +49,11 @@
         return &_pid_info;
     }
 
+    double getLastInput(void)
+    {
+        return lastInput;
+    }
+
   protected:
     ZoneInterface* _owner;
 
@@ -57,4 +62,5 @@
     ec::pid_info_t _pid_info;
     double _setpoint;
     std::string _id;
+    double lastInput = std::numeric_limits<double>::quiet_NaN();
 };
diff --git a/pid/util.cpp b/pid/util.cpp
index 9d666ef..a5936b0 100644
--- a/pid/util.cpp
+++ b/pid/util.cpp
@@ -34,6 +34,8 @@
     info->out_lim.max = initial.out_lim.max;
     info->slew_neg = initial.slew_neg;
     info->slew_pos = initial.slew_pos;
+    info->negativeHysteresis = initial.negativeHysteresis;
+    info->positiveHysteresis = initial.positiveHysteresis;
 }
 
 void dumpPIDStruct(ec::pid_info_t* info)