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/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;
 };