Scripts and main daemon

This includes the scripts for the YAML parsing and the
main execution point.

Change-Id: If42154c621353b23370b63d4e58f6c75bca8b356
Signed-off-by: Patrick Venture <venture@google.com>
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..a23eb91
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,73 @@
+AM_DEFAULT_SOURCE_EXT = .cpp
+
+sbin_PROGRAMS = swampd setsensor
+
+BUILT_SOURCES = sensorlist-gen.cpp pidlist-gen.cpp zoneinfo-gen.cpp
+CLEANFILES = $(BUILT_SOURCES)
+
+sensorlist-gen.cpp:
+	$(AM_V_GEN)@SENSORGEN@ -o $(top_builddir) generate-cpp
+
+pidlist-gen.cpp:
+	$(AM_V_GEN)@PIDGEN@ -o $(top_builddir) generate-cpp
+
+zoneinfo-gen.cpp:
+	$(AM_V_GEN)@ZONEGEN@ -o $(top_builddir) generate-cpp
+
+setsensor_SOURCES = setsensor.cpp
+setsensor_LDADD = $(SDBUSPLUS_LIBS) $(PHOSPHOR_DBUS_INTERFACES_LIBS) $(PHOSPHOR_LOGGING_LIBS)
+setsensor_CXXFLAGS = $(SDBUSPLUS_CFLAGS) $(PHOSPHOR_DBUS_INTERFACES_CFLAGS) $(PHOSPHOR_LOGGING_CFLAGS)
+
+swampd_SOURCES = main.cpp util.cpp
+swampd_LDADD = $(SDBUSPLUS_LIBS) libswampd.la
+swampd_CXXFLAGS = $(SDBUSPLUS_CFLAGS)
+
+noinst_LTLIBRARIES = libswampd.la
+libswampd_la_LDFLAGS = -static
+libswampd_la_LIBADD = \
+	-lstdc++fs \
+	-lconfig++ \
+	$(SDBUSPLUS_LIBS) \
+	$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+	$(PHOSPHOR_LOGGING_LIBS)
+libswampd_la_CXXFLAGS = \
+	$(SDBUSPLUS_CFLAGS) \
+	$(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
+	$(PHOSPHOR_LOGGING_CFLAGS)
+
+libswampd_la_SOURCES = \
+	notimpl/readonly.cpp \
+	notimpl/writeonly.cpp \
+	dbus/util.cpp \
+	dbus/dbuspassive.cpp \
+	dbus/dbusactiveread.cpp \
+	sysfs/sysfsread.cpp \
+	sysfs/sysfswrite.cpp \
+	sysfs/util.cpp \
+	sensors/pluggable.cpp \
+	sensors/host.cpp \
+	sensors/manager.cpp \
+	pid/ec/pid.cpp \
+	pid/controller.cpp \
+	pid/fancontroller.cpp \
+	pid/thermalcontroller.cpp \
+	pid/zone.cpp \
+	pid/util.cpp \
+	pid/pidthread.cpp \
+	threads/busthread.cpp \
+	experiments/drive.cpp \
+	$(BUILT_SOURCES)
+
+libmanualcmdsdir = ${libdir}/ipmid-providers
+libmanualcmds_LTLIBRARIES = libmanualcmds.la
+libmanualcmds_la_SOURCES = \
+	ipmi/manualcmds.cpp
+libmanualcmds_la_LDFLAGS = $(SYSTEMD_LIBS) \
+                            $(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+                            $(PHOSPHOR_LOGGING_LIBS) \
+                            -version-info 0:0:0 -shared
+libmanualcmds_la_CXXFLAGS = $(SYSTEMD_CFLAGS) \
+                             $(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
+                             $(PHOSPHOR_LOGGING_CFLAGS)
+
+SUBDIRS = .
diff --git a/README b/README
new file mode 100644
index 0000000..968e06d
--- /dev/null
+++ b/README
@@ -0,0 +1,47 @@
+# Introduction
+
+Swampd's primary function is to drive the fans of a system given various inputs.
+
+# Layout
+
+The code is broken out into modules as follows:
+
+* `dbus` - Any read or write interface that uses dbus primarily.
+* `experiments` - Small execution paths that allow for fan examination
+including how quickly fans respond to changes.
+* `ipmi` - Manual control for any zone is handled by receiving an IPMI message.
+This holds the ipmid provider for receiving those messages and sending them
+onto swampd.
+* `notimpl` - These are read-only and write-only interface implementations that
+can be dropped into a pluggable sensor to make it complete.
+* `pid` - This contains all the PID associated code, including the zone
+defintion, controller definition, and the PID computational code.
+* `scripts` - This contains the scripts that convert YAML into C++.
+* `sensors` - This contains a couple of sensor types including the pluggable
+sensor's definition.  It also holds the sensor manager.
+* `sysfs` - This contains code that reads from or writes to sysfs.
+* `threads` - Most of swampd's threads run in this method where there's just a
+dbus bus that we manage.
+
+
+# Design
+
+One defines a series of sensors and a series of PIDs that utilize those sensors
+as inputs (and outputs).
+
+ The thermal PID
+
+ set-point -------->|---|---> RPM
+ current value ---->|___|
+
+ The fan PID
+
+ set-point -------->|---|---> pwm%
+ current value ---->|___|
+
+ How to get set-point for fan PIDs
+
+ thermal-out ----->|-----|
+ thermal-out ----->|     |-----> RPM
+ thermal-out ----->| MAX |
+ thermal-out ----->|_____|
diff --git a/bootstrap.sh b/bootstrap.sh
new file mode 100644
index 0000000..fce7cfa
--- /dev/null
+++ b/bootstrap.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+AUTOCONF_FILES="Makefile.in aclocal.m4 ar-lib autom4te.cache compile \
+        config.guess config.h.in config.sub configure depcomp install-sh \
+        ltmain.sh missing *libtool test-driver"
+
+case $1 in
+    clean)
+        test -f Makefile && make maintainer-clean
+        for file in ${AUTOCONF_FILES}; do
+            find -name "$file" | xargs -r rm -rf
+        done
+        exit 0
+        ;;
+esac
+
+autoreconf -i
+echo 'Run "./configure ${CONFIGURE_FLAGS} && make"'
+
diff --git a/conf.hpp b/conf.hpp
new file mode 100644
index 0000000..ad8a1a9
--- /dev/null
+++ b/conf.hpp
@@ -0,0 +1,51 @@
+#pragma once
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "pid/ec/pid.hpp"
+
+
+/*
+ * General sensor structure used for configuration.
+ */
+struct sensor
+{
+    /* Used for listen if readpath is passive. */
+    std::string type;
+    /* Can be a sensor path or a dbus path. */
+    std::string readpath;
+    std::string writepath;
+    /* min/max values for writing a percentage or error checking. */
+    int64_t min;
+    int64_t max;
+    int64_t timeout;
+};
+
+/*
+ * Structure for holding the configuration of a PID.
+ */
+struct controller_info
+{
+    std::string type;                // fan or margin or temp?
+    std::vector<std::string> inputs; // one or more sensors.
+    float setpoint;                  // initial setpoint for thermal.
+    ec::pidinfo info;                // pid details
+};
+
+/*
+ * General zone structure used for configuration.  A zone is a list of PIDs
+ * and a set of configuration settings.  This structure gets filled out with
+ * the zone configuration settings and not the PID details.
+ */
+struct zone
+{
+    /* The minimum RPM value we would ever want. */
+    float minthermalrpm;
+
+    /* If the sensors are in fail-safe mode, this is the percentage to use. */
+    float failsafepercent;
+};
+
+using PIDConf = std::map<std::string, struct controller_info>;
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..b86d0c6
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,70 @@
+# Initialization
+AC_PREREQ([2.69])
+AC_INIT([swampd], [1.0], [https://www.google.com])
+AC_LANG([C++])
+AC_CONFIG_HEADERS([config.h])
+AM_INIT_AUTOMAKE([subdir-objects -Wall -Werror foreign dist-xz])
+AM_SILENT_RULES([yes])
+
+# Checks for programs.
+AC_PROG_CXX
+AM_PROG_AR
+AC_PROG_INSTALL
+AC_PROG_MAKE_SET
+
+# Python
+AM_PATH_PYTHON([2.7], [AC_SUBST([PYTHON], [echo "$PYTHON"])],
+[AC_MSG_ERROR([Could not find python-2.7 installed...python-2.7 is required])])
+
+# Checks for typedefs, structures, and compiler characteristics.
+AX_CXX_COMPILE_STDCXX_14([noext])
+AX_APPEND_COMPILE_FLAGS([-Wall -Werror], [CXXFLAGS])
+
+# Checks for libraries.
+# AC_CHECK_LIB([mapper], [mapper_get_service], ,[AC_MSG_ERROR([Could not find libmapper...openbmc/phosphor-objmgr package required])])
+PKG_CHECK_MODULES([SYSTEMD], [libsystemd >= 221], [], [AC_MSG_ERROR(["systemd required and not found"])])
+PKG_CHECK_MODULES([SDBUSPLUS], [sdbusplus], ,[AC_MSG_ERROR([The openbmc/sdbusplus package is required])])
+PKG_CHECK_MODULES([PHOSPHOR_LOGGING], [phosphor-logging], ,[AC_MSG_ERROR([The openbmc/phosphor-logging package is required])])
+PKG_CHECK_MODULES([PHOSPHOR_DBUS_INTERFACES], [phosphor-dbus-interfaces], [], [AC_MSG_ERROR(["phosphor-dbus-interfaces required and not found."])])
+AC_CHECK_HEADER(experimental/any, ,[AC_MSG_ERROR([Could not find experimental/any...libstdc++fs developement package required])])
+
+# Checks for library functions.
+LT_INIT # Required for systemd linking
+
+# Check/set gtest specific functions.
+AX_PTHREAD([GTEST_CPPFLAGS="-DGTEST_HAS_PTHREAD=1"],[GTEST_CPPFLAGS="-DGTEST_HAS_PTHREAD=0"])
+AC_SUBST(GTEST_CPPFLAGS)
+
+AC_ARG_ENABLE([oe-sdk],
+    AS_HELP_STRING([--enable-oe-sdk], [Link testcases absolutely against OE SDK so they can be ran within it.])
+)
+AC_ARG_VAR(OECORE_TARGET_SYSROOT,
+    [Path to the OE SDK SYSROOT])
+AS_IF([test "x$enable_oe_sdk" == "xyes"],
+    AS_IF([test "x$OECORE_TARGET_SYSROOT" == "x"],
+          AC_MSG_ERROR([OECORE_TARGET_SYSROOT must be set with --enable-oe-sdk])
+    )
+    AC_MSG_NOTICE([Enabling OE-SDK at $OECORE_TARGET_SYSROOT])
+    [
+        testcase_flags="-Wl,-rpath,\${OECORE_TARGET_SYSROOT}/lib"
+        testcase_flags="${testcase_flags} -Wl,-rpath,\${OECORE_TARGET_SYSROOT}/usr/lib"
+        testcase_flags="${testcase_flags} -Wl,-dynamic-linker,`find \${OECORE_TARGET_SYSROOT}/lib/ld-*.so | sort -r -n | head -n1`"
+    ]
+    AC_SUBST([OESDK_TESTCASE_FLAGS], [$testcase_flags])
+)
+
+AS_IF([test "x$SENSOR_YAML_GEN" == "x"], [SENSOR_YAML_GEN="sensor-example.yaml"])
+SENSORGEN="$PYTHON ${srcdir}/scripts/sensor_gen.py -i $SENSOR_YAML_GEN"
+AC_SUBST(SENSORGEN)
+
+AS_IF([test "x$PID_YAML_GEN" == "x"], [PID_YAML_GEN="pid-example.yaml"])
+PIDGEN="$PYTHON ${srcdir}/scripts/pid_gen.py -i $PID_YAML_GEN"
+AC_SUBST(PIDGEN)
+
+AS_IF([test "x$ZONE_YAML_GEN" == "x"], [ZONE_YAML_GEN="zone-example.yaml"])
+ZONEGEN="$PYTHON ${srcdir}/scripts/zone_gen.py -i $ZONE_YAML_GEN"
+AC_SUBST(ZONEGEN)
+
+# Create configured output
+AC_CONFIG_FILES([Makefile])
+AC_OUTPUT
diff --git a/examples/set-point b/examples/set-point
new file mode 100644
index 0000000..a77fd92
--- /dev/null
+++ b/examples/set-point
@@ -0,0 +1 @@
+6000
diff --git a/examples/swampd.conf b/examples/swampd.conf
new file mode 100644
index 0000000..c17a75c
--- /dev/null
+++ b/examples/swampd.conf
@@ -0,0 +1,97 @@
+
+sensors = (
+  { name = "fan1"
+    type = "fan"
+    readpath = "/xyz/openbmc_project/sensors/fan_tach/fan1"
+    writepath = "/sys/class/hwmon/hwmon0/pwm0"
+    min = 0
+  { name = "fan2"
+    type = "fan"
+    readpath = "/xyz/openbmc_project/sensors/fan_tach/fan2"
+    writepath = "/sys/class/hwmon/hwmon0/pwm1"
+    min = 0
+    max = 255
+    timeout = 0 },
+  { name = "fan3"
+    type = "fan"
+    readpath = "/xyz/openbmc_project/sensors/fan_tach/fan3"
+    writepath = "/sys/class/hwmon/hwmon0/pwm2"
+    min = 0
+    max = 255
+    timeout = 0 },
+  { name = "fan4"
+    type = "fan"
+    readpath = "/xyz/openbmc_project/sensors/fan_tach/fan4"
+    writepath = "/sys/class/hwmon/hwmon0/pwm3"
+    min = 0
+    max = 255
+    timeout = 0 },
+  { name = "fan5"
+    type = "fan"
+    readpath = "/xyz/openbmc_project/sensors/fan_tach/fan5"
+    writepath = ""
+    min = 0
+    max = 0
+    timeout = 0 },
+  { name = "fan6"
+    type = "fan"
+    readpath = "/xyz/openbmc_project/sensors/fan_tach/fan6"
+    writepath = ""
+    min = 0
+    max = 0
+    timeout = 0 },
+  { name = "fan7"
+    type = "fan"
+    readpath = "/xyz/openbmc_project/sensors/fan_tach/fan7"
+    writepath = ""
+    min = 0
+    max = 0
+    timeout = 0 },
+  { name = "fan8"
+    type = "fan"
+    readpath = "/xyz/openbmc_project/sensors/fan_tach/fan8"
+    writepath = ""
+    min = 0
+    max = 0
+    timeout = 0 }
+);
+
+zones = (
+  { id = 0x01
+    minthermalrpm = 3000.0
+    failsafepwm = 90.0
+    pids = (
+      { name = "allfans"
+        type = "fan"
+        inputs = (
+          "fan1",
+          "fan2",
+          "fan3",
+          "fan4",
+          "fan5",
+          "fan6",
+          "fan7",
+          "fan8"
+        )
+        set-point = 90.0
+        pid = {
+          sampleperiod = 0.1
+          p_coefficient = 0.01
+          i_coefficient = 0.001
+          ff_off_coefficient = 0.0
+          ff_gain_coefficient = 0.0
+          i_limit = {
+            min: 0.0
+            max: 100.0
+          }
+          out_limit = {
+            min: 0.0
+            max: 100.0
+          }
+          slew_neg = 0.0
+          slew_pos = 0.0
+        }
+      }
+    )
+  }
+);
diff --git a/experiments/README b/experiments/README
new file mode 100644
index 0000000..6ed6285
--- /dev/null
+++ b/experiments/README
@@ -0,0 +1,4 @@
+To determine the fan's ability to change speed we ran several experiments.
+These files are kept here not simply for posterity but also if we need to run
+them again or test new platforms.
+
diff --git a/experiments/drive.cpp b/experiments/drive.cpp
new file mode 100644
index 0000000..a6fbd9c
--- /dev/null
+++ b/experiments/drive.cpp
@@ -0,0 +1,273 @@
+/**
+ * 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 <iostream>
+#include <memory>
+#include <tuple>
+
+#include "drive.hpp"
+
+#include "interfaces.hpp"
+#include "sensors/pluggable.hpp"
+#include "sysfs/sysfswrite.hpp"
+#include "sysfs/sysfsread.hpp"
+
+using tstamp = std::chrono::high_resolution_clock::time_point;
+
+#define DRIVE_TIME 1
+#define DRIVE_GOAL 2
+#define DRIVE DRIVE_TIME
+#define MAX_PWM 255
+
+static std::unique_ptr<Sensor> Create(
+    std::string readpath,
+    std::string writepath)
+{
+    return std::make_unique<PluggableSensor>(
+               readpath,
+               0, /* default the timeout to disabled */
+               std::make_unique<SysFsRead>(readpath),
+               std::make_unique<SysFsWrite>(writepath, 0, MAX_PWM));
+}
+
+int64_t getAverage(std::tuple<tstamp, int64_t, int64_t>& values)
+{
+    return (std::get<1>(values) + std::get<2>(values)) / 2;
+}
+
+bool valueClose(int64_t value, int64_t goal)
+{
+#if 0
+    int64_t delta = 100; /* within 100 */
+    if (value < (goal + delta) &&
+        value > (goal - delta))
+    {
+        return true;
+    }
+#endif
+
+    /* let's make sure it's below goal. */
+    if (value < goal)
+    {
+        return true;
+    }
+
+    return false;
+}
+
+static void driveGoal(
+    int64_t& seriesCnt,
+    int64_t setPwm,
+    int64_t goal,
+    std::vector<std::tuple<tstamp, int64_t, int64_t>>& series,
+    std::vector<std::unique_ptr<Sensor>>& fanSensors)
+{
+    bool reading = true;
+
+    auto& fan0 = fanSensors.at(0);
+    auto& fan1 = fanSensors.at(1);
+
+    fan0->write(setPwm);
+    fan1->write(setPwm);
+
+    while (reading)
+    {
+        bool check = false;
+        ReadReturn r0 = fan0->read();
+        ReadReturn r1 = fan1->read();
+        int64_t n0 = static_cast<int64_t>(r0.value);
+        int64_t n1 = static_cast<int64_t>(r1.value);
+
+        tstamp t1 = std::chrono::high_resolution_clock::now();
+
+        series.push_back(std::make_tuple(t1, n0, n1));
+        seriesCnt += 1;
+
+        int64_t avgn = (n0 + n1) / 2;
+        /* check last three values against goal if this is close */
+        check = valueClose(avgn, goal);
+
+        /* We know the last entry is within range. */
+        if (check && seriesCnt > 3)
+        {
+            /* n-2 values */
+            std::tuple<tstamp, int64_t, int64_t> nm2 = series.at(seriesCnt - 3);
+            /* n-1 values */
+            std::tuple<tstamp, int64_t, int64_t> nm1 = series.at(seriesCnt - 2);
+
+            int64_t avgnm2 = getAverage(nm2);
+            int64_t avgnm1 = getAverage(nm1);
+
+            int64_t together = (avgnm2 + avgnm1) / 2;
+
+            reading = !valueClose(together, goal);
+
+            if (!reading)
+            {
+                std::cerr << "finished reaching goal\n";
+            }
+        }
+
+        /* Early abort for testing. */
+        if (seriesCnt > 150000)
+        {
+            std::cerr << "aborting after 150000 reads.\n";
+            reading = false;
+        }
+    }
+
+    return;
+}
+
+static void driveTime(
+    int64_t& seriesCnt,
+    int64_t setPwm,
+    int64_t goal,
+    std::vector<std::tuple<tstamp, int64_t, int64_t>>& series,
+    std::vector<std::unique_ptr<Sensor>>& fanSensors)
+{
+    using namespace std::literals::chrono_literals;
+
+    bool reading = true;
+
+    auto& fan0 = fanSensors.at(0);
+    auto& fan1 = fanSensors.at(1);
+
+    auto& s0 = series.at(0);
+    tstamp t0 = std::get<0>(s0);
+
+    fan0->write(setPwm);
+    fan1->write(setPwm);
+
+    while (reading)
+    {
+        ReadReturn r0 = fan0->read();
+        ReadReturn r1 = fan1->read();
+        int64_t n0 = static_cast<int64_t>(r0.value);
+        int64_t n1 = static_cast<int64_t>(r1.value);
+        tstamp t1 = std::chrono::high_resolution_clock::now();
+
+        series.push_back(std::make_tuple(t1, n0, n1));
+
+        auto duration = std::chrono::duration_cast<std::chrono::microseconds>
+                        (t1 - t0).count();
+        if (duration >= (20000000us).count())
+        {
+            reading = false;
+        }
+    }
+
+    return;
+}
+
+int driveMain(void)
+{
+    /* Time series of the data, the timestamp after both are read and the values. */
+    std::vector<std::tuple<tstamp, int64_t, int64_t>> series;
+    int64_t seriesCnt = 0; /* in case vector count isn't constant time */
+    int drive = DRIVE;
+
+    /*
+     * The fan map:
+     *  --> 0 | 4
+     *  --> 1 | 5
+     *  --> 2 | 6
+     *  --> 3 | 7
+     */
+    std::vector<std::string> fans =
+    {
+        "/sys/class/hwmon/hwmon0/fan0_input",
+        "/sys/class/hwmon/hwmon0/fan4_input"
+    };
+
+    std::vector<std::string> pwms =
+    {
+        "/sys/class/hwmon/hwmon0/pwm0",
+        "/sys/class/hwmon/hwmon0/pwm4"
+    };
+
+    std::vector<std::unique_ptr<Sensor>> fanSensors;
+
+    auto fan0 = Create(fans[0], pwms[0]);
+    auto fan1 = Create(fans[1], pwms[1]);
+
+    ReadReturn r0 = fan0->read();
+    ReadReturn r1 = fan1->read();
+    int64_t pwm0_value = static_cast<int64_t>(r0.value);
+    int64_t pwm1_value = static_cast<int64_t>(r1.value);
+
+    if (MAX_PWM != pwm0_value || MAX_PWM != pwm1_value)
+    {
+        std::cerr << "bad PWM starting point.\n";
+        return -EINVAL;
+    }
+
+    r0 = fan0->read();
+    r1 = fan1->read();
+    int64_t fan0_start = r0.value;
+    int64_t fan1_start = r1.value;
+    tstamp t1 = std::chrono::high_resolution_clock::now();
+
+    /*
+     * I've done experiments, and seen 9080,10243 as a starting point
+     * which leads to a 50% goal of 4830.5, which is higher than the
+     * average that they reach, 4668.  -- i guess i could try to figure out
+     * a good increase from one to the other, but how fast they're going
+     * actually influences how much they influence, so at slower speeds the
+     * improvement is less.
+     */
+
+    series.push_back(std::make_tuple(t1, fan0_start, fan1_start));
+    seriesCnt += 1;
+
+    int64_t average = (fan0_start + fan1_start) / 2;
+    int64_t goal = 0.5 * average;
+
+    std::cerr << "goal: " << goal << "\n";
+
+    // fan0 @ 128: 4691
+    // fan4 @ 128: 4707
+
+    fanSensors.push_back(std::move(fan0));
+    fanSensors.push_back(std::move(fan1));
+
+    if (DRIVE_TIME == drive)
+    {
+        driveTime(seriesCnt, 128, goal, series, fanSensors);
+    }
+    else if (DRIVE_GOAL == drive)
+    {
+        driveGoal(seriesCnt, 128, goal, series, fanSensors);
+    }
+    tstamp tp = t1;
+
+    /* Output the values and the timepoints as a time series for review. */
+    for (auto& t : series)
+    {
+        tstamp ts = std::get<0>(t);
+        int64_t n0 = std::get<1>(t);
+        int64_t n1 = std::get<2>(t);
+
+        auto duration = std::chrono::duration_cast<std::chrono::microseconds>
+                        (ts - tp).count();
+        std::cout << duration << "us, " << n0 << ", " << n1 << "\n";
+
+        tp = ts;
+    }
+
+    return 0;
+}
+
diff --git a/experiments/drive.hpp b/experiments/drive.hpp
new file mode 100644
index 0000000..94e74f3
--- /dev/null
+++ b/experiments/drive.hpp
@@ -0,0 +1,3 @@
+#pragma once
+
+int driveMain(void);
diff --git a/interfaces.hpp b/interfaces.hpp
new file mode 100644
index 0000000..8e7fc0b
--- /dev/null
+++ b/interfaces.hpp
@@ -0,0 +1,57 @@
+#pragma once
+
+#include <chrono>
+
+
+struct ReadReturn {
+    double value;
+    std::chrono::high_resolution_clock::time_point updated;
+};
+
+
+/*
+ * A ReadInterface is a plug-in for the PluggableSensor and anyone implementing
+ * this basically is providing a way to read a sensor.
+ */
+class ReadInterface
+{
+    public:
+        ReadInterface() { }
+
+        virtual ~ReadInterface() { }
+
+        virtual ReadReturn read(void) = 0;
+};
+
+/*
+ * A WriteInterface is a plug-in for the PluggableSensor and anyone implementing
+ * this basically is providing a way to write a sensor.
+ */
+class WriteInterface
+{
+    public:
+        WriteInterface(int64_t min, int64_t max)
+            : _min(min),
+              _max(max)
+        { }
+
+        virtual ~WriteInterface() { }
+
+        virtual void write(double value) = 0;
+
+        /*
+         * All WriteInterfaces have min/max available in case they want to error
+         * check.
+         */
+        int64_t getMin(void)
+        {
+            return _min;
+        }
+        int64_t getMax(void)
+        {
+            return _max;
+        }
+    private:
+        int64_t _min;
+        int64_t _max;
+};
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000..6f8f070
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,181 @@
+/**
+ * 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 <experimental/any>
+#include <getopt.h>
+#include <iostream>
+#include <map>
+#include <memory>
+#include <mutex> /* not yet used. */
+#include <thread>
+#include <vector>
+
+#include <sdbusplus/bus.hpp>
+
+/* Configuration. */
+#include "conf.hpp"
+
+/* Misc. */
+#include "util.hpp"
+
+/* Controllers & Sensors. */
+#include "interfaces.hpp"
+#include "pid/zone.hpp"
+#include "sensors/manager.hpp"
+
+/* Threads. */
+#include "pid/pidthread.hpp"
+#include "threads/busthread.hpp"
+
+
+/* The YAML converted sensor list. */
+extern std::map<std::string, struct sensor> SensorConfig;
+/* The YAML converted PID list. */
+extern std::map<int64_t, PIDConf> ZoneConfig;
+/* The YAML converted Zone configuration. */
+extern std::map<int64_t, struct zone> ZoneDetailsConfig;
+
+int main(int argc, char* argv[])
+{
+    int rc = 0;
+    int c;
+    std::string configPath = "";
+
+    while (1)
+    {
+        static struct option long_options[] =
+        {
+            {"conf", required_argument, 0, 'c'},
+            {0, 0, 0, 0}
+        };
+
+        int option_index = 0;
+        c = getopt_long(argc, argv, "c:", long_options, &option_index);
+
+        if (c == -1)
+        {
+            break;
+        }
+
+        switch (c)
+        {
+            case 'c':
+                configPath = std::string {optarg};
+                break;
+            default:
+                /* skip garbage. */
+                continue;
+        }
+    }
+
+    auto ModeControlBus = sdbusplus::bus::new_default();
+    std::shared_ptr<SensorManager> mgmr;
+    std::map<int64_t, std::shared_ptr<PIDZone>> zones;
+
+    // Create a manger for the ModeBus because we own it.
+    static constexpr auto modeRoot = "/xyz/openbmc_project/settings/fanctrl";
+    sdbusplus::server::manager::manager(ModeControlBus, modeRoot);
+
+    /*
+     * When building the sensors, if any of the dbus passive ones aren't on the
+     * bus, it'll fail immediately.
+     */
+    if (configPath.length() > 0)
+    {
+        try
+        {
+            mgmr = BuildSensorsFromConfig(configPath);
+            zones = BuildZonesFromConfig(configPath, mgmr, ModeControlBus);
+        }
+        catch (const std::exception& e)
+        {
+            std::cerr << "Failed during building: " << e.what() << "\n";
+            exit(EXIT_FAILURE); /* fatal error. */
+        }
+    }
+    else
+    {
+        mgmr = BuildSensors(SensorConfig);
+        zones = BuildZones(ZoneConfig, ZoneDetailsConfig, mgmr, ModeControlBus);
+    }
+
+    if (0 == zones.size())
+    {
+        std::cerr << "No zones defined, exiting.\n";
+        return rc;
+    }
+
+    /*
+     * All sensors are managed by one manager, but each zone has a pointer to
+     * it.
+     */
+
+    auto& HostSensorBus = mgmr->getHostBus();
+    auto& PassiveListeningBus = mgmr->getPassiveBus();
+
+    std::cerr << "Starting threads\n";
+
+    /* TODO(venture): Ask SensorManager if we have any passive sensors. */
+    struct ThreadParams p =
+    {
+        std::ref(PassiveListeningBus),
+        ""
+    };
+    std::thread l(BusThread, std::ref(p));
+
+    /* TODO(venture): Ask SensorManager if we have any host sensors. */
+    static constexpr auto hostBus = "xyz.openbmc_project.Hwmon.external";
+    struct ThreadParams e =
+    {
+        std::ref(HostSensorBus),
+        hostBus
+    };
+    std::thread te(BusThread, std::ref(e));
+
+    static constexpr auto modeBus = "xyz.openbmc_project.State.FanCtrl";
+    struct ThreadParams m =
+    {
+        std::ref(ModeControlBus),
+        modeBus
+    };
+    std::thread tm(BusThread, std::ref(m));
+
+    std::vector<std::thread> zoneThreads;
+
+    /* TODO(venture): This was designed to have one thread per zone, but really
+     * it could have one thread for all the zones and iterate through each
+     * sequentially as it goes -- and it'd probably be fast enough to do that,
+     * however, a system isn't likely going to have more than a couple zones.
+     * If it only has a couple zones, then this is fine.
+     */
+    for (auto& i : zones)
+    {
+        std::cerr << "pushing zone" << std::endl;
+        zoneThreads.push_back(std::thread(PIDControlThread, i.second));
+    }
+
+    l.join();
+    te.join();
+    tm.join();
+    for (auto& t : zoneThreads)
+    {
+        t.join();
+    }
+
+    return rc;
+}
+
diff --git a/scripts/README b/scripts/README
new file mode 100644
index 0000000..3b732e8
--- /dev/null
+++ b/scripts/README
@@ -0,0 +1,25 @@
+# Sensor Config
+This program is only meant to control fans given thermal sensor readings.
+
+All sensors in phosphor-dbus-interfaces for OpenBMC use Sensor.Value as their
+accessor.  This provides read-only access to information.  The goal of the
+configuration is to specify how it can be read and if it's a fan, how the PID
+output can be written.  Initially there'll only be sysfs and passive dbus
+access.  If a writepath for a sensor is a dbus path, then the system will need
+to verify which Control.Fan* interfaces is registered and send values to the
+Target property of that interface.
+
+The min/max specified are to range a writePercent to the sensor.  The current
+FanController object outputs the new fan speed goal as a PWM percentage.  Other
+fan PID control objects may not, and they can leave the fields as 0 & 0.
+
+The only requirement for a sensor is that it isn't writeonly.  Only fans are
+expected to have a writepath set, and in this current version non-fan sensors
+are assumed readonly. 
+
+The sensor names are unique across all zones.
+
+# PID Config
+
+The PID configuation is a list of PIDs per zone.
+
diff --git a/scripts/pid-example.txt b/scripts/pid-example.txt
new file mode 100644
index 0000000..3549c8d
--- /dev/null
+++ b/scripts/pid-example.txt
@@ -0,0 +1,24 @@
+0x01:  /* zone ID */
+  fan2-6: /* PID name */
+    type: fan /* Type of PID, fan, temp, or margin. */
+    inputs:   /* Sensor names that are inputs for the PID */
+      fan2
+      fan6
+    /* For temp/margin PIDs this is the set-point, ignored otherwise (float) */
+    set-point: 90.0
+    pid: /* The PID calculation configuration. */
+      sampleperiod: 0.1        /* The input sample period. (float) */
+      p_coefficient: 0.01      /* The proportional coefficient. (float) */
+      i_coefficient: 0.001     /* The integral coefficient. (float) */
+      /* The feed-forward offset coefficient. (float) */
+      ff_off_coefficient: 0.0
+      /* The feed-forward gain coefficient. (float) */
+      ff_gain_coefficient: 0.0
+      i_limit:   /* The integral limit clamp, min, max (float) */
+        min: 0
+        max: 100
+      out_limit: /* the PID output clamp, min, max (float) */
+        min: 0
+        max: 100
+      slew_neg: -100 /* The slew negative value. (float) */
+      slew_pos: 0 /* The slew positive value. (float) */
diff --git a/scripts/pid-example.yaml b/scripts/pid-example.yaml
new file mode 100644
index 0000000..ea89b26
--- /dev/null
+++ b/scripts/pid-example.yaml
@@ -0,0 +1,59 @@
+0x01:
+  fan2-6:
+    type: fan
+    inputs:
+      fan2
+      fan6
+    set-point: 90.0
+    pid:
+      sampleperiod: 0.1
+      p_coefficient: 0.01
+      i_coefficient: 0.001
+      ff_off_coefficient: 0.0
+      ff_gain_coefficient: 0.0
+      i_limit:
+        min: 0
+        max: 100
+      out_limit:
+        min: 0
+        max: 100
+      slew_neg: 0
+      slew_pos: 0
+  temp1:
+    type: temp
+    inputs:
+      temp1
+    set-point: 30.0
+    pid:
+      sampleperiod: 1
+      p_coefficient: 94.0
+      i_coefficient: 2.0
+      ff_off_coefficient: 0.0
+      ff_gain_coefficient: 0.0
+      i_limit:
+        min: 3000
+        max: 10000
+      out_limit:
+        min: 3000
+        max: 10000
+      slew_neg: 0
+      slew_pos: 0
+  sluggish0:
+    type: margin
+    inputs:
+      sluggish0
+    set-point: 50
+    pid:
+      sampleperiod: 1
+      p_coefficient: 94.0
+      i_coefficient: 2.0
+      ff_off_coefficient: 0.0
+      ff_gain_coefficient: 0.0
+      i_limit:
+        min: 3000
+        max: 10000
+      out_limit:
+        min: 3000
+        max: 10000
+      slew_neg: 0
+      slew_pos: 0
diff --git a/scripts/pid_gen.py b/scripts/pid_gen.py
new file mode 100644
index 0000000..cd62199
--- /dev/null
+++ b/scripts/pid_gen.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+
+import os
+import sys
+import yaml
+import argparse
+from mako.template import Template
+
+
+def generate_cpp(sensor_yaml, output_dir):
+    with open(os.path.join(script_dir, sensor_yaml), 'r') as f:
+        ifile = yaml.safe_load(f)
+        if not isinstance(ifile, dict):
+            ifile = {}
+
+        # Render the mako template
+
+        t = Template(filename=os.path.join(
+                     script_dir,
+                     "writepid.mako.cpp"))
+
+        output_cpp = os.path.join(output_dir, "pidlist-gen.cpp")
+        with open(output_cpp, 'w') as fd:
+            fd.write(t.render(PIDDict=ifile))
+
+
+def main():
+
+    valid_commands = {
+        'generate-cpp': generate_cpp
+    }
+    parser = argparse.ArgumentParser(
+        description="IPMI Sensor parser and code generator")
+
+    parser.add_argument(
+        '-i', '--sensor_yaml', dest='sensor_yaml',
+        default='example.yaml', help='input sensor yaml file to parse')
+
+    parser.add_argument(
+        "-o", "--output-dir", dest="outputdir",
+        default=".",
+        help="output directory")
+
+    parser.add_argument(
+        'command', metavar='COMMAND', type=str,
+        choices=valid_commands.keys(),
+        help='Command to run.')
+
+    args = parser.parse_args()
+
+    if (not (os.path.isfile(os.path.join(script_dir, args.sensor_yaml)))):
+        sys.exit("Can not find input yaml file " + args.sensor_yaml)
+
+    function = valid_commands[args.command]
+    function(args.sensor_yaml, args.outputdir)
+
+
+if __name__ == '__main__':
+    script_dir = os.path.dirname(os.path.realpath(__file__))
+    main()
diff --git a/scripts/sensor-example.txt b/scripts/sensor-example.txt
new file mode 100644
index 0000000..0915163
--- /dev/null
+++ b/scripts/sensor-example.txt
@@ -0,0 +1,29 @@
+fan2: /* Name of the sensor. */
+  type: fan /* Type of sensor, fan, temp, margin */
+  /* How the sensor can be read[1] */
+  readpath: /xyz/openbmc_project/sensors/fan_tach/fan2
+  /* How the sensor can be set[2] */
+  writepath: /sys/class/hwmon/hwmon0/pwm1
+  /* The minimum value used for scaling writes (int64) */
+  min: 0
+  /* The maximum value used for scaling writes (int64) */
+  max: 255
+  /* The timeout value for the sensor, used for failsafe, 0 means no timeout
+   * (int64)
+   */
+  timeout: 0
+
+[1] readpath has multiple options:
+* If it has "/xyz/openbmc_project/extsensors/" in it, it's an EXTERNAL or
+  host-provided sensor.
+* If it has "/xyz/openbmc_project/" in it, it's a sensor whose value is
+  received passively over dbus.
+* If it has "/sys/" in it, it's a sensor read directly from sysfs.
+
+[2]
+* This can be left blank if the sensor is read-only.
+* If it has "/sys/" in it, it's a sensor written to sysfs.
+  * If min and max are non-zero, it'll convert the value to within the range.
+    and output that modified value.  So, if it receives a value of .90 and min
+    is 0, and max is 255, it'll convert that to a value of 229.5 that is then
+    cast to int64_t.
diff --git a/scripts/sensor-example.yaml b/scripts/sensor-example.yaml
new file mode 100644
index 0000000..7378bff
--- /dev/null
+++ b/scripts/sensor-example.yaml
@@ -0,0 +1,31 @@
+fan2:
+  type: fan
+  readpath: /xyz/openbmc_project/sensors/fan_tach/fan2
+  writepath: /sys/class/hwmon/hwmon0/pwm1
+  min: 0
+  max: 255
+  timeout: 0
+
+fan6:
+  type: fan
+  readpath: /xyz/openbmc_project/sensors/fan_tach/fan6
+  writepath:
+  min: 0
+  max: 0
+  timeout: 0
+
+temp1:
+  type: temp
+  readpath: /xyz/openbmc_project/sensors/temperature/temp1
+  writepath: 
+  min: 0
+  max: 0
+  timeout: 0
+
+sluggish0:
+  type: margin
+  readpath: /xyz/openbmc_project/extsensors/margin/sluggish0
+  writepath:
+  min: 0
+  max: 0
+  timeout: 0
diff --git a/scripts/sensor_gen.py b/scripts/sensor_gen.py
new file mode 100644
index 0000000..7e9c6ca
--- /dev/null
+++ b/scripts/sensor_gen.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+
+import os
+import sys
+import yaml
+import argparse
+from mako.template import Template
+
+
+def generate_cpp(sensor_yaml, output_dir):
+    with open(os.path.join(script_dir, sensor_yaml), 'r') as f:
+        ifile = yaml.safe_load(f)
+        if not isinstance(ifile, dict):
+            ifile = {}
+
+        # Render the mako template
+
+        t = Template(filename=os.path.join(
+                     script_dir,
+                     "writesensor.mako.cpp"))
+
+        output_cpp = os.path.join(output_dir, "sensorlist-gen.cpp")
+        with open(output_cpp, 'w') as fd:
+            fd.write(t.render(sensorDict=ifile))
+
+
+def main():
+
+    valid_commands = {
+        'generate-cpp': generate_cpp
+    }
+    parser = argparse.ArgumentParser(
+        description="IPMI Sensor parser and code generator")
+
+    parser.add_argument(
+        '-i', '--sensor_yaml', dest='sensor_yaml',
+        default='example.yaml', help='input sensor yaml file to parse')
+
+    parser.add_argument(
+        "-o", "--output-dir", dest="outputdir",
+        default=".",
+        help="output directory")
+
+    parser.add_argument(
+        'command', metavar='COMMAND', type=str,
+        choices=valid_commands.keys(),
+        help='Command to run.')
+
+    args = parser.parse_args()
+
+    if (not (os.path.isfile(os.path.join(script_dir, args.sensor_yaml)))):
+        sys.exit("Can not find input yaml file " + args.sensor_yaml)
+
+    function = valid_commands[args.command]
+    function(args.sensor_yaml, args.outputdir)
+
+
+if __name__ == '__main__':
+    script_dir = os.path.dirname(os.path.realpath(__file__))
+    main()
\ No newline at end of file
diff --git a/scripts/writepid.mako.cpp b/scripts/writepid.mako.cpp
new file mode 100644
index 0000000..f168e10
--- /dev/null
+++ b/scripts/writepid.mako.cpp
@@ -0,0 +1,48 @@
+## This file is a template.  The comment below is emitted
+## into the rendered file; feel free to edit this file.
+
+// !!! WARNING: This is GENERATED Code... Please do NOT edit !!!
+
+#include <map>
+#include "conf.hpp"
+
+std::map<int64_t, PIDConf> ZoneConfig = {
+% for zone in PIDDict.iterkeys():
+    % if zone:
+    {${zone},
+        {
+            % for key, details in PIDDict[zone].iteritems():
+            {"${key}",
+                {"${details['type']}",
+                 {
+                   % for item in details['inputs'].split():
+                   "${item}",
+                   % endfor
+                 },
+                 <%
+                      # If the PID type is a fan, set-point field is unused,
+                      # so just use a default of 0.  If the PID is not a type
+                      # of fan, require the set-point field.
+                      if 'fan' == details['type']:
+                          setpoint = 0
+                      else:
+                          setpoint = details['set-point']
+                  %>
+                 ${setpoint},
+                 {${details['pid']['sampleperiod']},
+                  ${details['pid']['p_coefficient']},
+                  ${details['pid']['i_coefficient']},
+                  ${details['pid']['ff_off_coefficient']},
+                  ${details['pid']['ff_gain_coefficient']},
+                  {${details['pid']['i_limit']['min']}, ${details['pid']['i_limit']['max']}},
+                  {${details['pid']['out_limit']['min']}, ${details['pid']['out_limit']['max']}},
+                  ${details['pid']['slew_neg']},
+                  ${details['pid']['slew_pos']}},
+                },
+            },
+            % endfor
+        },
+    },
+   % endif
+% endfor
+};
diff --git a/scripts/writesensor.mako.cpp b/scripts/writesensor.mako.cpp
new file mode 100644
index 0000000..8be30bb
--- /dev/null
+++ b/scripts/writesensor.mako.cpp
@@ -0,0 +1,36 @@
+## This file is a template.  The comment below is emitted
+## into the rendered file; feel free to edit this file.
+
+// !!! WARNING: This is GENERATED Code... Please do NOT edit !!!
+
+#include <map>
+#include "conf.hpp"
+
+std::map<std::string, struct sensor> SensorConfig = {
+% for key in sensorDict.iterkeys():
+   % if key:
+   <%
+           sensor = sensorDict[key]
+           type = sensor["type"]
+           readpath = sensor["readpath"]
+           writepath = sensor.get("writepath", "")
+           min = sensor.get("min", 0)
+           max = sensor.get("max", 0)
+           # Presently only thermal inputs have their timeout
+           # checked, but we should default it as 2s, which is
+           # the previously hard-coded value.
+           # If it's a fan sensor though, let's set the default
+           # to 0.
+           if type == "fan":
+               timeout = sensor.get("timeout", 0)
+           else:
+               timeout = sensor.get("timeout", 2)
+   %>
+    {"${key}",
+        {"${type}","${readpath}","${writepath}", ${min}, ${max}, ${timeout}},
+    },
+   % endif
+% endfor
+};
+
+
diff --git a/scripts/writezone.mako.cpp b/scripts/writezone.mako.cpp
new file mode 100644
index 0000000..57b9908
--- /dev/null
+++ b/scripts/writezone.mako.cpp
@@ -0,0 +1,22 @@
+## This file is a template.  The comment below is emitted
+## into the rendered file; feel free to edit this file.
+
+// !!! WARNING: This is GENERATED Code... Please do NOT edit !!!
+
+#include <map>
+#include "conf.hpp"
+
+std::map<int64_t, struct zone> ZoneDetailsConfig = {
+% for zone in ZoneDict.iterkeys():
+   % if zone:
+   <%
+           zConf = ZoneDict[zone]
+           min = zConf["minthermalrpm"]
+           percent = zConf["failsafepercent"]
+   %>
+    {${zone},
+        {${min}, ${percent}},
+    },
+   % endif
+% endfor
+};
diff --git a/scripts/zone-example.txt b/scripts/zone-example.txt
new file mode 100644
index 0000000..752fc01
--- /dev/null
+++ b/scripts/zone-example.txt
@@ -0,0 +1,4 @@
+0x01: /* The zone ID */
+  minthermalrpm: 3000.0 /* The minimum thermal RPM value. (float) */
+  /* The percent to use when the zone is in fail-safe mode. (float) */
+  failsafepercent: 90.0
diff --git a/scripts/zone-example.yaml b/scripts/zone-example.yaml
new file mode 100644
index 0000000..ef422a5
--- /dev/null
+++ b/scripts/zone-example.yaml
@@ -0,0 +1,3 @@
+0x01:
+  minthermalrpm: 3000.0
+  failsafepercent: 90.0
diff --git a/scripts/zone_gen.py b/scripts/zone_gen.py
new file mode 100644
index 0000000..a98a3b5
--- /dev/null
+++ b/scripts/zone_gen.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+
+import os
+import sys
+import yaml
+import argparse
+from mako.template import Template
+
+
+def generate_cpp(zoneinfo_yaml, output_dir):
+    with open(os.path.join(script_dir, zoneinfo_yaml), 'r') as f:
+        ifile = yaml.safe_load(f)
+        if not isinstance(ifile, dict):
+            ifile = {}
+
+        # Render the mako template
+
+        t = Template(filename=os.path.join(
+                     script_dir,
+                     "writezone.mako.cpp"))
+
+        output_cpp = os.path.join(output_dir, "zoneinfo-gen.cpp")
+        with open(output_cpp, 'w') as fd:
+            fd.write(t.render(ZoneDict=ifile))
+
+
+def main():
+
+    valid_commands = {
+        'generate-cpp': generate_cpp
+    }
+    parser = argparse.ArgumentParser(
+        description="IPMI Zone info parser and code generator")
+
+    parser.add_argument(
+        '-i', '--zoneinfo_yaml', dest='zoneinfo_yaml',
+        default='example.yaml', help='input zone yaml file to parse')
+
+    parser.add_argument(
+        "-o", "--output-dir", dest="outputdir",
+        default=".",
+        help="output directory")
+
+    parser.add_argument(
+        'command', metavar='COMMAND', type=str,
+        choices=valid_commands.keys(),
+        help='Command to run.')
+
+    args = parser.parse_args()
+
+    if (not (os.path.isfile(os.path.join(script_dir, args.zoneinfo_yaml)))):
+        sys.exit("Can not find input yaml file " + args.zoneinfo_yaml)
+
+    function = valid_commands[args.command]
+    function(args.zoneinfo_yaml, args.outputdir)
+
+
+if __name__ == '__main__':
+    script_dir = os.path.dirname(os.path.realpath(__file__))
+    main()
diff --git a/setsensor.cpp b/setsensor.cpp
new file mode 100644
index 0000000..7daa762
--- /dev/null
+++ b/setsensor.cpp
@@ -0,0 +1,100 @@
+#include <iostream>
+#include <string>
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/message.hpp>
+
+/* Fan Control */
+static constexpr auto objectPath = "/xyz/openbmc_project/settings/fanctrl/zone";
+static constexpr auto busName = "xyz.openbmc_project.State.FanCtrl";
+static constexpr auto intf = "xyz.openbmc_project.Control.FanCtrl.Mode";
+static constexpr auto property = "Manual";
+using Value = sdbusplus::message::variant<bool>;
+
+/* Host Sensor. */
+static constexpr auto sobjectPath =
+    "/xyz/openbmc_project/extsensors/margin/sluggish0";
+static constexpr auto sbusName = "xyz.openbmc_project.Hwmon.external";
+static constexpr auto sintf = "xyz.openbmc_project.Sensor.Value";
+static constexpr auto sproperty = "Value";
+using sValue = sdbusplus::message::variant<int64_t>;
+
+
+static constexpr auto propertiesintf = "org.freedesktop.DBus.Properties";
+
+static void SetHostSensor(void)
+{
+    int64_t value = 300;
+    sValue v {value};
+
+    std::string busname {sbusName};
+    auto PropertyWriteBus = sdbusplus::bus::new_default();
+    std::string path {sobjectPath};
+
+    auto pimMsg = PropertyWriteBus.new_method_call(
+                      busname.c_str(),
+                      path.c_str(),
+                      propertiesintf,
+                      "Set");
+
+    pimMsg.append(sintf);
+    pimMsg.append(sproperty);
+    pimMsg.append(v);
+
+    auto responseMsg = PropertyWriteBus.call(pimMsg);
+    if (responseMsg.is_method_error())
+    {
+        fprintf(stderr, "call to Set the host sensor value failed.\n");
+    }
+    else
+    {
+        fprintf(stderr, "call to Set the host sensor value succeeded.\n");
+    }
+}
+
+static std::string GetControlPath(int8_t zone)
+{
+    return std::string(objectPath) + std::to_string(zone);
+}
+
+static void SetManualMode(int8_t zone)
+{
+    bool setValue = (bool)0x01;
+
+    Value v {setValue};
+
+    std::string busname {busName};
+    auto PropertyWriteBus = sdbusplus::bus::new_default();
+    std::string path = GetControlPath(zone);
+
+    auto pimMsg = PropertyWriteBus.new_method_call(
+                      busname.c_str(),
+                      path.c_str(),
+                      propertiesintf,
+                      "Set");
+
+    pimMsg.append(intf);
+    pimMsg.append(property);
+    pimMsg.append(v);
+
+    auto responseMsg = PropertyWriteBus.call(pimMsg);
+    if (responseMsg.is_method_error())
+    {
+        fprintf(stderr, "call to Set the manual mode failed.\n");
+    }
+    else
+    {
+        fprintf(stderr, "call to Set the manual mode succeeded.\n");
+    }
+}
+
+int main(int argc, char* argv[])
+{
+    int rc = 0;
+
+    int64_t zone = 0x01;
+
+    SetManualMode(zone);
+    SetHostSensor();
+    return rc;
+}
diff --git a/threads/busthread.cpp b/threads/busthread.cpp
new file mode 100644
index 0000000..f007bf9
--- /dev/null
+++ b/threads/busthread.cpp
@@ -0,0 +1,37 @@
+/**
+ * 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 <string>
+
+#include "busthread.hpp"
+
+
+void BusThread(struct ThreadParams& params)
+{
+    if (params.name.length() > 0)
+    {
+        params.bus.request_name(params.name.c_str());
+    }
+
+    while (true)
+    {
+        params.bus.process_discard();
+        /* block indefinitely for updates. */
+        params.bus.wait();
+    }
+
+    return;
+}
diff --git a/threads/busthread.hpp b/threads/busthread.hpp
new file mode 100644
index 0000000..a3234b8
--- /dev/null
+++ b/threads/busthread.hpp
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <sdbusplus/bus.hpp>
+
+struct ThreadParams
+{
+    sdbusplus::bus::bus& bus;
+    std::string name;
+};
+
+void BusThread(struct ThreadParams& params);
diff --git a/util.cpp b/util.cpp
new file mode 100644
index 0000000..cf9ef81
--- /dev/null
+++ b/util.cpp
@@ -0,0 +1,72 @@
+/**
+ * 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 <string>
+
+#include "util.hpp"
+
+
+static constexpr auto external_sensor =
+    "/xyz/openbmc_project/extsensors/"; // type/
+static constexpr auto openbmc_sensor = "/xyz/openbmc_project/"; // type/
+static constexpr auto sysfs = "/sys/";
+
+
+IOInterfaceType GetWriteInterfaceType(const std::string& path)
+{
+    std::string::size_type n;
+
+    if (path.empty() || "None" == path)
+    {
+        return IOInterfaceType::NONE;
+    }
+
+    if (path.find(sysfs) != std::string::npos)
+    {
+        // A sysfs read sensor.
+        return IOInterfaceType::SYSFS;
+    }
+
+    return IOInterfaceType::UNKNOWN;
+}
+
+IOInterfaceType GetReadInterfaceType(const std::string& path)
+{
+    std::string::size_type n;
+
+    if (path.empty() || "None" == path)
+    {
+        return IOInterfaceType::NONE;
+    }
+
+    if (path.find(external_sensor) != std::string::npos)
+    {
+        return IOInterfaceType::EXTERNAL;
+    }
+
+    if (path.find(openbmc_sensor) != std::string::npos)
+    {
+        return IOInterfaceType::DBUSPASSIVE;
+    }
+
+    if (path.find(sysfs) != std::string::npos)
+    {
+        return IOInterfaceType::SYSFS;
+    }
+
+    return IOInterfaceType::UNKNOWN;
+}
+
diff --git a/util.hpp b/util.hpp
new file mode 100644
index 0000000..2c5eccd
--- /dev/null
+++ b/util.hpp
@@ -0,0 +1,25 @@
+#pragma once
+
+/* This program assumes sensors use the Sensor.Value interface
+ * and for sensor->write() I only implemented sysfs as a type,
+ * but -- how would it know whether to use Control.FanSpeed or Control.FanPwm?
+ *
+ * One could get the interface list for the object and search for Control.*
+ * but, it needs to know the maximum, minimum.  The only sensors it wants to write
+ * in this code base are Fans...
+ */
+enum class IOInterfaceType
+{
+    NONE, // There is no interface.
+    EXTERNAL,
+    DBUSPASSIVE,
+    DBUSACTIVE, // This means for write that it needs to look up the interface.
+    SYSFS,
+    UNKNOWN
+};
+
+/* WriteInterfaceType is different because Dbusactive/passive. how to know... */
+IOInterfaceType GetWriteInterfaceType(const std::string& path);
+
+IOInterfaceType GetReadInterfaceType(const std::string& path);
+