Move source files into application-specific sub-directories

Currently, dbus-sensors implement multiple applications:
 - psusensor
 - adcsensor
 - intelcpusensor
 - hwmontempsensor
 - ipmbsensor
 - nvmesensor
 - externalsensor
 - mcutempsensor
 - intrusionsensor
 - fansensor
 - exitairtempsensor

This commit is to create separate directories for each application so
that things can be separated more easily and the files are smaller,
instead of creating one huge file for the sensor implementation.

There was some discussion in discord on this. [1][2]

[1]: https://discord.com/channels/775381525260664832/1187158775438778408/1284106093756289067
[2]: https://discord.com/channels/775381525260664832/867820390406422538/1303217796821553214

Signed-off-by: George Liu <liuxiwei@ieisystem.com>
Change-Id: I258fc2ee7d8f939c7b83a07350395e78775b2b8d
diff --git a/src/psu/PSUEvent.cpp b/src/psu/PSUEvent.cpp
new file mode 100644
index 0000000..438faa2
--- /dev/null
+++ b/src/psu/PSUEvent.cpp
@@ -0,0 +1,379 @@
+/*
+// 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.
+*/
+
+#include "PSUEvent.hpp"
+
+#include "SensorPaths.hpp"
+#include "Utils.hpp"
+
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/error.hpp>
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/random_access_file.hpp>
+#include <boost/container/flat_map.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+#include <array>
+#include <chrono>
+#include <cstddef>
+#include <iostream>
+#include <memory>
+#include <set>
+#include <stdexcept>
+#include <string>
+#include <utility>
+#include <vector>
+
+PSUCombineEvent::PSUCombineEvent(
+    sdbusplus::asio::object_server& objectServer,
+    std::shared_ptr<sdbusplus::asio::connection>& conn,
+    boost::asio::io_context& io, const std::string& psuName,
+    const PowerState& powerState, EventPathList& eventPathList,
+    GroupEventPathList& groupEventPathList, const std::string& combineEventName,
+    double pollRate) : objServer(objectServer)
+{
+    std::string psuNameEscaped = sensor_paths::escapePathForDbus(psuName);
+    eventInterface = objServer.add_interface(
+        "/xyz/openbmc_project/State/Decorator/" + psuNameEscaped + "_" +
+            combineEventName,
+        "xyz.openbmc_project.State.Decorator.OperationalStatus");
+    eventInterface->register_property("functional", true);
+
+    if (!eventInterface->initialize())
+    {
+        std::cerr << "error initializing event interface\n";
+    }
+
+    std::shared_ptr<std::set<std::string>> combineEvent =
+        std::make_shared<std::set<std::string>>();
+    for (const auto& [eventName, paths] : eventPathList)
+    {
+        std::shared_ptr<std::set<std::string>> assert =
+            std::make_shared<std::set<std::string>>();
+        std::shared_ptr<bool> state = std::make_shared<bool>(false);
+
+        std::string eventPSUName = eventName + psuName;
+        for (const auto& path : paths)
+        {
+            auto p = std::make_shared<PSUSubEvent>(
+                eventInterface, path, conn, io, powerState, eventName,
+                eventName, assert, combineEvent, state, psuName, pollRate);
+            p->setupRead();
+
+            events[eventPSUName].emplace_back(p);
+            asserts.emplace_back(assert);
+            states.emplace_back(state);
+        }
+    }
+
+    for (const auto& [eventName, groupEvents] : groupEventPathList)
+    {
+        for (const auto& [groupEventName, paths] : groupEvents)
+        {
+            std::shared_ptr<std::set<std::string>> assert =
+                std::make_shared<std::set<std::string>>();
+            std::shared_ptr<bool> state = std::make_shared<bool>(false);
+
+            std::string eventPSUName = groupEventName + psuName;
+            for (const auto& path : paths)
+            {
+                auto p = std::make_shared<PSUSubEvent>(
+                    eventInterface, path, conn, io, powerState, groupEventName,
+                    eventName, assert, combineEvent, state, psuName, pollRate);
+                p->setupRead();
+                events[eventPSUName].emplace_back(p);
+
+                asserts.emplace_back(assert);
+                states.emplace_back(state);
+            }
+        }
+    }
+}
+
+PSUCombineEvent::~PSUCombineEvent()
+{
+    // Clear unique_ptr first
+    for (auto& [psuName, subEvents] : events)
+    {
+        for (auto& subEventPtr : subEvents)
+        {
+            subEventPtr.reset();
+        }
+    }
+    events.clear();
+    objServer.remove_interface(eventInterface);
+}
+
+static boost::container::flat_map<std::string,
+                                  std::pair<std::string, std::string>>
+    logID = {
+        {"PredictiveFailure",
+         {"OpenBMC.0.1.PowerSupplyFailurePredicted",
+          "OpenBMC.0.1.PowerSupplyPredictedFailureRecovered"}},
+        {"Failure",
+         {"OpenBMC.0.1.PowerSupplyFailed", "OpenBMC.0.1.PowerSupplyRecovered"}},
+        {"ACLost",
+         {"OpenBMC.0.1.PowerSupplyPowerLost",
+          "OpenBMC.0.1.PowerSupplyPowerRestored"}},
+        {"FanFault",
+         {"OpenBMC.0.1.PowerSupplyFanFailed",
+          "OpenBMC.0.1.PowerSupplyFanRecovered"}},
+        {"ConfigureError",
+         {"OpenBMC.0.1.PowerSupplyConfigurationError",
+          "OpenBMC.0.1.PowerSupplyConfigurationErrorRecovered"}}};
+
+PSUSubEvent::PSUSubEvent(
+    std::shared_ptr<sdbusplus::asio::dbus_interface> eventInterface,
+    const std::string& path, std::shared_ptr<sdbusplus::asio::connection>& conn,
+    boost::asio::io_context& 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) :
+    eventInterface(std::move(eventInterface)), asserts(std::move(asserts)),
+    combineEvent(std::move(combineEvent)), assertState(std::move(state)),
+    path(path), eventName(eventName), readState(powerState), waitTimer(io),
+
+    inputDev(io, path, boost::asio::random_access_file::read_only),
+    psuName(psuName), groupEventName(groupEventName), systemBus(conn)
+{
+    buffer = std::make_shared<std::array<char, 128>>();
+    if (pollRate > 0.0)
+    {
+        eventPollMs = static_cast<unsigned int>(pollRate * 1000);
+    }
+
+    auto found = logID.find(eventName);
+    if (found == logID.end())
+    {
+        assertMessage.clear();
+        deassertMessage.clear();
+    }
+    else
+    {
+        assertMessage = found->second.first;
+        deassertMessage = found->second.second;
+    }
+
+    auto fanPos = path.find("fan");
+    if (fanPos != std::string::npos)
+    {
+        fanName = path.substr(fanPos);
+        auto fanNamePos = fanName.find('_');
+        if (fanNamePos != std::string::npos)
+        {
+            fanName = fanName.substr(0, fanNamePos);
+        }
+    }
+}
+
+PSUSubEvent::~PSUSubEvent()
+{
+    waitTimer.cancel();
+    inputDev.close();
+}
+
+void PSUSubEvent::setupRead()
+{
+    if (!readingStateGood(readState))
+    {
+        // Deassert the event
+        updateValue(0);
+        restartRead();
+        return;
+    }
+    if (!buffer)
+    {
+        std::cerr << "Buffer was invalid?";
+        return;
+    }
+
+    std::weak_ptr<PSUSubEvent> weakRef = weak_from_this();
+    inputDev.async_read_some_at(
+        0, boost::asio::buffer(buffer->data(), buffer->size() - 1),
+        [weakRef, buffer{buffer}](const boost::system::error_code& ec,
+                                  std::size_t bytesTransferred) {
+            std::shared_ptr<PSUSubEvent> self = weakRef.lock();
+            if (self)
+            {
+                self->handleResponse(ec, bytesTransferred);
+            }
+        });
+}
+
+void PSUSubEvent::restartRead()
+{
+    std::weak_ptr<PSUSubEvent> weakRef = weak_from_this();
+    waitTimer.expires_after(std::chrono::milliseconds(eventPollMs));
+    waitTimer.async_wait([weakRef](const boost::system::error_code& ec) {
+        if (ec == boost::asio::error::operation_aborted)
+        {
+            return;
+        }
+        std::shared_ptr<PSUSubEvent> self = weakRef.lock();
+        if (self)
+        {
+            self->setupRead();
+        }
+    });
+}
+
+void PSUSubEvent::handleResponse(const boost::system::error_code& err,
+                                 size_t bytesTransferred)
+{
+    if (err == boost::asio::error::operation_aborted)
+    {
+        return;
+    }
+
+    if ((err == boost::system::errc::bad_file_descriptor) ||
+        (err == boost::asio::error::misc_errors::not_found))
+    {
+        return;
+    }
+    if (!buffer)
+    {
+        std::cerr << "Buffer was invalid?";
+        return;
+    }
+    // null terminate the string so we don't walk off the end
+    std::array<char, 128>& bufferRef = *buffer;
+    bufferRef[bytesTransferred] = '\0';
+
+    if (!err)
+    {
+        try
+        {
+            int nvalue = std::stoi(bufferRef.data());
+            updateValue(nvalue);
+            errCount = 0;
+        }
+        catch (const std::invalid_argument&)
+        {
+            errCount++;
+        }
+    }
+    else
+    {
+        errCount++;
+    }
+    if (errCount >= warnAfterErrorCount)
+    {
+        if (errCount == warnAfterErrorCount)
+        {
+            std::cerr << "Failure to read event at " << path << "\n";
+        }
+        updateValue(0);
+        errCount++;
+    }
+    restartRead();
+}
+
+// Any of the sub events of one event is asserted, then the event will be
+// asserted. Only if none of the sub events are asserted, the event will be
+// deasserted.
+void PSUSubEvent::updateValue(const int& newValue)
+{
+    // Take no action if value already equal
+    // Same semantics as Sensor::updateValue(const double&)
+    if (newValue == value)
+    {
+        return;
+    }
+
+    if (newValue == 0)
+    {
+        // log deassert only after all asserts are gone
+        if (!(*asserts).empty())
+        {
+            auto found = (*asserts).find(path);
+            if (found == (*asserts).end())
+            {
+                return;
+            }
+            (*asserts).erase(path);
+
+            return;
+        }
+        if (*assertState)
+        {
+            *assertState = false;
+            auto foundCombine = (*combineEvent).find(groupEventName);
+            if (foundCombine == (*combineEvent).end())
+            {
+                return;
+            }
+            (*combineEvent).erase(groupEventName);
+            if (!deassertMessage.empty())
+            {
+                // Fan Failed has two args
+                if (deassertMessage == "OpenBMC.0.1.PowerSupplyFanRecovered")
+                {
+                    lg2::info("{EVENT} deassert", "EVENT", eventName,
+                              "REDFISH_MESSAGE_ID", deassertMessage,
+                              "REDFISH_MESSAGE_ARGS",
+                              (psuName + ',' + fanName));
+                }
+                else
+                {
+                    lg2::info("{EVENT} deassert", "EVENT", eventName,
+                              "REDFISH_MESSAGE_ID", deassertMessage,
+                              "REDFISH_MESSAGE_ARGS", psuName);
+                }
+            }
+
+            if ((*combineEvent).empty())
+            {
+                eventInterface->set_property("functional", true);
+            }
+        }
+    }
+    else
+    {
+        std::cerr << "PSUSubEvent asserted by " << path << "\n";
+
+        if ((!*assertState) && ((*asserts).empty()))
+        {
+            *assertState = true;
+            if (!assertMessage.empty())
+            {
+                // Fan Failed has two args
+                if (assertMessage == "OpenBMC.0.1.PowerSupplyFanFailed")
+                {
+                    lg2::warning("{EVENT} assert", "EVENT", eventName,
+                                 "REDFISH_MESSAGE_ID", assertMessage,
+                                 "REDFISH_MESSAGE_ARGS",
+                                 (psuName + ',' + fanName));
+                }
+                else
+                {
+                    lg2::warning("{EVENT} assert", "EVENT", eventName,
+                                 "REDFISH_MESSAGE_ID", assertMessage,
+                                 "REDFISH_MESSAGE_ARGS", psuName);
+                }
+            }
+            if ((*combineEvent).empty())
+            {
+                eventInterface->set_property("functional", false);
+            }
+            (*combineEvent).emplace(groupEventName);
+        }
+        (*asserts).emplace(path);
+    }
+    value = newValue;
+}
diff --git a/src/psu/PSUEvent.hpp b/src/psu/PSUEvent.hpp
new file mode 100644
index 0000000..d761f45
--- /dev/null
+++ b/src/psu/PSUEvent.hpp
@@ -0,0 +1,101 @@
+/*
+// 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_context.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>
+
+using EventPathList =
+    boost::container::flat_map<std::string, std::vector<std::string>>;
+using GroupEventPathList =
+    boost::container::flat_map<std::string, EventPathList>;
+
+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_context& 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();
+
+  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_context& io, const std::string& psuName,
+                    const PowerState& powerState, EventPathList& eventPathList,
+                    GroupEventPathList& 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/psu/PSUSensor.cpp b/src/psu/PSUSensor.cpp
new file mode 100644
index 0000000..4d410fe
--- /dev/null
+++ b/src/psu/PSUSensor.cpp
@@ -0,0 +1,255 @@
+/*
+// 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.
+*/
+
+#include "PSUSensor.hpp"
+
+#include "DeviceMgmt.hpp"
+#include "SensorPaths.hpp"
+#include "Thresholds.hpp"
+#include "Utils.hpp"
+#include "sensor.hpp"
+
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/error.hpp>
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/random_access_file.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+#include <array>
+#include <chrono>
+#include <cstddef>
+#include <iostream>
+#include <limits>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <utility>
+#include <vector>
+
+static constexpr const char* sensorPathPrefix = "/xyz/openbmc_project/sensors/";
+
+static constexpr bool debug = false;
+
+PSUSensor::PSUSensor(
+    const std::string& path, const std::string& objectType,
+    sdbusplus::asio::object_server& objectServer,
+    std::shared_ptr<sdbusplus::asio::connection>& conn,
+    boost::asio::io_context& io, const std::string& sensorName,
+    std::vector<thresholds::Threshold>&& thresholdsIn,
+    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,
+    const std::shared_ptr<I2CDevice>& i2cDevice) :
+    Sensor(escapeName(sensorName), std::move(thresholdsIn), sensorConfiguration,
+           objectType, false, false, max, min, conn, powerState),
+    i2cDevice(i2cDevice), objServer(objectServer),
+    inputDev(io, path, boost::asio::random_access_file::read_only),
+    waitTimer(io), path(path), sensorFactor(factor), sensorOffset(offset),
+    thresholdTimer(io)
+{
+    buffer = std::make_shared<std::array<char, 128>>();
+    std::string unitPath = sensor_paths::getPathForUnits(sensorUnits);
+    if constexpr (debug)
+    {
+        std::cerr << "Constructed sensor: path " << path << " type "
+                  << objectType << " config " << sensorConfiguration
+                  << " typename " << unitPath << " factor " << factor << " min "
+                  << min << " max " << max << " offset " << offset << " name \""
+                  << sensorName << "\"\n";
+    }
+    if (pollRate > 0.0)
+    {
+        sensorPollMs = static_cast<unsigned int>(pollRate * 1000);
+    }
+
+    std::string dbusPath = sensorPathPrefix + unitPath + "/" + name;
+
+    sensorInterface = objectServer.add_interface(
+        dbusPath, "xyz.openbmc_project.Sensor.Value");
+
+    for (const auto& threshold : thresholds)
+    {
+        std::string interface = thresholds::getInterface(threshold.level);
+        thresholdInterfaces[static_cast<size_t>(threshold.level)] =
+            objectServer.add_interface(dbusPath, interface);
+    }
+
+    // This should be called before initializing association.
+    // createInventoryAssoc() does add more associations before doing
+    // register and initialize "Associations" property.
+    if (label.empty() || tSize == thresholds.size())
+    {
+        setInitialProperties(sensorUnits);
+    }
+    else
+    {
+        setInitialProperties(sensorUnits, label, tSize);
+    }
+
+    association = objectServer.add_interface(dbusPath, association::interface);
+
+    createInventoryAssoc(conn, association, configurationPath);
+}
+
+PSUSensor::~PSUSensor()
+{
+    deactivate();
+
+    objServer.remove_interface(sensorInterface);
+    for (const auto& iface : thresholdInterfaces)
+    {
+        objServer.remove_interface(iface);
+    }
+    objServer.remove_interface(association);
+}
+
+bool PSUSensor::isActive()
+{
+    return inputDev.is_open();
+}
+
+void PSUSensor::activate(const std::string& newPath,
+                         const std::shared_ptr<I2CDevice>& newI2CDevice)
+{
+    if (isActive())
+    {
+        // Avoid activating an active sensor
+        return;
+    }
+    path = newPath;
+    i2cDevice = newI2CDevice;
+    inputDev.open(path, boost::asio::random_access_file::read_only);
+    markAvailable(true);
+    setupRead();
+}
+
+void PSUSensor::deactivate()
+{
+    markAvailable(false);
+    // close the input dev to cancel async operations
+    inputDev.close();
+    waitTimer.cancel();
+    i2cDevice = nullptr;
+    path = "";
+}
+
+void PSUSensor::setupRead()
+{
+    if (!readingStateGood())
+    {
+        markAvailable(false);
+        updateValue(std::numeric_limits<double>::quiet_NaN());
+        restartRead();
+        return;
+    }
+
+    if (buffer == nullptr)
+    {
+        std::cerr << "Buffer was invalid?";
+        return;
+    }
+
+    std::weak_ptr<PSUSensor> weak = weak_from_this();
+    // Note, we are building a asio buffer that is one char smaller than
+    // the actual data structure, so that we can always append the null
+    // terminator.  This can go away once std::from_chars<double> is available
+    // in the standard
+    inputDev.async_read_some_at(
+        0, boost::asio::buffer(buffer->data(), buffer->size() - 1),
+        [weak, buffer{buffer}](const boost::system::error_code& ec,
+                               size_t bytesRead) {
+            std::shared_ptr<PSUSensor> self = weak.lock();
+            if (!self)
+            {
+                return;
+            }
+
+            self->handleResponse(ec, bytesRead);
+        });
+}
+
+void PSUSensor::restartRead()
+{
+    std::weak_ptr<PSUSensor> weakRef = weak_from_this();
+    waitTimer.expires_after(std::chrono::milliseconds(sensorPollMs));
+    waitTimer.async_wait([weakRef](const boost::system::error_code& ec) {
+        if (ec == boost::asio::error::operation_aborted)
+        {
+            std::cerr << "Failed to reschedule\n";
+            return;
+        }
+        std::shared_ptr<PSUSensor> self = weakRef.lock();
+        if (self)
+        {
+            self->setupRead();
+        }
+    });
+}
+
+// Create a buffer expected to be able to hold more characters than will be
+// present in the input file.
+void PSUSensor::handleResponse(const boost::system::error_code& err,
+                               size_t bytesRead)
+{
+    if (err == boost::asio::error::operation_aborted)
+    {
+        std::cerr << "Read aborted\n";
+        return;
+    }
+    if ((err == boost::system::errc::bad_file_descriptor) ||
+        (err == boost::asio::error::misc_errors::not_found))
+    {
+        std::cerr << "Bad file descriptor for " << path << "\n";
+        return;
+    }
+    if (err || bytesRead == 0)
+    {
+        if (readingStateGood())
+        {
+            std::cerr << name << " read failed\n";
+        }
+        restartRead();
+        return;
+    }
+
+    // null terminate the string so we don't walk off the end
+    std::array<char, 128>& bufferRef = *buffer;
+    bufferRef[bytesRead] = '\0';
+
+    try
+    {
+        rawValue = std::stod(bufferRef.data());
+        updateValue((rawValue / sensorFactor) + sensorOffset);
+    }
+    catch (const std::invalid_argument&)
+    {
+        std::cerr << "Could not parse  input from " << path << "\n";
+        incrementError();
+    }
+
+    restartRead();
+}
+
+void PSUSensor::checkThresholds()
+{
+    if (!readingStateGood())
+    {
+        return;
+    }
+
+    thresholds::checkThresholdsPowerDelay(weak_from_this(), thresholdTimer);
+}
diff --git a/src/psu/PSUSensor.hpp b/src/psu/PSUSensor.hpp
new file mode 100644
index 0000000..09601f8
--- /dev/null
+++ b/src/psu/PSUSensor.hpp
@@ -0,0 +1,82 @@
+#pragma once
+
+#include "DeviceMgmt.hpp"
+#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_context& 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,
+              const std::shared_ptr<I2CDevice>& i2cDevice);
+    ~PSUSensor() override;
+    void setupRead();
+    void activate(const std::string& newPath,
+                  const std::shared_ptr<I2CDevice>& newI2CDevice);
+    void deactivate();
+    bool isActive();
+
+    std::shared_ptr<I2CDevice> getI2CDevice() const
+    {
+        return i2cDevice;
+    }
+
+  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;
+    std::shared_ptr<I2CDevice> i2cDevice;
+    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() 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/psu/PSUSensorMain.cpp b/src/psu/PSUSensorMain.cpp
new file mode 100644
index 0000000..3f9997c
--- /dev/null
+++ b/src/psu/PSUSensorMain.cpp
@@ -0,0 +1,1272 @@
+/*
+// 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.
+*/
+
+#include "DeviceMgmt.hpp"
+#include "PSUEvent.hpp"
+#include "PSUSensor.hpp"
+#include "PwmSensor.hpp"
+#include "SensorPaths.hpp"
+#include "Thresholds.hpp"
+#include "Utils.hpp"
+#include "VariantVisitors.hpp"
+
+#include <boost/algorithm/string/case_conv.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/asio/error.hpp>
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/post.hpp>
+#include <boost/asio/steady_timer.hpp>
+#include <boost/container/flat_map.hpp>
+#include <boost/container/flat_set.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <sdbusplus/exception.hpp>
+#include <sdbusplus/message.hpp>
+#include <sdbusplus/message/native_types.hpp>
+
+#include <algorithm>
+#include <array>
+#include <cctype>
+#include <chrono>
+#include <cmath>
+#include <cstddef>
+#include <cstdint>
+#include <exception>
+#include <filesystem>
+#include <fstream>
+#include <functional>
+#include <iostream>
+#include <memory>
+#include <regex>
+#include <stdexcept>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <variant>
+#include <vector>
+
+static constexpr bool debug = false;
+static std::regex i2cDevRegex(R"((\/i2c\-\d+\/\d+-[a-fA-F0-9]{4,4})(\/|$))");
+
+static const I2CDeviceTypeMap sensorTypes{
+    {"ADC128D818", I2CDeviceType{"adc128d818", true}},
+    {"ADM1266", I2CDeviceType{"adm1266", true}},
+    {"ADM1272", I2CDeviceType{"adm1272", true}},
+    {"ADM1275", I2CDeviceType{"adm1275", true}},
+    {"ADM1278", I2CDeviceType{"adm1278", true}},
+    {"ADM1293", I2CDeviceType{"adm1293", true}},
+    {"ADS1015", I2CDeviceType{"ads1015", true}},
+    {"ADS7830", I2CDeviceType{"ads7830", true}},
+    {"AHE50DC_FAN", I2CDeviceType{"ahe50dc_fan", true}},
+    {"BMR490", I2CDeviceType{"bmr490", true}},
+    {"cffps", I2CDeviceType{"cffps", true}},
+    {"cffps1", I2CDeviceType{"cffps", true}},
+    {"cffps2", I2CDeviceType{"cffps", true}},
+    {"cffps3", I2CDeviceType{"cffps", true}},
+    {"DPS800", I2CDeviceType{"dps800", true}},
+    {"INA219", I2CDeviceType{"ina219", true}},
+    {"INA230", I2CDeviceType{"ina230", true}},
+    {"INA238", I2CDeviceType{"ina238", true}},
+    {"IPSPS1", I2CDeviceType{"ipsps1", true}},
+    {"IR38060", I2CDeviceType{"ir38060", true}},
+    {"IR38164", I2CDeviceType{"ir38164", true}},
+    {"IR38263", I2CDeviceType{"ir38263", true}},
+    {"ISL28022", I2CDeviceType{"isl28022", true}},
+    {"ISL68137", I2CDeviceType{"isl68137", true}},
+    {"ISL68220", I2CDeviceType{"isl68220", true}},
+    {"ISL68223", I2CDeviceType{"isl68223", true}},
+    {"ISL69225", I2CDeviceType{"isl69225", true}},
+    {"ISL69243", I2CDeviceType{"isl69243", true}},
+    {"ISL69260", I2CDeviceType{"isl69260", true}},
+    {"LM25066", I2CDeviceType{"lm25066", true}},
+    {"LTC2945", I2CDeviceType{"ltc2945", true}},
+    {"LTC4286", I2CDeviceType{"ltc4286", true}},
+    {"LTC4287", I2CDeviceType{"ltc4287", true}},
+    {"MAX5970", I2CDeviceType{"max5970", true}},
+    {"MAX11607", I2CDeviceType{"max11607", false}},
+    {"MAX11615", I2CDeviceType{"max11615", false}},
+    {"MAX11617", I2CDeviceType{"max11617", false}},
+    {"MAX16601", I2CDeviceType{"max16601", true}},
+    {"MAX20710", I2CDeviceType{"max20710", true}},
+    {"MAX20730", I2CDeviceType{"max20730", true}},
+    {"MAX20734", I2CDeviceType{"max20734", true}},
+    {"MAX20796", I2CDeviceType{"max20796", true}},
+    {"MAX34451", I2CDeviceType{"max34451", true}},
+    {"MP2856", I2CDeviceType{"mp2856", true}},
+    {"MP2857", I2CDeviceType{"mp2857", true}},
+    {"MP2971", I2CDeviceType{"mp2971", true}},
+    {"MP2973", I2CDeviceType{"mp2973", true}},
+    {"MP2975", I2CDeviceType{"mp2975", true}},
+    {"MP5023", I2CDeviceType{"mp5023", true}},
+    {"MP5990", I2CDeviceType{"mp5990", true}},
+    {"MPQ8785", I2CDeviceType{"mpq8785", true}},
+    {"NCP4200", I2CDeviceType{"ncp4200", true}},
+    {"PLI1209BC", I2CDeviceType{"pli1209bc", true}},
+    {"pmbus", I2CDeviceType{"pmbus", true}},
+    {"PXE1610", I2CDeviceType{"pxe1610", true}},
+    {"RAA228000", I2CDeviceType{"raa228000", true}},
+    {"RAA228004", I2CDeviceType{"raa228004", true}},
+    {"RAA228228", I2CDeviceType{"raa228228", true}},
+    {"RAA228620", I2CDeviceType{"raa228620", true}},
+    {"RAA229001", I2CDeviceType{"raa229001", true}},
+    {"RAA229004", I2CDeviceType{"raa229004", true}},
+    {"RAA229126", I2CDeviceType{"raa229126", true}},
+    {"RTQ6056", I2CDeviceType{"rtq6056", false}},
+    {"SBRMI", I2CDeviceType{"sbrmi", true}},
+    {"smpro_hwmon", I2CDeviceType{"smpro", false}},
+    {"TDA38640", I2CDeviceType{"tda38640", true}},
+    {"TPS53679", I2CDeviceType{"tps53679", true}},
+    {"TPS546D24", I2CDeviceType{"tps546d24", true}},
+    {"XDP710", I2CDeviceType{"xdp710", true}},
+    {"XDPE11280", I2CDeviceType{"xdpe11280", true}},
+    {"XDPE12284", I2CDeviceType{"xdpe12284", true}},
+    {"XDPE152C4", I2CDeviceType{"xdpe152c4", true}},
+};
+
+enum class DevTypes
+{
+    Unknown = 0,
+    HWMON,
+    IIO
+};
+
+struct DevParams
+{
+    unsigned int matchIndex = 0;
+    std::string matchRegEx;
+    std::string nameRegEx;
+};
+
+namespace fs = std::filesystem;
+
+static boost::container::flat_map<std::string, std::shared_ptr<PSUSensor>>
+    sensors;
+static boost::container::flat_map<std::string, std::unique_ptr<PSUCombineEvent>>
+    combineEvents;
+static boost::container::flat_map<std::string, std::unique_ptr<PwmSensor>>
+    pwmSensors;
+static boost::container::flat_map<std::string, std::string> sensorTable;
+static boost::container::flat_map<std::string, PSUProperty> labelMatch;
+static EventPathList eventMatch;
+static EventPathList limitEventMatch;
+
+static boost::container::flat_map<size_t, bool> cpuPresence;
+static boost::container::flat_map<DevTypes, DevParams> devParamMap;
+
+// Function CheckEvent will check each attribute from eventMatch table in the
+// sysfs. If the attributes exists in sysfs, then store the complete path
+// of the attribute into eventPathList.
+void checkEvent(const std::string& directory, const EventPathList& eventMatch,
+                EventPathList& eventPathList)
+{
+    for (const auto& match : eventMatch)
+    {
+        const std::vector<std::string>& eventAttrs = match.second;
+        const std::string& eventName = match.first;
+        for (const auto& eventAttr : eventAttrs)
+        {
+            std::string eventPath = directory;
+            eventPath += "/";
+            eventPath += eventAttr;
+
+            std::ifstream eventFile(eventPath);
+            if (!eventFile.good())
+            {
+                continue;
+            }
+
+            eventPathList[eventName].push_back(eventPath);
+        }
+    }
+}
+
+// Check Group Events which contains more than one targets in each combine
+// events.
+void checkGroupEvent(const std::string& directory,
+                     GroupEventPathList& groupEventPathList)
+{
+    EventPathList pathList;
+    std::vector<fs::path> eventPaths;
+    if (!findFiles(fs::path(directory), R"(fan\d+_(alarm|fault))", eventPaths))
+    {
+        return;
+    }
+
+    for (const auto& eventPath : eventPaths)
+    {
+        std::string attrName = eventPath.filename();
+        pathList[attrName.substr(0, attrName.find('_'))].push_back(eventPath);
+    }
+    groupEventPathList["FanFault"] = pathList;
+}
+
+// Function checkEventLimits will check all the psu related xxx_input attributes
+// in sysfs to see if xxx_crit_alarm xxx_lcrit_alarm xxx_max_alarm
+// xxx_min_alarm exist, then store the existing paths of the alarm attributes
+// to eventPathList.
+void checkEventLimits(const std::string& sensorPathStr,
+                      const EventPathList& limitEventMatch,
+                      EventPathList& eventPathList)
+{
+    auto attributePartPos = sensorPathStr.find_last_of('_');
+    if (attributePartPos == std::string::npos)
+    {
+        // There is no '_' in the string, skip it
+        return;
+    }
+    auto attributePart =
+        std::string_view(sensorPathStr).substr(attributePartPos + 1);
+    if (attributePart != "input")
+    {
+        // If the sensor is not xxx_input, skip it
+        return;
+    }
+
+    auto prefixPart = sensorPathStr.substr(0, attributePartPos + 1);
+    for (const auto& limitMatch : limitEventMatch)
+    {
+        const std::vector<std::string>& limitEventAttrs = limitMatch.second;
+        const std::string& eventName = limitMatch.first;
+        for (const auto& limitEventAttr : limitEventAttrs)
+        {
+            auto limitEventPath = prefixPart + limitEventAttr;
+            std::ifstream eventFile(limitEventPath);
+            if (!eventFile.good())
+            {
+                continue;
+            }
+            eventPathList[eventName].push_back(limitEventPath);
+        }
+    }
+}
+
+static void checkPWMSensor(
+    const fs::path& sensorPath, std::string& labelHead,
+    const std::string& interfacePath,
+    std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
+    sdbusplus::asio::object_server& objectServer, const std::string& psuName)
+{
+    if (!labelHead.starts_with("fan"))
+    {
+        return;
+    }
+    std::string labelHeadIndex = labelHead.substr(3);
+
+    const std::string& sensorPathStr = sensorPath.string();
+    const std::string& pwmPathStr =
+        boost::replace_all_copy(sensorPathStr, "input", "target");
+    std::ifstream pwmFile(pwmPathStr);
+    if (!pwmFile.good())
+    {
+        return;
+    }
+
+    auto findPWMSensor = pwmSensors.find(psuName + labelHead);
+    if (findPWMSensor != pwmSensors.end())
+    {
+        return;
+    }
+
+    std::string name = "Pwm_";
+    name += psuName;
+    name += "_Fan_";
+    name += labelHeadIndex;
+
+    std::string objPath = interfacePath;
+    objPath += "_Fan_";
+    objPath += labelHeadIndex;
+
+    pwmSensors[psuName + labelHead] = std::make_unique<PwmSensor>(
+        name, pwmPathStr, dbusConnection, objectServer, objPath, "PSU");
+}
+
+static void createSensorsCallback(
+    boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
+    std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
+    const ManagedObjectType& sensorConfigs,
+    const std::shared_ptr<boost::container::flat_set<std::string>>&
+        sensorsChanged,
+    bool activateOnly)
+{
+    int numCreated = 0;
+    bool firstScan = sensorsChanged == nullptr;
+
+    auto devices = instantiateDevices(sensorConfigs, sensors, sensorTypes);
+
+    std::vector<fs::path> pmbusPaths;
+    findFiles(fs::path("/sys/bus/iio/devices"), "name", pmbusPaths);
+    findFiles(fs::path("/sys/class/hwmon"), "name", pmbusPaths);
+    if (pmbusPaths.empty())
+    {
+        std::cerr << "No PSU sensors in system\n";
+        return;
+    }
+
+    boost::container::flat_set<std::string> directories;
+    for (const auto& pmbusPath : pmbusPaths)
+    {
+        EventPathList eventPathList;
+        GroupEventPathList groupEventPathList;
+
+        std::ifstream nameFile(pmbusPath);
+        if (!nameFile.good())
+        {
+            std::cerr << "Failure finding pmbus path " << pmbusPath << "\n";
+            continue;
+        }
+
+        std::string pmbusName;
+        std::getline(nameFile, pmbusName);
+        nameFile.close();
+
+        if (sensorTypes.find(pmbusName) == sensorTypes.end())
+        {
+            // To avoid this error message, add your driver name to
+            // the pmbusNames vector at the top of this file.
+            std::cerr << "Driver name " << pmbusName
+                      << " not found in sensor whitelist\n";
+            continue;
+        }
+
+        auto directory = pmbusPath.parent_path();
+
+        auto ret = directories.insert(directory.string());
+        if (!ret.second)
+        {
+            std::cerr << "Duplicate path " << directory.string() << "\n";
+            continue; // check if path has already been searched
+        }
+
+        DevTypes devType = DevTypes::HWMON;
+        std::string deviceName;
+        if (directory.parent_path() == "/sys/class/hwmon")
+        {
+            std::string devicePath = fs::canonical(directory / "device");
+            std::smatch match;
+            // Find /i2c-<bus>/<bus>-<address> match in device path
+            std::regex_search(devicePath, match, i2cDevRegex);
+            if (match.empty())
+            {
+                std::cerr << "Found bad device path " << devicePath << "\n";
+                continue;
+            }
+            // Extract <bus>-<address>
+            std::string matchStr = match[1];
+            deviceName = matchStr.substr(matchStr.find_last_of('/') + 1);
+        }
+        else
+        {
+            deviceName = fs::canonical(directory).parent_path().stem();
+            devType = DevTypes::IIO;
+        }
+
+        size_t bus = 0;
+        size_t addr = 0;
+        if (!getDeviceBusAddr(deviceName, bus, addr))
+        {
+            continue;
+        }
+
+        const SensorBaseConfigMap* baseConfig = nullptr;
+        const SensorData* sensorData = nullptr;
+        const std::string* interfacePath = nullptr;
+        std::string sensorType;
+        size_t thresholdConfSize = 0;
+
+        for (const auto& [path, cfgData] : sensorConfigs)
+        {
+            sensorData = &cfgData;
+            for (const auto& [type, dt] : sensorTypes)
+            {
+                auto sensorBase = sensorData->find(configInterfaceName(type));
+                if (sensorBase != sensorData->end())
+                {
+                    baseConfig = &sensorBase->second;
+                    sensorType = type;
+                    break;
+                }
+            }
+            if (baseConfig == nullptr)
+            {
+                std::cerr << "error finding base configuration for "
+                          << deviceName << "\n";
+                continue;
+            }
+
+            auto configBus = baseConfig->find("Bus");
+            auto configAddress = baseConfig->find("Address");
+
+            if (configBus == baseConfig->end() ||
+                configAddress == baseConfig->end())
+            {
+                std::cerr << "error finding necessary entry in configuration\n";
+                continue;
+            }
+
+            const uint64_t* confBus =
+                std::get_if<uint64_t>(&(configBus->second));
+            const uint64_t* confAddr =
+                std::get_if<uint64_t>(&(configAddress->second));
+            if (confBus == nullptr || confAddr == nullptr)
+            {
+                std::cerr
+                    << "Cannot get bus or address, invalid configuration\n";
+                continue;
+            }
+
+            if ((*confBus != bus) || (*confAddr != addr))
+            {
+                if constexpr (debug)
+                {
+                    std::cerr << "Configuration skipping " << *confBus << "-"
+                              << *confAddr << " because not " << bus << "-"
+                              << addr << "\n";
+                }
+                continue;
+            }
+
+            std::vector<thresholds::Threshold> confThresholds;
+            if (!parseThresholdsFromConfig(*sensorData, confThresholds))
+            {
+                std::cerr << "error populating total thresholds\n";
+            }
+            thresholdConfSize = confThresholds.size();
+
+            interfacePath = &path.str;
+            break;
+        }
+        if (interfacePath == nullptr)
+        {
+            // To avoid this error message, add your export map entry,
+            // from Entity Manager, to sensorTypes at the top of this file.
+            std::cerr << "failed to find match for " << deviceName << "\n";
+            continue;
+        }
+
+        auto findI2CDev = devices.find(*interfacePath);
+
+        std::shared_ptr<I2CDevice> i2cDev;
+        if (findI2CDev != devices.end())
+        {
+            if (activateOnly && !findI2CDev->second.second)
+            {
+                continue;
+            }
+            i2cDev = findI2CDev->second.first;
+        }
+
+        auto findPSUName = baseConfig->find("Name");
+        if (findPSUName == baseConfig->end())
+        {
+            std::cerr << "could not determine configuration name for "
+                      << deviceName << "\n";
+            continue;
+        }
+        const std::string* psuName =
+            std::get_if<std::string>(&(findPSUName->second));
+        if (psuName == nullptr)
+        {
+            std::cerr << "Cannot find psu name, invalid configuration\n";
+            continue;
+        }
+
+        auto findCPU = baseConfig->find("CPURequired");
+        if (findCPU != baseConfig->end())
+        {
+            size_t index = std::visit(VariantToIntVisitor(), findCPU->second);
+            auto presenceFind = cpuPresence.find(index);
+            if (presenceFind == cpuPresence.end() || !presenceFind->second)
+            {
+                continue;
+            }
+        }
+
+        // on rescans, only update sensors we were signaled by
+        if (!firstScan)
+        {
+            std::string psuNameStr = "/" + escapeName(*psuName);
+            auto it =
+                std::find_if(sensorsChanged->begin(), sensorsChanged->end(),
+                             [psuNameStr](std::string& s) {
+                                 return s.ends_with(psuNameStr);
+                             });
+
+            if (it == sensorsChanged->end())
+            {
+                continue;
+            }
+            sensorsChanged->erase(it);
+        }
+        checkEvent(directory.string(), eventMatch, eventPathList);
+        checkGroupEvent(directory.string(), groupEventPathList);
+
+        PowerState readState = getPowerState(*baseConfig);
+
+        /* Check if there are more sensors in the same interface */
+        int i = 1;
+        std::vector<std::string> psuNames;
+        do
+        {
+            // Individual string fields: Name, Name1, Name2, Name3, ...
+            psuNames.push_back(
+                escapeName(std::get<std::string>(findPSUName->second)));
+            findPSUName = baseConfig->find("Name" + std::to_string(i++));
+        } while (findPSUName != baseConfig->end());
+
+        std::vector<fs::path> sensorPaths;
+        if (!findFiles(directory, devParamMap[devType].matchRegEx, sensorPaths,
+                       0))
+        {
+            std::cerr << "No PSU non-label sensor in PSU\n";
+            continue;
+        }
+
+        /* read max value in sysfs for in, curr, power, temp, ... */
+        if (!findFiles(directory, R"(\w\d+_max$)", sensorPaths, 0))
+        {
+            if constexpr (debug)
+            {
+                std::cerr << "No max name in PSU \n";
+            }
+        }
+
+        float pollRate = getPollRate(*baseConfig, PSUSensor::defaultSensorPoll);
+
+        /* Find array of labels to be exposed if it is defined in config */
+        std::vector<std::string> findLabels;
+        auto findLabelObj = baseConfig->find("Labels");
+        if (findLabelObj != baseConfig->end())
+        {
+            findLabels =
+                std::get<std::vector<std::string>>(findLabelObj->second);
+        }
+
+        std::regex sensorNameRegEx(devParamMap[devType].nameRegEx);
+        std::smatch matches;
+
+        for (const auto& sensorPath : sensorPaths)
+        {
+            bool maxLabel = false;
+            std::string labelHead;
+            std::string sensorPathStr = sensorPath.string();
+            std::string sensorNameStr = sensorPath.filename();
+            std::string sensorNameSubStr;
+            if (std::regex_search(sensorNameStr, matches, sensorNameRegEx))
+            {
+                // hwmon *_input filename without number:
+                // in, curr, power, temp, ...
+                // iio in_*_raw filename without number:
+                // voltage, temp, pressure, ...
+                sensorNameSubStr = matches[devParamMap[devType].matchIndex];
+            }
+            else
+            {
+                std::cerr << "Could not extract the alpha prefix from "
+                          << sensorNameStr;
+                continue;
+            }
+
+            std::string labelPath;
+
+            if (devType == DevTypes::HWMON)
+            {
+                /* find and differentiate _max and _input to replace "label" */
+                size_t pos = sensorPathStr.find('_');
+                if (pos != std::string::npos)
+                {
+                    std::string sensorPathStrMax = sensorPathStr.substr(pos);
+                    if (sensorPathStrMax == "_max")
+                    {
+                        labelPath = boost::replace_all_copy(sensorPathStr,
+                                                            "max", "label");
+                        maxLabel = true;
+                    }
+                    else
+                    {
+                        labelPath = boost::replace_all_copy(sensorPathStr,
+                                                            "input", "label");
+                        maxLabel = false;
+                    }
+                }
+                else
+                {
+                    continue;
+                }
+
+                std::ifstream labelFile(labelPath);
+                if (!labelFile.good())
+                {
+                    if constexpr (debug)
+                    {
+                        std::cerr << "Input file " << sensorPath
+                                  << " has no corresponding label file\n";
+                    }
+                    // hwmon *_input filename with number:
+                    // temp1, temp2, temp3, ...
+                    labelHead =
+                        sensorNameStr.substr(0, sensorNameStr.find('_'));
+                }
+                else
+                {
+                    std::string label;
+                    std::getline(labelFile, label);
+                    labelFile.close();
+                    auto findSensor = sensors.find(label);
+                    if (findSensor != sensors.end())
+                    {
+                        continue;
+                    }
+
+                    // hwmon corresponding *_label file contents:
+                    // vin1, vout1, ...
+                    labelHead = label.substr(0, label.find(' '));
+                }
+
+                /* append "max" for labelMatch */
+                if (maxLabel)
+                {
+                    labelHead.insert(0, "max");
+                }
+
+                checkPWMSensor(sensorPath, labelHead, *interfacePath,
+                               dbusConnection, objectServer, psuNames[0]);
+            }
+            else if (devType == DevTypes::IIO)
+            {
+                auto findIIOHyphen = sensorNameStr.find_last_of('_');
+                labelHead = sensorNameStr.substr(0, findIIOHyphen);
+            }
+
+            if constexpr (debug)
+            {
+                std::cerr << "Sensor type=\"" << sensorNameSubStr
+                          << "\" label=\"" << labelHead << "\"\n";
+            }
+
+            if (!findLabels.empty())
+            {
+                /* Check if this labelHead is enabled in config file */
+                if (std::find(findLabels.begin(), findLabels.end(),
+                              labelHead) == findLabels.end())
+                {
+                    if constexpr (debug)
+                    {
+                        std::cerr << "could not find " << labelHead
+                                  << " in the Labels list\n";
+                    }
+                    continue;
+                }
+            }
+
+            auto findProperty = labelMatch.find(sensorNameSubStr);
+            if (findProperty == labelMatch.end())
+            {
+                if constexpr (debug)
+                {
+                    std::cerr << "Could not find matching default property for "
+                              << sensorNameSubStr << "\n";
+                }
+                continue;
+            }
+
+            // Protect the hardcoded labelMatch list from changes,
+            // by making a copy and modifying that instead.
+            // Avoid bleedthrough of one device's customizations to
+            // the next device, as each should be independently customizable.
+            PSUProperty psuProperty = findProperty->second;
+
+            // Use label head as prefix for reading from config file,
+            // example if temp1: temp1_Name, temp1_Scale, temp1_Min, ...
+            std::string keyName = labelHead + "_Name";
+            std::string keyScale = labelHead + "_Scale";
+            std::string keyMin = labelHead + "_Min";
+            std::string keyMax = labelHead + "_Max";
+            std::string keyOffset = labelHead + "_Offset";
+            std::string keyPowerState = labelHead + "_PowerState";
+
+            bool customizedName = false;
+            auto findCustomName = baseConfig->find(keyName);
+            if (findCustomName != baseConfig->end())
+            {
+                try
+                {
+                    psuProperty.labelTypeName = std::visit(
+                        VariantToStringVisitor(), findCustomName->second);
+                }
+                catch (const std::invalid_argument&)
+                {
+                    std::cerr << "Unable to parse " << keyName << "\n";
+                    continue;
+                }
+
+                // All strings are valid, including empty string
+                customizedName = true;
+            }
+
+            bool customizedScale = false;
+            auto findCustomScale = baseConfig->find(keyScale);
+            if (findCustomScale != baseConfig->end())
+            {
+                try
+                {
+                    psuProperty.sensorScaleFactor = std::visit(
+                        VariantToUnsignedIntVisitor(), findCustomScale->second);
+                }
+                catch (const std::invalid_argument&)
+                {
+                    std::cerr << "Unable to parse " << keyScale << "\n";
+                    continue;
+                }
+
+                // Avoid later division by zero
+                if (psuProperty.sensorScaleFactor > 0)
+                {
+                    customizedScale = true;
+                }
+                else
+                {
+                    std::cerr << "Unable to accept " << keyScale << "\n";
+                    continue;
+                }
+            }
+
+            auto findCustomMin = baseConfig->find(keyMin);
+            if (findCustomMin != baseConfig->end())
+            {
+                try
+                {
+                    psuProperty.minReading = std::visit(
+                        VariantToDoubleVisitor(), findCustomMin->second);
+                }
+                catch (const std::invalid_argument&)
+                {
+                    std::cerr << "Unable to parse " << keyMin << "\n";
+                    continue;
+                }
+            }
+
+            auto findCustomMax = baseConfig->find(keyMax);
+            if (findCustomMax != baseConfig->end())
+            {
+                try
+                {
+                    psuProperty.maxReading = std::visit(
+                        VariantToDoubleVisitor(), findCustomMax->second);
+                }
+                catch (const std::invalid_argument&)
+                {
+                    std::cerr << "Unable to parse " << keyMax << "\n";
+                    continue;
+                }
+            }
+
+            auto findCustomOffset = baseConfig->find(keyOffset);
+            if (findCustomOffset != baseConfig->end())
+            {
+                try
+                {
+                    psuProperty.sensorOffset = std::visit(
+                        VariantToDoubleVisitor(), findCustomOffset->second);
+                }
+                catch (const std::invalid_argument&)
+                {
+                    std::cerr << "Unable to parse " << keyOffset << "\n";
+                    continue;
+                }
+            }
+
+            // if we find label head power state set ,override the powerstate.
+            auto findPowerState = baseConfig->find(keyPowerState);
+            if (findPowerState != baseConfig->end())
+            {
+                std::string powerState = std::visit(VariantToStringVisitor(),
+                                                    findPowerState->second);
+                setReadState(powerState, readState);
+            }
+            if (!(psuProperty.minReading < psuProperty.maxReading))
+            {
+                std::cerr << "Min must be less than Max\n";
+                continue;
+            }
+
+            // If the sensor name is being customized by config file,
+            // then prefix/suffix composition becomes not necessary,
+            // and in fact not wanted, because it gets in the way.
+            std::string psuNameFromIndex;
+            std::string nameIndexStr = "1";
+            if (!customizedName)
+            {
+                /* Find out sensor name index for this label */
+                std::regex rgx("[A-Za-z]+([0-9]+)");
+                size_t nameIndex{0};
+                if (std::regex_search(labelHead, matches, rgx))
+                {
+                    nameIndexStr = matches[1];
+                    nameIndex = std::stoi(nameIndexStr);
+
+                    // Decrement to preserve alignment, because hwmon
+                    // human-readable filenames and labels use 1-based
+                    // numbering, but the "Name", "Name1", "Name2", etc. naming
+                    // convention (the psuNames vector) uses 0-based numbering.
+                    if (nameIndex > 0)
+                    {
+                        --nameIndex;
+                    }
+                }
+                else
+                {
+                    nameIndex = 0;
+                }
+
+                if (psuNames.size() <= nameIndex)
+                {
+                    std::cerr << "Could not pair " << labelHead
+                              << " with a Name field\n";
+                    continue;
+                }
+
+                psuNameFromIndex = psuNames[nameIndex];
+
+                if constexpr (debug)
+                {
+                    std::cerr << "Sensor label head " << labelHead
+                              << " paired with " << psuNameFromIndex
+                              << " at index " << nameIndex << "\n";
+                }
+            }
+
+            if (devType == DevTypes::HWMON)
+            {
+                checkEventLimits(sensorPathStr, limitEventMatch, eventPathList);
+            }
+
+            // Similarly, if sensor scaling factor is being customized,
+            // then the below power-of-10 constraint becomes unnecessary,
+            // as config should be able to specify an arbitrary divisor.
+            unsigned int factor = psuProperty.sensorScaleFactor;
+            if (!customizedScale)
+            {
+                // Preserve existing usage of hardcoded labelMatch table below
+                factor = std::pow(10.0, factor);
+
+                /* Change first char of substring to uppercase */
+                char firstChar =
+                    static_cast<char>(std::toupper(sensorNameSubStr[0]));
+                std::string strScaleFactor =
+                    firstChar + sensorNameSubStr.substr(1) + "ScaleFactor";
+
+                // Preserve existing configs by accepting earlier syntax,
+                // example CurrScaleFactor, PowerScaleFactor, ...
+                auto findScaleFactor = baseConfig->find(strScaleFactor);
+                if (findScaleFactor != baseConfig->end())
+                {
+                    factor = std::visit(VariantToIntVisitor(),
+                                        findScaleFactor->second);
+                }
+
+                if constexpr (debug)
+                {
+                    std::cerr << "Sensor scaling factor " << factor
+                              << " string " << strScaleFactor << "\n";
+                }
+            }
+
+            std::vector<thresholds::Threshold> sensorThresholds;
+            if (!parseThresholdsFromConfig(*sensorData, sensorThresholds,
+                                           &labelHead))
+            {
+                std::cerr << "error populating thresholds for "
+                          << sensorNameSubStr << "\n";
+            }
+
+            auto findSensorUnit = sensorTable.find(sensorNameSubStr);
+            if (findSensorUnit == sensorTable.end())
+            {
+                std::cerr << sensorNameSubStr
+                          << " is not a recognized sensor type\n";
+                continue;
+            }
+
+            if constexpr (debug)
+            {
+                std::cerr << "Sensor properties: Name \""
+                          << psuProperty.labelTypeName << "\" Scale "
+                          << psuProperty.sensorScaleFactor << " Min "
+                          << psuProperty.minReading << " Max "
+                          << psuProperty.maxReading << " Offset "
+                          << psuProperty.sensorOffset << "\n";
+            }
+
+            std::string sensorName = psuProperty.labelTypeName;
+            if (customizedName)
+            {
+                if (sensorName.empty())
+                {
+                    // Allow selective disabling of an individual sensor,
+                    // by customizing its name to an empty string.
+                    std::cerr << "Sensor disabled, empty string\n";
+                    continue;
+                }
+            }
+            else
+            {
+                // Sensor name not customized, do prefix/suffix composition,
+                // preserving default behavior by using psuNameFromIndex.
+                sensorName = psuNameFromIndex + " " + psuProperty.labelTypeName;
+
+                // The labelTypeName of a fan can be:
+                // "Fan Speed 1", "Fan Speed 2", "Fan Speed 3" ...
+                if (labelHead == "fan" + nameIndexStr)
+                {
+                    sensorName += nameIndexStr;
+                }
+            }
+
+            if constexpr (debug)
+            {
+                std::cerr << "Sensor name \"" << sensorName << "\" path \""
+                          << sensorPathStr << "\" type \"" << sensorType
+                          << "\"\n";
+            }
+            // destruct existing one first if already created
+
+            auto& sensor = sensors[sensorName];
+            if (!activateOnly)
+            {
+                sensor = nullptr;
+            }
+
+            if (sensor != nullptr)
+            {
+                sensor->activate(sensorPathStr, i2cDev);
+            }
+            else
+            {
+                sensors[sensorName] = std::make_shared<PSUSensor>(
+                    sensorPathStr, sensorType, objectServer, dbusConnection, io,
+                    sensorName, std::move(sensorThresholds), *interfacePath,
+                    readState, findSensorUnit->second, factor,
+                    psuProperty.maxReading, psuProperty.minReading,
+                    psuProperty.sensorOffset, labelHead, thresholdConfSize,
+                    pollRate, i2cDev);
+                sensors[sensorName]->setupRead();
+                ++numCreated;
+                if constexpr (debug)
+                {
+                    std::cerr
+                        << "Created " << numCreated << " sensors so far\n";
+                }
+            }
+        }
+
+        if (devType == DevTypes::HWMON)
+        {
+            // OperationalStatus event
+            combineEvents[*psuName + "OperationalStatus"] = nullptr;
+            combineEvents[*psuName + "OperationalStatus"] =
+                std::make_unique<PSUCombineEvent>(
+                    objectServer, dbusConnection, io, *psuName, readState,
+                    eventPathList, groupEventPathList, "OperationalStatus",
+                    pollRate);
+        }
+    }
+
+    if constexpr (debug)
+    {
+        std::cerr << "Created total of " << numCreated << " sensors\n";
+    }
+}
+
+static void
+    getPresentCpus(std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
+{
+    static const int depth = 2;
+    static const int numKeys = 1;
+    GetSubTreeType cpuSubTree;
+
+    try
+    {
+        auto getItems = dbusConnection->new_method_call(
+            mapper::busName, mapper::path, mapper::interface, mapper::subtree);
+        getItems.append(cpuInventoryPath, static_cast<int32_t>(depth),
+                        std::array<const char*, numKeys>{
+                            "xyz.openbmc_project.Inventory.Item"});
+        auto getItemsResp = dbusConnection->call(getItems);
+        getItemsResp.read(cpuSubTree);
+    }
+    catch (sdbusplus::exception_t& e)
+    {
+        std::cerr << "error getting inventory item subtree: " << e.what()
+                  << "\n";
+        return;
+    }
+
+    for (const auto& [path, objDict] : cpuSubTree)
+    {
+        auto obj = sdbusplus::message::object_path(path).filename();
+        boost::to_lower(obj);
+
+        if (!obj.starts_with("cpu") || objDict.empty())
+        {
+            continue;
+        }
+        const std::string& owner = objDict.begin()->first;
+
+        std::variant<bool> respValue;
+        try
+        {
+            auto getPresence = dbusConnection->new_method_call(
+                owner.c_str(), path.c_str(), "org.freedesktop.DBus.Properties",
+                "Get");
+            getPresence.append("xyz.openbmc_project.Inventory.Item", "Present");
+            auto resp = dbusConnection->call(getPresence);
+            resp.read(respValue);
+        }
+        catch (sdbusplus::exception_t& e)
+        {
+            std::cerr << "Error in getting CPU presence: " << e.what() << "\n";
+            continue;
+        }
+
+        auto* present = std::get_if<bool>(&respValue);
+        if (present != nullptr && *present)
+        {
+            int cpuIndex = 0;
+            try
+            {
+                cpuIndex = std::stoi(obj.substr(obj.size() - 1));
+            }
+            catch (const std::exception& e)
+            {
+                std::cerr << "Error converting CPU index, " << e.what() << '\n';
+                continue;
+            }
+            cpuPresence[cpuIndex] = *present;
+        }
+    }
+}
+
+void createSensors(
+    boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
+    std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
+    const std::shared_ptr<boost::container::flat_set<std::string>>&
+        sensorsChanged,
+    bool activateOnly)
+{
+    auto getter = std::make_shared<GetSensorConfiguration>(
+        dbusConnection, [&io, &objectServer, &dbusConnection, sensorsChanged,
+                         activateOnly](const ManagedObjectType& sensorConfigs) {
+            createSensorsCallback(io, objectServer, dbusConnection,
+                                  sensorConfigs, sensorsChanged, activateOnly);
+        });
+    std::vector<std::string> types(sensorTypes.size());
+    for (const auto& [type, dt] : sensorTypes)
+    {
+        types.push_back(type);
+    }
+    getter->getConfiguration(types);
+}
+
+void propertyInitialize()
+{
+    sensorTable = {{"power", sensor_paths::unitWatts},
+                   {"curr", sensor_paths::unitAmperes},
+                   {"temp", sensor_paths::unitDegreesC},
+                   {"in", sensor_paths::unitVolts},
+                   {"voltage", sensor_paths::unitVolts},
+                   {"fan", sensor_paths::unitRPMs}};
+
+    labelMatch = {
+        {"pin", PSUProperty("Input Power", 3000, 0, 6, 0)},
+        {"pout", PSUProperty("Output Power", 3000, 0, 6, 0)},
+        {"power", PSUProperty("Output Power", 3000, 0, 6, 0)},
+        {"maxpin", PSUProperty("Max Input Power", 3000, 0, 6, 0)},
+        {"vin", PSUProperty("Input Voltage", 300, 0, 3, 0)},
+        {"maxvin", PSUProperty("Max Input Voltage", 300, 0, 3, 0)},
+        {"in_voltage", PSUProperty("Output Voltage", 255, 0, 3, 0)},
+        {"voltage", PSUProperty("Output Voltage", 255, 0, 3, 0)},
+        {"vout", PSUProperty("Output Voltage", 255, 0, 3, 0)},
+        {"vmon", PSUProperty("Auxiliary Input Voltage", 255, 0, 3, 0)},
+        {"in", PSUProperty("Output Voltage", 255, 0, 3, 0)},
+        {"iin", PSUProperty("Input Current", 20, 0, 3, 0)},
+        {"iout", PSUProperty("Output Current", 255, 0, 3, 0)},
+        {"curr", PSUProperty("Output Current", 255, 0, 3, 0)},
+        {"maxiout", PSUProperty("Max Output Current", 255, 0, 3, 0)},
+        {"temp", PSUProperty("Temperature", 127, -128, 3, 0)},
+        {"maxtemp", PSUProperty("Max Temperature", 127, -128, 3, 0)},
+        {"fan", PSUProperty("Fan Speed ", 30000, 0, 0, 0)}};
+
+    limitEventMatch = {{"PredictiveFailure", {"max_alarm", "min_alarm"}},
+                       {"Failure", {"crit_alarm", "lcrit_alarm"}}};
+
+    eventMatch = {{"PredictiveFailure", {"power1_alarm"}},
+                  {"Failure", {"in2_alarm"}},
+                  {"ACLost", {"in1_beep"}},
+                  {"ConfigureError", {"in1_fault"}}};
+
+    devParamMap = {
+        {DevTypes::HWMON, {1, R"(\w\d+_input$)", "([A-Za-z]+)[0-9]*_"}},
+        {DevTypes::IIO,
+         {2, R"(\w+_(raw|input)$)", "^(in|out)_([A-Za-z]+)[0-9]*_"}}};
+}
+
+static void powerStateChanged(
+    PowerState type, bool newState,
+    boost::container::flat_map<std::string, std::shared_ptr<PSUSensor>>&
+        sensors,
+    boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
+    std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
+{
+    if (newState)
+    {
+        createSensors(io, objectServer, dbusConnection, nullptr, true);
+    }
+    else
+    {
+        for (auto& [path, sensor] : sensors)
+        {
+            if (sensor != nullptr && sensor->readState == type)
+            {
+                sensor->deactivate();
+            }
+        }
+    }
+}
+
+int main()
+{
+    boost::asio::io_context io;
+    auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
+
+    sdbusplus::asio::object_server objectServer(systemBus, true);
+    objectServer.add_manager("/xyz/openbmc_project/sensors");
+    objectServer.add_manager("/xyz/openbmc_project/control");
+    systemBus->request_name("xyz.openbmc_project.PSUSensor");
+    auto sensorsChanged =
+        std::make_shared<boost::container::flat_set<std::string>>();
+
+    propertyInitialize();
+
+    auto powerCallBack = [&io, &objectServer,
+                          &systemBus](PowerState type, bool state) {
+        powerStateChanged(type, state, sensors, io, objectServer, systemBus);
+    };
+
+    setupPowerMatchCallback(systemBus, powerCallBack);
+
+    boost::asio::post(io, [&]() {
+        createSensors(io, objectServer, systemBus, nullptr, false);
+    });
+    boost::asio::steady_timer filterTimer(io);
+    std::function<void(sdbusplus::message_t&)> eventHandler =
+        [&](sdbusplus::message_t& message) {
+            if (message.is_method_error())
+            {
+                std::cerr << "callback method error\n";
+                return;
+            }
+            sensorsChanged->insert(message.get_path());
+            filterTimer.expires_after(std::chrono::seconds(3));
+            filterTimer.async_wait([&](const boost::system::error_code& ec) {
+                if (ec == boost::asio::error::operation_aborted)
+                {
+                    return;
+                }
+                if (ec)
+                {
+                    std::cerr << "timer error\n";
+                }
+                createSensors(io, objectServer, systemBus, sensorsChanged,
+                              false);
+            });
+        };
+
+    boost::asio::steady_timer cpuFilterTimer(io);
+    std::function<void(sdbusplus::message_t&)> cpuPresenceHandler =
+        [&](sdbusplus::message_t& message) {
+            std::string path = message.get_path();
+            boost::to_lower(path);
+
+            sdbusplus::message::object_path cpuPath(path);
+            std::string cpuName = cpuPath.filename();
+            if (!cpuName.starts_with("cpu"))
+            {
+                return;
+            }
+            size_t index = 0;
+            try
+            {
+                index = std::stoi(path.substr(path.size() - 1));
+            }
+            catch (const std::invalid_argument&)
+            {
+                std::cerr << "Found invalid path " << path << "\n";
+                return;
+            }
+
+            std::string objectName;
+            boost::container::flat_map<std::string, std::variant<bool>> values;
+            message.read(objectName, values);
+            auto findPresence = values.find("Present");
+            if (findPresence == values.end())
+            {
+                return;
+            }
+            try
+            {
+                cpuPresence[index] = std::get<bool>(findPresence->second);
+            }
+            catch (const std::bad_variant_access& err)
+            {
+                return;
+            }
+
+            if (!cpuPresence[index])
+            {
+                return;
+            }
+            cpuFilterTimer.expires_after(std::chrono::seconds(1));
+            cpuFilterTimer.async_wait([&](const boost::system::error_code& ec) {
+                if (ec == boost::asio::error::operation_aborted)
+                {
+                    return;
+                }
+                if (ec)
+                {
+                    std::cerr << "timer error\n";
+                    return;
+                }
+                createSensors(io, objectServer, systemBus, nullptr, false);
+            });
+        };
+
+    std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
+        setupPropertiesChangedMatches(*systemBus, sensorTypes, eventHandler);
+
+    matches.emplace_back(std::make_unique<sdbusplus::bus::match_t>(
+        static_cast<sdbusplus::bus_t&>(*systemBus),
+        "type='signal',member='PropertiesChanged',path_namespace='" +
+            std::string(cpuInventoryPath) +
+            "',arg0namespace='xyz.openbmc_project.Inventory.Item'",
+        cpuPresenceHandler));
+
+    getPresentCpus(systemBus);
+
+    setupManufacturingModeMatch(*systemBus);
+    io.run();
+}
diff --git a/src/psu/meson.build b/src/psu/meson.build
new file mode 100644
index 0000000..8f8df66
--- /dev/null
+++ b/src/psu/meson.build
@@ -0,0 +1,17 @@
+src_inc = include_directories('..')
+
+executable(
+    'psusensor',
+    'PSUEvent.cpp',
+    'PSUSensor.cpp',
+    'PSUSensorMain.cpp',
+    dependencies: [
+        default_deps,
+        devicemgmt_dep,
+        pwmsensor_dep,
+        thresholds_dep,
+        utils_dep,
+    ],
+    include_directories: src_inc,
+    install: true,
+)
\ No newline at end of file