Support derivative term in PID algorithm and support to set cycle interval time from fan table

1. Support to calculate derivative term in PID algorithm.
2. Add two properties: cycleIntervalTimeMS and updateThermalsTimeMS
in fan table that could be used to decide "time interval of PID control loop"
and "time interval to update thermals' cached value".

Tested:

- PID algorithm:
    1. Check pid-control-service could calculate output PWM
       according to the fan table.

	[Test log]
        root@greatlakes:~# systemctl status phosphor-pid-control -l
        * phosphor-pid-control.service - Phosphor-Pid-Control Margin-based Fan Control Daemon
             Loaded: loaded (/lib/systemd/system/phosphor-pid-control.service; enabled; preset: enabled)
             Active: active (running) since Fri 2018-03-09 05:09:35 PST; 1min 47s ago
           Main PID: 3105 (swampd)
             CGroup: /system.slice/phosphor-pid-control.service
                     `-3105 /usr/bin/swampd -c /usr/share/entity-manager/configurations/fan-table.json
        ...
        Mar 09 05:10:29 greatlakes phosphor-pid-control[3105]: PID Zone 1 max SetPoint 3.75 requested by
        PID_NIC_SENSOR_TEMP BMC_SENSOR_FAN0_TACH BMC_SENSOR_FAN2_TACH BMC_SENSOR_FAN4_TACH BMC_SENSOR_FAN6_TACH

- Cycle interval time:
    1. Set cycleIntervalTimeMS and updateThermalsTimeMS
       to 1000 ms in fan table
    2. Check service would update thermal every second from debug log.

	[Test log]
        root@greatlakes:~# journalctl -u phosphor-pid-control --since "Mar 09 04:52:16"
        Mar 09 04:52:16 greatlakes systemd[1]: Started Phosphor-Pid-Control Margin-based Fan Control Daemon.
        ...
        Mar 09 04:53:28 greatlakes phosphor-pid-control[2795]: processThermals
        Mar 09 04:53:28 greatlakes phosphor-pid-control[2795]: processFans
        Mar 09 04:53:29 greatlakes phosphor-pid-control[2795]: processThermals
        Mar 09 04:53:29 greatlakes phosphor-pid-control[2795]: processFans
        Mar 09 04:53:30 greatlakes phosphor-pid-control[2795]: processThermals
        Mar 09 04:53:30 greatlakes phosphor-pid-control[2795]: processFans

Change-Id: I04e1b440603c3ad66a1e26c96451992785da6fe6
Signed-off-by: Bonnie Lo <Bonnie_Lo@wiwynn.com>
diff --git a/pid/builder.cpp b/pid/builder.cpp
index 7be7897..d7a60b4 100644
--- a/pid/builder.cpp
+++ b/pid/builder.cpp
@@ -69,8 +69,8 @@
 
         auto zone = std::make_shared<DbusPidZone>(
             zoneId, zoneConf->second.minThermalOutput,
-            zoneConf->second.failsafePercent, mgr, modeControlBus,
-            getControlPath(zoneId).c_str(), deferSignals);
+            zoneConf->second.failsafePercent, zoneConf->second.cycleTime, mgr,
+            modeControlBus, getControlPath(zoneId).c_str(), deferSignals);
 
         std::cerr << "Zone Id: " << zone->getZoneID() << "\n";
 
diff --git a/pid/buildjson.cpp b/pid/buildjson.cpp
index e078fdb..cf7df67 100644
--- a/pid/buildjson.cpp
+++ b/pid/buildjson.cpp
@@ -20,6 +20,7 @@
 
 #include <nlohmann/json.hpp>
 
+#include <iostream>
 #include <map>
 #include <tuple>
 
@@ -60,6 +61,7 @@
         p.at("samplePeriod").get_to(c.pidInfo.ts);
         p.at("proportionalCoeff").get_to(c.pidInfo.proportionalCoeff);
         p.at("integralCoeff").get_to(c.pidInfo.integralCoeff);
+        p.at("derivativeCoeff").get_to(c.pidInfo.derivativeCoeff);
         p.at("feedFwdOffsetCoeff").get_to(c.pidInfo.feedFwdOffset);
         p.at("feedFwdGainCoeff").get_to(c.pidInfo.feedFwdGain);
         p.at("integralLimit_min").get_to(c.pidInfo.integralLimit.min);
@@ -139,6 +141,56 @@
         thisZoneConfig.minThermalOutput = zone["minThermalOutput"];
         thisZoneConfig.failsafePercent = zone["failsafePercent"];
 
+        auto findTimeInterval = zone.find("cycleIntervalTimeMS");
+        if (findTimeInterval != zone.end())
+        {
+            uint64_t tmp;
+            findTimeInterval->get_to(tmp);
+            if (tmp != 0)
+            {
+                thisZoneConfig.cycleTime.cycleIntervalTimeMS = tmp;
+            }
+            else
+            {
+                std::cerr << "cycleIntervalTimeMS cannot be 0. Use default "
+                          << thisZoneConfig.cycleTime.cycleIntervalTimeMS
+                          << " ms\n";
+            }
+        }
+
+        auto findUpdateThermalsTime = zone.find("updateThermalsTimeMS");
+        if (findUpdateThermalsTime != zone.end())
+        {
+            uint64_t tmp;
+            findUpdateThermalsTime->get_to(tmp);
+            if (tmp != 0)
+            {
+                thisZoneConfig.cycleTime.updateThermalsTimeMS = tmp;
+            }
+            else
+            {
+                std::cerr << "updateThermalsTimeMS cannot be 0. Use default "
+                          << thisZoneConfig.cycleTime.updateThermalsTimeMS
+                          << " ms\n";
+            }
+        }
+
+        double updateCount =
+            double(thisZoneConfig.cycleTime.updateThermalsTimeMS) /
+            double(thisZoneConfig.cycleTime.cycleIntervalTimeMS);
+
+        /* Check if updateThermalsTimeMS could be divided by cycleIntervalTimeMS
+         * without leaving a remainder */
+        if (updateCount != std::ceil(updateCount))
+        {
+            std::cerr
+                << "updateThermalsTimeMS cannot be divided by "
+                   "cycleIntervalTimeMS without leaving a remainder. Using the "
+                   "smallest integer that is not less than the result.\n";
+            updateCount = std::ceil(updateCount);
+        }
+        thisZoneConfig.cycleTime.updateThermalsTimeMS = updateCount;
+
         auto pids = zone["pids"];
         for (const auto& pid : pids)
         {
diff --git a/pid/ec/pid.cpp b/pid/ec/pid.cpp
index 98968f7..10db1dd 100644
--- a/pid/ec/pid.cpp
+++ b/pid/ec/pid.cpp
@@ -48,6 +48,7 @@
 
     double proportionalTerm;
     double integralTerm = 0.0f;
+    double derivativeTerm = 0.0f;
     double feedFwdTerm = 0.0f;
 
     double output;
@@ -67,11 +68,15 @@
                              pidinfoptr->integralLimit.max);
     }
 
+    // piD
+    derivativeTerm = pidinfoptr->derivativeCoeff *
+                     ((error - pidinfoptr->lastError) / pidinfoptr->ts);
+
     // FF
     feedFwdTerm =
         (setpoint + pidinfoptr->feedFwdOffset) * pidinfoptr->feedFwdGain;
 
-    output = proportionalTerm + integralTerm + feedFwdTerm;
+    output = proportionalTerm + integralTerm + derivativeTerm + feedFwdTerm;
     output = clamp(output, pidinfoptr->outLim.min, pidinfoptr->outLim.max);
 
     // slew rate
@@ -115,6 +120,7 @@
                          pidinfoptr->integralLimit.max);
     pidinfoptr->integral = integralTerm;
     pidinfoptr->initialized = true;
+    pidinfoptr->lastError = error;
     pidinfoptr->lastOutput = output;
 
     return output;
diff --git a/pid/ec/pid.hpp b/pid/ec/pid.hpp
index 29c7bb3..255c911 100644
--- a/pid/ec/pid.hpp
+++ b/pid/ec/pid.hpp
@@ -23,9 +23,11 @@
     double ts;         // sample time in seconds
     double integral;   // intergal of error
     double lastOutput; // value of last output
+    double lastError;  // value of last error
 
     double proportionalCoeff; // coeff for P
     double integralCoeff;     // coeff for I
+    double derivativeCoeff;   // coeff for D
     double feedFwdOffset;     // offset coeff for feed-forward term
     double feedFwdGain;       // gain for feed-forward term
 
@@ -45,6 +47,7 @@
     double ts;                  // sample time in seconds
     double proportionalCoeff;   // coeff for P
     double integralCoeff;       // coeff for I
+    double derivativeCoeff;     // coeff for D
     double feedFwdOffset;       // offset coeff for feed-forward term
     double feedFwdGain;         // gain for feed-forward term
     ec::limits_t integralLimit; // clamp of integral
diff --git a/pid/pidloop.cpp b/pid/pidloop.cpp
index a5d0daf..aba2b6e 100644
--- a/pid/pidloop.cpp
+++ b/pid/pidloop.cpp
@@ -48,7 +48,7 @@
 
 void pidControlLoop(std::shared_ptr<ZoneInterface> zone,
                     std::shared_ptr<boost::asio::steady_timer> timer,
-                    const bool* isCanceling, bool first, int ms100cnt)
+                    const bool* isCanceling, bool first, uint64_t cycleCnt)
 {
     if (*isCanceling)
         return;
@@ -64,8 +64,9 @@
         processThermals(zone);
     }
 
-    timer->expires_after(std::chrono::milliseconds(100));
-    timer->async_wait([zone, timer, ms100cnt, isCanceling](
+    timer->expires_after(
+        std::chrono::milliseconds(zone->getCycleIntervalTime()));
+    timer->async_wait([zone, timer, cycleCnt, isCanceling](
                           const boost::system::error_code& ec) mutable {
         if (ec == boost::asio::error::operation_aborted)
         {
@@ -102,16 +103,16 @@
         // Check if we should just go back to sleep.
         if (zone->getManualMode())
         {
-            pidControlLoop(zone, timer, isCanceling, false, ms100cnt);
+            pidControlLoop(zone, timer, isCanceling, false, cycleCnt);
             return;
         }
 
         // Get the latest fan speeds.
         zone->updateFanTelemetry();
 
-        if (10 <= ms100cnt)
+        if (zone->getUpdateThermalsCycle() <= cycleCnt)
         {
-            ms100cnt = 0;
+            cycleCnt = 0;
 
             processThermals(zone);
         }
@@ -126,9 +127,9 @@
             zone->writeLog(out.str());
         }
 
-        ms100cnt += 1;
+        cycleCnt += 1;
 
-        pidControlLoop(zone, timer, isCanceling, false, ms100cnt);
+        pidControlLoop(zone, timer, isCanceling, false, cycleCnt);
     });
 }
 
diff --git a/pid/pidloop.hpp b/pid/pidloop.hpp
index f9b78b3..0a143e7 100644
--- a/pid/pidloop.hpp
+++ b/pid/pidloop.hpp
@@ -17,11 +17,11 @@
  * @param[in] isCanceling - bool ptr to indicate whether pidControlLoop is being
  * canceled.
  * @param[in] first - boolean to denote if initialization needs to be run.
- * @param[in] ms100cnt - loop timer counter.
+ * @param[in] cycleCnt - loop timer counter.
  */
 void pidControlLoop(std::shared_ptr<ZoneInterface> zone,
                     std::shared_ptr<boost::asio::steady_timer> timer,
                     const bool* isCanceling, bool first = true,
-                    int ms100cnt = 0);
+                    uint64_t cycleCnt = 0);
 
 } // namespace pid_control
diff --git a/pid/util.cpp b/pid/util.cpp
index 646376d..ac6edb1 100644
--- a/pid/util.cpp
+++ b/pid/util.cpp
@@ -29,6 +29,7 @@
     info->ts = initial.ts;
     info->proportionalCoeff = initial.proportionalCoeff;
     info->integralCoeff = initial.integralCoeff;
+    info->derivativeCoeff = initial.derivativeCoeff;
     info->feedFwdOffset = initial.feedFwdOffset;
     info->feedFwdGain = initial.feedFwdGain;
     info->integralLimit.min = initial.integralLimit.min;
@@ -46,6 +47,7 @@
     std::cerr << " ts: " << info->ts
               << " proportionalCoeff: " << info->proportionalCoeff
               << " integralCoeff: " << info->integralCoeff
+              << " derivativeCoeff: " << info->derivativeCoeff
               << " feedFwdOffset: " << info->feedFwdOffset
               << " feedFwdGain: " << info->feedFwdGain
               << " integralLimit.min: " << info->integralLimit.min
@@ -54,6 +56,7 @@
               << " outLim.max: " << info->outLim.max
               << " slewNeg: " << info->slewNeg << " slewPos: " << info->slewPos
               << " last_output: " << info->lastOutput
+              << " last_error: " << info->lastError
               << " integral: " << info->integral << std::endl;
 
     return;
diff --git a/pid/zone.cpp b/pid/zone.cpp
index 585cbeb..cac4577 100644
--- a/pid/zone.cpp
+++ b/pid/zone.cpp
@@ -141,6 +141,16 @@
     return _minThermalOutputSetPt;
 }
 
+uint64_t DbusPidZone::getCycleIntervalTime(void) const
+{
+    return _cycleTime.cycleIntervalTimeMS;
+}
+
+uint64_t DbusPidZone::getUpdateThermalsCycle(void) const
+{
+    return _cycleTime.updateThermalsTimeMS;
+}
+
 void DbusPidZone::addFanPID(std::unique_ptr<Controller> pid)
 {
     _fans.push_back(std::move(pid));
diff --git a/pid/zone.hpp b/pid/zone.hpp
index 179b24d..41514a7 100644
--- a/pid/zone.hpp
+++ b/pid/zone.hpp
@@ -36,14 +36,14 @@
 {
   public:
     DbusPidZone(int64_t zone, double minThermalOutput, double failSafePercent,
-                const SensorManager& mgr, sdbusplus::bus_t& bus,
-                const char* objPath, bool defer) :
+                conf::CycleTime cycleTime, const SensorManager& mgr,
+                sdbusplus::bus_t& bus, const char* objPath, bool defer) :
         ModeObject(bus, objPath,
                    defer ? ModeObject::action::defer_emit
                          : ModeObject::action::emit_object_added),
         _zoneId(zone), _maximumSetPoint(),
         _minThermalOutputSetPt(minThermalOutput),
-        _failSafePercent(failSafePercent), _mgr(mgr)
+        _failSafePercent(failSafePercent), _cycleTime(cycleTime), _mgr(mgr)
     {
         if (loggingEnabled)
         {
@@ -68,6 +68,8 @@
     void clearRPMCeilings(void) override;
     double getFailSafePercent(void) const override;
     double getMinThermalSetPoint(void) const;
+    uint64_t getCycleIntervalTime(void) const override;
+    uint64_t getUpdateThermalsCycle(void) const override;
 
     Sensor* getSensor(const std::string& name) override;
     void determineMaxSetPointRequest(void) override;
@@ -107,6 +109,7 @@
     bool _redundantWrite = false;
     const double _minThermalOutputSetPt;
     const double _failSafePercent;
+    const conf::CycleTime _cycleTime;
 
     std::set<std::string> _failSafeSensors;
 
diff --git a/pid/zone_interface.hpp b/pid/zone_interface.hpp
index 7d59497..d7f73b4 100644
--- a/pid/zone_interface.hpp
+++ b/pid/zone_interface.hpp
@@ -87,6 +87,10 @@
      */
     virtual double getFailSafePercent() const = 0;
 
+    /** Return the zone's cycle time settings */
+    virtual uint64_t getCycleIntervalTime(void) const = 0;
+    virtual uint64_t getUpdateThermalsCycle(void) const = 0;
+
     /** Return if the zone is set to manual mode.  false equates to automatic
      * mode (the default).
      */