PID Objects & Algo

These are the PID controller implementations for fans,
and thermals.  This also includes the PID algorithm used.

Change-Id: I30471fbf7a8a7ed65f78bf105970d62815fedc56
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/pid/README b/pid/README
new file mode 100644
index 0000000..93e48f3
--- /dev/null
+++ b/pid/README
@@ -0,0 +1,11 @@
+ThermalControllers and FanControllers are derived objects from a common PID
+Controller object.  The design implemented in this structure is a facsimile of
+what was published in the Chrome OS source.
+
+One has any number of ThermalControllers that run through a PID step to
+generate a set-point RPM to reach its thermal set-point.  The maximum ouput
+from the set of ThermalControllers is taken as the input to all the
+FanController PID loops.
+
+Each group of these controllers is managed within a zone.  A PIDZone object
+helps manage them by providing a sensor value cache and overall execution.
diff --git a/pid/controller.cpp b/pid/controller.cpp
new file mode 100644
index 0000000..a5360fe
--- /dev/null
+++ b/pid/controller.cpp
@@ -0,0 +1,48 @@
+/**
+ * Copyright 2017 Google Inc.
+ *
+ * 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 <algorithm>
+#include <chrono>
+#include <iostream>
+#include <map>
+#include <memory>
+#include <thread>
+#include <vector>
+
+#include "controller.hpp"
+#include "ec/pid.hpp"
+
+
+void PIDController::pid_process(void)
+{
+    float input;
+    float setpt;
+    float output;
+
+    // Get setpt value
+    setpt = setpt_proc();
+
+    // Get input value
+    input = input_proc();
+
+    // Calculate new output
+    output = ec::pid(get_pid_info(), input, setpt);
+
+    // Output new value
+    output_proc(output);
+
+    return;
+}
diff --git a/pid/controller.hpp b/pid/controller.hpp
new file mode 100644
index 0000000..0b1de3c
--- /dev/null
+++ b/pid/controller.hpp
@@ -0,0 +1,58 @@
+#pragma once
+
+#include <memory>
+#include <vector>
+
+#include "fan.hpp"
+#include "ec/pid.hpp"
+
+class PIDZone;
+
+/*
+ * Base class for PID controllers.  Each PID that implements this needs to
+ * provide an input_proc, setpt_proc, and output_proc.
+ */
+class PIDController
+{
+    public:
+        PIDController(const std::string& id, std::shared_ptr<PIDZone> owner)
+            : _owner(owner),
+              _id(id)
+        { }
+
+        virtual ~PIDController() { }
+
+        virtual float input_proc(void) = 0;
+        virtual float setpt_proc(void) = 0;
+        virtual void output_proc(float value) = 0;
+
+        void pid_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:
+        std::shared_ptr<PIDZone> _owner;
+
+    private:
+        // parameters
+        ec::pid_info_t  _pid_info;
+        float           _setpoint;
+        std::string     _id;
+};
+
diff --git a/pid/ec/pid.cpp b/pid/ec/pid.cpp
new file mode 100644
index 0000000..a1c4e41
--- /dev/null
+++ b/pid/ec/pid.cpp
@@ -0,0 +1,118 @@
+/**
+ * Copyright 2017 Google Inc.
+ *
+ * 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 "pid.hpp"
+
+namespace ec
+{
+
+/********************************
+ *  clamp
+ *
+ */
+static float clamp(float x, float min, float max)
+{
+    if (x < min)
+    {
+        return min;
+    }
+    else if (x > max)
+    {
+        return max;
+    }
+    return x;
+}
+
+/********************************
+ *  pid code
+ *  Note: Codes assumes the ts field is non-zero
+ */
+float pid(pid_info_t* pidinfoptr, float input, float setpoint)
+{
+    float error;
+
+    float p_term = 0.0f;
+    float i_term = 0.0f;
+    float ff_term = 0.0f;
+
+    float output;
+
+    // calculate P, I, D, FF
+
+    // Pid
+    error   = setpoint - input;
+    p_term  = pidinfoptr->p_c * error;
+
+    // pId
+    if (0.0f != pidinfoptr->i_c)
+    {
+        i_term  = pidinfoptr->integral;
+        i_term  += error * pidinfoptr->i_c * pidinfoptr->ts;
+        i_term  = clamp(i_term, pidinfoptr->i_lim.min, pidinfoptr->i_lim.max);
+    }
+
+    // FF
+    ff_term = (setpoint + pidinfoptr->ff_off) * pidinfoptr->ff_gain;
+
+    output = p_term + i_term + ff_term;
+    output = clamp(output, pidinfoptr->out_lim.min, pidinfoptr->out_lim.max);
+
+    // slew rate
+    // TODO(aarena) - Simplify logic as Andy suggested by creating dynamic
+    // out_lim_min/max that are affected by slew rate control and just clamping
+    // to those instead of effectively clamping twice.
+    if (pidinfoptr->initialized)
+    {
+        if (pidinfoptr->slew_neg != 0.0f)
+        {
+            // Don't decrease too fast
+            float min_out = pidinfoptr->last_output + pidinfoptr->slew_neg *
+                            pidinfoptr->ts;
+            if (output < min_out)
+            {
+                output = min_out;
+            }
+        }
+        if (pidinfoptr->slew_pos != 0.0f)
+        {
+            // Don't increase too fast
+            float max_out = pidinfoptr->last_output + pidinfoptr->slew_pos *
+                            pidinfoptr->ts;
+            if (output > max_out)
+            {
+                output = max_out;
+            }
+        }
+
+        if (pidinfoptr->slew_neg != 0.0f || pidinfoptr->slew_pos != 0.0f)
+        {
+            // Back calculate integral term for the cases where we limited the
+            // output
+            i_term = output - p_term;
+        }
+    }
+
+    // Clamp again because having limited the output may result in a
+    // larger integral term
+    i_term  = clamp(i_term, pidinfoptr->i_lim.min, pidinfoptr->i_lim.max);
+    pidinfoptr->integral = i_term;
+    pidinfoptr->initialized    = true;
+    pidinfoptr->last_output    = output;
+
+    return output;
+}
+
+}
diff --git a/pid/ec/pid.hpp b/pid/ec/pid.hpp
new file mode 100644
index 0000000..e138933
--- /dev/null
+++ b/pid/ec/pid.hpp
@@ -0,0 +1,53 @@
+#pragma once
+
+#include <cstdint>
+
+namespace ec
+{
+
+typedef struct
+{
+    float       min;
+    float       max;
+} limits_t;
+
+/* Note: If you update these structs you need to update the copy code in
+ * pid/util.cpp.
+ */
+typedef struct
+{
+    bool        initialized;        // has pid been initialized
+
+    float       ts;                 // sample time in seconds
+    float       integral;           // intergal of error
+    float       last_output;        // value of last output
+
+    float       p_c;                // coeff for P
+    float       i_c;                // coeff for I
+    float       ff_off;             // offset coeff for feed-forward term
+    float       ff_gain;            // gain for feed-forward term
+
+    limits_t    i_lim;              // clamp of integral
+    limits_t    out_lim;            // clamp of output
+    float       slew_neg;
+    float       slew_pos;
+} pid_info_t;
+
+float pid(pid_info_t* pidinfoptr, float input, float setpoint);
+
+/* Condensed version for use by the configuration. */
+struct pidinfo
+{
+    float        ts;                 // sample time in seconds
+    float        p_c;                // coeff for P
+    float        i_c;                // coeff for I
+    float        ff_off;             // offset coeff for feed-forward term
+    float        ff_gain;            // gain for feed-forward term
+    ec::limits_t i_lim;              // clamp of integral
+    ec::limits_t out_lim;            // clamp of output
+    float        slew_neg;
+    float        slew_pos;
+};
+
+
+}
diff --git a/pid/fan.hpp b/pid/fan.hpp
new file mode 100644
index 0000000..7792d04
--- /dev/null
+++ b/pid/fan.hpp
@@ -0,0 +1,9 @@
+
+#pragma once
+
+enum class FanSpeedDirection
+{
+    DOWN,
+    UP,
+    NEUTRAL, /* not sure this will ever happen, but for completeness. */
+};
diff --git a/pid/fancontroller.cpp b/pid/fancontroller.cpp
new file mode 100644
index 0000000..b5949a8
--- /dev/null
+++ b/pid/fancontroller.cpp
@@ -0,0 +1,146 @@
+/**
+ * Copyright 2017 Google Inc.
+ *
+ * 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 <algorithm>
+#include <iostream>
+
+#include "fancontroller.hpp"
+#include "util.hpp"
+#include "zone.hpp"
+
+std::unique_ptr<PIDController> FanController::CreateFanPid(
+    std::shared_ptr<PIDZone> owner,
+    const std::string& id,
+    std::vector<std::string>& inputs,
+    ec::pidinfo initial)
+{
+    auto fan = std::make_unique<FanController>(id, inputs, owner);
+    ec::pid_info_t* info = fan->get_pid_info();
+
+    InitializePIDStruct(info, &initial);
+
+    return fan;
+}
+
+float FanController::input_proc(void)
+{
+    float sum = 0;
+    double value = 0;
+    std::vector<int64_t> values;
+    std::vector<int64_t>::iterator result;
+
+    try
+    {
+        for (const auto& name : _inputs)
+        {
+            value = _owner->getCachedValue(name);
+            /* If we have a fan we can't read, its value will be 0 for at least
+             * some boards, while others... the fan will drop off dbus (if
+             * that's how it's being read and in that case its value will never
+             * be updated anymore, which is relatively harmless, except, when
+             * something tries to read its value through IPMI, and can't, they
+             * sort of have to guess -- all the other fans are reporting, why
+             * not this one?  Maybe it's unable to be read, so it's "bad."
+             */
+            if (value > 0)
+            {
+                values.push_back(value);
+                sum += value;
+            }
+        }
+    }
+    catch (const std::exception& e)
+    {
+        std::cerr << "exception on input_proc.\n";
+        throw;
+    }
+
+    if (values.size() > 0)
+    {
+        /* When this is a configuration option in a later update, this code
+         * will be updated.
+         */
+        //value = sum / _inputs.size();
+
+        /* the fan PID algorithm was unstable with average, and seemed to work
+         * better with minimum.  I had considered making this choice a variable
+         * in the configuration, and I will.
+         */
+        result = std::min_element(values.begin(), values.end());
+        value = *result;
+    }
+
+    return static_cast<float>(value);
+}
+
+float FanController::setpt_proc(void)
+{
+    float maxRPM = _owner->getMaxRPMRequest();
+
+    // store for reference, and check if more or less.
+    float prev = get_setpoint();
+
+    if (maxRPM > prev)
+    {
+        setFanDirection(FanSpeedDirection::UP);
+    }
+    else if (prev > maxRPM)
+    {
+        setFanDirection(FanSpeedDirection::DOWN);
+    }
+    else
+    {
+        setFanDirection(FanSpeedDirection::NEUTRAL);
+    }
+
+    set_setpoint(maxRPM);
+
+    return (maxRPM);
+}
+
+void FanController::output_proc(float value)
+{
+    float percent = value;
+
+    /* If doing tuning logging, don't go into failsafe mode. */
+#ifndef __TUNING_LOGGING__
+    if (_owner->getFailSafeMode())
+    {
+        /* In case it's being set to 100% */
+        if (percent < _owner->getFailSafePercent())
+        {
+            percent = _owner->getFailSafePercent();
+        }
+    }
+#endif
+
+    // value and kFanFailSafeDutyCycle are 10 for 10% so let's fix that.
+    percent /= 100;
+
+    // PidSensorMap for writing.
+    for (auto& it : _inputs)
+    {
+        auto& sensor = _owner->getSensor(it);
+        sensor->write(static_cast<double>(percent));
+    }
+
+#ifdef __TUNING_LOGGING__
+    _owner->getLogHandle() << "," << percent;
+#endif
+
+    return;
+}
+
diff --git a/pid/fancontroller.hpp b/pid/fancontroller.hpp
new file mode 100644
index 0000000..0816991
--- /dev/null
+++ b/pid/fancontroller.hpp
@@ -0,0 +1,46 @@
+#pragma once
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "controller.hpp"
+#include "fan.hpp"
+#include "ec/pid.hpp"
+
+
+/*
+ * A FanController is a PID controller that reads a number of fans and given
+ * the output then tries to set them to the goal values set by the thermal
+ * controllers.
+ */
+class FanController : public PIDController
+{
+    public:
+        static std::unique_ptr<PIDController> CreateFanPid(
+            std::shared_ptr<PIDZone> owner,
+            const std::string& id,
+            std::vector<std::string>& inputs,
+            ec::pidinfo initial);
+
+        FanController(const std::string& id,
+                      std::vector<std::string>& inputs,
+                      std::shared_ptr<PIDZone> owner)
+            : PIDController(id, owner),
+              _inputs(inputs),
+              _direction(FanSpeedDirection::NEUTRAL)
+        { }
+
+        float input_proc(void) override;
+        float setpt_proc(void) override;
+        void output_proc(float value) override;
+
+        void setFanDirection(FanSpeedDirection direction)
+        {
+            _direction = direction;
+        };
+
+    private:
+        std::vector<std::string> _inputs;
+        FanSpeedDirection _direction;
+};
diff --git a/pid/pidthread.cpp b/pid/pidthread.cpp
new file mode 100644
index 0000000..38b2516
--- /dev/null
+++ b/pid/pidthread.cpp
@@ -0,0 +1,107 @@
+/**
+ * Copyright 2017 Google Inc.
+ *
+ * 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 <chrono>
+#include <map>
+#include <memory>
+#include <thread>
+#include <vector>
+
+#include "pidthread.hpp"
+
+#include "pid/controller.hpp"
+#include "sensors/sensor.hpp"
+
+
+static void ProcessThermals(std::shared_ptr<PIDZone> zone)
+{
+    // Get the latest margins.
+    zone->updateSensors();
+    // Zero out the RPM set point goals.
+    zone->clearRPMSetPoints();
+    // Run the margin PIDs.
+    zone->process_thermals();
+    // Get the maximum RPM set-point.
+    zone->determineMaxRPMRequest();
+}
+
+
+void PIDControlThread(std::shared_ptr<PIDZone> zone)
+{
+    int ms100cnt = 0;
+    /*
+     * This should sleep on the conditional wait for the listen thread to tell
+     * us it's in sync.  But then we also need a timeout option in case
+     * phosphor-hwmon is down, we can go into some weird failure more.
+     *
+     * Another approach would be to start all sensors in worst-case values,
+     * and fail-safe mode and then clear out of fail-safe mode once we start
+     * getting values.  Which I think it is a solid approach.
+     *
+     * For now this runs before it necessarily has any sensor values.  For the
+     * host sensors they start out in fail-safe mode.  For the fans, they start
+     * out as 0 as input and then are adjusted once they have values.
+     *
+     * If a fan has failed, it's value will be whatever we're told or however
+     * we retrieve it.  This program disregards fan values of 0, so any code
+     * providing a fan speed can set to 0 on failure and that fan value will be
+     * effectively ignored.  The PID algorithm will be unhappy but nothing bad
+     * will happen.
+     *
+     * TODO(venture): If the fan value is 0 should that loop just be skipped?
+     * Right now, a 0 value is ignored in FanController::input_proc()
+     */
+#ifdef __TUNING_LOGGING__
+    zone->initializeLog();
+#endif
+    zone->initializeCache();
+    ProcessThermals(zone);
+
+    while (true)
+    {
+        using namespace std::literals::chrono_literals;
+        std::this_thread::sleep_for(0.1s);
+
+        // Check if we should just go back to sleep.
+        if (zone->getManualMode())
+        {
+            continue;
+        }
+
+        // Get the latest fan speeds.
+        zone->updateFanTelemetry();
+
+        if (10 <= ms100cnt)
+        {
+            ms100cnt = 0;
+
+            ProcessThermals(zone);
+        }
+
+        // Run the fan PIDs every iteration.
+        zone->process_fans();
+
+#ifdef __TUNING_LOGGING__
+        zone->getLogHandle() << std::endl;
+#endif
+
+        ms100cnt += 1;
+    }
+
+    return;
+}
+
+
diff --git a/pid/pidthread.hpp b/pid/pidthread.hpp
new file mode 100644
index 0000000..670d558
--- /dev/null
+++ b/pid/pidthread.hpp
@@ -0,0 +1,6 @@
+#pragma once
+
+#include "pid/zone.hpp"
+
+/* Given a zone, run through the loops. */
+void PIDControlThread(std::shared_ptr<PIDZone> zone);
diff --git a/pid/thermalcontroller.cpp b/pid/thermalcontroller.cpp
new file mode 100644
index 0000000..c44503f
--- /dev/null
+++ b/pid/thermalcontroller.cpp
@@ -0,0 +1,76 @@
+/**
+ * Copyright 2017 Google Inc.
+ *
+ * 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 "thermalcontroller.hpp"
+#include "util.hpp"
+#include "zone.hpp"
+
+
+std::unique_ptr<PIDController> ThermalController::CreateThermalPid(
+    std::shared_ptr<PIDZone> owner,
+    const std::string& id,
+    std::vector<std::string>& inputs,
+    float setpoint,
+    ec::pidinfo initial)
+{
+    auto thermal = std::make_unique<ThermalController>(id, inputs, owner);
+
+    ec::pid_info_t* info = thermal->get_pid_info();
+    thermal->set_setpoint(setpoint);
+
+    InitializePIDStruct(info, &initial);
+
+    return thermal;
+}
+
+//bmc_host_sensor_value_float
+float ThermalController::input_proc(void)
+{
+    /*
+     * This only supports one thermal input because it doesn't yet know how to
+     * handle merging them, probably max?
+     */
+    double value = _owner->getCachedValue(_inputs.at(0));
+    return static_cast<float>(value);
+}
+
+// bmc_get_setpt
+float ThermalController::setpt_proc(void)
+{
+    float setpoint = get_setpoint();
+
+    /* TODO(venture): Thermal setpoint invalid? */
+#if 0
+    if (-1 == setpoint)
+    {
+        return 0.0f;
+    }
+    else
+    {
+        return setpoint;
+    }
+#endif
+    return setpoint;
+}
+
+// bmc_set_pid_output
+void ThermalController::output_proc(float value)
+{
+    _owner->addRPMSetPoint(value);
+
+    return;
+}
+
diff --git a/pid/thermalcontroller.hpp b/pid/thermalcontroller.hpp
new file mode 100644
index 0000000..32616ac
--- /dev/null
+++ b/pid/thermalcontroller.hpp
@@ -0,0 +1,39 @@
+#pragma once
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "controller.hpp"
+#include "ec/pid.hpp"
+
+
+/*
+ * A ThermalController is a PID controller that reads a number of sensors and
+ * provides the set-points for the fans.
+ */
+class ThermalController : public PIDController
+{
+    public:
+        static std::unique_ptr<PIDController> CreateThermalPid(
+            std::shared_ptr<PIDZone> owner,
+            const std::string& id,
+            std::vector<std::string>& inputs,
+            float setpoint,
+            ec::pidinfo initial);
+
+        ThermalController(const std::string& id,
+                          std::vector<std::string>& inputs,
+                          std::shared_ptr<PIDZone> owner)
+            : PIDController(id, owner),
+              _inputs(inputs)
+        { }
+
+        float input_proc(void) override;
+        float setpt_proc(void) override;
+        void output_proc(float value) override;
+
+    private:
+        std::vector<std::string> _inputs;
+};
+
diff --git a/pid/util.cpp b/pid/util.cpp
new file mode 100644
index 0000000..79da7e1
--- /dev/null
+++ b/pid/util.cpp
@@ -0,0 +1,57 @@
+/**
+ * Copyright 2017 Google Inc.
+ *
+ * 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 <cstring>
+#include <iostream>
+
+#include "ec/pid.hpp"
+
+void InitializePIDStruct(ec::pid_info_t* info, ec::pidinfo* initial)
+{
+    std::memset(info, 0x00, sizeof(ec::pid_info_t));
+
+    info->ts = initial->ts;
+    info->p_c = initial->p_c;
+    info->i_c = initial->i_c;
+    info->ff_off = initial->ff_off;
+    info->ff_gain = initial->ff_gain;
+    info->i_lim.min = initial->i_lim.min;
+    info->i_lim.max = initial->i_lim.max;
+    info->out_lim.min = initial->out_lim.min;
+    info->out_lim.max = initial->out_lim.max;
+    info->slew_neg = initial->slew_neg;
+    info->slew_pos = initial->slew_pos;
+}
+
+void DumpPIDStruct(ec::pid_info_t *info)
+{
+    std::cerr << " ts: " << info->ts
+              << " p_c: " << info->p_c
+              << " i_c: " << info->i_c
+              << " ff_off: " << info->ff_off
+              << " ff_gain: " << info->ff_gain
+              << " i_lim.min: " << info->i_lim.min
+              << " i_lim.max: " << info->i_lim.max
+              << " out_lim.min: " << info->out_lim.min
+              << " out_lim.max: " << info->out_lim.max
+              << " slew_neg: " << info->slew_neg
+              << " slew_pos: " << info->slew_pos
+              << " last_output: " << info->last_output
+              << " integral: " << info->integral
+              << std::endl;
+
+    return;
+}
diff --git a/pid/util.hpp b/pid/util.hpp
new file mode 100644
index 0000000..eb6e713
--- /dev/null
+++ b/pid/util.hpp
@@ -0,0 +1,12 @@
+#pragma once
+
+#include "ec/pid.hpp"
+
+
+/*
+ * Given a configuration structure, fill out the information we use within the
+ * PID loop.
+ */
+void InitializePIDStruct(ec::pid_info_t* info, ec::pidinfo* initial);
+
+void DumpPIDStruct(ec::pid_info_t *info);
diff --git a/pid/zone.cpp b/pid/zone.cpp
new file mode 100644
index 0000000..04b3176
--- /dev/null
+++ b/pid/zone.cpp
@@ -0,0 +1,553 @@
+/**
+ * Copyright 2017 Google Inc.
+ *
+ * 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.
+ */
+
+/* Configuration. */
+#include "conf.hpp"
+
+#include "zone.hpp"
+
+#include <algorithm>
+#include <chrono>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+#include <libconfig.h++>
+#include <memory>
+
+#include "pid/controller.hpp"
+#include "pid/fancontroller.hpp"
+#include "pid/thermalcontroller.hpp"
+#include "pid/ec/pid.hpp"
+
+
+using tstamp = std::chrono::high_resolution_clock::time_point;
+using namespace std::literals::chrono_literals;
+
+static constexpr bool deferSignals = true;
+static constexpr auto objectPath = "/xyz/openbmc_project/settings/fanctrl/zone";
+
+float PIDZone::getMaxRPMRequest(void) const
+{
+    return _maximumRPMSetPt;
+}
+
+bool PIDZone::getManualMode(void) const
+{
+    return _manualMode;
+}
+
+void PIDZone::setManualMode(bool mode)
+{
+    _manualMode = mode;
+}
+
+bool PIDZone::getFailSafeMode(void) const
+{
+    // If any keys are present at least one sensor is in fail safe mode.
+    return !_failSafeSensors.empty();
+}
+
+int64_t PIDZone::getZoneId(void) const
+{
+    return _zoneId;
+}
+
+void PIDZone::addRPMSetPoint(float setpoint)
+{
+    _RPMSetPoints.push_back(setpoint);
+}
+
+void PIDZone::clearRPMSetPoints(void)
+{
+    _RPMSetPoints.clear();
+}
+
+float PIDZone::getFailSafePercent(void) const
+{
+    return _failSafePercent;
+}
+
+float PIDZone::getMinThermalRpmSetPt(void) const
+{
+    return _minThermalRpmSetPt;
+}
+
+void PIDZone::addFanPID(std::unique_ptr<PIDController> pid)
+{
+    _fans.push_back(std::move(pid));
+}
+
+void PIDZone::addThermalPID(std::unique_ptr<PIDController> pid)
+{
+    _thermals.push_back(std::move(pid));
+}
+
+double PIDZone::getCachedValue(const std::string& name)
+{
+    return _cachedValuesByName.at(name);
+}
+
+void PIDZone::addFanInput(std::string fan)
+{
+    _fanInputs.push_back(fan);
+}
+
+void PIDZone::addThermalInput(std::string therm)
+{
+    _thermalInputs.push_back(therm);
+}
+
+void PIDZone::determineMaxRPMRequest(void)
+{
+    float max = 0;
+    std::vector<float>::iterator result;
+
+    if (_RPMSetPoints.size() > 0)
+    {
+        result = std::max_element(_RPMSetPoints.begin(), _RPMSetPoints.end());
+        max = *result;
+    }
+
+    /*
+     * If the maximum RPM set-point output is below the minimum RPM
+     * set-point, set it to the minimum.
+     */
+    max = std::max(getMinThermalRpmSetPt(), max);
+
+#ifdef __TUNING_LOGGING__
+    /*
+     * We received no set-points from thermal sensors.
+     * This is a case experienced during tuning where they only specify
+     * fan sensors and one large fan PID for all the fans.
+     */
+    static constexpr auto setpointpath = "/etc/thermal.d/set-point";
+    try
+    {
+        int value;
+        std::ifstream ifs;
+        ifs.open(setpointpath);
+        if (ifs.good()) {
+            ifs >> value;
+            max = value; // expecting RPM set-point, not pwm%
+        }
+    }
+    catch (const std::exception& e)
+    {
+        /* This exception is uninteresting. */
+        std::cerr << "Unable to read from '" << setpointpath << "'\n";
+    }
+#endif
+
+    _maximumRPMSetPt = max;
+    return;
+}
+
+#ifdef __TUNING_LOGGING__
+void PIDZone::initializeLog(void)
+{
+    /* Print header for log file. */
+
+    _log << "epoch_ms,setpt";
+
+    for (auto& f : _fanInputs)
+    {
+        _log << "," << f;
+    }
+    _log << std::endl;
+
+    return;
+}
+
+std::ofstream& PIDZone::getLogHandle(void)
+{
+    return _log;
+}
+#endif
+
+/*
+ * TODO(venture) This is effectively updating the cache and should check if the
+ * values they're using to update it are new or old, or whatnot.  For instance,
+ * if we haven't heard from the host in X time we need to detect this failure.
+ *
+ * I haven't decided if the Sensor should have a lastUpdated method or whether
+ * that should be for the ReadInterface or etc...
+ */
+
+/**
+ * We want the PID loop to run with values cached, so this will get all the
+ * fan tachs for the loop.
+ */
+void PIDZone::updateFanTelemetry(void)
+{
+    /* TODO(venture): Should I just make _log point to /dev/null when logging
+     * is disabled?  I think it's a waste to try and log things even if the
+     * data is just being dropped though.
+     */
+#ifdef __TUNING_LOGGING__
+    tstamp now = std::chrono::high_resolution_clock::now();
+    _log << std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
+    _log << "," << _maximumRPMSetPt;
+#endif
+
+    for (auto& f : _fanInputs)
+    {
+        auto& sensor = _mgr->getSensor(f);
+        ReadReturn r = sensor->read();
+        _cachedValuesByName[f] = r.value;
+
+        /*
+         * TODO(venture): We should check when these were last read.
+         * However, these are the fans, so if I'm not getting updated values
+         * for them... what should I do?
+         */
+#ifdef __TUNING_LOGGING__
+        _log << "," << r.value;
+#endif
+    }
+
+    return;
+}
+
+void PIDZone::updateSensors(void)
+{
+    using namespace std::chrono;
+    /* margin and temp are stored as temp */
+    tstamp now = high_resolution_clock::now();
+
+    for (auto& t : _thermalInputs)
+    {
+        auto& sensor = _mgr->getSensor(t);
+        ReadReturn r = sensor->read();
+        int64_t timeout = sensor->GetTimeout();
+
+        _cachedValuesByName[t] = r.value;
+        tstamp then = r.updated;
+
+        /* Only go into failsafe if the timeout is set for
+         * the sensor.
+         */
+        if (timeout > 0)
+        {
+            auto duration = duration_cast<std::chrono::seconds>
+                    (now - then).count();
+            auto period = std::chrono::seconds(timeout).count();
+            if (duration >= period)
+            {
+                //std::cerr << "Entering fail safe mode.\n";
+                _failSafeSensors.insert(t);
+            }
+            else
+            {
+                // Check if it's in there: remove it.
+                auto kt = _failSafeSensors.find(t);
+                if (kt != _failSafeSensors.end())
+                {
+                    _failSafeSensors.erase(kt);
+                }
+            }
+        }
+    }
+
+    return;
+}
+
+void PIDZone::initializeCache(void)
+{
+    for (auto& f : _fanInputs)
+    {
+        _cachedValuesByName[f] = 0;
+    }
+
+    for (auto& t : _thermalInputs)
+    {
+        _cachedValuesByName[t] = 0;
+
+        // Start all sensors in fail-safe mode.
+        _failSafeSensors.insert(t);
+    }
+}
+
+void PIDZone::dumpCache(void)
+{
+    std::cerr << "Cache values now: \n";
+    for (auto& k : _cachedValuesByName)
+    {
+        std::cerr << k.first << ": " << k.second << "\n";
+    }
+}
+
+void PIDZone::process_fans(void)
+{
+    for (auto& p : _fans)
+    {
+        p->pid_process();
+    }
+}
+
+void PIDZone::process_thermals(void)
+{
+    for (auto& p : _thermals)
+    {
+        p->pid_process();
+    }
+}
+
+std::unique_ptr<Sensor>& PIDZone::getSensor(std::string name)
+{
+    return _mgr->getSensor(name);
+}
+
+bool PIDZone::manual(bool value)
+{
+    std::cerr << "manual: " << value << std::endl;
+    setManualMode(value);
+    return ModeObject::manual(value);
+}
+
+bool PIDZone::failSafe() const
+{
+    return getFailSafeMode();
+}
+
+static std::string GetControlPath(int64_t zone)
+{
+    return std::string(objectPath) + std::to_string(zone);
+}
+
+std::map<int64_t, std::shared_ptr<PIDZone>> BuildZones(
+            std::map<int64_t, PIDConf>& ZonePids,
+            std::map<int64_t, struct zone>& ZoneConfigs,
+            std::shared_ptr<SensorManager> mgr,
+            sdbusplus::bus::bus& ModeControlBus)
+{
+    std::map<int64_t, std::shared_ptr<PIDZone>> zones;
+
+    for (auto& zi : ZonePids)
+    {
+        auto zoneId = static_cast<int64_t>(zi.first);
+        /* The above shouldn't be necessary but is, and I am having trouble
+         * locating my notes on why.  If I recall correctly it was casting it
+         * down to a byte in at least some cases causing weird behaviors.
+         */
+
+        auto zoneConf = ZoneConfigs.find(zoneId);
+        if (zoneConf == ZoneConfigs.end())
+        {
+            /* The Zone doesn't have a configuration, bail. */
+            static constexpr auto err =
+                "Bailing during load, missing Zone Configuration";
+            std::cerr << err << std::endl;
+            throw std::runtime_error(err);
+        }
+
+        PIDConf& PIDConfig = zi.second;
+
+        auto zone = std::make_shared<PIDZone>(
+                        zoneId,
+                        zoneConf->second.minthermalrpm,
+                        zoneConf->second.failsafepercent,
+                        mgr,
+                        ModeControlBus,
+                        GetControlPath(zi.first).c_str(),
+                        deferSignals);
+
+        zones[zoneId] = zone;
+
+        std::cerr << "Zone Id: " << zone->getZoneId() << "\n";
+
+        // For each PID create a Controller and a Sensor.
+        PIDConf::iterator pit = PIDConfig.begin();
+        while (pit != PIDConfig.end())
+        {
+            std::vector<std::string> inputs;
+            std::string name = pit->first;
+            struct controller_info* info = &pit->second;
+
+            std::cerr << "PID name: " << name << "\n";
+
+            /*
+             * TODO(venture): Need to check if input is known to the
+             * SensorManager.
+             */
+            if (info->type == "fan")
+            {
+                for (auto i : info->inputs)
+                {
+                    inputs.push_back(i);
+                    zone->addFanInput(i);
+                }
+
+                auto pid = FanController::CreateFanPid(
+                               zone,
+                               name,
+                               inputs,
+                               info->info);
+                zone->addFanPID(std::move(pid));
+            }
+            else if (info->type == "temp" || info->type == "margin")
+            {
+                for (auto i : info->inputs)
+                {
+                    inputs.push_back(i);
+                    zone->addThermalInput(i);
+                }
+
+                auto pid = ThermalController::CreateThermalPid(
+                               zone,
+                               name,
+                               inputs,
+                               info->setpoint,
+                               info->info);
+                zone->addThermalPID(std::move(pid));
+            }
+
+            std::cerr << "inputs: ";
+            for (auto& i : inputs)
+            {
+                std::cerr << i << ", ";
+            }
+            std::cerr << "\n";
+
+            ++pit;
+        }
+
+        zone->emit_object_added();
+    }
+
+    return zones;
+}
+
+std::map<int64_t, std::shared_ptr<PIDZone>> BuildZonesFromConfig(
+            std::string& path,
+            std::shared_ptr<SensorManager> mgr,
+            sdbusplus::bus::bus& ModeControlBus)
+{
+    using namespace libconfig;
+    // zone -> pids
+    std::map<int64_t, PIDConf> pidConfig;
+    // zone -> configs
+    std::map<int64_t, struct zone> zoneConfig;
+
+    std::cerr << "entered BuildZonesFromConfig\n";
+
+    Config cfg;
+
+    /* The load was modeled after the example source provided. */
+    try
+    {
+        cfg.readFile(path.c_str());
+    }
+    catch (const FileIOException& fioex)
+    {
+        std::cerr << "I/O error while reading file: " << fioex.what() << std::endl;
+        throw;
+    }
+    catch (const ParseException& pex)
+    {
+        std::cerr << "Parse error at " << pex.getFile() << ":" << pex.getLine()
+                  << " - " << pex.getError() << std::endl;
+        throw;
+    }
+
+    try
+    {
+        const Setting& root = cfg.getRoot();
+        const Setting& zones = root["zones"];
+        int count = zones.getLength();
+
+        /* For each zone. */
+        for (int i = 0; i < count; ++i)
+        {
+            const Setting& zoneSettings = zones[i];
+
+            int id;
+            PIDConf thisZone;
+            struct zone thisZoneConfig;
+
+            zoneSettings.lookupValue("id", id);
+
+            thisZoneConfig.minthermalrpm =
+                    zoneSettings.lookup("minthermalrpm");
+            thisZoneConfig.failsafepercent =
+                    zoneSettings.lookup("failsafepercent");
+
+            const Setting& pids = zoneSettings["pids"];
+            int pidCount = pids.getLength();
+
+            for (int j = 0; j < pidCount; ++j)
+            {
+                const Setting& pid = pids[j];
+
+                std::string name;
+                controller_info info;
+
+                /*
+                 * Mysteriously if you use lookupValue on these, and the type
+                 * is float.  It won't work right.
+                 *
+                 * If the configuration file value doesn't look explicitly like
+                 * a float it won't let you assign it to one.
+                 */
+                name = pid.lookup("name").c_str();
+                info.type = pid.lookup("type").c_str();
+                /* 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");
+
+                std::cerr << "out_lim.min: " << info.info.out_lim.min << "\n";
+                std::cerr << "out_lim.max: " << info.info.out_lim.max << "\n";
+
+                const Setting& inputs = pid["inputs"];
+                int icount = inputs.getLength();
+
+                for (int z = 0; z < icount; ++z)
+                {
+                    std::string v;
+                    v = pid["inputs"][z].c_str();
+                    info.inputs.push_back(v);
+                }
+
+                thisZone[name] = info;
+            }
+
+            pidConfig[static_cast<int64_t>(id)] = thisZone;
+            zoneConfig[static_cast<int64_t>(id)] = thisZoneConfig;
+        }
+    }
+    catch (const SettingTypeException &setex)
+    {
+        std::cerr << "Setting '" << setex.getPath() << "' type exception!" << std::endl;
+        throw;
+    }
+    catch (const SettingNotFoundException& snex)
+    {
+        std::cerr << "Setting '" << snex.getPath() << "' not found!" << std::endl;
+        throw;
+    }
+
+    return BuildZones(pidConfig, zoneConfig, mgr, ModeControlBus);
+}
diff --git a/pid/zone.hpp b/pid/zone.hpp
new file mode 100644
index 0000000..d75bf59
--- /dev/null
+++ b/pid/zone.hpp
@@ -0,0 +1,124 @@
+#pragma once
+
+#include <fstream>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "conf.hpp"
+#include "controller.hpp"
+#include "sensors/sensor.hpp"
+#include "sensors/manager.hpp"
+
+#include "xyz/openbmc_project/Control/FanCtrl/Mode/server.hpp"
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server.hpp>
+
+
+template <typename... T>
+using ServerObject = typename sdbusplus::server::object::object<T...>;
+using ModeInterface =
+    sdbusplus::xyz::openbmc_project::Control::FanCtrl::server::Mode;
+using ModeObject = ServerObject<ModeInterface>;
+
+/*
+ * The PIDZone inherits from the Mode object so that it can listen for control
+ * mode changes.  It primarily holds all PID loops and holds the sensor value
+ * cache that's used per iteration of the PID loops.
+ */
+class PIDZone : public ModeObject
+{
+    public:
+        PIDZone(int64_t zone,
+                float minThermalRpm,
+                float failSafePercent,
+                std::shared_ptr<SensorManager> mgr,
+                sdbusplus::bus::bus& bus,
+                const char* objPath,
+                bool defer)
+            : ModeObject(bus, objPath, defer),
+              _zoneId(zone),
+              _maximumRPMSetPt(),
+              _minThermalRpmSetPt(minThermalRpm),
+              _failSafePercent(failSafePercent),
+              _mgr(mgr)
+        {
+#ifdef __TUNING_LOGGING__
+            _log.open("/tmp/swampd.log");
+#endif
+        }
+
+        float getMaxRPMRequest(void) const;
+        bool getManualMode(void) const;
+
+        /* Could put lock around this since it's accessed from two threads, but
+         * only one reader/one writer.
+         */
+        void setManualMode(bool mode);
+        bool getFailSafeMode(void) const;
+        int64_t getZoneId(void) const;
+        void addRPMSetPoint(float setpoint);
+        void clearRPMSetPoints(void);
+        float getFailSafePercent(void) const;
+        float getMinThermalRpmSetPt(void) const;
+
+        std::unique_ptr<Sensor>& getSensor(std::string name);
+        void determineMaxRPMRequest(void);
+        void updateFanTelemetry(void);
+        void updateSensors(void);
+        void initializeCache(void);
+        void dumpCache(void);
+        void process_fans(void);
+        void process_thermals(void);
+
+        void addFanPID(std::unique_ptr<PIDController> pid);
+        void addThermalPID(std::unique_ptr<PIDController> pid);
+        double getCachedValue(const std::string& name);
+        void addFanInput(std::string fan);
+        void addThermalInput(std::string therm);
+
+#ifdef __TUNING_LOGGING__
+        void initializeLog(void);
+        std::ofstream& getLogHandle(void);
+#endif
+
+        /* Method for setting the manual mode over dbus */
+        bool manual(bool value) override;
+        /* Method for reading whether in fail-safe mode over dbus */
+        bool failSafe() const override;
+
+    private:
+#ifdef __TUNING_LOGGING__
+        std::ofstream _log;
+#endif
+
+        const int64_t _zoneId;
+        float _maximumRPMSetPt = 0;
+        bool _manualMode = false;
+        const float _minThermalRpmSetPt;
+        const float _failSafePercent;
+
+        std::set<std::string> _failSafeSensors;
+
+        std::vector<float> _RPMSetPoints;
+        std::vector<std::string> _fanInputs;
+        std::vector<std::string> _thermalInputs;
+        std::map<std::string, double> _cachedValuesByName;
+        std::shared_ptr<SensorManager> _mgr;
+
+        std::vector<std::unique_ptr<PIDController>> _fans;
+        std::vector<std::unique_ptr<PIDController>> _thermals;
+};
+
+std::map<int64_t, std::shared_ptr<PIDZone>> BuildZones(
+            std::map<int64_t, PIDConf>& ZonePids,
+            std::map<int64_t, struct zone>& ZoneConfigs,
+            std::shared_ptr<SensorManager> mgmr,
+            sdbusplus::bus::bus& ModeControlBus);
+
+std::map<int64_t, std::shared_ptr<PIDZone>> BuildZonesFromConfig(
+            std::string& path,
+            std::shared_ptr<SensorManager> mgmr,
+            sdbusplus::bus::bus& ModeControlBus);