sensors: Align source structure away from anti-patterns

The anti-patterns document comments on source structure, specifically
on placing internal headers in a parallel subtree[1]. dbus-sensors is an
example of violating this anti-pattern, so fix it.

[1]: https://github.com/openbmc/docs/blob/master/anti-patterns.md#placing-internal-headers-in-a-parallel-subtree

Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Change-Id: I50ecaddd53fa9c9b7a0441af9de5e60bd94e47c6
diff --git a/src/ADCSensor.cpp b/src/ADCSensor.cpp
index fe2c8a7..2fc35e0 100644
--- a/src/ADCSensor.cpp
+++ b/src/ADCSensor.cpp
@@ -14,9 +14,10 @@
 // limitations under the License.
 */
 
+#include "ADCSensor.hpp"
+
 #include <unistd.h>
 
-#include <ADCSensor.hpp>
 #include <boost/asio/read_until.hpp>
 #include <sdbusplus/asio/connection.hpp>
 #include <sdbusplus/asio/object_server.hpp>
diff --git a/src/ADCSensor.hpp b/src/ADCSensor.hpp
new file mode 100644
index 0000000..72fcd90
--- /dev/null
+++ b/src/ADCSensor.hpp
@@ -0,0 +1,92 @@
+#pragma once
+
+#include "Thresholds.hpp"
+#include "sensor.hpp"
+
+#include <boost/asio/streambuf.hpp>
+#include <gpiod.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+#include <memory>
+#include <optional>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+class BridgeGpio
+{
+  public:
+    BridgeGpio(const std::string& name, const int polarity,
+               const float setupTime) :
+        setupTimeMs(static_cast<unsigned int>(setupTime * 1000))
+    {
+        line = gpiod::find_line(name);
+        if (!line)
+        {
+            std::cerr << "Error finding gpio: " << name << "\n";
+        }
+        else
+        {
+            try
+            {
+                line.request({"adcsensor",
+                              gpiod::line_request::DIRECTION_OUTPUT,
+                              polarity == gpiod::line::ACTIVE_HIGH
+                                  ? 0
+                                  : gpiod::line_request::FLAG_ACTIVE_LOW});
+            }
+            catch (const std::system_error&)
+            {
+                std::cerr << "Error requesting gpio: " << name << "\n";
+            }
+        }
+    }
+
+    void set(int value)
+    {
+        if (line)
+        {
+            try
+            {
+                line.set_value(value);
+            }
+            catch (const std::system_error& exc)
+            {
+                std::cerr << "Error set_value: " << exc.what() << "\n";
+            }
+        }
+    }
+
+    unsigned int setupTimeMs;
+
+  private:
+    gpiod::line line;
+};
+
+class ADCSensor : public Sensor, public std::enable_shared_from_this<ADCSensor>
+{
+  public:
+    ADCSensor(const std::string& path,
+              sdbusplus::asio::object_server& objectServer,
+              std::shared_ptr<sdbusplus::asio::connection>& conn,
+              boost::asio::io_service& io, const std::string& sensorName,
+              std::vector<thresholds::Threshold>&& thresholds,
+              double scaleFactor, float pollRate, PowerState readState,
+              const std::string& sensorConfiguration,
+              std::optional<BridgeGpio>&& bridgeGpio);
+    ~ADCSensor() override;
+    void setupRead(void);
+
+  private:
+    sdbusplus::asio::object_server& objServer;
+    boost::asio::posix::stream_descriptor inputDev;
+    boost::asio::steady_timer waitTimer;
+    std::shared_ptr<boost::asio::streambuf> readBuf;
+    std::string path;
+    double scaleFactor;
+    unsigned int sensorPollMs;
+    std::optional<BridgeGpio> bridgeGpio;
+    thresholds::ThresholdTimer thresholdTimer;
+    void handleResponse(const boost::system::error_code& err);
+    void checkThresholds(void) override;
+};
diff --git a/src/ADCSensorMain.cpp b/src/ADCSensorMain.cpp
index 3cbb5f6..f184d23 100644
--- a/src/ADCSensorMain.cpp
+++ b/src/ADCSensorMain.cpp
@@ -14,9 +14,10 @@
 // limitations under the License.
 */
 
-#include <ADCSensor.hpp>
-#include <Utils.hpp>
-#include <VariantVisitors.hpp>
+#include "ADCSensor.hpp"
+#include "Utils.hpp"
+#include "VariantVisitors.hpp"
+
 #include <boost/algorithm/string/case_conv.hpp>
 #include <boost/algorithm/string/replace.hpp>
 #include <boost/container/flat_set.hpp>
diff --git a/src/ChassisIntrusionSensor.cpp b/src/ChassisIntrusionSensor.cpp
index 2bb04ce..f7e7783 100644
--- a/src/ChassisIntrusionSensor.cpp
+++ b/src/ChassisIntrusionSensor.cpp
@@ -14,11 +14,12 @@
 // limitations under the License.
 */
 
+#include "ChassisIntrusionSensor.hpp"
+
 #include <fcntl.h>
 #include <sys/ioctl.h>
 #include <unistd.h>
 
-#include <ChassisIntrusionSensor.hpp>
 #include <boost/asio/io_service.hpp>
 #include <sdbusplus/asio/object_server.hpp>
 
diff --git a/src/ChassisIntrusionSensor.hpp b/src/ChassisIntrusionSensor.hpp
new file mode 100644
index 0000000..8954033
--- /dev/null
+++ b/src/ChassisIntrusionSensor.hpp
@@ -0,0 +1,63 @@
+#pragma once
+
+#include <boost/asio/io_service.hpp>
+#include <boost/asio/steady_timer.hpp>
+#include <gpiod.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+#include <memory>
+#include <string>
+
+enum IntrusionSensorType
+{
+    pch,
+    gpio
+};
+
+class ChassisIntrusionSensor
+{
+  public:
+    ChassisIntrusionSensor(
+        boost::asio::io_service& io,
+        std::shared_ptr<sdbusplus::asio::dbus_interface> iface);
+
+    ~ChassisIntrusionSensor();
+
+    void start(IntrusionSensorType type, int busId, int slaveAddr,
+               bool gpioInverted);
+
+  private:
+    std::shared_ptr<sdbusplus::asio::dbus_interface> mIface;
+    std::shared_ptr<sdbusplus::asio::connection> mDbusConn;
+
+    IntrusionSensorType mType{IntrusionSensorType::gpio};
+
+    // intrusion status. 0: not intruded, 1: intruded
+    std::string mValue = "unknown";
+    std::string mOldValue = "unknown";
+
+    // valid if it is PCH register via i2c
+    int mBusId{-1};
+    int mSlaveAddr{-1};
+    boost::asio::steady_timer mPollTimer;
+
+    // valid if it is via GPIO
+    bool mGpioInverted{false};
+    std::string mPinName = "CHASSIS_INTRUSION";
+    gpiod::line mGpioLine;
+    boost::asio::posix::stream_descriptor mGpioFd;
+
+    // common members
+    bool mOverridenState = false;
+    bool mInternalSet = false;
+
+    bool mInitialized = false;
+
+    void updateValue(const std::string& newValue);
+    static int i2cReadFromPch(int busId, int slaveAddr);
+    void pollSensorStatusByPch();
+    void readGpio();
+    void pollSensorStatusByGpio();
+    void initGpioDeviceFile();
+    int setSensorValue(const std::string& req, std::string& propertyValue);
+};
diff --git a/src/DeviceMgmt.cpp b/src/DeviceMgmt.cpp
index f137908..ffc0376 100644
--- a/src/DeviceMgmt.cpp
+++ b/src/DeviceMgmt.cpp
@@ -1,4 +1,4 @@
-#include <DeviceMgmt.hpp>
+#include "DeviceMgmt.hpp"
 
 #include <filesystem>
 #include <fstream>
diff --git a/src/DeviceMgmt.hpp b/src/DeviceMgmt.hpp
new file mode 100644
index 0000000..07b28b5
--- /dev/null
+++ b/src/DeviceMgmt.hpp
@@ -0,0 +1,56 @@
+#pragma once
+
+#include "Utils.hpp"
+
+#include <boost/container/flat_map.hpp>
+
+#include <functional>
+#include <optional>
+#include <string_view>
+
+struct I2CDeviceType
+{
+    const char* name;
+    bool createsHWMon;
+};
+
+using I2CDeviceTypeMap =
+    boost::container::flat_map<std::string, I2CDeviceType, std::less<>>;
+
+struct I2CDeviceParams
+{
+    I2CDeviceParams(const I2CDeviceType& type, uint64_t bus, uint64_t address) :
+        type(&type), bus(bus), address(address){};
+
+    const I2CDeviceType* type;
+    uint64_t bus;
+    uint64_t address;
+
+    bool devicePresent(void) const;
+    bool deviceStatic(void) const;
+};
+
+std::optional<I2CDeviceParams>
+    getI2CDeviceParams(const I2CDeviceTypeMap& dtmap,
+                       const SensorBaseConfigMap& cfg);
+
+class I2CDevice
+{
+  public:
+    explicit I2CDevice(I2CDeviceParams params);
+    ~I2CDevice();
+
+  private:
+    I2CDeviceParams params;
+
+    int create(void) const;
+    int destroy(void) const;
+};
+
+// HACK: this declaration "should" live in Utils.hpp, but that leads to a
+// tangle of header-dependency hell because each header needs types declared
+// in the other.
+std::vector<std::unique_ptr<sdbusplus::bus::match_t>>
+    setupPropertiesChangedMatches(
+        sdbusplus::asio::connection& bus, const I2CDeviceTypeMap& typeMap,
+        const std::function<void(sdbusplus::message_t&)>& handler);
diff --git a/src/ExitAirTempSensor.cpp b/src/ExitAirTempSensor.cpp
index 7f947e5..d126def 100644
--- a/src/ExitAirTempSensor.cpp
+++ b/src/ExitAirTempSensor.cpp
@@ -14,9 +14,11 @@
 // limitations under the License.
 */
 
-#include <ExitAirTempSensor.hpp>
-#include <Utils.hpp>
-#include <VariantVisitors.hpp>
+#include "ExitAirTempSensor.hpp"
+
+#include "Utils.hpp"
+#include "VariantVisitors.hpp"
+
 #include <boost/algorithm/string/replace.hpp>
 #include <boost/container/flat_map.hpp>
 #include <sdbusplus/asio/connection.hpp>
diff --git a/src/ExitAirTempSensor.hpp b/src/ExitAirTempSensor.hpp
new file mode 100644
index 0000000..5bd9735
--- /dev/null
+++ b/src/ExitAirTempSensor.hpp
@@ -0,0 +1,84 @@
+#pragma once
+#include <boost/container/flat_map.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <sensor.hpp>
+
+#include <chrono>
+#include <limits>
+#include <memory>
+#include <string>
+#include <vector>
+
+struct ExitAirTempSensor;
+struct CFMSensor : public Sensor, std::enable_shared_from_this<CFMSensor>
+{
+    std::vector<std::string> tachs;
+    double c1 = 0.0;
+    double c2 = 0.0;
+    double maxCFM = 0.0;
+    double tachMinPercent = 0.0;
+    double tachMaxPercent = 0.0;
+
+    std::shared_ptr<ExitAirTempSensor> parent;
+
+    CFMSensor(std::shared_ptr<sdbusplus::asio::connection>& conn,
+              const std::string& name, const std::string& sensorConfiguration,
+              sdbusplus::asio::object_server& objectServer,
+              std::vector<thresholds::Threshold>&& thresholdData,
+              std::shared_ptr<ExitAirTempSensor>& parent);
+    ~CFMSensor() override;
+
+    bool calculate(double& /*value*/);
+    void updateReading(void);
+    void setupMatches(void);
+    void createMaxCFMIface(void);
+    void addTachRanges(const std::string& serviceName, const std::string& path);
+    void checkThresholds(void) override;
+    uint64_t getMaxRpm(uint64_t cfmMax) const;
+
+  private:
+    std::vector<sdbusplus::bus::match_t> matches;
+    boost::container::flat_map<std::string, double> tachReadings;
+    boost::container::flat_map<std::string, std::pair<double, double>>
+        tachRanges;
+    std::shared_ptr<sdbusplus::asio::dbus_interface> pwmLimitIface;
+    std::shared_ptr<sdbusplus::asio::dbus_interface> cfmLimitIface;
+    sdbusplus::asio::object_server& objServer;
+};
+
+struct ExitAirTempSensor :
+    public Sensor,
+    std::enable_shared_from_this<ExitAirTempSensor>
+{
+
+    double powerFactorMin = 0.0;
+    double powerFactorMax = 0.0;
+    double qMin = 0.0;
+    double qMax = 0.0;
+    double alphaS = 0.0;
+    double alphaF = 0.0;
+    double pOffset = 0.0;
+
+    ExitAirTempSensor(std::shared_ptr<sdbusplus::asio::connection>& conn,
+                      const std::string& name,
+                      const std::string& sensorConfiguration,
+                      sdbusplus::asio::object_server& objectServer,
+                      std::vector<thresholds::Threshold>&& thresholdData);
+    ~ExitAirTempSensor() override;
+
+    void checkThresholds(void) override;
+    void updateReading(void);
+    void setupMatches(void);
+
+  private:
+    double lastReading = 0.0;
+
+    std::vector<sdbusplus::bus::match_t> matches;
+    double inletTemp = std::numeric_limits<double>::quiet_NaN();
+    boost::container::flat_map<std::string, double> powerReadings;
+
+    sdbusplus::asio::object_server& objServer;
+    std::chrono::time_point<std::chrono::steady_clock> lastTime;
+    static double getTotalCFM(void);
+    bool calculate(double& val);
+};
diff --git a/src/ExternalSensor.hpp b/src/ExternalSensor.hpp
new file mode 100644
index 0000000..20dd2f0
--- /dev/null
+++ b/src/ExternalSensor.hpp
@@ -0,0 +1,66 @@
+#pragma once
+
+#include "Thresholds.hpp"
+#include "sensor.hpp"
+
+#include <sdbusplus/asio/object_server.hpp>
+
+#include <chrono>
+#include <string>
+#include <vector>
+
+class ExternalSensor :
+    public Sensor,
+    public std::enable_shared_from_this<ExternalSensor>
+{
+  public:
+    ExternalSensor(const std::string& objectType,
+                   sdbusplus::asio::object_server& objectServer,
+                   std::shared_ptr<sdbusplus::asio::connection>& conn,
+                   const std::string& sensorName,
+                   const std::string& sensorUnits,
+                   std::vector<thresholds::Threshold>&& thresholdsIn,
+                   const std::string& sensorConfiguration, double maxReading,
+                   double minReading, double timeoutSecs,
+                   const PowerState& powerState);
+    ~ExternalSensor() override;
+
+    // Call this immediately after calling the constructor
+    void initWriteHook(
+        std::function<void(std::chrono::steady_clock::time_point now)>&&
+            writeHookIn);
+
+    // Returns true if sensor has external Value that is subject to timeout
+    bool isAliveAndPerishable(void) const;
+
+    // Returns true if AliveAndPerishable and timeout has not yet happened
+    bool
+        isAliveAndFresh(const std::chrono::steady_clock::time_point& now) const;
+
+    // Marks the time when Value successfully received from external source
+    void writeBegin(const std::chrono::steady_clock::time_point& now);
+
+    // Marks sensor as timed out, replacing Value with floating-point "NaN"
+    void writeInvalidate(void);
+
+    // Returns amount of time elapsed since last writeBegin() happened
+    std::chrono::steady_clock::duration
+        ageElapsed(const std::chrono::steady_clock::time_point& now) const;
+
+    // Returns amount of time remaining until sensor timeout will happen
+    std::chrono::steady_clock::duration
+        ageRemaining(const std::chrono::steady_clock::time_point& now) const;
+
+  private:
+    sdbusplus::asio::object_server& objServer;
+
+    std::chrono::steady_clock::time_point writeLast;
+    std::chrono::steady_clock::duration writeTimeout;
+    bool writeAlive{false};
+    bool writePerishable;
+    std::function<void(const std::chrono::steady_clock::time_point& now)>
+        writeHook;
+
+    void checkThresholds(void) override;
+    void externalSetTrigger(void);
+};
diff --git a/src/FanMain.cpp b/src/FanMain.cpp
index 7260131..2c51396 100644
--- a/src/FanMain.cpp
+++ b/src/FanMain.cpp
@@ -14,10 +14,11 @@
 // limitations under the License.
 */
 
-#include <PwmSensor.hpp>
-#include <TachSensor.hpp>
-#include <Utils.hpp>
-#include <VariantVisitors.hpp>
+#include "PwmSensor.hpp"
+#include "TachSensor.hpp"
+#include "Utils.hpp"
+#include "VariantVisitors.hpp"
+
 #include <boost/algorithm/string/replace.hpp>
 #include <boost/container/flat_map.hpp>
 #include <boost/container/flat_set.hpp>
diff --git a/src/FileHandle.cpp b/src/FileHandle.cpp
index af32192..227ce23 100644
--- a/src/FileHandle.cpp
+++ b/src/FileHandle.cpp
@@ -1,8 +1,8 @@
+#include "FileHandle.hpp"
+
 #include <fcntl.h>
 #include <unistd.h>
 
-#include <FileHandle.hpp>
-
 #include <iostream>
 #include <stdexcept>
 
diff --git a/src/FileHandle.hpp b/src/FileHandle.hpp
new file mode 100644
index 0000000..a2a75d8
--- /dev/null
+++ b/src/FileHandle.hpp
@@ -0,0 +1,27 @@
+#pragma once
+
+#include <filesystem>
+#include <ios>
+
+// An RAII compliant object for holding a posix file descriptor
+class FileHandle
+{
+  private:
+    int fd;
+
+  public:
+    FileHandle() = delete;
+    FileHandle(const FileHandle&) = delete;
+    FileHandle& operator=(const FileHandle&) = delete;
+    FileHandle(FileHandle&& /*in*/) noexcept;
+    FileHandle& operator=(FileHandle&& /*in*/) noexcept;
+
+    explicit FileHandle(const std::filesystem::path& name,
+                        std::ios_base::openmode mode = std::ios_base::in |
+                                                       std::ios_base::out);
+
+    explicit FileHandle(int fd);
+
+    ~FileHandle();
+    int handle() const;
+};
\ No newline at end of file
diff --git a/src/HwmonTempMain.cpp b/src/HwmonTempMain.cpp
index e777c42..7ac03a3 100644
--- a/src/HwmonTempMain.cpp
+++ b/src/HwmonTempMain.cpp
@@ -14,9 +14,10 @@
 // limitations under the License.
 */
 
-#include <DeviceMgmt.hpp>
-#include <HwmonTempSensor.hpp>
-#include <Utils.hpp>
+#include "DeviceMgmt.hpp"
+#include "HwmonTempSensor.hpp"
+#include "Utils.hpp"
+
 #include <boost/algorithm/string/replace.hpp>
 #include <boost/container/flat_map.hpp>
 #include <boost/container/flat_set.hpp>
diff --git a/src/HwmonTempSensor.cpp b/src/HwmonTempSensor.cpp
index 144f9d9..b4042ff 100644
--- a/src/HwmonTempSensor.cpp
+++ b/src/HwmonTempSensor.cpp
@@ -14,9 +14,10 @@
 // limitations under the License.
 */
 
+#include "HwmonTempSensor.hpp"
+
 #include <unistd.h>
 
-#include <HwmonTempSensor.hpp>
 #include <boost/asio/read_until.hpp>
 #include <sdbusplus/asio/connection.hpp>
 #include <sdbusplus/asio/object_server.hpp>
diff --git a/src/HwmonTempSensor.hpp b/src/HwmonTempSensor.hpp
new file mode 100644
index 0000000..27ffccd
--- /dev/null
+++ b/src/HwmonTempSensor.hpp
@@ -0,0 +1,65 @@
+#pragma once
+
+#include "DeviceMgmt.hpp"
+#include "Thresholds.hpp"
+#include "sensor.hpp"
+
+#include <boost/asio/random_access_file.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+#include <string>
+#include <vector>
+
+struct SensorParams
+{
+    double minValue;
+    double maxValue;
+    double offsetValue;
+    double scaleValue;
+    std::string units;
+    std::string typeName;
+};
+
+class HwmonTempSensor :
+    public Sensor,
+    public std::enable_shared_from_this<HwmonTempSensor>
+{
+  public:
+    HwmonTempSensor(const std::string& path, const std::string& objectType,
+                    sdbusplus::asio::object_server& objectServer,
+                    std::shared_ptr<sdbusplus::asio::connection>& conn,
+                    boost::asio::io_service& io, const std::string& sensorName,
+                    std::vector<thresholds::Threshold>&& thresholds,
+                    const struct SensorParams& thisSensorParameters,
+                    float pollRate, const std::string& sensorConfiguration,
+                    PowerState powerState,
+                    const std::shared_ptr<I2CDevice>& i2cDevice);
+    ~HwmonTempSensor() override;
+    void setupRead(void);
+    void activate(const std::string& newPath,
+                  const std::shared_ptr<I2CDevice>& newI2CDevice);
+    void deactivate(void);
+    bool isActive(void);
+
+    std::shared_ptr<I2CDevice> getI2CDevice() const
+    {
+        return i2cDevice;
+    }
+
+  private:
+    // Ordering is important here; readBuf is first so that it's not destroyed
+    // while async operations from other member fields might still be using it.
+    std::array<char, 128> readBuf{};
+    std::shared_ptr<I2CDevice> i2cDevice;
+    sdbusplus::asio::object_server& objServer;
+    boost::asio::random_access_file inputDev;
+    boost::asio::steady_timer waitTimer;
+    std::string path;
+    double offsetValue;
+    double scaleValue;
+    unsigned int sensorPollMs;
+
+    void handleResponse(const boost::system::error_code& err, size_t bytesRead);
+    void restartRead();
+    void checkThresholds(void) override;
+};
diff --git a/src/IntelCPUSensor.cpp b/src/IntelCPUSensor.cpp
index 3410574..2995d60 100644
--- a/src/IntelCPUSensor.cpp
+++ b/src/IntelCPUSensor.cpp
@@ -14,10 +14,12 @@
 // limitations under the License.
 */
 
+#include "IntelCPUSensor.hpp"
+
+#include "Utils.hpp"
+
 #include <unistd.h>
 
-#include <IntelCPUSensor.hpp>
-#include <Utils.hpp>
 #include <boost/algorithm/string/replace.hpp>
 #include <boost/asio/read_until.hpp>
 #include <sdbusplus/asio/connection.hpp>
diff --git a/src/IntelCPUSensor.hpp b/src/IntelCPUSensor.hpp
new file mode 100644
index 0000000..399e765
--- /dev/null
+++ b/src/IntelCPUSensor.hpp
@@ -0,0 +1,116 @@
+#pragma once
+
+#include "Thresholds.hpp"
+#include "Utils.hpp"
+
+#include <boost/asio/streambuf.hpp>
+#include <boost/container/flat_map.hpp>
+#include <gpiod.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+#include <sensor.hpp>
+
+#include <filesystem>
+#include <fstream>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <variant>
+#include <vector>
+
+class IntelCPUSensor :
+    public Sensor,
+    public std::enable_shared_from_this<IntelCPUSensor>
+{
+  public:
+    IntelCPUSensor(const std::string& path, const std::string& objectType,
+                   sdbusplus::asio::object_server& objectServer,
+                   std::shared_ptr<sdbusplus::asio::connection>& conn,
+                   boost::asio::io_service& io, const std::string& sensorName,
+                   std::vector<thresholds::Threshold>&& thresholds,
+                   const std::string& configuration, int cpuId, bool show,
+                   double dtsOffset);
+    ~IntelCPUSensor() override;
+    static constexpr unsigned int sensorScaleFactor = 1000;
+    static constexpr unsigned int sensorPollMs = 1000;
+    static constexpr size_t warnAfterErrorCount = 10;
+    static constexpr const char* labelTcontrol = "Tcontrol";
+    void setupRead(void);
+
+  private:
+    sdbusplus::asio::object_server& objServer;
+    boost::asio::streambuf readBuf;
+    boost::asio::posix::stream_descriptor inputDev;
+    boost::asio::steady_timer waitTimer;
+    std::string nameTcontrol;
+    std::string path;
+    double privTcontrol;
+    double dtsOffset;
+    bool show;
+    size_t pollTime;
+    bool loggedInterfaceDown = false;
+    uint8_t minMaxReadCounter{0};
+    int fd{};
+    void handleResponse(const boost::system::error_code& err);
+    void checkThresholds(void) override;
+    void updateMinMaxValues(void);
+    void restartRead(void);
+};
+
+extern boost::container::flat_map<std::string, std::shared_ptr<IntelCPUSensor>>
+    gCpuSensors;
+
+// this is added to intelcpusensor.hpp to avoid having every sensor have to link
+// against libgpiod, if another sensor needs it we may move it to utils
+inline bool cpuIsPresent(const SensorBaseConfigMap& gpioConfig)
+{
+    static boost::container::flat_map<std::string, bool> cpuPresence;
+
+    auto findName = gpioConfig.find("Name");
+    if (findName == gpioConfig.end())
+    {
+        return false;
+    }
+    std::string gpioName =
+        std::visit(VariantToStringVisitor(), findName->second);
+
+    auto findIndex = cpuPresence.find(gpioName);
+    if (findIndex != cpuPresence.end())
+    {
+        return findIndex->second;
+    }
+
+    bool activeHigh = true;
+    auto findPolarity = gpioConfig.find("Polarity");
+    if (findPolarity != gpioConfig.end())
+    {
+        if (std::string("Low") ==
+            std::visit(VariantToStringVisitor(), findPolarity->second))
+        {
+            activeHigh = false;
+        }
+    }
+
+    auto line = gpiod::find_line(gpioName);
+    if (!line)
+    {
+        std::cerr << "Error requesting gpio: " << gpioName << "\n";
+        return false;
+    }
+
+    bool resp = false;
+    try
+    {
+        line.request({"cpusensor", gpiod::line_request::DIRECTION_INPUT,
+                      activeHigh ? 0 : gpiod::line_request::FLAG_ACTIVE_LOW});
+        resp = (line.get_value() != 0);
+    }
+    catch (const std::system_error&)
+    {
+        std::cerr << "Error reading gpio: " << gpioName << "\n";
+        return false;
+    }
+
+    cpuPresence[gpioName] = resp;
+
+    return resp;
+}
diff --git a/src/IntelCPUSensorMain.cpp b/src/IntelCPUSensorMain.cpp
index aa06628..ecc36dc 100644
--- a/src/IntelCPUSensorMain.cpp
+++ b/src/IntelCPUSensorMain.cpp
@@ -14,11 +14,12 @@
 // limitations under the License.
 */
 
+#include "IntelCPUSensor.hpp"
+#include "Utils.hpp"
+#include "VariantVisitors.hpp"
+
 #include <fcntl.h>
 
-#include <IntelCPUSensor.hpp>
-#include <Utils.hpp>
-#include <VariantVisitors.hpp>
 #include <boost/algorithm/string/replace.hpp>
 #include <boost/container/flat_map.hpp>
 #include <boost/container/flat_set.hpp>
diff --git a/src/IntrusionSensorMain.cpp b/src/IntrusionSensorMain.cpp
index 3b63b6e..fb8510c 100644
--- a/src/IntrusionSensorMain.cpp
+++ b/src/IntrusionSensorMain.cpp
@@ -14,8 +14,9 @@
 // limitations under the License.
 */
 
-#include <ChassisIntrusionSensor.hpp>
-#include <Utils.hpp>
+#include "ChassisIntrusionSensor.hpp"
+#include "Utils.hpp"
+
 #include <boost/asio/io_service.hpp>
 #include <boost/container/flat_map.hpp>
 #include <phosphor-logging/lg2.hpp>
diff --git a/src/IpmbSDRSensor.cpp b/src/IpmbSDRSensor.cpp
index 1470340..73cfb43 100644
--- a/src/IpmbSDRSensor.cpp
+++ b/src/IpmbSDRSensor.cpp
@@ -1,4 +1,4 @@
-#include <IpmbSDRSensor.hpp>
+#include "IpmbSDRSensor.hpp"
 
 const constexpr char* ipmbService = "xyz.openbmc_project.Ipmi.Channel.Ipmb";
 const constexpr char* ipmbDbusPath = "/xyz/openbmc_project/Ipmi/Channel/Ipmb";
diff --git a/src/IpmbSDRSensor.hpp b/src/IpmbSDRSensor.hpp
new file mode 100644
index 0000000..4284e7a
--- /dev/null
+++ b/src/IpmbSDRSensor.hpp
@@ -0,0 +1,118 @@
+#pragma once
+
+#include <sensor.hpp>
+
+using IpmbMethodType =
+    std::tuple<int, uint8_t, uint8_t, uint8_t, uint8_t, std::vector<uint8_t>>;
+
+enum class SDRType
+{
+    sdrType01 = 1,
+    sdrType02 = 2,
+    sdrType03 = 3
+};
+
+namespace sdr
+{
+// IPMB Commands
+static constexpr uint8_t netfnStorageReq = 0x0a;
+static constexpr uint8_t cmdStorageGetSdrInfo = 0x20;
+static constexpr uint8_t cmdStorageReserveSdr = 0x22;
+static constexpr uint8_t cmdStorageGetSdr = 0x23;
+
+// Get SDR Commands
+static constexpr uint8_t sdrNxtRecLSB = 0;
+static constexpr uint8_t sdrNxtRecMSB = 1;
+static constexpr uint8_t perCountByte = 16;
+
+// Sensor Record Bytes
+static constexpr uint8_t sdrType = 5;
+static constexpr uint8_t dataLengthByte = 6;
+static constexpr uint8_t sdrSensorNum = 9;
+
+} // namespace sdr
+
+namespace sdrtype01
+{
+// Negative Handle Commands
+static constexpr uint8_t maxPosReadingMargin = 127;
+static constexpr uint8_t twosCompVal = 128;
+static constexpr double thermalConst = 256;
+
+static constexpr uint8_t sdrSensNoThres = 0;
+static constexpr uint8_t sensorCapability = 13;
+static constexpr uint8_t sdrNegHandle = 24;
+static constexpr uint8_t sdrUnitType = 25;
+static constexpr uint8_t sdrLinearByte = 27;
+
+// SDR Type 1 Thresholds Commands
+static constexpr uint8_t mDataByte = 28;
+static constexpr uint8_t mTolDataByte = 29;
+static constexpr uint8_t bDataByte = 30;
+static constexpr uint8_t bAcuDataByte = 31;
+static constexpr uint8_t rbExpDataByte = 33;
+static constexpr uint8_t upperCriticalThreshold = 43;
+static constexpr uint8_t lowerCriticalThreshold = 46;
+static constexpr uint8_t nameLengthByte = 53;
+
+} // namespace sdrtype01
+
+struct SensorInfo
+{
+    std::string sensorReadName;
+    uint8_t sensorUnit = 0;
+    double thresUpperCri = 0;
+    double thresLowerCri = 0;
+    uint8_t sensorNumber = 0;
+    uint8_t sensCap = 0;
+};
+
+struct SensorValConversion
+{
+    uint16_t mValue = 0;
+    double bValue = 0;
+    double expoVal = 0;
+    uint8_t negRead = 0;
+};
+
+inline std::map<int, std::vector<SensorInfo>> sensorRecord;
+inline std::map<int, std::map<uint8_t, SensorValConversion>> sensorValRecord;
+
+class IpmbSDRDevice : public std::enable_shared_from_this<IpmbSDRDevice>
+{
+  public:
+    IpmbSDRDevice(std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
+                  uint8_t cmdAddr);
+
+    uint8_t commandAddress = 0;
+    int hostIndex = 0;
+
+    std::shared_ptr<sdbusplus::asio::connection> conn;
+
+    std::vector<uint8_t> sdrData;
+    uint16_t validRecordCount = 1;
+    uint8_t iCnt = 0;
+    uint8_t nextRecordIDLSB = 0;
+    uint8_t nextRecordIDMSB = 0;
+
+    std::vector<uint8_t> sdrCommandData = {};
+
+    void getSDRRepositoryInfo();
+
+    void reserveSDRRepository(uint16_t recordCount);
+
+    void getSDRSensorData(uint16_t recordCount, uint8_t resrvIDLSB,
+                          uint8_t resrvIDMSB);
+
+    void handleSDRData(const std::vector<uint8_t>& data, uint16_t recordCount,
+                       uint8_t resrvIDLSB, uint8_t resrvIDMSB);
+
+    void checkSDRData(std::vector<uint8_t>& sdrDataBytes,
+                      uint8_t dataLength) const;
+
+    static void checkSDRType01Threshold(std::vector<uint8_t>& sdrDataBytes,
+                                        int busIndex, std::string tempName);
+
+    inline static double sensorValCalculation(uint16_t mValue, double bValue,
+                                              double expValue, double value);
+};
diff --git a/src/IpmbSensor.cpp b/src/IpmbSensor.cpp
index ba39214..9bc884e 100644
--- a/src/IpmbSensor.cpp
+++ b/src/IpmbSensor.cpp
@@ -14,10 +14,12 @@
 // limitations under the License.
 */
 
-#include <IpmbSDRSensor.hpp>
-#include <IpmbSensor.hpp>
-#include <Utils.hpp>
-#include <VariantVisitors.hpp>
+#include "IpmbSensor.hpp"
+
+#include "IpmbSDRSensor.hpp"
+#include "Utils.hpp"
+#include "VariantVisitors.hpp"
+
 #include <boost/container/flat_map.hpp>
 #include <sdbusplus/asio/connection.hpp>
 #include <sdbusplus/asio/object_server.hpp>
diff --git a/src/IpmbSensor.hpp b/src/IpmbSensor.hpp
new file mode 100644
index 0000000..6467b57
--- /dev/null
+++ b/src/IpmbSensor.hpp
@@ -0,0 +1,128 @@
+#pragma once
+#include <boost/asio/steady_timer.hpp>
+#include <boost/container/flat_map.hpp>
+#include <sensor.hpp>
+
+#include <chrono>
+#include <limits>
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+enum class IpmbType
+{
+    none,
+    meSensor,
+    PXE1410CVR,
+    IR38363VR,
+    ADM1278HSC,
+    mpsVR
+};
+
+enum class IpmbSubType
+{
+    none,
+    temp,
+    curr,
+    power,
+    volt,
+    util
+};
+
+enum class ReadingFormat
+{
+    byte0,
+    byte3,
+    elevenBit,
+    elevenBitShift,
+    linearElevenBit
+};
+
+namespace ipmi
+{
+namespace sensor
+{
+constexpr uint8_t netFn = 0x04;
+constexpr uint8_t getSensorReading = 0x2d;
+
+static bool isValid(const std::vector<uint8_t>& data)
+{
+    constexpr auto readingUnavailableBit = 5;
+
+    // Proper 'Get Sensor Reading' response has at least 4 bytes, including
+    // Completion Code. Our IPMB stack strips Completion Code from payload so we
+    // compare here against the rest of payload
+    if (data.size() < 3)
+    {
+        return false;
+    }
+
+    // Per IPMI 'Get Sensor Reading' specification
+    if ((data[1] & (1 << readingUnavailableBit)) != 0)
+    {
+        return false;
+    }
+
+    return true;
+}
+
+} // namespace sensor
+namespace me_bridge
+{
+constexpr uint8_t netFn = 0x2e;
+constexpr uint8_t sendRawPmbus = 0xd9;
+} // namespace me_bridge
+} // namespace ipmi
+
+using IpmbMethodType =
+    std::tuple<int, uint8_t, uint8_t, uint8_t, uint8_t, std::vector<uint8_t>>;
+
+struct IpmbSensor :
+    public Sensor,
+    public std::enable_shared_from_this<IpmbSensor>
+{
+    IpmbSensor(std::shared_ptr<sdbusplus::asio::connection>& conn,
+               boost::asio::io_service& io, const std::string& name,
+               const std::string& sensorConfiguration,
+               sdbusplus::asio::object_server& objectServer,
+               std::vector<thresholds::Threshold>&& thresholdData,
+               uint8_t deviceAddress, uint8_t hostSMbusIndex, float pollRate,
+               std::string& sensorTypeName);
+    ~IpmbSensor() override;
+
+    void checkThresholds(void) override;
+    void read(void);
+    void init(void);
+    std::string getSubTypeUnits(void) const;
+    void loadDefaults(void);
+    void runInitCmd(void);
+    bool processReading(const std::vector<uint8_t>& data, double& resp);
+    void parseConfigValues(const SensorBaseConfigMap& entry);
+    bool sensorClassType(const std::string& sensorClass);
+    void sensorSubType(const std::string& sensorTypeName);
+
+    IpmbType type = IpmbType::none;
+    IpmbSubType subType = IpmbSubType::none;
+    double scaleVal = 1.0;
+    double offsetVal = 0.0;
+    uint8_t commandAddress = 0;
+    uint8_t netfn = 0;
+    uint8_t command = 0;
+    uint8_t deviceAddress = 0;
+    uint8_t errorCount = 0;
+    uint8_t hostSMbusIndex = 0;
+    std::vector<uint8_t> commandData;
+    std::optional<uint8_t> initCommand;
+    std::vector<uint8_t> initData;
+    int sensorPollMs;
+
+    ReadingFormat readingFormat = ReadingFormat::byte0;
+
+  private:
+    void sendIpmbRequest(void);
+    sdbusplus::asio::object_server& objectServer;
+    boost::asio::steady_timer waitTimer;
+    void ipmbRequestCompletionCb(const boost::system::error_code& ec,
+                                 const IpmbMethodType& response);
+};
diff --git a/src/MCUTempSensor.cpp b/src/MCUTempSensor.cpp
index f3ada3c..a5d964d 100644
--- a/src/MCUTempSensor.cpp
+++ b/src/MCUTempSensor.cpp
@@ -13,9 +13,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 */
-#include <MCUTempSensor.hpp>
-#include <Utils.hpp>
-#include <VariantVisitors.hpp>
+
+#include "MCUTempSensor.hpp"
+
+#include "Utils.hpp"
+#include "VariantVisitors.hpp"
+
 #include <boost/container/flat_map.hpp>
 #include <sdbusplus/asio/connection.hpp>
 #include <sdbusplus/asio/object_server.hpp>
diff --git a/src/MCUTempSensor.hpp b/src/MCUTempSensor.hpp
new file mode 100644
index 0000000..c4c2406
--- /dev/null
+++ b/src/MCUTempSensor.hpp
@@ -0,0 +1,34 @@
+#pragma once
+#include <boost/asio/steady_timer.hpp>
+#include <boost/container/flat_map.hpp>
+#include <sensor.hpp>
+
+#include <chrono>
+#include <limits>
+#include <memory>
+#include <string>
+#include <vector>
+
+struct MCUTempSensor : public Sensor
+{
+    MCUTempSensor(std::shared_ptr<sdbusplus::asio::connection>& conn,
+                  boost::asio::io_service& io, const std::string& name,
+                  const std::string& sensorConfiguration,
+                  sdbusplus::asio::object_server& objectServer,
+                  std::vector<thresholds::Threshold>&& thresholdData,
+                  uint8_t busId, uint8_t mcuAddress, uint8_t tempReg);
+    ~MCUTempSensor() override;
+
+    void checkThresholds(void) override;
+    void read(void);
+    void init(void);
+
+    uint8_t busId;
+    uint8_t mcuAddress;
+    uint8_t tempReg;
+
+  private:
+    int getMCURegsInfoWord(uint8_t regs, int16_t* pu16data) const;
+    sdbusplus::asio::object_server& objectServer;
+    boost::asio::steady_timer waitTimer;
+};
diff --git a/src/NVMeBasicContext.hpp b/src/NVMeBasicContext.hpp
new file mode 100644
index 0000000..b17fc05
--- /dev/null
+++ b/src/NVMeBasicContext.hpp
@@ -0,0 +1,48 @@
+#pragma once
+
+#include "NVMeContext.hpp"
+
+#include <boost/asio/io_service.hpp>
+#include <boost/asio/posix/stream_descriptor.hpp>
+
+#include <thread>
+
+class NVMeBasicContext : public NVMeContext
+{
+  public:
+    NVMeBasicContext(boost::asio::io_service& io, int rootBus);
+    ~NVMeBasicContext() override = default;
+    void pollNVMeDevices() override;
+    void readAndProcessNVMeSensor() override;
+    void processResponse(std::shared_ptr<NVMeSensor>& sensor, void* msg,
+                         size_t len) override;
+
+  private:
+    NVMeBasicContext(boost::asio::io_service& io, int rootBus, int cmdOut,
+                     int streamIn, int streamOut, int cmdIn);
+    boost::asio::io_service& io;
+
+    // The IO thread must be destructed after the stream descriptors, so
+    // initialise it first. http://eel.is/c++draft/class.base.init#note-6
+    //
+    // Providing a stop-source to the thread execution function isn't
+    // particularly useful as it will spend most of its time blocked in a system
+    // call - ioctl() for the actual device communication, or read() and write()
+    // on the pipes associated with reqStream and respStream. Rather than trying
+    // to force a stop, rely on read()/write() failures from closed pipes to
+    // coerce it to exit and thus allow completion of the join().
+    std::jthread thread;
+
+    // Destruction of the stream descriptors has the effect of issuing cancel(),
+    // destroying the closure of the callback where we might be carrying
+    // weak_ptrs to `this`.
+    // https://www.boost.org/doc/libs/1_79_0/doc/html/boost_asio/reference/posix__basic_descriptor/_basic_descriptor.html
+    boost::asio::posix::stream_descriptor reqStream;
+    boost::asio::posix::stream_descriptor respStream;
+
+    enum
+    {
+        NVME_MI_BASIC_SFLGS_DRIVE_NOT_READY = 0x40,
+        NVME_MI_BASIC_SFLGS_DRIVE_FUNCTIONAL = 0x20,
+    };
+};
diff --git a/src/NVMeContext.hpp b/src/NVMeContext.hpp
new file mode 100644
index 0000000..5be03e0
--- /dev/null
+++ b/src/NVMeContext.hpp
@@ -0,0 +1,109 @@
+#pragma once
+
+#include "NVMeSensor.hpp"
+
+#include <boost/asio/io_service.hpp>
+#include <boost/asio/steady_timer.hpp>
+
+#include <memory>
+#include <stdexcept>
+
+class NVMeContext : public std::enable_shared_from_this<NVMeContext>
+{
+  public:
+    NVMeContext(boost::asio::io_service& io, int rootBus) :
+        scanTimer(io), rootBus(rootBus), pollCursor(sensors.end())
+    {
+        if (rootBus < 0)
+        {
+            throw std::invalid_argument(
+                "Invalid root bus: Bus ID must not be negative");
+        }
+    }
+
+    virtual ~NVMeContext()
+    {
+        close();
+    }
+
+    void addSensor(const std::shared_ptr<NVMeSensor>& sensor)
+    {
+        sensors.emplace_back(sensor);
+    }
+
+    std::optional<std::shared_ptr<NVMeSensor>>
+        getSensorAtPath(const std::string& path)
+    {
+        for (auto& sensor : sensors)
+        {
+            if (sensor->configurationPath == path)
+            {
+                return sensor;
+            }
+        }
+
+        return std::nullopt;
+    }
+
+    // Post-condition: The sensor list does not contain the provided sensor
+    // Post-condition: pollCursor is a valid iterator for the sensor list
+    void removeSensor(const std::shared_ptr<NVMeSensor>& sensor)
+    {
+        // Locate the sensor that we're removing in the sensor list
+        auto found = std::find(sensors.begin(), sensors.end(), sensor);
+
+        // If we failed to find the sensor in the list the post-condition is
+        // already satisfied
+        if (found == sensors.end())
+        {
+            return;
+        }
+
+        // We've found the sensor in the list
+
+        // If we're not actively polling the sensor list, then remove the sensor
+        if (pollCursor == sensors.end())
+        {
+            sensors.erase(found);
+            return;
+        }
+
+        // We're actively polling the sensor list
+
+        // If we're not polling the specific sensor that has been removed, then
+        // remove the sensor
+        if (*pollCursor != *found)
+        {
+            sensors.erase(found);
+            return;
+        }
+
+        // We're polling the sensor that is being removed
+
+        // Remove the sensor and update the poll cursor so the cursor remains
+        // valid
+        pollCursor = sensors.erase(found);
+    }
+
+    virtual void close()
+    {
+        scanTimer.cancel();
+    }
+
+    virtual void pollNVMeDevices() = 0;
+
+    virtual void readAndProcessNVMeSensor() = 0;
+
+    virtual void processResponse(std::shared_ptr<NVMeSensor>& sensor, void* msg,
+                                 size_t len) = 0;
+
+  protected:
+    boost::asio::steady_timer scanTimer;
+    int rootBus; // Root bus for this drive
+    std::list<std::shared_ptr<NVMeSensor>> sensors;
+    std::list<std::shared_ptr<NVMeSensor>>::iterator pollCursor;
+};
+
+using NVMEMap = boost::container::flat_map<int, std::shared_ptr<NVMeContext>>;
+
+NVMEMap& getNVMEMap(void);
diff --git a/src/NVMeSensor.cpp b/src/NVMeSensor.cpp
index d32d166..4301158 100644
--- a/src/NVMeSensor.cpp
+++ b/src/NVMeSensor.cpp
@@ -14,7 +14,7 @@
 // limitations under the License.
 */
 
-#include <NVMeSensor.hpp>
+#include "NVMeSensor.hpp"
 
 #include <iostream>
 
diff --git a/src/NVMeSensor.hpp b/src/NVMeSensor.hpp
new file mode 100644
index 0000000..4e0000f
--- /dev/null
+++ b/src/NVMeSensor.hpp
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <boost/asio/io_service.hpp>
+#include <sensor.hpp>
+
+class NVMeSensor : public Sensor
+{
+  public:
+    static constexpr const char* sensorType = "NVME1000";
+
+    NVMeSensor(sdbusplus::asio::object_server& objectServer,
+               boost::asio::io_service& io,
+               std::shared_ptr<sdbusplus::asio::connection>& conn,
+               const std::string& sensorName,
+               std::vector<thresholds::Threshold>&& thresholds,
+               const std::string& sensorConfiguration, int busNumber);
+    ~NVMeSensor() override;
+
+    NVMeSensor& operator=(const NVMeSensor& other) = delete;
+
+    bool sample();
+
+    int bus;
+
+  private:
+    const unsigned int scanDelayTicks = 5 * 60;
+    sdbusplus::asio::object_server& objServer;
+    unsigned int scanDelay{0};
+
+    void checkThresholds(void) override;
+};
diff --git a/src/NVMeSensorMain.cpp b/src/NVMeSensorMain.cpp
index 96a3017..9ad0706 100644
--- a/src/NVMeSensorMain.cpp
+++ b/src/NVMeSensorMain.cpp
@@ -14,9 +14,10 @@
 // limitations under the License.
 */
 
-#include <NVMeBasicContext.hpp>
-#include <NVMeContext.hpp>
-#include <NVMeSensor.hpp>
+#include "NVMeBasicContext.hpp"
+#include "NVMeContext.hpp"
+#include "NVMeSensor.hpp"
+
 #include <boost/asio/steady_timer.hpp>
 
 #include <optional>
diff --git a/src/PSUEvent.cpp b/src/PSUEvent.cpp
index 27bb8b1..ce8301e 100644
--- a/src/PSUEvent.cpp
+++ b/src/PSUEvent.cpp
@@ -14,8 +14,10 @@
 // limitations under the License.
 */
 
-#include <PSUEvent.hpp>
-#include <SensorPaths.hpp>
+#include "PSUEvent.hpp"
+
+#include "SensorPaths.hpp"
+
 #include <boost/asio/io_service.hpp>
 #include <boost/asio/read_until.hpp>
 #include <boost/container/flat_map.hpp>
diff --git a/src/PSUEvent.hpp b/src/PSUEvent.hpp
new file mode 100644
index 0000000..12fc2ef
--- /dev/null
+++ b/src/PSUEvent.hpp
@@ -0,0 +1,102 @@
+/*
+// Copyright (c) 2019 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#pragma once
+
+#include "Utils.hpp"
+
+#include <boost/asio/io_service.hpp>
+#include <boost/asio/random_access_file.hpp>
+#include <boost/asio/steady_timer.hpp>
+#include <boost/container/flat_map.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+#include <array>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+class PSUSubEvent : public std::enable_shared_from_this<PSUSubEvent>
+{
+  public:
+    PSUSubEvent(std::shared_ptr<sdbusplus::asio::dbus_interface> eventInterface,
+                const std::string& path,
+                std::shared_ptr<sdbusplus::asio::connection>& conn,
+                boost::asio::io_service& io, const PowerState& powerState,
+                const std::string& groupEventName, const std::string& eventName,
+                std::shared_ptr<std::set<std::string>> asserts,
+                std::shared_ptr<std::set<std::string>> combineEvent,
+                std::shared_ptr<bool> state, const std::string& psuName,
+                double pollRate);
+    ~PSUSubEvent();
+
+    std::shared_ptr<sdbusplus::asio::dbus_interface> eventInterface;
+    std::shared_ptr<std::set<std::string>> asserts;
+    std::shared_ptr<std::set<std::string>> combineEvent;
+    std::shared_ptr<bool> assertState;
+    void setupRead(void);
+
+  private:
+    int value = 0;
+    size_t errCount{0};
+    std::string path;
+    std::string eventName;
+
+    PowerState readState;
+    boost::asio::steady_timer waitTimer;
+    std::shared_ptr<std::array<char, 128>> buffer;
+    void restartRead();
+    void handleResponse(const boost::system::error_code& err,
+                        size_t bytesTransferred);
+    void updateValue(const int& newValue);
+    boost::asio::random_access_file inputDev;
+    std::string psuName;
+    std::string groupEventName;
+    std::string fanName;
+    std::string assertMessage;
+    std::string deassertMessage;
+    std::shared_ptr<sdbusplus::asio::connection> systemBus;
+    unsigned int eventPollMs = defaultEventPollMs;
+    static constexpr unsigned int defaultEventPollMs = 1000;
+    static constexpr size_t warnAfterErrorCount = 10;
+};
+
+class PSUCombineEvent
+{
+  public:
+    PSUCombineEvent(
+        sdbusplus::asio::object_server& objectServer,
+        std::shared_ptr<sdbusplus::asio::connection>& conn,
+        boost::asio::io_service& io, const std::string& psuName,
+        const PowerState& powerState,
+        boost::container::flat_map<std::string, std::vector<std::string>>&
+            eventPathList,
+        boost::container::flat_map<
+            std::string,
+            boost::container::flat_map<std::string, std::vector<std::string>>>&
+            groupEventPathList,
+        const std::string& combineEventName, double pollRate);
+    ~PSUCombineEvent();
+
+    sdbusplus::asio::object_server& objServer;
+    std::shared_ptr<sdbusplus::asio::dbus_interface> eventInterface;
+    boost::container::flat_map<std::string,
+                               std::vector<std::shared_ptr<PSUSubEvent>>>
+        events;
+    std::vector<std::shared_ptr<std::set<std::string>>> asserts;
+    std::vector<std::shared_ptr<bool>> states;
+};
diff --git a/src/PSUSensor.cpp b/src/PSUSensor.cpp
index 6e2934f..986fe59 100644
--- a/src/PSUSensor.cpp
+++ b/src/PSUSensor.cpp
@@ -14,9 +14,10 @@
 // limitations under the License.
 */
 
+#include "PSUSensor.hpp"
+
 #include <unistd.h>
 
-#include <PSUSensor.hpp>
 #include <boost/asio/random_access_file.hpp>
 #include <boost/asio/read_until.hpp>
 #include <sdbusplus/asio/connection.hpp>
diff --git a/src/PSUSensor.hpp b/src/PSUSensor.hpp
new file mode 100644
index 0000000..379b9d9
--- /dev/null
+++ b/src/PSUSensor.hpp
@@ -0,0 +1,71 @@
+#pragma once
+
+#include "PwmSensor.hpp"
+#include "Thresholds.hpp"
+#include "sensor.hpp"
+
+#include <boost/asio/random_access_file.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+#include <array>
+#include <memory>
+#include <string>
+#include <utility>
+
+class PSUSensor : public Sensor, public std::enable_shared_from_this<PSUSensor>
+{
+  public:
+    PSUSensor(const std::string& path, const std::string& objectType,
+              sdbusplus::asio::object_server& objectServer,
+              std::shared_ptr<sdbusplus::asio::connection>& conn,
+              boost::asio::io_service& io, const std::string& sensorName,
+              std::vector<thresholds::Threshold>&& thresholds,
+              const std::string& sensorConfiguration,
+              const PowerState& powerState, const std::string& sensorUnits,
+              unsigned int factor, double max, double min, double offset,
+              const std::string& label, size_t tSize, double pollRate);
+    ~PSUSensor() override;
+    void setupRead(void);
+
+  private:
+    // Note, this buffer is a shared_ptr because during a read, its lifetime
+    // might have to outlive the PSUSensor class if the object gets destroyed
+    // while in the middle of a read operation
+    std::shared_ptr<std::array<char, 128>> buffer;
+    sdbusplus::asio::object_server& objServer;
+    boost::asio::random_access_file inputDev;
+    boost::asio::steady_timer waitTimer;
+    std::string path;
+    unsigned int sensorFactor;
+    double sensorOffset;
+    thresholds::ThresholdTimer thresholdTimer;
+    void restartRead();
+    void handleResponse(const boost::system::error_code& err, size_t bytesRead);
+    void checkThresholds(void) override;
+    unsigned int sensorPollMs = defaultSensorPollMs;
+
+    static constexpr size_t warnAfterErrorCount = 10;
+
+  public:
+    static constexpr double defaultSensorPoll = 1.0;
+    static constexpr unsigned int defaultSensorPollMs =
+        static_cast<unsigned int>(defaultSensorPoll * 1000);
+};
+
+class PSUProperty
+{
+  public:
+    PSUProperty(std::string name, double max, double min, unsigned int factor,
+                double offset) :
+        labelTypeName(std::move(name)),
+        maxReading(max), minReading(min), sensorScaleFactor(factor),
+        sensorOffset(offset)
+    {}
+    ~PSUProperty() = default;
+
+    std::string labelTypeName;
+    double maxReading;
+    double minReading;
+    unsigned int sensorScaleFactor;
+    double sensorOffset;
+};
diff --git a/src/PSUSensorMain.cpp b/src/PSUSensorMain.cpp
index 38f92a7..ad01a3e 100644
--- a/src/PSUSensorMain.cpp
+++ b/src/PSUSensorMain.cpp
@@ -14,9 +14,10 @@
 // limitations under the License.
 */
 
-#include <PSUEvent.hpp>
-#include <PSUSensor.hpp>
-#include <Utils.hpp>
+#include "PSUEvent.hpp"
+#include "PSUSensor.hpp"
+#include "Utils.hpp"
+
 #include <boost/algorithm/string/replace.hpp>
 #include <boost/container/flat_map.hpp>
 #include <boost/container/flat_set.hpp>
diff --git a/src/PwmSensor.cpp b/src/PwmSensor.cpp
index 0ee8c88..ad54dfe 100644
--- a/src/PwmSensor.cpp
+++ b/src/PwmSensor.cpp
@@ -13,8 +13,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 */
-#include <PwmSensor.hpp>
-#include <Utils.hpp>
+
+#include "PwmSensor.hpp"
+
+#include "Utils.hpp"
+
 #include <sdbusplus/asio/object_server.hpp>
 
 #include <fstream>
diff --git a/src/PwmSensor.hpp b/src/PwmSensor.hpp
new file mode 100644
index 0000000..f70079b
--- /dev/null
+++ b/src/PwmSensor.hpp
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "sensor.hpp"
+
+#include <sdbusplus/asio/object_server.hpp>
+
+#include <memory>
+#include <string>
+
+class PwmSensor
+{
+  public:
+    PwmSensor(const std::string& name, const std::string& sysPath,
+              std::shared_ptr<sdbusplus::asio::connection>& conn,
+              sdbusplus::asio::object_server& objectServer,
+              const std::string& sensorConfiguration,
+              const std::string& sensorType, bool isValueMutable = false);
+    ~PwmSensor();
+
+  private:
+    std::string sysPath;
+    sdbusplus::asio::object_server& objectServer;
+    std::string name;
+    std::shared_ptr<sdbusplus::asio::dbus_interface> sensorInterface;
+    std::shared_ptr<sdbusplus::asio::dbus_interface> controlInterface;
+    std::shared_ptr<sdbusplus::asio::dbus_interface> association;
+    std::shared_ptr<sdbusplus::asio::dbus_interface> valueMutabilityInterface;
+    double pwmMax;
+    void setValue(uint32_t value);
+    uint32_t getValue(bool errThrow = true);
+};
diff --git a/src/SensorPaths.cpp b/src/SensorPaths.cpp
index 7eb8d09..bd8f173 100644
--- a/src/SensorPaths.cpp
+++ b/src/SensorPaths.cpp
@@ -1,4 +1,4 @@
-#include <SensorPaths.hpp>
+#include "SensorPaths.hpp"
 
 #include <cstring>
 #include <regex>
diff --git a/src/SensorPaths.hpp b/src/SensorPaths.hpp
new file mode 100644
index 0000000..e2475fa
--- /dev/null
+++ b/src/SensorPaths.hpp
@@ -0,0 +1,35 @@
+#pragma once
+
+#include <cstring>
+#include <regex>
+#include <string>
+
+namespace sensor_paths
+{
+
+// This is an allowlist of the units a sensor can measure. Should be in sync
+// with
+// phosphor-dbus-interfaces/blob/master/yaml/xyz/openbmc_project/Sensor/Value.interface.yaml#L38
+
+constexpr const char* unitDegreesC =
+    "xyz.openbmc_project.Sensor.Value.Unit.DegreesC";
+constexpr const char* unitRPMs = "xyz.openbmc_project.Sensor.Value.Unit.RPMS";
+constexpr const char* unitVolts = "xyz.openbmc_project.Sensor.Value.Unit.Volts";
+constexpr const char* unitMeters =
+    "xyz.openbmc_project.Sensor.Value.Unit.Meters";
+constexpr const char* unitAmperes =
+    "xyz.openbmc_project.Sensor.Value.Unit.Amperes";
+constexpr const char* unitWatts = "xyz.openbmc_project.Sensor.Value.Unit.Watts";
+constexpr const char* unitJoules =
+    "xyz.openbmc_project.Sensor.Value.Unit.Joules";
+constexpr const char* unitPercent =
+    "xyz.openbmc_project.Sensor.Value.Unit.Percent";
+constexpr const char* unitCFM = "xyz.openbmc_project.Sensor.Value.Unit.CFM";
+constexpr const char* unitPascals =
+    "xyz.openbmc_project.Sensor.Value.Unit.Pascals";
+
+std::string getPathForUnits(const std::string& units);
+
+std::string escapePathForDbus(const std::string& name);
+
+} // namespace sensor_paths
diff --git a/src/TachSensor.cpp b/src/TachSensor.cpp
index 35185cd..9a1ff7c 100644
--- a/src/TachSensor.cpp
+++ b/src/TachSensor.cpp
@@ -14,10 +14,12 @@
 // limitations under the License.
 */
 
+#include "TachSensor.hpp"
+
+#include "Utils.hpp"
+
 #include <unistd.h>
 
-#include <TachSensor.hpp>
-#include <Utils.hpp>
 #include <boost/asio/read_until.hpp>
 #include <gpiod.hpp>
 #include <sdbusplus/asio/connection.hpp>
diff --git a/src/TachSensor.hpp b/src/TachSensor.hpp
new file mode 100644
index 0000000..92c5202
--- /dev/null
+++ b/src/TachSensor.hpp
@@ -0,0 +1,126 @@
+#pragma once
+
+#include "Thresholds.hpp"
+#include "sensor.hpp"
+
+#include <boost/asio/random_access_file.hpp>
+#include <boost/container/flat_map.hpp>
+#include <boost/container/flat_set.hpp>
+#include <gpiod.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
+
+class PresenceSensor
+{
+  public:
+    PresenceSensor(const std::string& gpioName, bool inverted,
+                   boost::asio::io_service& io, const std::string& name);
+    ~PresenceSensor();
+
+    void monitorPresence(void);
+    void read(void);
+    bool getValue(void) const;
+
+  private:
+    bool status = true;
+    gpiod::line gpioLine;
+    boost::asio::posix::stream_descriptor gpioFd;
+    std::string name;
+};
+
+namespace redundancy
+{
+constexpr const char* full = "Full";
+constexpr const char* degraded = "Degraded";
+constexpr const char* failed = "Failed";
+} // namespace redundancy
+
+class RedundancySensor
+{
+  public:
+    RedundancySensor(size_t count, const std::vector<std::string>& children,
+                     sdbusplus::asio::object_server& objectServer,
+                     const std::string& sensorConfiguration);
+    ~RedundancySensor();
+
+    void update(const std::string& name, bool failed);
+
+  private:
+    size_t count;
+    std::string state = redundancy::full;
+    std::shared_ptr<sdbusplus::asio::dbus_interface> iface;
+    std::shared_ptr<sdbusplus::asio::dbus_interface> association;
+    sdbusplus::asio::object_server& objectServer;
+    boost::container::flat_map<std::string, bool> statuses;
+};
+
+class TachSensor :
+    public Sensor,
+    public std::enable_shared_from_this<TachSensor>
+{
+  public:
+    TachSensor(const std::string& path, const std::string& objectType,
+               sdbusplus::asio::object_server& objectServer,
+               std::shared_ptr<sdbusplus::asio::connection>& conn,
+               std::unique_ptr<PresenceSensor>&& presence,
+               std::optional<RedundancySensor>* redundancy,
+               boost::asio::io_service& io, const std::string& fanName,
+               std::vector<thresholds::Threshold>&& thresholds,
+               const std::string& sensorConfiguration,
+               const std::pair<double, double>& limits,
+               const PowerState& powerState,
+               const std::optional<std::string>& led);
+    ~TachSensor() override;
+    void setupRead();
+
+  private:
+    // Ordering is important here; readBuf is first so that it's not destroyed
+    // while async operations from other member fields might still be using it.
+    std::array<char, 128> readBuf{};
+    sdbusplus::asio::object_server& objServer;
+    std::optional<RedundancySensor>* redundancy;
+    std::unique_ptr<PresenceSensor> presence;
+    std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface;
+    std::shared_ptr<sdbusplus::asio::dbus_interface> itemAssoc;
+    boost::asio::random_access_file inputDev;
+    boost::asio::steady_timer waitTimer;
+    std::string path;
+    std::optional<std::string> led;
+    bool ledState = false;
+
+    void handleResponse(const boost::system::error_code& err, size_t bytesRead);
+    void restartRead(size_t pollTime);
+    void checkThresholds(void) override;
+};
+
+inline void logFanInserted(const std::string& device)
+{
+    const auto* msg = "OpenBMC.0.1.FanInserted";
+    lg2::error("Fan Inserted", "REDFISH_MESSAGE_ID", msg,
+               "REDFISH_MESSAGE_ARGS", device);
+}
+
+inline void logFanRemoved(const std::string& device)
+{
+    const auto* msg = "OpenBMC.0.1.FanRemoved";
+    lg2::error("Fan Removed", "REDFISH_MESSAGE_ID", msg, "REDFISH_MESSAGE_ARGS",
+               device);
+}
+
+inline void logFanRedundancyLost(void)
+{
+    const auto* msg = "OpenBMC.0.1.FanRedundancyLost";
+    lg2::error("Fan Inserted", "REDFISH_MESSAGE_ID", msg);
+}
+
+inline void logFanRedundancyRestored(void)
+{
+    const auto* msg = "OpenBMC.0.1.FanRedundancyRegained";
+    lg2::error("Fan Removed", "REDFISH_MESSAGE_ID", msg);
+}
diff --git a/src/Thresholds.cpp b/src/Thresholds.cpp
index ee0456d..aa73063 100644
--- a/src/Thresholds.cpp
+++ b/src/Thresholds.cpp
@@ -1,8 +1,10 @@
-#include <Thresholds.hpp>
-#include <VariantVisitors.hpp>
+#include "Thresholds.hpp"
+
+#include "VariantVisitors.hpp"
+#include "sensor.hpp"
+
 #include <boost/algorithm/string/replace.hpp>
 #include <boost/container/flat_map.hpp>
-#include <sensor.hpp>
 
 #include <array>
 #include <cmath>
diff --git a/src/Thresholds.hpp b/src/Thresholds.hpp
new file mode 100644
index 0000000..cd89efc
--- /dev/null
+++ b/src/Thresholds.hpp
@@ -0,0 +1,155 @@
+#pragma once
+
+#include "Utils.hpp"
+
+#include <boost/asio/io_service.hpp>
+#include <boost/asio/steady_timer.hpp>
+#include <nlohmann/json.hpp>
+
+#include <list>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+struct Sensor;
+namespace thresholds
+{
+enum class Level
+{
+    WARNING,
+    CRITICAL,
+    PERFORMANCELOSS,
+    SOFTSHUTDOWN,
+    HARDSHUTDOWN,
+    ERROR
+};
+enum class Direction
+{
+    HIGH,
+    LOW,
+    ERROR
+};
+struct Threshold
+{
+    Threshold(
+        const Level& lev, const Direction& dir, const double& val,
+        const double hysteresis = std::numeric_limits<double>::quiet_NaN(),
+        bool write = true) :
+        level(lev),
+        direction(dir), value(val), hysteresis(hysteresis), writeable(write)
+    {}
+    Level level;
+    Direction direction;
+    double value;
+    double hysteresis;
+    bool writeable;
+
+    bool operator==(const Threshold& rhs) const
+    {
+        return (level == rhs.level && direction == rhs.direction &&
+                value == rhs.value);
+    }
+};
+
+void assertThresholds(Sensor* sensor, double assertValue,
+                      thresholds::Level level, thresholds::Direction direction,
+                      bool assert);
+
+struct TimerUsed
+{
+    bool used;
+    Level level;
+    Direction direction;
+    bool assert;
+};
+
+using TimerPair = std::pair<struct TimerUsed, boost::asio::steady_timer>;
+
+struct ThresholdTimer
+{
+
+    explicit ThresholdTimer(boost::asio::io_service& ioService) : io(ioService)
+    {}
+
+    bool hasActiveTimer(const Threshold& threshold, bool assert)
+    {
+        for (TimerPair& timer : timers)
+        {
+            if (timer.first.used)
+            {
+                if ((timer.first.level == threshold.level) &&
+                    (timer.first.direction == threshold.direction) &&
+                    (timer.first.assert == assert))
+                {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    void stopTimer(const Threshold& threshold, bool assert)
+    {
+        struct TimerUsed timerUsed = {};
+        for (TimerPair& timer : timers)
+        {
+            timerUsed = timer.first;
+            if (timerUsed.used)
+            {
+                if ((timerUsed.level == threshold.level) &&
+                    (timerUsed.direction == threshold.direction) &&
+                    (timerUsed.assert == assert))
+                {
+                    timer.second.cancel();
+                }
+            }
+        }
+    }
+
+    void startTimer(const std::weak_ptr<Sensor>& weakSensor,
+                    const Threshold& threshold, bool assert,
+                    double assertValue);
+
+    boost::asio::io_service& io;
+    std::list<TimerPair> timers;
+};
+
+bool parseThresholdsFromConfig(
+    const SensorData& sensorData,
+    std::vector<thresholds::Threshold>& thresholdVector,
+    const std::string* matchLabel = nullptr, const int* sensorIndex = nullptr);
+
+bool parseThresholdsFromAttr(
+    std::vector<thresholds::Threshold>& thresholdVector,
+    const std::string& inputPath, const double& scaleFactor,
+    const double& offset = 0);
+
+struct ThresholdDefinition
+{
+    Level level;
+    uint8_t sevOrder;
+    const char* levelName;
+};
+
+constexpr static std::array<thresholds::ThresholdDefinition, 5> thresProp = {
+    {{Level::WARNING, 0, "Warning"},
+     {Level::CRITICAL, 1, "Critical"},
+     {Level::PERFORMANCELOSS, 2, "PerformanceLoss"},
+     {Level::SOFTSHUTDOWN, 3, "SoftShutdown"},
+     {Level::HARDSHUTDOWN, 4, "HardShutdown"}}};
+
+std::string getInterface(Level level);
+
+void persistThreshold(const std::string& path, const std::string& baseInterface,
+                      const thresholds::Threshold& threshold,
+                      std::shared_ptr<sdbusplus::asio::connection>& conn,
+                      size_t thresholdCount, const std::string& label);
+
+void updateThresholds(Sensor* sensor);
+// returns false if a critical threshold has been crossed, true otherwise
+bool checkThresholds(Sensor* sensor);
+void checkThresholdsPowerDelay(const std::weak_ptr<Sensor>& weakSensor,
+                               ThresholdTimer& thresholdTimer);
+
+} // namespace thresholds
diff --git a/src/Utils.cpp b/src/Utils.cpp
index eb4620d..56428bc 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -14,10 +14,12 @@
 // limitations under the License.
 */
 
+#include "Utils.hpp"
+
 #include "dbus-sensor_config.h"
 
-#include <DeviceMgmt.hpp>
-#include <Utils.hpp>
+#include "DeviceMgmt.hpp"
+
 #include <boost/container/flat_map.hpp>
 #include <sdbusplus/asio/connection.hpp>
 #include <sdbusplus/asio/object_server.hpp>
diff --git a/src/Utils.hpp b/src/Utils.hpp
new file mode 100644
index 0000000..444030c
--- /dev/null
+++ b/src/Utils.hpp
@@ -0,0 +1,390 @@
+#pragma once
+
+#include "VariantVisitors.hpp"
+
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/asio/steady_timer.hpp>
+#include <boost/container/flat_map.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/message/types.hpp>
+
+#include <filesystem>
+#include <functional>
+#include <iostream>
+#include <memory>
+#include <optional>
+#include <regex>
+#include <span>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <variant>
+#include <vector>
+
+const constexpr char* jsonStore = "/var/configuration/flattened.json";
+const constexpr char* inventoryPath = "/xyz/openbmc_project/inventory";
+const constexpr char* entityManagerName = "xyz.openbmc_project.EntityManager";
+
+constexpr const char* cpuInventoryPath =
+    "/xyz/openbmc_project/inventory/system/chassis/motherboard";
+const std::regex illegalDbusRegex("[^A-Za-z0-9_]");
+
+using BasicVariantType =
+    std::variant<std::vector<std::string>, std::string, int64_t, uint64_t,
+                 double, int32_t, uint32_t, int16_t, uint16_t, uint8_t, bool>;
+using SensorBaseConfigMap =
+    boost::container::flat_map<std::string, BasicVariantType>;
+using SensorBaseConfiguration = std::pair<std::string, SensorBaseConfigMap>;
+using SensorData = boost::container::flat_map<std::string, SensorBaseConfigMap>;
+using ManagedObjectType =
+    boost::container::flat_map<sdbusplus::message::object_path, SensorData>;
+
+using GetSubTreeType = std::vector<
+    std::pair<std::string,
+              std::vector<std::pair<std::string, std::vector<std::string>>>>>;
+using Association = std::tuple<std::string, std::string, std::string>;
+
+inline std::string escapeName(const std::string& sensorName)
+{
+    return boost::replace_all_copy(sensorName, " ", "_");
+}
+
+enum class PowerState
+{
+    on,
+    biosPost,
+    always,
+    chassisOn
+};
+
+std::optional<std::string> openAndRead(const std::string& hwmonFile);
+std::optional<std::string>
+    getFullHwmonFilePath(const std::string& directory,
+                         const std::string& hwmonBaseName,
+                         const std::set<std::string>& permitSet);
+std::set<std::string> getPermitSet(const SensorBaseConfigMap& config);
+bool findFiles(const std::filesystem::path& dirPath,
+               std::string_view matchString,
+               std::vector<std::filesystem::path>& foundPaths,
+               int symlinkDepth = 1);
+bool isPowerOn(void);
+bool hasBiosPost(void);
+bool isChassisOn(void);
+void setupPowerMatchCallback(
+    const std::shared_ptr<sdbusplus::asio::connection>& conn,
+    std::function<void(PowerState type, bool state)>&& callback);
+void setupPowerMatch(const std::shared_ptr<sdbusplus::asio::connection>& conn);
+bool getSensorConfiguration(
+    const std::string& type,
+    const std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
+    ManagedObjectType& resp, bool useCache);
+
+bool getSensorConfiguration(
+    const std::string& type,
+    const std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
+    ManagedObjectType& resp);
+
+void createAssociation(
+    std::shared_ptr<sdbusplus::asio::dbus_interface>& association,
+    const std::string& path);
+
+// replaces limits if MinReading and MaxReading are found.
+void findLimits(std::pair<double, double>& limits,
+                const SensorBaseConfiguration* data);
+
+bool readingStateGood(const PowerState& powerState);
+
+constexpr const char* configInterfacePrefix =
+    "xyz.openbmc_project.Configuration.";
+
+inline std::string configInterfaceName(const std::string& type)
+{
+    return std::string(configInterfacePrefix) + type;
+}
+
+namespace mapper
+{
+constexpr const char* busName = "xyz.openbmc_project.ObjectMapper";
+constexpr const char* path = "/xyz/openbmc_project/object_mapper";
+constexpr const char* interface = "xyz.openbmc_project.ObjectMapper";
+constexpr const char* subtree = "GetSubTree";
+} // namespace mapper
+
+namespace properties
+{
+constexpr const char* interface = "org.freedesktop.DBus.Properties";
+constexpr const char* get = "Get";
+constexpr const char* set = "Set";
+} // namespace properties
+
+namespace power
+{
+const static constexpr char* busname = "xyz.openbmc_project.State.Host";
+const static constexpr char* interface = "xyz.openbmc_project.State.Host";
+const static constexpr char* path = "/xyz/openbmc_project/state/host0";
+const static constexpr char* property = "CurrentHostState";
+} // namespace power
+
+namespace chassis
+{
+const static constexpr char* busname = "xyz.openbmc_project.State.Chassis";
+const static constexpr char* interface = "xyz.openbmc_project.State.Chassis";
+const static constexpr char* path = "/xyz/openbmc_project/state/chassis0";
+const static constexpr char* property = "CurrentPowerState";
+const static constexpr char* sOn = "On";
+} // namespace chassis
+
+namespace post
+{
+const static constexpr char* busname =
+    "xyz.openbmc_project.State.OperatingSystem";
+const static constexpr char* interface =
+    "xyz.openbmc_project.State.OperatingSystem.Status";
+const static constexpr char* path = "/xyz/openbmc_project/state/os";
+const static constexpr char* property = "OperatingSystemState";
+} // namespace post
+
+namespace association
+{
+const static constexpr char* interface =
+    "xyz.openbmc_project.Association.Definitions";
+} // namespace association
+
+template <typename T>
+inline T loadVariant(const SensorBaseConfigMap& data, const std::string& key)
+{
+    auto it = data.find(key);
+    if (it == data.end())
+    {
+        std::cerr << "Configuration missing " << key << "\n";
+        throw std::invalid_argument("Key Missing");
+    }
+    if constexpr (std::is_same_v<T, double>)
+    {
+        return std::visit(VariantToDoubleVisitor(), it->second);
+    }
+    else if constexpr (std::is_unsigned_v<T>)
+    {
+        return std::visit(VariantToUnsignedIntVisitor(), it->second);
+    }
+    else if constexpr (std::is_same_v<T, std::string>)
+    {
+        return std::visit(VariantToStringVisitor(), it->second);
+    }
+    else
+    {
+        static_assert(!std::is_same_v<T, T>, "Type Not Implemented");
+    }
+}
+
+inline void setReadState(const std::string& str, PowerState& val)
+{
+
+    if (str == "On")
+    {
+        val = PowerState::on;
+    }
+    else if (str == "BiosPost")
+    {
+        val = PowerState::biosPost;
+    }
+    else if (str == "Always")
+    {
+        val = PowerState::always;
+    }
+    else if (str == "ChassisOn")
+    {
+        val = PowerState::chassisOn;
+    }
+}
+
+inline PowerState getPowerState(const SensorBaseConfigMap& cfg)
+{
+    PowerState state = PowerState::always;
+    auto findPowerState = cfg.find("PowerState");
+    if (findPowerState != cfg.end())
+    {
+        std::string powerState =
+            std::visit(VariantToStringVisitor(), findPowerState->second);
+        setReadState(powerState, state);
+    }
+    return state;
+}
+
+inline float getPollRate(const SensorBaseConfigMap& cfg, float dflt)
+{
+    float pollRate = dflt;
+    auto findPollRate = cfg.find("PollRate");
+    if (findPollRate != cfg.end())
+    {
+        pollRate = std::visit(VariantToFloatVisitor(), findPollRate->second);
+        if (!std::isfinite(pollRate) || pollRate <= 0.0F)
+        {
+            pollRate = dflt; // poll time invalid, fall back to default
+        }
+    }
+    return pollRate;
+}
+
+inline void setLed(const std::shared_ptr<sdbusplus::asio::connection>& conn,
+                   const std::string& name, bool on)
+{
+    conn->async_method_call(
+        [name](const boost::system::error_code ec) {
+        if (ec)
+        {
+            std::cerr << "Failed to set LED " << name << "\n";
+        }
+        },
+        "xyz.openbmc_project.LED.GroupManager",
+        "/xyz/openbmc_project/led/groups/" + name, properties::interface,
+        properties::set, "xyz.openbmc_project.Led.Group", "Asserted",
+        std::variant<bool>(on));
+}
+
+void createInventoryAssoc(
+    const std::shared_ptr<sdbusplus::asio::connection>& conn,
+    const std::shared_ptr<sdbusplus::asio::dbus_interface>& association,
+    const std::string& path);
+
+struct GetSensorConfiguration :
+    std::enable_shared_from_this<GetSensorConfiguration>
+{
+    GetSensorConfiguration(
+        std::shared_ptr<sdbusplus::asio::connection> connection,
+        std::function<void(ManagedObjectType& resp)>&& callbackFunc) :
+        dbusConnection(std::move(connection)),
+        callback(std::move(callbackFunc))
+    {}
+
+    void getPath(const std::string& path, const std::string& interface,
+                 const std::string& owner, size_t retries = 5)
+    {
+        if (retries > 5)
+        {
+            retries = 5;
+        }
+        std::shared_ptr<GetSensorConfiguration> self = shared_from_this();
+
+        self->dbusConnection->async_method_call(
+            [self, path, interface, owner, retries](
+                const boost::system::error_code ec, SensorBaseConfigMap& data) {
+            if (ec)
+            {
+                std::cerr << "Error getting " << path << ": retries left"
+                          << retries - 1 << "\n";
+                if (retries == 0U)
+                {
+                    return;
+                }
+                auto timer = std::make_shared<boost::asio::steady_timer>(
+                    self->dbusConnection->get_io_context());
+                timer->expires_after(std::chrono::seconds(10));
+                timer->async_wait([self, timer, path, interface, owner,
+                                   retries](boost::system::error_code ec) {
+                    if (ec)
+                    {
+                        std::cerr << "Timer error!\n";
+                        return;
+                    }
+                    self->getPath(path, interface, owner, retries - 1);
+                });
+                return;
+            }
+
+            self->respData[path][interface] = std::move(data);
+            },
+            owner, path, "org.freedesktop.DBus.Properties", "GetAll",
+            interface);
+    }
+
+    void getConfiguration(const std::vector<std::string>& types,
+                          size_t retries = 0)
+    {
+        if (retries > 5)
+        {
+            retries = 5;
+        }
+
+        std::vector<std::string> interfaces(types.size());
+        for (const auto& type : types)
+        {
+            interfaces.push_back(configInterfaceName(type));
+        }
+
+        std::shared_ptr<GetSensorConfiguration> self = shared_from_this();
+        dbusConnection->async_method_call(
+            [self, interfaces, retries](const boost::system::error_code ec,
+                                        const GetSubTreeType& ret) {
+            if (ec)
+            {
+                std::cerr << "Error calling mapper\n";
+                if (retries == 0U)
+                {
+                    return;
+                }
+                auto timer = std::make_shared<boost::asio::steady_timer>(
+                    self->dbusConnection->get_io_context());
+                timer->expires_after(std::chrono::seconds(10));
+                timer->async_wait([self, timer, interfaces,
+                                   retries](boost::system::error_code ec) {
+                    if (ec)
+                    {
+                        std::cerr << "Timer error!\n";
+                        return;
+                    }
+                    self->getConfiguration(interfaces, retries - 1);
+                });
+
+                return;
+            }
+            for (const auto& [path, objDict] : ret)
+            {
+                if (objDict.empty())
+                {
+                    return;
+                }
+                const std::string& owner = objDict.begin()->first;
+
+                for (const std::string& interface : objDict.begin()->second)
+                {
+                    // anything that starts with a requested configuration
+                    // is good
+                    if (std::find_if(interfaces.begin(), interfaces.end(),
+                                     [interface](const std::string& possible) {
+                        return interface.starts_with(possible);
+                        }) == interfaces.end())
+                    {
+                        continue;
+                    }
+                    self->getPath(path, interface, owner);
+                }
+            }
+            },
+            mapper::busName, mapper::path, mapper::interface, mapper::subtree,
+            "/", 0, interfaces);
+    }
+
+    ~GetSensorConfiguration()
+    {
+        callback(respData);
+    }
+
+    std::shared_ptr<sdbusplus::asio::connection> dbusConnection;
+    std::function<void(ManagedObjectType& resp)> callback;
+    ManagedObjectType respData;
+};
+
+// The common scheme for sysfs files naming is: <type><number>_<item>.
+// This function returns optionally these 3 elements as a tuple.
+std::optional<std::tuple<std::string, std::string, std::string>>
+    splitFileName(const std::filesystem::path& filePath);
+std::optional<double> readFile(const std::string& thresholdFile,
+                               const double& scaleFactor);
+void setupManufacturingModeMatch(sdbusplus::asio::connection& conn);
+bool getManufacturingMode();
+std::vector<std::unique_ptr<sdbusplus::bus::match_t>>
+    setupPropertiesChangedMatches(
+        sdbusplus::asio::connection& bus, std::span<const char* const> types,
+        const std::function<void(sdbusplus::message_t&)>& handler);
diff --git a/src/VariantVisitors.hpp b/src/VariantVisitors.hpp
new file mode 100644
index 0000000..27185ca
--- /dev/null
+++ b/src/VariantVisitors.hpp
@@ -0,0 +1,69 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+
+#pragma once
+#include <boost/type_index.hpp>
+
+#include <stdexcept>
+#include <string>
+#include <variant>
+
+namespace details
+{
+
+template <typename U>
+struct VariantToNumericVisitor
+{
+    template <typename T>
+    U operator()(const T& t) const
+    {
+        if constexpr (std::is_arithmetic_v<T>)
+        {
+            return static_cast<U>(t);
+        }
+        throw std::invalid_argument(
+            "Cannot translate type " +
+            boost::typeindex::type_id<T>().pretty_name() + " to " +
+            boost::typeindex::type_id<U>().pretty_name());
+    }
+};
+
+} // namespace details
+
+using VariantToFloatVisitor = details::VariantToNumericVisitor<float>;
+using VariantToIntVisitor = details::VariantToNumericVisitor<int>;
+using VariantToUnsignedIntVisitor =
+    details::VariantToNumericVisitor<unsigned int>;
+using VariantToDoubleVisitor = details::VariantToNumericVisitor<double>;
+
+struct VariantToStringVisitor
+{
+    template <typename T>
+    std::string operator()(const T& t) const
+    {
+        if constexpr (std::is_same_v<T, std::string>)
+        {
+            return t;
+        }
+        else if constexpr (std::is_arithmetic_v<T>)
+        {
+            return std::to_string(t);
+        }
+        throw std::invalid_argument(
+            "Cannot translate type " +
+            boost::typeindex::type_id<T>().pretty_name() + " to string");
+    }
+};
diff --git a/src/dbus-sensor_config.h.in b/src/dbus-sensor_config.h.in
new file mode 100644
index 0000000..6dc9931
--- /dev/null
+++ b/src/dbus-sensor_config.h.in
@@ -0,0 +1,9 @@
+#pragma once
+
+#include <cstdint>
+
+// clang-format off
+constexpr const int validateUnsecureFeature = @VALIDATION_UNSECURE_FEATURE@;
+
+constexpr const int insecureSensorOverride = @INSECURE_UNRESTRICTED_SENSOR_OVERRIDE@;
+// clang-format on
\ No newline at end of file
diff --git a/src/meson.build b/src/meson.build
index 9d44db3..6f47fb0 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -1,3 +1,60 @@
+conf_data = configuration_data()
+conf_data.set10('VALIDATION_UNSECURE_FEATURE', get_option('validate-unsecure-feature').enabled())
+conf_data.set10('INSECURE_UNRESTRICTED_SENSOR_OVERRIDE', get_option('insecure-sensor-override').enabled())
+configure_file(input: 'dbus-sensor_config.h.in',
+               output: 'dbus-sensor_config.h',
+               configuration: conf_data)
+
+thresholds_a = static_library(
+    'thresholds_a',
+    'Thresholds.cpp',
+    dependencies: default_deps,
+)
+
+thresholds_dep = declare_dependency(
+    link_with: [ thresholds_a ],
+    dependencies: default_deps,
+)
+
+utils_a = static_library(
+    'utils_a',
+    [
+        'FileHandle.cpp',
+        'SensorPaths.cpp',
+        'Utils.cpp',
+    ],
+    dependencies: default_deps,
+)
+
+utils_dep = declare_dependency(
+    link_with: [ utils_a ],
+    dependencies: [ sdbusplus ],
+)
+
+devicemgmt_a = static_library(
+    'devicemgmt_a',
+    [
+        'DeviceMgmt.cpp',
+    ],
+    dependencies: default_deps,
+)
+
+devicemgmt_dep = declare_dependency(
+    link_with: [ devicemgmt_a ],
+    dependencies: default_deps,
+)
+
+pwmsensor_a = static_library(
+    'pwmsensor_a',
+    'PwmSensor.cpp',
+    dependencies: [ default_deps, thresholds_dep ],
+)
+
+pwmsensor_dep = declare_dependency(
+    link_with: [ pwmsensor_a ],
+    dependencies: [ default_deps, thresholds_dep ],
+)
+
 peci_incdirs = []
 if not meson.get_compiler('cpp').has_header('linux/peci-ioctl.h')
     peci_incdirs = ['../include']
@@ -15,8 +72,6 @@
             utils_dep,
         ],
         cpp_args: uring_args,
-        implicit_include_directories: false,
-        include_directories: '../include',
         install: true,
     )
 endif
@@ -32,10 +87,7 @@
             thresholds_dep,
             utils_dep,
         ],
-        implicit_include_directories: false,
-        include_directories: [
-            '../include'
-        ] + peci_incdirs,
+        include_directories: peci_incdirs,
         install: true,
     )
 endif
@@ -50,8 +102,6 @@
             utils_dep,
         ],
         cpp_args: uring_args,
-        implicit_include_directories: false,
-        include_directories: '../include',
         install: true,
     )
 endif
@@ -69,8 +119,6 @@
             utils_dep,
         ],
         cpp_args: uring_args,
-        implicit_include_directories: false,
-        include_directories: '../include',
         install: true,
     )
 endif
@@ -87,8 +135,6 @@
             utils_dep,
         ],
         cpp_args: uring_args,
-        implicit_include_directories: false,
-        include_directories: '../include',
         install: true,
     )
 endif
@@ -105,8 +151,6 @@
             utils_dep,
         ],
         cpp_args: uring_args,
-        implicit_include_directories: false,
-        include_directories: '../include',
         install: true,
     )
 endif
@@ -122,8 +166,6 @@
             utils_dep,
         ],
         cpp_args: uring_args,
-        implicit_include_directories: false,
-        include_directories: '../include',
         install: true,
     )
 endif
@@ -139,8 +181,6 @@
             utils_dep,
         ],
         cpp_args: uring_args,
-        implicit_include_directories: false,
-        include_directories: '../include',
         install: true,
     )
 endif
@@ -156,8 +196,6 @@
         sources: nvme_srcs,
         dependencies: nvme_deps,
         cpp_args: uring_args,
-        implicit_include_directories: false,
-        include_directories: '../include',
         install: true,
     )
 endif
@@ -175,8 +213,6 @@
             utils_dep,
         ],
         cpp_args: uring_args,
-        implicit_include_directories: false,
-        include_directories: '../include',
         install: true,
     )
 endif
@@ -192,8 +228,6 @@
             utils_dep,
         ],
         cpp_args: uring_args,
-        implicit_include_directories: false,
-        include_directories: '../include',
         install: true,
     )
 endif
diff --git a/src/sensor.hpp b/src/sensor.hpp
new file mode 100644
index 0000000..d2ba8af
--- /dev/null
+++ b/src/sensor.hpp
@@ -0,0 +1,584 @@
+#pragma once
+
+#include "dbus-sensor_config.h"
+
+#include "SensorPaths.hpp"
+#include "Thresholds.hpp"
+#include "Utils.hpp"
+
+#include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/exception.hpp>
+
+#include <limits>
+#include <memory>
+#include <string>
+#include <vector>
+
+constexpr size_t sensorFailedPollTimeMs = 5000;
+
+// Enable useful logging with sensor instrumentation
+// This is intentionally not DEBUG, avoid clash with usage in .cpp files
+constexpr bool enableInstrumentation = false;
+
+constexpr const char* sensorValueInterface = "xyz.openbmc_project.Sensor.Value";
+constexpr const char* valueMutabilityInterfaceName =
+    "xyz.openbmc_project.Sensor.ValueMutability";
+constexpr const char* availableInterfaceName =
+    "xyz.openbmc_project.State.Decorator.Availability";
+constexpr const char* operationalInterfaceName =
+    "xyz.openbmc_project.State.Decorator.OperationalStatus";
+constexpr const size_t errorThreshold = 5;
+
+struct SensorInstrumentation
+{
+    // These are for instrumentation for debugging
+    int numCollectsGood = 0;
+    int numCollectsMiss = 0;
+    int numStreakGreats = 0;
+    int numStreakMisses = 0;
+    double minCollected = 0.0;
+    double maxCollected = 0.0;
+};
+
+struct SetSensorError : sdbusplus::exception_t
+{
+    const char* name() const noexcept override
+    {
+        return "xyz.openbmc_project.Common.Errors.NotAllowed";
+    }
+    const char* description() const noexcept override
+    {
+        return "Not allowed to set property value.";
+    }
+    int get_errno() const noexcept override
+    {
+        return EACCES;
+    }
+};
+
+struct Sensor
+{
+    Sensor(const std::string& name,
+           std::vector<thresholds::Threshold>&& thresholdData,
+           const std::string& configurationPath, const std::string& objectType,
+           bool isSettable, bool isMutable, const double max, const double min,
+           std::shared_ptr<sdbusplus::asio::connection>& conn,
+           PowerState readState = PowerState::always) :
+        name(sensor_paths::escapePathForDbus(name)),
+        configurationPath(configurationPath),
+        objectType(configInterfaceName(objectType)),
+        isSensorSettable(isSettable), isValueMutable(isMutable), maxValue(max),
+        minValue(min), thresholds(std::move(thresholdData)),
+        hysteresisTrigger((max - min) * 0.01),
+        hysteresisPublish((max - min) * 0.0001), dbusConnection(conn),
+        readState(readState),
+        instrumentation(enableInstrumentation
+                            ? std::make_unique<SensorInstrumentation>()
+                            : nullptr)
+    {}
+    virtual ~Sensor() = default;
+    virtual void checkThresholds(void) = 0;
+    std::string name;
+    std::string configurationPath;
+    std::string objectType;
+    bool isSensorSettable;
+
+    /* A flag indicates if properties of xyz.openbmc_project.Sensor.Value
+     * interface are mutable. If mutable, then
+     * xyz.openbmc_project.Sensor.ValueMutability interface will be
+     * instantiated.
+     */
+    bool isValueMutable;
+    double maxValue;
+    double minValue;
+    std::vector<thresholds::Threshold> thresholds;
+    std::shared_ptr<sdbusplus::asio::dbus_interface> sensorInterface;
+    std::shared_ptr<sdbusplus::asio::dbus_interface> association;
+    std::shared_ptr<sdbusplus::asio::dbus_interface> availableInterface;
+    std::shared_ptr<sdbusplus::asio::dbus_interface> operationalInterface;
+    std::shared_ptr<sdbusplus::asio::dbus_interface> valueMutabilityInterface;
+    double value = std::numeric_limits<double>::quiet_NaN();
+    double rawValue = std::numeric_limits<double>::quiet_NaN();
+    bool overriddenState = false;
+    bool internalSet = false;
+    double hysteresisTrigger;
+    double hysteresisPublish;
+    std::shared_ptr<sdbusplus::asio::connection> dbusConnection;
+    PowerState readState;
+    size_t errCount{0};
+    std::unique_ptr<SensorInstrumentation> instrumentation;
+
+    // This member variable provides a hook that can be used to receive
+    // notification whenever this Sensor's value is externally set via D-Bus.
+    // If interested, assign your own lambda to this variable, during
+    // construction of your Sensor subclass. See ExternalSensor for example.
+    std::function<void()> externalSetHook;
+
+    using Level = thresholds::Level;
+    using Direction = thresholds::Direction;
+
+    std::array<std::shared_ptr<sdbusplus::asio::dbus_interface>,
+               thresholds::thresProp.size()>
+        thresholdInterfaces;
+
+    std::shared_ptr<sdbusplus::asio::dbus_interface>
+        getThresholdInterface(Level lev)
+    {
+        size_t index = static_cast<size_t>(lev);
+        if (index >= thresholdInterfaces.size())
+        {
+            std::cout << "Unknown threshold level \n";
+            return nullptr;
+        }
+        std::shared_ptr<sdbusplus::asio::dbus_interface> interface =
+            thresholdInterfaces[index];
+        return interface;
+    }
+
+    void updateInstrumentation(double readValue) const
+    {
+        // Do nothing if this feature is not enabled
+        if constexpr (!enableInstrumentation)
+        {
+            return;
+        }
+        if (!instrumentation)
+        {
+            return;
+        }
+
+        // Save some typing
+        auto& inst = *instrumentation;
+
+        // Show constants if first reading (even if unsuccessful)
+        if ((inst.numCollectsGood == 0) && (inst.numCollectsMiss == 0))
+        {
+            std::cerr << "Sensor " << name << ": Configuration min=" << minValue
+                      << ", max=" << maxValue << ", type=" << objectType
+                      << ", path=" << configurationPath << "\n";
+        }
+
+        // Sensors can use "nan" to indicate unavailable reading
+        if (!std::isfinite(readValue))
+        {
+            // Only show this if beginning a new streak
+            if (inst.numStreakMisses == 0)
+            {
+                std::cerr << "Sensor " << name
+                          << ": Missing reading, Reading counts good="
+                          << inst.numCollectsGood
+                          << ", miss=" << inst.numCollectsMiss
+                          << ", Prior good streak=" << inst.numStreakGreats
+                          << "\n";
+            }
+
+            inst.numStreakGreats = 0;
+            ++(inst.numCollectsMiss);
+            ++(inst.numStreakMisses);
+
+            return;
+        }
+
+        // Only show this if beginning a new streak and not the first time
+        if ((inst.numStreakGreats == 0) && (inst.numCollectsGood != 0))
+        {
+            std::cerr << "Sensor " << name
+                      << ": Recovered reading, Reading counts good="
+                      << inst.numCollectsGood
+                      << ", miss=" << inst.numCollectsMiss
+                      << ", Prior miss streak=" << inst.numStreakMisses << "\n";
+        }
+
+        // Initialize min/max if the first successful reading
+        if (inst.numCollectsGood == 0)
+        {
+            std::cerr << "Sensor " << name << ": First reading=" << readValue
+                      << "\n";
+
+            inst.minCollected = readValue;
+            inst.maxCollected = readValue;
+        }
+
+        inst.numStreakMisses = 0;
+        ++(inst.numCollectsGood);
+        ++(inst.numStreakGreats);
+
+        // Only provide subsequent output if new min/max established
+        if (readValue < inst.minCollected)
+        {
+            std::cerr << "Sensor " << name << ": Lowest reading=" << readValue
+                      << "\n";
+
+            inst.minCollected = readValue;
+        }
+
+        if (readValue > inst.maxCollected)
+        {
+            std::cerr << "Sensor " << name << ": Highest reading=" << readValue
+                      << "\n";
+
+            inst.maxCollected = readValue;
+        }
+    }
+
+    int setSensorValue(const double& newValue, double& oldValue)
+    {
+        if (!internalSet)
+        {
+            if (insecureSensorOverride == 0 && !isSensorSettable &&
+                !getManufacturingMode())
+            {
+                throw SetSensorError();
+            }
+
+            oldValue = newValue;
+            overriddenState = true;
+            // check thresholds for external set
+            value = newValue;
+            checkThresholds();
+
+            // Trigger the hook, as an external set has just happened
+            if (externalSetHook)
+            {
+                externalSetHook();
+            }
+        }
+        else if (!overriddenState)
+        {
+            oldValue = newValue;
+        }
+        return 1;
+    }
+
+    void setInitialProperties(const std::string& unit,
+                              const std::string& label = std::string(),
+                              size_t thresholdSize = 0)
+    {
+        if (readState == PowerState::on || readState == PowerState::biosPost ||
+            readState == PowerState::chassisOn)
+        {
+            setupPowerMatch(dbusConnection);
+        }
+
+        createAssociation(association, configurationPath);
+
+        sensorInterface->register_property("Unit", unit);
+        sensorInterface->register_property("MaxValue", maxValue);
+        sensorInterface->register_property("MinValue", minValue);
+        sensorInterface->register_property(
+            "Value", value, [this](const double& newValue, double& oldValue) {
+                return setSensorValue(newValue, oldValue);
+            });
+
+        fillMissingThresholds();
+
+        for (auto& threshold : thresholds)
+        {
+            if (std::isnan(threshold.hysteresis))
+            {
+                threshold.hysteresis = hysteresisTrigger;
+            }
+
+            std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
+                getThresholdInterface(threshold.level);
+
+            if (!iface)
+            {
+                std::cout << "trying to set uninitialized interface\n";
+                continue;
+            }
+
+            std::string level =
+                propertyLevel(threshold.level, threshold.direction);
+            std::string alarm =
+                propertyAlarm(threshold.level, threshold.direction);
+
+            if ((level.empty()) || (alarm.empty()))
+            {
+                continue;
+            }
+            size_t thresSize =
+                label.empty() ? thresholds.size() : thresholdSize;
+            iface->register_property(
+                level, threshold.value,
+                [&, label, thresSize](const double& request, double& oldValue) {
+                oldValue = request; // todo, just let the config do this?
+                threshold.value = request;
+                thresholds::persistThreshold(configurationPath, objectType,
+                                             threshold, dbusConnection,
+                                             thresSize, label);
+                // Invalidate previously remembered value,
+                // so new thresholds will be checked during next update,
+                // even if sensor reading remains unchanged.
+                value = std::numeric_limits<double>::quiet_NaN();
+
+                // Although tempting, don't call checkThresholds() from here
+                // directly. Let the regular sensor monitor call the same
+                // using updateValue(), which can check conditions like
+                // poweron, etc., before raising any event.
+                return 1;
+                });
+            iface->register_property(alarm, false);
+        }
+        if (!sensorInterface->initialize())
+        {
+            std::cerr << "error initializing value interface\n";
+        }
+
+        for (auto& thresIface : thresholdInterfaces)
+        {
+            if (thresIface)
+            {
+                if (!thresIface->initialize(true))
+                {
+                    std::cerr << "Error initializing threshold interface \n";
+                }
+            }
+        }
+
+        if (isValueMutable)
+        {
+            valueMutabilityInterface =
+                std::make_shared<sdbusplus::asio::dbus_interface>(
+                    dbusConnection, sensorInterface->get_object_path(),
+                    valueMutabilityInterfaceName);
+            valueMutabilityInterface->register_property("Mutable", true);
+            if (!valueMutabilityInterface->initialize())
+            {
+                std::cerr
+                    << "error initializing sensor value mutability interface\n";
+                valueMutabilityInterface = nullptr;
+            }
+        }
+
+        if (!availableInterface)
+        {
+            availableInterface =
+                std::make_shared<sdbusplus::asio::dbus_interface>(
+                    dbusConnection, sensorInterface->get_object_path(),
+                    availableInterfaceName);
+            availableInterface->register_property(
+                "Available", true, [this](const bool propIn, bool& old) {
+                    if (propIn == old)
+                    {
+                        return 1;
+                    }
+                    old = propIn;
+                    if (!propIn)
+                    {
+                        updateValue(std::numeric_limits<double>::quiet_NaN());
+                    }
+                    return 1;
+                });
+            availableInterface->initialize();
+        }
+        if (!operationalInterface)
+        {
+            operationalInterface =
+                std::make_shared<sdbusplus::asio::dbus_interface>(
+                    dbusConnection, sensorInterface->get_object_path(),
+                    operationalInterfaceName);
+            operationalInterface->register_property("Functional", true);
+            operationalInterface->initialize();
+        }
+    }
+
+    static std::string propertyLevel(const Level lev, const Direction dir)
+    {
+        for (const thresholds::ThresholdDefinition& prop :
+             thresholds::thresProp)
+        {
+            if (prop.level == lev)
+            {
+                if (dir == Direction::HIGH)
+                {
+                    return std::string(prop.levelName) + "High";
+                }
+                if (dir == Direction::LOW)
+                {
+                    return std::string(prop.levelName) + "Low";
+                }
+            }
+        }
+        return "";
+    }
+
+    static std::string propertyAlarm(const Level lev, const Direction dir)
+    {
+        for (const thresholds::ThresholdDefinition& prop :
+             thresholds::thresProp)
+        {
+            if (prop.level == lev)
+            {
+                if (dir == Direction::HIGH)
+                {
+                    return std::string(prop.levelName) + "AlarmHigh";
+                }
+                if (dir == Direction::LOW)
+                {
+                    return std::string(prop.levelName) + "AlarmLow";
+                }
+            }
+        }
+        return "";
+    }
+
+    bool readingStateGood() const
+    {
+        return ::readingStateGood(readState);
+    }
+
+    void markFunctional(bool isFunctional)
+    {
+        if (operationalInterface)
+        {
+            operationalInterface->set_property("Functional", isFunctional);
+        }
+        if (isFunctional)
+        {
+            errCount = 0;
+        }
+        else
+        {
+            updateValue(std::numeric_limits<double>::quiet_NaN());
+        }
+    }
+
+    void markAvailable(bool isAvailable)
+    {
+        if (availableInterface)
+        {
+            availableInterface->set_property("Available", isAvailable);
+            errCount = 0;
+        }
+    }
+
+    void incrementError()
+    {
+        if (!readingStateGood())
+        {
+            markAvailable(false);
+            return;
+        }
+
+        if (errCount >= errorThreshold)
+        {
+            return;
+        }
+
+        errCount++;
+        if (errCount == errorThreshold)
+        {
+            std::cerr << "Sensor " << name << " reading error!\n";
+            markFunctional(false);
+        }
+    }
+
+    bool inError() const
+    {
+        return errCount >= errorThreshold;
+    }
+
+    void updateValue(const double& newValue)
+    {
+        // Ignore if overriding is enabled
+        if (overriddenState)
+        {
+            return;
+        }
+
+        if (!readingStateGood())
+        {
+            markAvailable(false);
+            updateValueProperty(std::numeric_limits<double>::quiet_NaN());
+            return;
+        }
+
+        updateValueProperty(newValue);
+        updateInstrumentation(newValue);
+
+        // Always check thresholds after changing the value,
+        // as the test against hysteresisTrigger now takes place in
+        // the thresholds::checkThresholds() method,
+        // which is called by checkThresholds() below,
+        // in all current implementations of sensors that have thresholds.
+        checkThresholds();
+        if (!std::isnan(newValue))
+        {
+            markFunctional(true);
+            markAvailable(true);
+        }
+    }
+
+    void updateProperty(
+        std::shared_ptr<sdbusplus::asio::dbus_interface>& interface,
+        double& oldValue, const double& newValue,
+        const char* dbusPropertyName) const
+    {
+        if (requiresUpdate(oldValue, newValue))
+        {
+            oldValue = newValue;
+            if (interface &&
+                !(interface->set_property(dbusPropertyName, newValue)))
+            {
+                std::cerr << "error setting property " << dbusPropertyName
+                          << " to " << newValue << "\n";
+            }
+        }
+    }
+
+    bool requiresUpdate(const double& lVal, const double& rVal) const
+    {
+        const auto lNan = std::isnan(lVal);
+        const auto rNan = std::isnan(rVal);
+        if (lNan || rNan)
+        {
+            return (lNan != rNan);
+        }
+        return std::abs(lVal - rVal) > hysteresisPublish;
+    }
+
+  private:
+    // If one of the thresholds for a dbus interface is provided
+    // we have to set the other one as dbus properties are never
+    // optional.
+    void fillMissingThresholds()
+    {
+        for (thresholds::Threshold& thisThreshold : thresholds)
+        {
+            bool foundOpposite = false;
+            thresholds::Direction opposite = thresholds::Direction::HIGH;
+            if (thisThreshold.direction == thresholds::Direction::HIGH)
+            {
+                opposite = thresholds::Direction::LOW;
+            }
+            for (thresholds::Threshold& otherThreshold : thresholds)
+            {
+                if (thisThreshold.level != otherThreshold.level)
+                {
+                    continue;
+                }
+                if (otherThreshold.direction != opposite)
+                {
+                    continue;
+                }
+                foundOpposite = true;
+                break;
+            }
+            if (foundOpposite)
+            {
+                continue;
+            }
+            thresholds.emplace_back(thisThreshold.level, opposite,
+                                    std::numeric_limits<double>::quiet_NaN());
+        }
+    }
+
+    void updateValueProperty(const double& newValue)
+    {
+        // Indicate that it is internal set call, not an external overwrite
+        internalSet = true;
+        updateProperty(sensorInterface, value, newValue, "Value");
+        internalSet = false;
+    }
+};