meson: add src/ folder

This creates a conventional structure of src/ and test/ which helps to
separate top-level files such as meson.build, OWNERS, ...
from implementation files.

Git history of individual files is still accessible via e.g.
```
git log --follow -- src/thresholds.hpp
```

Tested: code compiles.

Change-Id: Ifff8b3e70437bc6a25cd6f65afd07d8a563d1a8c
Signed-off-by: Alexander Hansen <alexander.hansen@9elements.com>
diff --git a/src/calculate.cpp b/src/calculate.cpp
new file mode 100644
index 0000000..ccdb723
--- /dev/null
+++ b/src/calculate.cpp
@@ -0,0 +1,81 @@
+#include "calculate.hpp"
+
+#include <algorithm>
+#include <limits>
+#include <numeric>
+
+namespace phosphor::virtual_sensor
+{
+
+double calculateModifiedMedianValue(std::vector<double>& values)
+{
+    size_t size = values.size();
+    std::sort(values.begin(), values.end());
+    switch (size)
+    {
+        case 2:
+            /* Choose biggest value */
+            return values.at(1);
+        case 0:
+            return std::numeric_limits<double>::quiet_NaN();
+        default:
+            /* Choose median value */
+            if (size % 2 == 0)
+            {
+                // Average of the two middle values
+                return (values.at(size / 2) + values.at(size / 2 - 1)) / 2;
+            }
+            else
+            {
+                return values.at((size - 1) / 2);
+            }
+    }
+}
+
+double calculateMaximumValue(std::vector<double>& values)
+{
+    auto maxIt = std::max_element(values.begin(), values.end());
+    if (maxIt == values.end())
+    {
+        return std::numeric_limits<double>::quiet_NaN();
+    }
+    return *maxIt;
+}
+
+double calculateMinimumValue(std::vector<double>& values)
+{
+    auto maxIt = std::min_element(values.begin(), values.end());
+    if (maxIt == values.end())
+    {
+        return std::numeric_limits<double>::quiet_NaN();
+    }
+    return *maxIt;
+}
+
+double calculateSumValue(std::vector<double>& values)
+{
+    if (values.empty())
+    {
+        return std::numeric_limits<double>::quiet_NaN();
+    }
+    return std::accumulate(values.begin(), values.end(), 0.0);
+}
+
+double calculateAverageValue(std::vector<double>& values)
+{
+    if (values.empty())
+    {
+        return std::numeric_limits<double>::quiet_NaN();
+    }
+    return std::accumulate(values.begin(), values.end(), 0.0) / values.size();
+}
+
+std::map<Interface, CalculationFunc> calculationIfaces{
+    {"xyz.openbmc_project.Configuration.Average", calculateAverageValue},
+    {"xyz.openbmc_project.Configuration.Maximum", calculateMaximumValue},
+    {"xyz.openbmc_project.Configuration.Minimum", calculateMinimumValue},
+    {"xyz.openbmc_project.Configuration.Sum", calculateSumValue},
+    {"xyz.openbmc_project.Configuration.ModifiedMedian",
+     calculateModifiedMedianValue}};
+
+} // namespace phosphor::virtual_sensor
diff --git a/src/calculate.hpp b/src/calculate.hpp
new file mode 100644
index 0000000..fdf200f
--- /dev/null
+++ b/src/calculate.hpp
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <functional>
+#include <map>
+#include <string>
+#include <vector>
+
+namespace phosphor::virtual_sensor
+{
+
+using Interface = std::string;
+using CalculationFunc = std::function<double(std::vector<double>&)>;
+extern std::map<Interface, CalculationFunc> calculationIfaces;
+
+} // namespace phosphor::virtual_sensor
diff --git a/src/dbusSensor.cpp b/src/dbusSensor.cpp
new file mode 100644
index 0000000..8b6a48f
--- /dev/null
+++ b/src/dbusSensor.cpp
@@ -0,0 +1,158 @@
+#include "dbusSensor.hpp"
+
+#include "virtualSensor.hpp"
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/bus/match.hpp>
+
+#include <cmath>
+static constexpr auto sensorIntf =
+    sdbusplus::common::xyz::openbmc_project::sensor::Value::interface;
+
+/** When the Entity Manager removes the sensor, the interfaceRemoveSignal sent
+ * uses the path /xyz/openbmc_project/sensors
+ * */
+static constexpr auto interfacesSensorPath = "/xyz/openbmc_project/sensors";
+
+namespace phosphor::virtual_sensor
+{
+
+DbusSensor::DbusSensor(sdbusplus::bus_t& bus, const std::string& path,
+                       VirtualSensor& virtualSensor) :
+    bus(bus), path(path), virtualSensor(virtualSensor),
+    signalPropChange(
+        bus, sdbusplus::bus::match::rules::propertiesChanged(path, sensorIntf),
+        [this](sdbusplus::message_t& message) {
+            handleDbusSignalPropChange(message);
+        }),
+    signalRemove(
+        bus,
+        sdbusplus::bus::match::rules::interfacesRemoved(interfacesSensorPath),
+        [this](sdbusplus::message_t& message) {
+            handleDbusSignalRemove(message);
+        })
+{
+    initSensorValue();
+}
+
+double DbusSensor::getSensorValue()
+{
+    return value;
+}
+
+void DbusSensor::initSensorValue()
+{
+    try
+    {
+        // If servName is not empty, reduce one DbusCall
+        if (servName.empty())
+        {
+            value = std::numeric_limits<double>::quiet_NaN();
+            servName = getService(bus, path, sensorIntf);
+        }
+
+        if (!servName.empty())
+        {
+            signalNameOwnerChanged.reset();
+            signalNameOwnerChanged = std::make_unique<sdbusplus::bus::match_t>(
+                bus,
+                sdbusplus::bus::match::rules::nameOwnerChanged() +
+                    sdbusplus::bus::match::rules::arg0namespace(servName),
+                [this](sdbusplus::message_t& message) {
+                    handleDbusSignalNameOwnerChanged(message);
+                });
+
+            value = getDbusProperty<double>(bus, servName, path, sensorIntf,
+                                            "Value");
+        }
+    }
+    catch (const std::exception& e)
+    {
+        value = std::numeric_limits<double>::quiet_NaN();
+    }
+
+    return;
+}
+
+void DbusSensor::handleDbusSignalNameOwnerChanged(sdbusplus::message_t& msg)
+{
+    try
+    {
+        auto [name, oldOwner,
+              newOwner] = msg.unpack<std::string, std::string, std::string>();
+
+        if (!oldOwner.empty() && !name.empty())
+        {
+            if (name == servName)
+            {
+                // Connection removed
+
+                value = std::numeric_limits<double>::quiet_NaN();
+                virtualSensor.updateVirtualSensor();
+            }
+        }
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("Error in dbusSensor NameOwnerChanged: {PATH}  {ERROR}",
+                   "PATH", path, "ERROR", e);
+    }
+}
+
+void DbusSensor::handleDbusSignalPropChange(sdbusplus::message_t& msg)
+{
+    try
+    {
+        using SensorValuePropertiesVariant = sdbusplus::server::xyz::
+            openbmc_project::sensor::Value::PropertiesVariant;
+        auto [msgIfce, msgData] =
+            msg.unpack<std::string,
+                       std::map<std::string, SensorValuePropertiesVariant>>();
+
+        if (auto itr = msgData.find("Value"); itr != msgData.end())
+        {
+            double tmpValue = std::get<double>(itr->second);
+            if (!std::isfinite(tmpValue))
+            {
+                if (std::isnan(value))
+                {
+                    return;
+                }
+
+                tmpValue = std::numeric_limits<double>::quiet_NaN();
+            }
+
+            if (tmpValue != value)
+            {
+                value = tmpValue;
+                virtualSensor.updateVirtualSensor();
+            }
+        }
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("Error in dbusSensor PropertyChange: {PATH}  {ERROR}",
+                   "PATH", path, "ERROR", e);
+    }
+}
+
+void DbusSensor::handleDbusSignalRemove(sdbusplus::message_t& msg)
+{
+    try
+    {
+        auto objPath = msg.unpack<sdbusplus::message::object_path>();
+
+        if (this->path == objPath)
+        {
+            value = std::numeric_limits<double>::quiet_NaN();
+            virtualSensor.updateVirtualSensor();
+        }
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("Error in dbusSensor interfaceRemove: {PATH}  {ERROR}",
+                   "PATH", path, "ERROR", e);
+    }
+}
+
+} // namespace phosphor::virtual_sensor
diff --git a/src/dbusSensor.hpp b/src/dbusSensor.hpp
new file mode 100644
index 0000000..41d8c69
--- /dev/null
+++ b/src/dbusSensor.hpp
@@ -0,0 +1,66 @@
+#pragma once
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/bus/match.hpp>
+
+namespace phosphor::virtual_sensor
+{
+
+class VirtualSensor;
+
+class DbusSensor
+{
+  public:
+    DbusSensor() = delete;
+    virtual ~DbusSensor() = default;
+
+    /** @brief Constructs DbusSensor
+     *
+     * @param[in] bus     - Handle to system dbus
+     * @param[in] path    - The Dbus path of sensor
+     */
+    DbusSensor(sdbusplus::bus_t& bus, const std::string& path,
+               VirtualSensor& virtualSensor);
+
+    /** @brief Get sensor value from local */
+    double getSensorValue();
+
+  private:
+    /** @brief sdbusplus bus client connection. */
+    sdbusplus::bus_t& bus;
+
+    /** @brief complete path for sensor */
+    std::string path{};
+
+    /** @brief service name for the sensor daemon */
+    std::string servName{};
+
+    /** @brief point to the VirtualSensor */
+    VirtualSensor& virtualSensor;
+
+    /** @brief signal for sensor value change */
+    sdbusplus::bus::match_t signalPropChange;
+
+    /** @brief signal for sensor interface remove */
+    sdbusplus::bus::match_t signalRemove;
+
+    /** @brief Match for this dbus sensor service destroy  */
+    std::unique_ptr<sdbusplus::bus::match_t> signalNameOwnerChanged;
+
+    /** @brief dbus sensor value */
+    double value = std::numeric_limits<double>::quiet_NaN();
+
+    /** @brief Get sensor value property from D-bus interface */
+    void initSensorValue();
+
+    /** @brief Handle for this dbus sensor NameOwnerChanged */
+    void handleDbusSignalNameOwnerChanged(sdbusplus::message_t& msg);
+
+    /** @brief Handle for this dbus sensor PropertyChanged */
+    void handleDbusSignalPropChange(sdbusplus::message_t& msg);
+
+    /** @brief Handle for this dbus sensor InterfaceRemove */
+    void handleDbusSignalRemove(sdbusplus::message_t& msg);
+};
+
+} // namespace phosphor::virtual_sensor
diff --git a/src/dbusUtils.cpp b/src/dbusUtils.cpp
new file mode 100644
index 0000000..1096ed0
--- /dev/null
+++ b/src/dbusUtils.cpp
@@ -0,0 +1,81 @@
+#include "dbusUtils.hpp"
+
+#include <xyz/openbmc_project/ObjectMapper/common.hpp>
+
+const char* propIntf = "org.freedesktop.DBus.Properties";
+const char* mapperBusName = "xyz.openbmc_project.ObjectMapper";
+const char* mapperPath = "/xyz/openbmc_project/object_mapper";
+const char* mapperIntf =
+    sdbusplus::common::xyz::openbmc_project::ObjectMapper::interface;
+
+const char* methodGetObject = "GetObject";
+const char* methodGet = "Get";
+const char* methodSet = "Set";
+
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+
+std::string getService(sdbusplus::bus_t& bus, const std::string& path,
+                       const char* intf)
+{
+    /* Get mapper object for sensor path */
+    auto mapper = bus.new_method_call(mapperBusName, mapperPath, mapperIntf,
+                                      methodGetObject);
+
+    mapper.append(path.c_str());
+    mapper.append(std::vector<std::string>({intf}));
+
+    std::unordered_map<std::string, std::vector<std::string>> resp;
+
+    try
+    {
+        auto msg = bus.call(mapper);
+        msg.read(resp);
+    }
+    catch (const sdbusplus::exception_t& ex)
+    {
+        if (ex.name() == std::string(sdbusplus::xyz::openbmc_project::Common::
+                                         Error::ResourceNotFound::errName))
+        {
+            // The service isn't on D-Bus yet.
+            return std::string{};
+        }
+        else if (ex.name() == std::string("org.freedesktop.DBus.Error.Timeout"))
+        {
+            lg2::info("Mapper timeout while looking up {PATH}", "PATH", path);
+            return std::string{};
+        }
+
+        throw;
+    }
+
+    if (resp.begin() == resp.end())
+    {
+        // Shouldn't happen, if the mapper can't find it it is handled above.
+        throw std::runtime_error("Unable to find Object: " + path);
+    }
+
+    return resp.begin()->first;
+}
+
+int setDbusProperty(sdbusplus::bus_t& bus, const std::string& service,
+                    const std::string& path, const std::string& intf,
+                    const std::string& property, const Value& value)
+{
+    try
+    {
+        auto method = bus.new_method_call(service.c_str(), path.c_str(),
+                                          propIntf, methodSet);
+        method.append(intf, property, value);
+        auto msg = bus.call(method);
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::error(
+            "Failed to set dbus property. service:{SERVICE} path:{PATH} intf:{INTF} Property:{PROP},{ERROR}",
+            "SERVICE", service, "PATH", path, "INTF", intf, "PROP", property,
+            "ERROR", e);
+        return -1;
+    }
+
+    return 0;
+}
diff --git a/src/dbusUtils.hpp b/src/dbusUtils.hpp
new file mode 100644
index 0000000..4f99872
--- /dev/null
+++ b/src/dbusUtils.hpp
@@ -0,0 +1,42 @@
+#pragma once
+
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+
+using Value = std::variant<int64_t, double, std::string, bool>;
+
+std::string getService(sdbusplus::bus_t& bus, const std::string& path,
+                       const char* intf);
+
+template <typename T>
+
+T getDbusProperty(sdbusplus::bus_t& bus, const std::string& service,
+                  const std::string& path, const std::string& intf,
+                  const std::string& property)
+{
+    Value value;
+
+    auto method = bus.new_method_call(service.c_str(), path.c_str(),
+                                      "org.freedesktop.DBus.Properties", "Get");
+
+    method.append(intf, property);
+
+    try
+    {
+        auto msg = bus.call(method);
+        msg.read(value);
+    }
+    catch (const sdbusplus::exception_t& ex)
+    {
+        return std::numeric_limits<T>::quiet_NaN();
+    }
+
+    return std::get<T>(value);
+}
+
+int setDbusProperty(sdbusplus::bus_t& bus, const std::string& service,
+                    const std::string& path, const std::string& intf,
+                    const std::string& property, const Value& value);
diff --git a/src/exprtkTools.hpp b/src/exprtkTools.hpp
new file mode 100644
index 0000000..936b134
--- /dev/null
+++ b/src/exprtkTools.hpp
@@ -0,0 +1,112 @@
+/*
+ * This define will disable the ability for expressions to have comments.
+ * Expressions that have comments when parsed with a build that has this
+ * option, will result in a compilation failure.
+ */
+// #define exprtk_disable_comments
+/*
+ * This define will disable the loop-wise 'break' and 'continue'
+ * capabilities. Any expression that contains those keywords will result
+ * in a compilation failure.
+ */
+#define exprtk_disable_break_continue
+/*
+ * This define will disable the short-circuit '&' (and) and '|' (or)
+ * operators
+ */
+#define exprtk_disable_sc_andor
+/*
+ * This define will disable all enhanced features such as strength
+ * reduction and special function optimisations and expression specific
+ * type instantiations. This feature will reduce compilation times and
+ * binary sizes but will also result in massive performance degradation
+ * of expression evaluations.
+ */
+#define exprtk_disable_enhanced_features
+/*
+ * This define will disable all string processing capabilities. Any
+ * expression that contains a string or string related syntax will result
+ * in a compilation failure.
+ */
+#define exprtk_disable_string_capabilities
+
+#define exprtk_disable_rtl_io_file
+#define exprtk_disable_return_statement
+#define exprtk_disable_rtl_io
+#define exprtk_disable_superscalar_unroll
+
+/* include main exprtk header library */
+#include <exprtk.hpp>
+
+#include <cmath>
+#include <limits>
+#include <numeric>
+
+/* For floating types. (float, double, long double et al) */
+template <typename T>
+struct FuncMaxIgnoreNaN : public exprtk::ivararg_function<T>
+{
+    FuncMaxIgnoreNaN()
+    {
+        exprtk::set_min_num_args(*this, 2);
+        exprtk::set_max_num_args(*this, 255);
+    }
+
+    inline T operator()(const std::vector<T>& argList)
+    {
+        return std::reduce(std::begin(argList), std::end(argList),
+                           std::numeric_limits<double>::quiet_NaN(),
+                           [](auto a, auto b) {
+                               if (std::isnan(b))
+                               {
+                                   return a;
+                               }
+                               if (std::isnan(a))
+                               {
+                                   return b;
+                               }
+                               return std::max(a, b);
+                           });
+    }
+};
+
+template <typename T>
+struct FuncSumIgnoreNaN : public exprtk::ivararg_function<T>
+{
+    inline T operator()(const std::vector<T>& argList)
+    {
+        return std::reduce(std::begin(argList), std::end(argList),
+                           std::numeric_limits<double>::quiet_NaN(),
+                           [](auto a, auto b) {
+                               if (std::isnan(b))
+                               {
+                                   return a;
+                               }
+                               if (std::isnan(a))
+                               {
+                                   return b;
+                               }
+                               return a + b;
+                           });
+    }
+};
+
+template <typename T>
+struct FuncIfNan : public exprtk::ifunction<T>
+{
+    using exprtk::ifunction<T>::operator();
+
+    FuncIfNan() : exprtk::ifunction<T>(2) {}
+
+    inline T operator()(const T& arg1, const T& arg2)
+    {
+        if (std::isnan(arg1))
+        {
+            return arg2;
+        }
+        else
+        {
+            return arg1;
+        }
+    }
+};
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..177a0e0
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,24 @@
+#include "virtualSensor.hpp"
+
+#include <sdbusplus/server.hpp>
+
+int main()
+{
+    // Get a handle to system dbus
+    auto bus = sdbusplus::bus::new_default();
+
+    // Add the ObjectManager interface
+    sdbusplus::server::manager_t objManager(bus,
+                                            "/xyz/openbmc_project/sensors");
+
+    // Create an virtual sensors object
+    phosphor::virtual_sensor::VirtualSensors virtualSensors(bus);
+
+    // Request service bus name
+    bus.request_name("xyz.openbmc_project.VirtualSensor");
+
+    // Run the dbus loop.
+    bus.process_loop();
+
+    return 0;
+}
diff --git a/src/meson.build b/src/meson.build
new file mode 100644
index 0000000..43f7f96
--- /dev/null
+++ b/src/meson.build
@@ -0,0 +1,19 @@
+executable(
+    'virtual-sensor',
+    [
+        'calculate.cpp',
+        'dbusSensor.cpp',
+        'dbusUtils.cpp',
+        'main.cpp',
+        'virtualSensor.cpp',
+    ],
+    dependencies: [
+        dependency('nlohmann_json', include_type: 'system'),
+        dependency('phosphor-dbus-interfaces'),
+        dependency('phosphor-logging'),
+        dependency('sdbusplus'),
+        exprtk,
+    ],
+    install: true,
+    install_dir: get_option('libexecdir') / meson.project_name(),
+)
diff --git a/src/thresholds.hpp b/src/thresholds.hpp
new file mode 100644
index 0000000..1aa8e37
--- /dev/null
+++ b/src/thresholds.hpp
@@ -0,0 +1,614 @@
+#pragma once
+
+#include "dbusUtils.hpp"
+
+#include <phosphor-logging/commit.hpp>
+#include <xyz/openbmc_project/Sensor/Threshold/Critical/server.hpp>
+#include <xyz/openbmc_project/Sensor/Threshold/HardShutdown/server.hpp>
+#include <xyz/openbmc_project/Sensor/Threshold/PerformanceLoss/server.hpp>
+#include <xyz/openbmc_project/Sensor/Threshold/SoftShutdown/server.hpp>
+#include <xyz/openbmc_project/Sensor/Threshold/Warning/server.hpp>
+#include <xyz/openbmc_project/Sensor/Threshold/event.hpp>
+#include <xyz/openbmc_project/Sensor/Value/server.hpp>
+
+const constexpr char* entityManagerBusName =
+    "xyz.openbmc_project.EntityManager";
+namespace phosphor::virtual_sensor
+{
+
+template <typename... T>
+using ServerObject = typename sdbusplus::server::object_t<T...>;
+
+namespace threshold_ns =
+    sdbusplus::xyz::openbmc_project::Sensor::Threshold::server;
+using Unit = sdbusplus::xyz::openbmc_project::Sensor::server::Value::Unit;
+using CriticalObject = ServerObject<threshold_ns::Critical>;
+using WarningObject = ServerObject<threshold_ns::Warning>;
+using SoftShutdownObject = ServerObject<threshold_ns::SoftShutdown>;
+using HardShutdownObject = ServerObject<threshold_ns::HardShutdown>;
+using PerformanceLossObject = ServerObject<threshold_ns::PerformanceLoss>;
+
+template <typename T>
+struct Threshold;
+
+struct Hysteresis
+{
+    double highHysteresis;
+    double lowHysteresis;
+    auto getHighHysteresis()
+    {
+        return this->highHysteresis;
+    }
+
+    auto getLowHysteresis()
+    {
+        return this->lowHysteresis;
+    }
+
+    auto setHighHysteresis(double value)
+    {
+        this->highHysteresis = value;
+    }
+
+    auto setLowHysteresis(double value)
+    {
+        this->lowHysteresis = value;
+    }
+};
+
+template <typename error>
+auto tryCommit(const std::string& objPath, double value, Unit unit,
+               double thresholdValue)
+    -> std::optional<sdbusplus::message::object_path>
+{
+    try
+    {
+        return lg2::commit(
+            error("SENSOR_NAME", objPath, "READING_VALUE", value, "UNITS", unit,
+                  "THRESHOLD_VALUE", thresholdValue));
+    }
+    catch (std::exception&)
+    {
+        lg2::error(
+            "Failed creating a threshold log entry for {SENSOR} with value {VALUE}",
+            "SENSOR", objPath, "VALUE", value);
+        return std::nullopt;
+    }
+}
+
+static inline void tryResolve(
+    std::optional<sdbusplus::message::object_path>& log)
+{
+    if (log)
+    {
+        try
+        {
+            lg2::resolve(*log);
+        }
+        catch (std::exception&)
+        {
+            lg2::error("Failed to resolve: {LOG}", "LOG", *log);
+        }
+        log.reset();
+    }
+}
+
+template <>
+struct Threshold<WarningObject> : public WarningObject, public Hysteresis
+{
+    static constexpr auto name = "Warning";
+    using WarningObject::WarningObject;
+    using ReadingAboveUpperWarningThreshold = sdbusplus::error::xyz::
+        openbmc_project::sensor::Threshold::ReadingAboveUpperWarningThreshold;
+    using ReadingBelowLowerWarningThreshold = sdbusplus::error::xyz::
+        openbmc_project::sensor::Threshold::ReadingBelowLowerWarningThreshold;
+    /** @brief sdbusplus bus client connection. */
+    sdbusplus::bus_t& bus;
+    std::string objPath;
+    Unit units;
+
+    /** @brief Virtual sensor path/interface in entityManagerDbus.
+     * This 3 value is used to set thresholds
+     */
+    std::string entityPath;
+    std::string entityInterfaceHigh;
+    std::string entityInterfaceLow;
+    std::optional<sdbusplus::message::object_path> assertedHighLog;
+    std::optional<sdbusplus::message::object_path> assertedLowLog;
+
+    /** @brief Constructor to put object onto bus at a dbus path.
+     *  @param[in] bus - Bus to attach to.
+     *  @param[in] path - Path to attach at.
+     *  @param[in] units - units
+     */
+    Threshold(sdbusplus::bus_t& bus, const char* path, Unit units) :
+        WarningObject(bus, path), bus(bus), objPath(std::string(path)),
+        units(units)
+    {}
+
+    auto high()
+    {
+        return WarningObject::warningHigh();
+    }
+    auto low()
+    {
+        return WarningObject::warningLow();
+    }
+
+    template <typename... Args>
+    auto alarmHigh(Args... args)
+    {
+        return warningAlarmHigh(std::forward<Args>(args)...);
+    }
+
+    template <typename... Args>
+    auto alarmLow(Args... args)
+    {
+        return warningAlarmLow(std::forward<Args>(args)...);
+    }
+
+    template <typename V>
+    auto alarmHighSignalAsserted(V value)
+    {
+        assertedHighLog = tryCommit<ReadingAboveUpperWarningThreshold>(
+            objPath, value, units, high());
+        return warningHighAlarmAsserted(value);
+    }
+
+    template <typename... Args>
+    auto alarmHighSignalDeasserted(Args... args)
+    {
+        tryResolve(assertedHighLog);
+        return warningHighAlarmDeasserted(std::forward<Args>(args)...);
+    }
+
+    template <typename V>
+    auto alarmLowSignalAsserted(V value)
+    {
+        assertedLowLog = tryCommit<ReadingBelowLowerWarningThreshold>(
+            objPath, value, units, low());
+        return warningLowAlarmAsserted(value);
+    }
+
+    template <typename... Args>
+    auto alarmLowSignalDeasserted(Args... args)
+    {
+        tryResolve(assertedLowLog);
+        return warningLowAlarmDeasserted(std::forward<Args>(args)...);
+    }
+
+    /** @brief Set value of WarningHigh */
+    virtual double warningHigh(double value)
+    {
+        if (!entityPath.empty() && !entityInterfaceHigh.empty())
+        {
+            // persistThreshold
+            setDbusProperty(bus, entityManagerBusName, entityPath,
+                            entityInterfaceHigh, "Value", value);
+        }
+        return WarningObject::warningHigh(value);
+    }
+
+    /** @brief Set value of WarningLow */
+    virtual double warningLow(double value)
+    {
+        if (!entityPath.empty() && !entityInterfaceLow.empty())
+        {
+            // persistThreshold
+            setDbusProperty(bus, entityManagerBusName, entityPath,
+                            entityInterfaceLow, "Value", value);
+        }
+        return WarningObject::warningLow(value);
+    }
+
+    /** @brief Set the entitymanager interface corresponding to virtualsensor
+     * warningLow
+     */
+    void setEntityInterfaceLow(const std::string& interfaceLow)
+    {
+        entityInterfaceLow = interfaceLow;
+    }
+
+    /** @brief Set the entitymanager interface corresponding to virtualsensor
+     * warningHigh
+     */
+    void setEntityInterfaceHigh(const std::string& interfaceHigh)
+    {
+        entityInterfaceHigh = interfaceHigh;
+    }
+
+    /** @brief Set the entitymanager path corresponding to virtualsensor warning
+     */
+    void setEntityPath(const std::string& path)
+    {
+        entityPath = path;
+    }
+};
+
+template <>
+struct Threshold<CriticalObject> : public CriticalObject, public Hysteresis
+{
+    static constexpr auto name = "Critical";
+
+    /** @brief sdbusplus bus client connection. */
+    sdbusplus::bus_t& bus;
+    std::string objPath;
+    Unit units;
+
+    /** @brief Virtual sensor path/interface in entityManagerDbus.
+     * This 3 value is used to set thresholds
+     */
+    std::string entityPath;
+    std::string entityInterfaceHigh;
+    std::string entityInterfaceLow;
+    std::optional<sdbusplus::message::object_path> assertedHighLog;
+    std::optional<sdbusplus::message::object_path> assertedLowLog;
+
+    using CriticalObject::CriticalObject;
+    using ReadingAboveUpperCriticalThreshold = sdbusplus::error::xyz::
+        openbmc_project::sensor::Threshold::ReadingAboveUpperCriticalThreshold;
+    using ReadingBelowLowerCriticalThreshold = sdbusplus::error::xyz::
+        openbmc_project::sensor::Threshold::ReadingBelowLowerCriticalThreshold;
+
+    /** @brief Constructor to put object onto bus at a dbus path.
+     *  @param[in] bus - Bus to attach to.
+     *  @param[in] path - Path to attach at.
+     *  @param[in] units - units
+     */
+    Threshold(sdbusplus::bus_t& bus, const char* path, Unit units) :
+        CriticalObject(bus, path), bus(bus), objPath(std::string(path)),
+        units(units)
+    {}
+
+    auto high()
+    {
+        return CriticalObject::criticalHigh();
+    }
+    auto low()
+    {
+        return CriticalObject::criticalLow();
+    }
+
+    template <typename... Args>
+    auto alarmHigh(Args... args)
+    {
+        return criticalAlarmHigh(std::forward<Args>(args)...);
+    }
+
+    template <typename... Args>
+    auto alarmLow(Args... args)
+    {
+        return criticalAlarmLow(std::forward<Args>(args)...);
+    }
+
+    template <typename V>
+    auto alarmHighSignalAsserted(V value)
+    {
+        assertedHighLog = tryCommit<ReadingAboveUpperCriticalThreshold>(
+            objPath, value, units, high());
+        return criticalHighAlarmAsserted(value);
+    }
+
+    template <typename... Args>
+    auto alarmHighSignalDeasserted(Args... args)
+    {
+        tryResolve(assertedHighLog);
+        return criticalHighAlarmDeasserted(std::forward<Args>(args)...);
+    }
+
+    template <typename V>
+    auto alarmLowSignalAsserted(V value)
+    {
+        assertedLowLog = tryCommit<ReadingBelowLowerCriticalThreshold>(
+            objPath, value, units, low());
+        return criticalLowAlarmAsserted(value);
+    }
+
+    template <typename... Args>
+    auto alarmLowSignalDeasserted(Args... args)
+    {
+        tryResolve(assertedLowLog);
+        return criticalLowAlarmDeasserted(std::forward<Args>(args)...);
+    }
+
+    /** @brief Set value of CriticalHigh */
+    virtual double criticalHigh(double value)
+    {
+        // persistThreshold
+        if (!entityPath.empty() && !entityInterfaceHigh.empty())
+        {
+            setDbusProperty(bus, entityManagerBusName, entityPath,
+                            entityInterfaceHigh, "Value", value);
+        }
+        return CriticalObject::criticalHigh(value);
+    }
+
+    /** @brief Set value of CriticalLow */
+    virtual double criticalLow(double value)
+    {
+        if (!entityPath.empty() && !entityInterfaceLow.empty())
+        {
+            setDbusProperty(bus, entityManagerBusName, entityPath,
+                            entityInterfaceLow, "Value", value);
+        }
+        return CriticalObject::criticalLow(value);
+    }
+
+    /** @brief Set the entitymanager interface corresponding to virtualsensor
+     * criticalLow
+     */
+    void setEntityInterfaceLow(const std::string& interfaceLow)
+    {
+        entityInterfaceLow = interfaceLow;
+    }
+
+    /** @brief Set the entitymanager interface corresponding to virtualsensor
+     * criticalLow
+     */
+    void setEntityInterfaceHigh(const std::string& interfaceHigh)
+    {
+        entityInterfaceHigh = interfaceHigh;
+    }
+
+    /** @brief Set the entitymanager path corresponding to virtualsensor warning
+     */
+    void setEntityPath(const std::string& path)
+    {
+        entityPath = path;
+    }
+};
+
+template <>
+struct Threshold<SoftShutdownObject> :
+    public SoftShutdownObject,
+    public Hysteresis
+{
+    static constexpr auto name = "SoftShutdown";
+
+    /** @brief sdbusplus bus client connection. */
+    sdbusplus::bus_t& bus;
+    std::string objPath;
+    Unit units;
+
+    using SoftShutdownObject::SoftShutdownObject;
+    using ReadingAboveUpperSoftShutdownThreshold =
+        sdbusplus::error::xyz::openbmc_project::sensor::Threshold::
+            ReadingAboveUpperSoftShutdownThreshold;
+    using ReadingBelowLowerSoftShutdownThreshold =
+        sdbusplus::error::xyz::openbmc_project::sensor::Threshold::
+            ReadingBelowLowerSoftShutdownThreshold;
+    std::optional<sdbusplus::message::object_path> assertedHighLog;
+    std::optional<sdbusplus::message::object_path> assertedLowLog;
+
+    /** @brief Constructor to put object onto bus at a dbus path.
+     *  @param[in] bus - Bus to attach to.
+     *  @param[in] path - Path to attach at.
+     *  @param[in] units - units
+     */
+    Threshold(sdbusplus::bus_t& bus, const char* path, Unit units) :
+        SoftShutdownObject(bus, path), bus(bus), objPath(std::string(path)),
+        units(units)
+    {}
+
+    auto high()
+    {
+        return softShutdownHigh();
+    }
+    auto low()
+    {
+        return softShutdownLow();
+    }
+
+    template <typename... Args>
+    auto alarmHigh(Args... args)
+    {
+        return softShutdownAlarmHigh(std::forward<Args>(args)...);
+    }
+
+    template <typename... Args>
+    auto alarmLow(Args... args)
+    {
+        return softShutdownAlarmLow(std::forward<Args>(args)...);
+    }
+
+    template <typename V>
+    auto alarmHighSignalAsserted(V value)
+    {
+        assertedHighLog = tryCommit<ReadingAboveUpperSoftShutdownThreshold>(
+            objPath, value, units, high());
+        return softShutdownHighAlarmAsserted(value);
+    }
+
+    template <typename... Args>
+    auto alarmHighSignalDeasserted(Args... args)
+    {
+        tryResolve(assertedHighLog);
+        return softShutdownHighAlarmDeasserted(std::forward<Args>(args)...);
+    }
+
+    template <typename V>
+    auto alarmLowSignalAsserted(V value)
+    {
+        assertedLowLog = tryCommit<ReadingBelowLowerSoftShutdownThreshold>(
+            objPath, value, units, low());
+        return softShutdownLowAlarmAsserted(value);
+    }
+
+    template <typename... Args>
+    auto alarmLowSignalDeasserted(Args... args)
+    {
+        tryResolve(assertedLowLog);
+        return softShutdownLowAlarmDeasserted(std::forward<Args>(args)...);
+    }
+};
+
+template <>
+struct Threshold<HardShutdownObject> :
+    public HardShutdownObject,
+    public Hysteresis
+{
+    static constexpr auto name = "HardShutdown";
+
+    /** @brief sdbusplus bus client connection. */
+    sdbusplus::bus_t& bus;
+    std::string objPath;
+    Unit units;
+
+    using HardShutdownObject::HardShutdownObject;
+    using ReadingAboveUpperHardShutdownThreshold =
+        sdbusplus::error::xyz::openbmc_project::sensor::Threshold::
+            ReadingAboveUpperHardShutdownThreshold;
+    using ReadingBelowLowerHardShutdownThreshold =
+        sdbusplus::error::xyz::openbmc_project::sensor::Threshold::
+            ReadingBelowLowerHardShutdownThreshold;
+    std::optional<sdbusplus::message::object_path> assertedHighLog;
+    std::optional<sdbusplus::message::object_path> assertedLowLog;
+
+    /** @brief Constructor to put object onto bus at a dbus path.
+     *  @param[in] bus - Bus to attach to.
+     *  @param[in] path - Path to attach at.
+     *  @param[in] units - units
+     */
+    Threshold(sdbusplus::bus_t& bus, const char* path, Unit units) :
+        HardShutdownObject(bus, path), bus(bus), objPath(std::string(path)),
+        units(units)
+    {}
+
+    auto high()
+    {
+        return hardShutdownHigh();
+    }
+    auto low()
+    {
+        return hardShutdownLow();
+    }
+
+    template <typename... Args>
+    auto alarmHigh(Args... args)
+    {
+        return hardShutdownAlarmHigh(std::forward<Args>(args)...);
+    }
+
+    template <typename... Args>
+    auto alarmLow(Args... args)
+    {
+        return hardShutdownAlarmLow(std::forward<Args>(args)...);
+    }
+
+    template <typename V>
+    auto alarmHighSignalAsserted(V value)
+    {
+        assertedHighLog = tryCommit<ReadingAboveUpperHardShutdownThreshold>(
+            objPath, value, units, high());
+        return hardShutdownHighAlarmAsserted(value);
+    }
+
+    template <typename... Args>
+    auto alarmHighSignalDeasserted(Args... args)
+    {
+        tryResolve(assertedHighLog);
+        return hardShutdownHighAlarmDeasserted(std::forward<Args>(args)...);
+    }
+
+    template <typename V>
+    auto alarmLowSignalAsserted(V value)
+    {
+        assertedLowLog = tryCommit<ReadingBelowLowerHardShutdownThreshold>(
+            objPath, value, units, low());
+        return hardShutdownLowAlarmAsserted(value);
+    }
+
+    template <typename... Args>
+    auto alarmLowSignalDeasserted(Args... args)
+    {
+        tryResolve(assertedLowLog);
+        return hardShutdownLowAlarmDeasserted(std::forward<Args>(args)...);
+    }
+};
+
+template <>
+struct Threshold<PerformanceLossObject> :
+    public PerformanceLossObject,
+    public Hysteresis
+{
+    static constexpr auto name = "PerformanceLoss";
+    /** @brief sdbusplus bus client connection. */
+    sdbusplus::bus_t& bus;
+    std::string objPath;
+    Unit units;
+
+    using PerformanceLossObject::PerformanceLossObject;
+    using ReadingAboveUpperPerformanceLossThreshold =
+        sdbusplus::error::xyz::openbmc_project::sensor::Threshold::
+            ReadingAboveUpperPerformanceLossThreshold;
+    using ReadingBelowLowerPerformanceLossThreshold =
+        sdbusplus::error::xyz::openbmc_project::sensor::Threshold::
+            ReadingBelowLowerPerformanceLossThreshold;
+    double performanceLossHighHysteresis;
+    double performanceLossLowHysteresis;
+    std::optional<sdbusplus::message::object_path> assertedHighLog;
+    std::optional<sdbusplus::message::object_path> assertedLowLog;
+
+    /** @brief Constructor to put object onto bus at a dbus path.
+     *  @param[in] bus - Bus to attach to.
+     *  @param[in] path - Path to attach at.
+     *  @param[in] units - units
+     */
+    Threshold(sdbusplus::bus_t& bus, const char* path, Unit units) :
+        PerformanceLossObject(bus, path), bus(bus), objPath(std::string(path)),
+        units(units)
+    {}
+
+    auto high()
+    {
+        return performanceLossHigh();
+    }
+    auto low()
+    {
+        return performanceLossLow();
+    }
+
+    template <typename... Args>
+    auto alarmHigh(Args... args)
+    {
+        return performanceLossAlarmHigh(std::forward<Args>(args)...);
+    }
+
+    template <typename... Args>
+    auto alarmLow(Args... args)
+    {
+        return performanceLossAlarmLow(std::forward<Args>(args)...);
+    }
+
+    template <typename V>
+    auto alarmHighSignalAsserted(V value)
+    {
+        assertedHighLog = tryCommit<ReadingAboveUpperPerformanceLossThreshold>(
+            objPath, value, units, high());
+        return performanceLossHighAlarmAsserted(value);
+    }
+
+    template <typename... Args>
+    auto alarmHighSignalDeasserted(Args... args)
+    {
+        tryResolve(assertedHighLog);
+        return performanceLossHighAlarmDeasserted(std::forward<Args>(args)...);
+    }
+
+    template <typename V>
+    auto alarmLowSignalAsserted(V value)
+    {
+        assertedLowLog = tryCommit<ReadingBelowLowerPerformanceLossThreshold>(
+            objPath, value, units, low());
+        return performanceLossLowAlarmAsserted(value);
+    }
+
+    template <typename... Args>
+    auto alarmLowSignalDeasserted(Args... args)
+    {
+        tryResolve(assertedLowLog);
+        return performanceLossLowAlarmDeasserted(std::forward<Args>(args)...);
+    }
+};
+
+} // namespace phosphor::virtual_sensor
diff --git a/src/virtualSensor.cpp b/src/virtualSensor.cpp
new file mode 100644
index 0000000..c6bc6da
--- /dev/null
+++ b/src/virtualSensor.cpp
@@ -0,0 +1,941 @@
+#include "virtualSensor.hpp"
+
+#include "calculate.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <fstream>
+
+static constexpr auto sensorDbusPath = "/xyz/openbmc_project/sensors/";
+static constexpr auto vsThresholdsIfaceSuffix = ".Thresholds";
+static constexpr auto defaultHysteresis = 0;
+
+PHOSPHOR_LOG2_USING_WITH_FLAGS;
+
+namespace phosphor::virtual_sensor
+{
+
+FuncMaxIgnoreNaN<double> VirtualSensor::funcMaxIgnoreNaN;
+FuncSumIgnoreNaN<double> VirtualSensor::funcSumIgnoreNaN;
+FuncIfNan<double> VirtualSensor::funcIfNan;
+
+std::map<std::string, ValueIface::Unit> unitMap = {
+    {"temperature", ValueIface::Unit::DegreesC},
+    {"fan_tach", ValueIface::Unit::RPMS},
+    {"fan_pwm", ValueIface::Unit::Percent},
+    {"voltage", ValueIface::Unit::Volts},
+    {"altitude", ValueIface::Unit::Meters},
+    {"current", ValueIface::Unit::Amperes},
+    {"power", ValueIface::Unit::Watts},
+    {"energy", ValueIface::Unit::Joules},
+    {"utilization", ValueIface::Unit::Percent},
+    {"airflow", ValueIface::Unit::CFM},
+    {"pressure", ValueIface::Unit::Pascals}};
+
+void printParams(const VirtualSensor::ParamMap& paramMap)
+{
+    for (const auto& p : paramMap)
+    {
+        const auto& p1 = p.first;
+        const auto& p2 = p.second;
+        auto val = p2->getParamValue();
+        debug("Parameter: {PARAM} = {VALUE}", "PARAM", p1, "VALUE", val);
+    }
+}
+
+double SensorParam::getParamValue()
+{
+    switch (paramType)
+    {
+        case constParam:
+            return value;
+            break;
+        case dbusParam:
+            return dbusSensor->getSensorValue();
+            break;
+        default:
+            throw std::invalid_argument("param type not supported");
+    }
+}
+
+using AssociationList =
+    std::vector<std::tuple<std::string, std::string, std::string>>;
+
+AssociationList getAssociationsFromJson(const Json& j)
+{
+    AssociationList assocs{};
+    try
+    {
+        j.get_to(assocs);
+    }
+    catch (const std::exception& ex)
+    {
+        error("Failed to parse association: {ERROR}", "ERROR", ex);
+    }
+    return assocs;
+}
+
+template <typename U>
+struct VariantToNumber
+{
+    template <typename T>
+    U operator()(const T& t) const
+    {
+        if constexpr (std::is_convertible<T, U>::value)
+        {
+            return static_cast<U>(t);
+        }
+        throw std::invalid_argument("Invalid number type in config\n");
+    }
+};
+
+template <typename U>
+U getNumberFromConfig(const PropertyMap& map, const std::string& name,
+                      bool required,
+                      U defaultValue = std::numeric_limits<U>::quiet_NaN())
+{
+    if (auto itr = map.find(name); itr != map.end())
+    {
+        return std::visit(VariantToNumber<U>(), itr->second);
+    }
+    else if (required)
+    {
+        error("Required field {NAME} missing in config", "NAME", name);
+        throw std::invalid_argument("Required field missing in config");
+    }
+    return defaultValue;
+}
+
+const std::string getThresholdType(const std::string& direction,
+                                   const std::string& severity)
+{
+    std::string suffix;
+
+    if (direction == "less than")
+    {
+        suffix = "Low";
+    }
+    else if (direction == "greater than")
+    {
+        suffix = "High";
+    }
+    else
+    {
+        throw std::invalid_argument(
+            "Invalid threshold direction specified in entity manager");
+    }
+    return severity + suffix;
+}
+
+std::string getSeverityField(const PropertyMap& propertyMap)
+{
+    static const std::array thresholdTypes{
+        "Warning", "Critical", "PerformanceLoss", "SoftShutdown",
+        "HardShutdown"};
+
+    std::string severity;
+    if (auto itr = propertyMap.find("Severity"); itr != propertyMap.end())
+    {
+        /* Severity should be a string, but can be an unsigned int */
+        if (std::holds_alternative<std::string>(itr->second))
+        {
+            severity = std::get<std::string>(itr->second);
+            if (0 == std::ranges::count(thresholdTypes, severity))
+            {
+                throw std::invalid_argument(
+                    "Invalid threshold severity specified in entity manager");
+            }
+        }
+        else
+        {
+            auto sev =
+                getNumberFromConfig<uint64_t>(propertyMap, "Severity", true);
+            /* Checking bounds ourselves so we throw invalid argument on
+             * invalid user input */
+            if (sev >= thresholdTypes.size())
+            {
+                throw std::invalid_argument(
+                    "Invalid threshold severity specified in entity manager");
+            }
+            severity = thresholdTypes.at(sev);
+        }
+    }
+    return severity;
+}
+
+void parseThresholds(Json& thresholds, const PropertyMap& propertyMap,
+                     const std::string& entityInterface = "")
+{
+    std::string direction;
+
+    auto value = getNumberFromConfig<double>(propertyMap, "Value", true);
+
+    auto severity = getSeverityField(propertyMap);
+
+    if (auto itr = propertyMap.find("Direction"); itr != propertyMap.end())
+    {
+        direction = std::get<std::string>(itr->second);
+    }
+
+    auto threshold = getThresholdType(direction, severity);
+    thresholds[threshold] = value;
+
+    auto hysteresis =
+        getNumberFromConfig<double>(propertyMap, "Hysteresis", false);
+    if (hysteresis != std::numeric_limits<double>::quiet_NaN())
+    {
+        thresholds[threshold + "Hysteresis"] = hysteresis;
+    }
+
+    if (!entityInterface.empty())
+    {
+        thresholds[threshold + "Direction"] = entityInterface;
+    }
+}
+
+void VirtualSensor::parseConfigInterface(const PropertyMap& propertyMap,
+                                         const std::string& sensorType,
+                                         const std::string& interface)
+{
+    /* Parse sensors / DBus params */
+    if (auto itr = propertyMap.find("Sensors"); itr != propertyMap.end())
+    {
+        auto sensors = std::get<std::vector<std::string>>(itr->second);
+        for (auto sensor : sensors)
+        {
+            std::replace(sensor.begin(), sensor.end(), ' ', '_');
+            auto sensorObjPath = sensorDbusPath + sensorType + "/" + sensor;
+
+            auto paramPtr =
+                std::make_unique<SensorParam>(bus, sensorObjPath, *this);
+            symbols.create_variable(sensor);
+            paramMap.emplace(std::move(sensor), std::move(paramPtr));
+        }
+    }
+    /* Get expression string */
+    if (!calculationIfaces.contains(interface))
+    {
+        throw std::invalid_argument("Invalid expression in interface");
+    }
+    exprStr = interface;
+
+    /* Get optional min and max input and output values */
+    ValueIface::maxValue(
+        getNumberFromConfig<double>(propertyMap, "MaxValue", false));
+    ValueIface::minValue(
+        getNumberFromConfig<double>(propertyMap, "MinValue", false));
+    maxValidInput =
+        getNumberFromConfig<double>(propertyMap, "MaxValidInput", false,
+                                    std::numeric_limits<double>::infinity());
+    minValidInput =
+        getNumberFromConfig<double>(propertyMap, "MinValidInput", false,
+                                    -std::numeric_limits<double>::infinity());
+}
+
+void VirtualSensor::initVirtualSensor(const Json& sensorConfig,
+                                      const std::string& objPath,
+                                      const std::string& type)
+{
+    static const Json empty{};
+
+    units = unitMap.at(type);
+
+    /* Get threshold values if defined in config */
+    auto threshold = sensorConfig.value("Threshold", empty);
+
+    createThresholds(threshold, objPath, units);
+
+    /* Get MaxValue, MinValue setting if defined in config */
+    auto confDesc = sensorConfig.value("Desc", empty);
+    if (auto maxConf = confDesc.find("MaxValue");
+        maxConf != confDesc.end() && maxConf->is_number())
+    {
+        ValueIface::maxValue(maxConf->get<double>());
+    }
+    if (auto minConf = confDesc.find("MinValue");
+        minConf != confDesc.end() && minConf->is_number())
+    {
+        ValueIface::minValue(minConf->get<double>());
+    }
+
+    /* Get optional association */
+    auto assocJson = sensorConfig.value("Associations", empty);
+    if (!assocJson.empty())
+    {
+        auto assocs = getAssociationsFromJson(assocJson);
+        if (!assocs.empty())
+        {
+            associationIface =
+                std::make_unique<AssociationObject>(bus, objPath.c_str());
+            associationIface->associations(assocs);
+        }
+    }
+
+    /* Get expression string */
+    static constexpr auto exprKey = "Expression";
+    if (sensorConfig.contains(exprKey))
+    {
+        auto& ref = sensorConfig.at(exprKey);
+        if (ref.is_array())
+        {
+            exprStr = std::string{};
+            for (auto& s : ref)
+            {
+                exprStr += s;
+            }
+        }
+        else if (ref.is_string())
+        {
+            exprStr = std::string{ref};
+        }
+    }
+
+    /* Get all the parameter listed in configuration */
+    auto params = sensorConfig.value("Params", empty);
+
+    /* Check for constant parameter */
+    const auto& consParams = params.value("ConstParam", empty);
+    if (!consParams.empty())
+    {
+        for (auto& j : consParams)
+        {
+            if (j.find("ParamName") != j.end())
+            {
+                auto paramPtr = std::make_unique<SensorParam>(j["Value"]);
+                std::string name = j["ParamName"];
+                symbols.create_variable(name);
+                paramMap.emplace(std::move(name), std::move(paramPtr));
+            }
+            else
+            {
+                /* Invalid configuration */
+                throw std::invalid_argument(
+                    "ParamName not found in configuration");
+            }
+        }
+    }
+
+    /* Check for dbus parameter */
+    auto dbusParams = params.value("DbusParam", empty);
+    if (!dbusParams.empty())
+    {
+        for (auto& j : dbusParams)
+        {
+            /* Get parameter dbus sensor descriptor */
+            auto desc = j.value("Desc", empty);
+            if ((!desc.empty()) && (j.find("ParamName") != j.end()))
+            {
+                std::string sensorType = desc.value("SensorType", "");
+                std::string name = desc.value("Name", "");
+
+                if (!sensorType.empty() && !name.empty())
+                {
+                    auto path = sensorDbusPath + sensorType + "/" + name;
+                    auto paramPtr =
+                        std::make_unique<SensorParam>(bus, path, *this);
+                    std::string paramName = j["ParamName"];
+                    symbols.create_variable(paramName);
+                    paramMap.emplace(std::move(paramName), std::move(paramPtr));
+                }
+            }
+        }
+    }
+
+    symbols.add_constants();
+    symbols.add_package(vecopsPackage);
+    symbols.add_function("maxIgnoreNaN", funcMaxIgnoreNaN);
+    symbols.add_function("sumIgnoreNaN", funcSumIgnoreNaN);
+    symbols.add_function("ifNan", funcIfNan);
+
+    expression.register_symbol_table(symbols);
+
+    /* parser from exprtk */
+    exprtk::parser<double> parser{};
+    if (!parser.compile(exprStr, expression))
+    {
+        error("Expression compilation failed");
+
+        for (std::size_t i = 0; i < parser.error_count(); ++i)
+        {
+            auto err = parser.get_error(i);
+            error("Error parsing token at {POSITION}: {ERROR}", "POSITION",
+                  err.token.position, "TYPE",
+                  exprtk::parser_error::to_str(err.mode), "ERROR",
+                  err.diagnostic);
+        }
+        throw std::runtime_error("Expression compilation failed");
+    }
+
+    /* Print all parameters for debug purpose only */
+    printParams(paramMap);
+}
+
+void VirtualSensor::createAssociation(const std::string& objPath,
+                                      const std::string& entityPath)
+{
+    if (objPath.empty() || entityPath.empty())
+    {
+        return;
+    }
+
+    std::filesystem::path p(entityPath);
+    auto assocsDbus =
+        AssociationList{{"chassis", "all_sensors", p.parent_path().string()}};
+    associationIface =
+        std::make_unique<AssociationObject>(bus, objPath.c_str());
+    associationIface->associations(assocsDbus);
+}
+
+void VirtualSensor::initVirtualSensor(
+    const InterfaceMap& interfaceMap, const std::string& objPath,
+    const std::string& sensorType, const std::string& calculationIface)
+{
+    Json thresholds;
+    const std::string vsThresholdsIntf =
+        calculationIface + vsThresholdsIfaceSuffix;
+
+    units = unitMap.at(sensorType);
+
+    for (const auto& [interface, propertyMap] : interfaceMap)
+    {
+        /* Each threshold is on it's own interface with a number as a suffix
+         * eg xyz.openbmc_project.Configuration.ModifiedMedian.Thresholds1 */
+        if (interface.find(vsThresholdsIntf) != std::string::npos)
+        {
+            parseThresholds(thresholds, propertyMap, interface);
+        }
+        else if (interface == calculationIface)
+        {
+            parseConfigInterface(propertyMap, sensorType, interface);
+        }
+    }
+
+    createThresholds(thresholds, objPath, units);
+    symbols.add_constants();
+    symbols.add_package(vecopsPackage);
+    expression.register_symbol_table(symbols);
+
+    createAssociation(objPath, entityPath);
+    /* Print all parameters for debug purpose only */
+    printParams(paramMap);
+}
+
+void VirtualSensor::setSensorValue(double value)
+{
+    value = std::clamp(value, ValueIface::minValue(), ValueIface::maxValue());
+    ValueIface::value(value);
+}
+
+double VirtualSensor::calculateValue(const std::string& calculation,
+                                     const VirtualSensor::ParamMap& paramMap)
+{
+    auto iter = calculationIfaces.find(calculation);
+    if (iter == calculationIfaces.end())
+    {
+        return std::numeric_limits<double>::quiet_NaN();
+    }
+
+    std::vector<double> values;
+    for (auto& param : paramMap)
+    {
+        auto& name = param.first;
+        if (auto var = symbols.get_variable(name))
+        {
+            if (!sensorInRange(var->ref()))
+            {
+                continue;
+            }
+            values.push_back(var->ref());
+        }
+    }
+
+    return iter->second(values);
+}
+
+bool VirtualSensor::sensorInRange(double value)
+{
+    if (value <= this->maxValidInput && value >= this->minValidInput)
+    {
+        return true;
+    }
+    return false;
+}
+
+void VirtualSensor::updateVirtualSensor()
+{
+    for (auto& param : paramMap)
+    {
+        auto& name = param.first;
+        auto& data = param.second;
+        if (auto var = symbols.get_variable(name))
+        {
+            var->ref() = data->getParamValue();
+        }
+        else
+        {
+            /* Invalid parameter */
+            throw std::invalid_argument("ParamName not found in symbols");
+        }
+    }
+    auto val = (!calculationIfaces.contains(exprStr))
+                   ? expression.value()
+                   : calculateValue(exprStr, paramMap);
+
+    /* Set sensor value to dbus interface */
+    setSensorValue(val);
+    debug("Sensor {NAME} = {VALUE}", "NAME", this->name, "VALUE", val);
+
+    /* Check sensor thresholds and log required message */
+    auto changed = false;
+    auto normal = checkThresholds(val, perfLossIface, changed);
+    normal &= checkThresholds(val, warningIface, changed);
+    normal &= checkThresholds(val, criticalIface, changed);
+    normal &= checkThresholds(val, softShutdownIface, changed);
+    normal &= checkThresholds(val, hardShutdownIface, changed);
+    if (changed && normal)
+    {
+        namespace Events =
+            sdbusplus::event::xyz::openbmc_project::sensor::Threshold;
+
+        try
+        {
+            lg2::commit(Events::SensorReadingNormalRange(
+                "SENSOR_NAME", objPath, "READING_VALUE", val, "UNITS", units));
+        }
+        catch (std::exception&)
+        {
+            lg2::debug("Failed to create normal range event {NAME}", "NAME",
+                       objPath);
+        }
+    }
+}
+
+void VirtualSensor::createThresholds(
+    const Json& threshold, const std::string& objPath, ValueIface::Unit units)
+{
+    if (threshold.empty())
+    {
+        return;
+    }
+    // Only create the threshold interfaces if
+    // at least one of their values is present.
+    if (threshold.contains("CriticalHigh") || threshold.contains("CriticalLow"))
+    {
+        criticalIface = std::make_unique<Threshold<CriticalObject>>(
+            bus, objPath.c_str(), units);
+
+        if (threshold.contains("CriticalHigh"))
+        {
+            criticalIface->setEntityInterfaceHigh(
+                threshold.value("CriticalHighDirection", ""));
+            debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
+                  "INTF", threshold.value("CriticalHighDirection", ""));
+        }
+        if (threshold.contains("CriticalLow"))
+        {
+            criticalIface->setEntityInterfaceLow(
+                threshold.value("CriticalLowDirection", ""));
+            debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
+                  "INTF", threshold.value("CriticalLowDirection", ""));
+        }
+
+        criticalIface->setEntityPath(entityPath);
+        debug("Sensor Threshold:{NAME} = path:{PATH}", "NAME", objPath, "PATH",
+              entityPath);
+
+        criticalIface->criticalHigh(threshold.value(
+            "CriticalHigh", std::numeric_limits<double>::quiet_NaN()));
+        criticalIface->criticalLow(threshold.value(
+            "CriticalLow", std::numeric_limits<double>::quiet_NaN()));
+        criticalIface->setHighHysteresis(
+            threshold.value("CriticalHighHysteresis", defaultHysteresis));
+        criticalIface->setLowHysteresis(
+            threshold.value("CriticalLowHysteresis", defaultHysteresis));
+    }
+
+    if (threshold.contains("WarningHigh") || threshold.contains("WarningLow"))
+    {
+        warningIface = std::make_unique<Threshold<WarningObject>>(
+            bus, objPath.c_str(), units);
+
+        if (threshold.contains("WarningHigh"))
+        {
+            warningIface->setEntityInterfaceHigh(
+                threshold.value("WarningHighDirection", ""));
+            debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
+                  "INTF", threshold.value("WarningHighDirection", ""));
+        }
+        if (threshold.contains("WarningLow"))
+        {
+            warningIface->setEntityInterfaceLow(
+                threshold.value("WarningLowDirection", ""));
+            debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
+                  "INTF", threshold.value("WarningLowDirection", ""));
+        }
+
+        warningIface->setEntityPath(entityPath);
+        debug("Sensor Threshold:{NAME} = path:{PATH}", "NAME", objPath, "PATH",
+              entityPath);
+
+        warningIface->warningHigh(threshold.value(
+            "WarningHigh", std::numeric_limits<double>::quiet_NaN()));
+        warningIface->warningLow(threshold.value(
+            "WarningLow", std::numeric_limits<double>::quiet_NaN()));
+        warningIface->setHighHysteresis(
+            threshold.value("WarningHighHysteresis", defaultHysteresis));
+        warningIface->setLowHysteresis(
+            threshold.value("WarningLowHysteresis", defaultHysteresis));
+    }
+
+    if (threshold.contains("HardShutdownHigh") ||
+        threshold.contains("HardShutdownLow"))
+    {
+        hardShutdownIface = std::make_unique<Threshold<HardShutdownObject>>(
+            bus, objPath.c_str(), units);
+
+        hardShutdownIface->hardShutdownHigh(threshold.value(
+            "HardShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
+        hardShutdownIface->hardShutdownLow(threshold.value(
+            "HardShutdownLow", std::numeric_limits<double>::quiet_NaN()));
+        hardShutdownIface->setHighHysteresis(
+            threshold.value("HardShutdownHighHysteresis", defaultHysteresis));
+        hardShutdownIface->setLowHysteresis(
+            threshold.value("HardShutdownLowHysteresis", defaultHysteresis));
+    }
+
+    if (threshold.contains("SoftShutdownHigh") ||
+        threshold.contains("SoftShutdownLow"))
+    {
+        softShutdownIface = std::make_unique<Threshold<SoftShutdownObject>>(
+            bus, objPath.c_str(), units);
+
+        softShutdownIface->softShutdownHigh(threshold.value(
+            "SoftShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
+        softShutdownIface->softShutdownLow(threshold.value(
+            "SoftShutdownLow", std::numeric_limits<double>::quiet_NaN()));
+        softShutdownIface->setHighHysteresis(
+            threshold.value("SoftShutdownHighHysteresis", defaultHysteresis));
+        softShutdownIface->setLowHysteresis(
+            threshold.value("SoftShutdownLowHysteresis", defaultHysteresis));
+    }
+
+    if (threshold.contains("PerformanceLossHigh") ||
+        threshold.contains("PerformanceLossLow"))
+    {
+        perfLossIface = std::make_unique<Threshold<PerformanceLossObject>>(
+            bus, objPath.c_str(), units);
+
+        perfLossIface->performanceLossHigh(threshold.value(
+            "PerformanceLossHigh", std::numeric_limits<double>::quiet_NaN()));
+        perfLossIface->performanceLossLow(threshold.value(
+            "PerformanceLossLow", std::numeric_limits<double>::quiet_NaN()));
+        perfLossIface->setHighHysteresis(threshold.value(
+            "PerformanceLossHighHysteresis", defaultHysteresis));
+        perfLossIface->setLowHysteresis(
+            threshold.value("PerformanceLossLowHysteresis", defaultHysteresis));
+    }
+}
+
+ManagedObjectType VirtualSensors::getObjectsFromDBus()
+{
+    ManagedObjectType objects;
+
+    try
+    {
+        auto method = bus.new_method_call(
+            "xyz.openbmc_project.EntityManager",
+            "/xyz/openbmc_project/inventory",
+            "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
+        auto reply = bus.call(method);
+        reply.read(objects);
+    }
+    catch (const sdbusplus::exception_t& ex)
+    {
+        // If entity manager isn't running yet, keep going.
+        if (std::string("org.freedesktop.DBus.Error.ServiceUnknown") !=
+            ex.name())
+        {
+            error("Could not reach entity-manager: {ERROR}", "ERROR", ex);
+            throw;
+        }
+    }
+
+    return objects;
+}
+
+void VirtualSensors::propertiesChanged(sdbusplus::message_t& msg)
+{
+    std::string interface;
+    PropertyMap properties;
+
+    msg.read(interface, properties);
+
+    /* We get multiple callbacks for one sensor. 'Type' is a required field and
+     * is a unique label so use to to only proceed once per sensor */
+    if (properties.contains("Type"))
+    {
+        if (calculationIfaces.contains(interface))
+        {
+            createVirtualSensorsFromDBus(interface);
+        }
+    }
+}
+
+/** @brief Parsing Virtual Sensor config JSON file  */
+Json VirtualSensors::parseConfigFile()
+{
+    using path = std::filesystem::path;
+    auto configFile = []() -> path {
+        static constexpr auto name = "virtual_sensor_config.json";
+
+        for (auto pathSeg : {std::filesystem::current_path(),
+                             path{"/var/lib/phosphor-virtual-sensor"},
+                             path{"/usr/share/phosphor-virtual-sensor"}})
+        {
+            auto file = pathSeg / name;
+            if (std::filesystem::exists(file))
+            {
+                return file;
+            }
+        }
+        return name;
+    }();
+
+    std::ifstream jsonFile(configFile);
+    if (!jsonFile.is_open())
+    {
+        error("config JSON file {FILENAME} not found", "FILENAME", configFile);
+        return {};
+    }
+
+    auto data = Json::parse(jsonFile, nullptr, false);
+    if (data.is_discarded())
+    {
+        error("config readings JSON parser failure with {FILENAME}", "FILENAME",
+              configFile);
+        throw std::exception{};
+    }
+
+    return data;
+}
+
+const std::string getSensorTypeFromUnit(const std::string& unit)
+{
+    std::string unitPrefix = "xyz.openbmc_project.Sensor.Value.Unit.";
+    for (auto [type, unitObj] : unitMap)
+    {
+        auto unitPath = ValueIface::convertUnitToString(unitObj);
+        if (unitPath == (unitPrefix + unit))
+        {
+            return type;
+        }
+    }
+    return "";
+}
+
+void VirtualSensors::setupMatches()
+{
+    /* Already setup */
+    if (!this->matches.empty())
+    {
+        return;
+    }
+
+    /* Setup matches */
+    auto eventHandler = [this](sdbusplus::message_t& message) {
+        if (message.is_method_error())
+        {
+            error("Callback method error");
+            return;
+        }
+        this->propertiesChanged(message);
+    };
+
+    for (const auto& [iface, _] : calculationIfaces)
+    {
+        auto match = std::make_unique<sdbusplus::bus::match_t>(
+            bus,
+            sdbusplus::bus::match::rules::propertiesChangedNamespace(
+                "/xyz/openbmc_project/inventory", iface),
+            eventHandler);
+        this->matches.emplace_back(std::move(match));
+    }
+}
+
+void VirtualSensors::createVirtualSensorsFromDBus(
+    const std::string& calculationIface)
+{
+    if (calculationIface.empty())
+    {
+        error("No calculation type supplied");
+        return;
+    }
+    auto objects = getObjectsFromDBus();
+
+    /* Get virtual sensors config data */
+    for (const auto& [path, interfaceMap] : objects)
+    {
+        /* Find Virtual Sensor interfaces */
+        auto intfIter = interfaceMap.find(calculationIface);
+        if (intfIter == interfaceMap.end())
+        {
+            continue;
+        }
+
+        std::string name = path.filename();
+        if (name.empty())
+        {
+            error("Virtual Sensor name not found in entity manager config");
+            continue;
+        }
+        if (virtualSensorsMap.contains(name))
+        {
+            error("A virtual sensor named {NAME} already exists", "NAME", name);
+            continue;
+        }
+
+        /* Extract the virtual sensor type as we need this to initialize the
+         * sensor */
+        std::string sensorType, sensorUnit;
+        auto propertyMap = intfIter->second;
+        auto proIter = propertyMap.find("Units");
+        if (proIter != propertyMap.end())
+        {
+            sensorUnit = std::get<std::string>(proIter->second);
+        }
+        sensorType = getSensorTypeFromUnit(sensorUnit);
+        if (sensorType.empty())
+        {
+            error("Sensor unit type {TYPE} is not supported", "TYPE",
+                  sensorUnit);
+            continue;
+        }
+
+        try
+        {
+            auto objpath = static_cast<std::string>(path);
+            auto virtObjPath = sensorDbusPath + sensorType + "/" + name;
+
+            auto virtualSensorPtr = std::make_unique<VirtualSensor>(
+                bus, virtObjPath.c_str(), interfaceMap, name, sensorType,
+                calculationIface, objpath);
+            info("Added a new virtual sensor: {NAME} {TYPE}", "NAME", name,
+                 "TYPE", sensorType);
+            virtualSensorPtr->updateVirtualSensor();
+
+            /* Initialize unit value for virtual sensor */
+            virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
+            virtualSensorPtr->emit_object_added();
+
+            virtualSensorsMap.emplace(name, std::move(virtualSensorPtr));
+
+            /* Setup match for interfaces removed */
+            auto intfRemoved = [this, objpath,
+                                name](sdbusplus::message_t& message) {
+                if (!virtualSensorsMap.contains(name))
+                {
+                    return;
+                }
+                sdbusplus::message::object_path path;
+                message.read(path);
+                if (static_cast<const std::string&>(path) == objpath)
+                {
+                    info("Removed a virtual sensor: {NAME}", "NAME", name);
+                    virtualSensorsMap.erase(name);
+                }
+            };
+            auto matchOnRemove = std::make_unique<sdbusplus::bus::match_t>(
+                bus,
+                sdbusplus::bus::match::rules::interfacesRemoved() +
+                    sdbusplus::bus::match::rules::argNpath(0, objpath),
+                intfRemoved);
+            /* TODO: slight race condition here. Check that the config still
+             * exists */
+            this->matches.emplace_back(std::move(matchOnRemove));
+        }
+        catch (const std::invalid_argument& ia)
+        {
+            error("Failed to set up virtual sensor: {ERROR}", "ERROR", ia);
+        }
+    }
+}
+
+void VirtualSensors::createVirtualSensors()
+{
+    static const Json empty{};
+
+    auto data = parseConfigFile();
+
+    // print values
+    debug("JSON: {JSON}", "JSON", data.dump());
+
+    /* Get virtual sensors  config data */
+    for (const auto& j : data)
+    {
+        auto desc = j.value("Desc", empty);
+        if (!desc.empty())
+        {
+            if (desc.value("Config", "") == "D-Bus")
+            {
+                /* Look on D-Bus for a virtual sensor config. Set up matches
+                 * first because the configs may not be on D-Bus yet and we
+                 * don't want to miss them */
+                setupMatches();
+
+                for (const auto& intf : std::views::keys(calculationIfaces))
+                {
+                    createVirtualSensorsFromDBus(intf);
+                }
+                continue;
+            }
+
+            std::string sensorType = desc.value("SensorType", "");
+            std::string name = desc.value("Name", "");
+            std::replace(name.begin(), name.end(), ' ', '_');
+
+            if (!name.empty() && !sensorType.empty())
+            {
+                if (unitMap.find(sensorType) == unitMap.end())
+                {
+                    error("Sensor type {TYPE} is not supported", "TYPE",
+                          sensorType);
+                }
+                else
+                {
+                    if (virtualSensorsMap.find(name) != virtualSensorsMap.end())
+                    {
+                        error("A virtual sensor named {NAME} already exists",
+                              "NAME", name);
+                        continue;
+                    }
+                    auto objPath = sensorDbusPath + sensorType + "/" + name;
+
+                    auto virtualSensorPtr = std::make_unique<VirtualSensor>(
+                        bus, objPath.c_str(), j, name, sensorType);
+
+                    info("Added a new virtual sensor: {NAME}", "NAME", name);
+                    virtualSensorPtr->updateVirtualSensor();
+
+                    /* Initialize unit value for virtual sensor */
+                    virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
+                    virtualSensorPtr->emit_object_added();
+
+                    virtualSensorsMap.emplace(std::move(name),
+                                              std::move(virtualSensorPtr));
+                }
+            }
+            else
+            {
+                error(
+                    "Sensor type ({TYPE}) or name ({NAME}) not found in config file",
+                    "NAME", name, "TYPE", sensorType);
+            }
+        }
+        else
+        {
+            error("Descriptor for new virtual sensor not found in config file");
+        }
+    }
+}
+
+} // namespace phosphor::virtual_sensor
diff --git a/src/virtualSensor.hpp b/src/virtualSensor.hpp
new file mode 100644
index 0000000..1b3683f
--- /dev/null
+++ b/src/virtualSensor.hpp
@@ -0,0 +1,319 @@
+#pragma once
+
+#include "dbusSensor.hpp"
+#include "exprtkTools.hpp"
+#include "thresholds.hpp"
+
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus.hpp>
+#include <xyz/openbmc_project/Association/Definitions/server.hpp>
+#include <xyz/openbmc_project/Sensor/Value/server.hpp>
+
+#include <map>
+#include <string>
+
+namespace phosphor::virtual_sensor
+{
+
+PHOSPHOR_LOG2_USING_WITH_FLAGS;
+
+using BasicVariantType =
+    std::variant<std::string, int64_t, uint64_t, double, int32_t, uint32_t,
+                 int16_t, uint16_t, uint8_t, bool, std::vector<std::string>>;
+
+using PropertyMap = std::map<std::string, BasicVariantType>;
+
+using InterfaceMap = std::map<std::string, PropertyMap>;
+
+using ManagedObjectType =
+    std::map<sdbusplus::message::object_path, InterfaceMap>;
+
+using Json = nlohmann::json;
+
+template <typename... T>
+using ServerObject = typename sdbusplus::server::object_t<T...>;
+
+using ValueIface = sdbusplus::xyz::openbmc_project::Sensor::server::Value;
+using ValueObject = ServerObject<ValueIface>;
+
+using AssociationIface =
+    sdbusplus::xyz::openbmc_project::Association::server::Definitions;
+using AssociationObject = ServerObject<AssociationIface>;
+
+class SensorParam
+{
+  public:
+    SensorParam() = delete;
+    virtual ~SensorParam() = default;
+
+    enum ParamType
+    {
+        constParam,
+        dbusParam
+    };
+
+    /** @brief Constructs SensorParam (type = constParam)
+     *
+     * @param[in] value - Value of constant parameter
+     */
+    explicit SensorParam(double value) : value(value), paramType(constParam) {}
+
+    /** @brief Constructs SensorParam (type = dbusParam)
+     *
+     * @param[in] bus     - Handle to system dbus
+     * @param[in] path    - The Dbus path of sensor
+     * @param[in] ctx     - sensor context for update
+     */
+    SensorParam(sdbusplus::bus_t& bus, const std::string& path,
+                VirtualSensor& ctx) :
+        dbusSensor(std::make_unique<DbusSensor>(bus, path, ctx)),
+        paramType(dbusParam)
+    {}
+
+    /** @brief Get sensor value property from D-bus interface */
+    double getParamValue();
+
+  private:
+    std::unique_ptr<DbusSensor> dbusSensor = nullptr;
+
+    /** @brief virtual sensor value */
+    double value = std::numeric_limits<double>::quiet_NaN();
+    ParamType paramType;
+};
+
+class VirtualSensor : public ValueObject
+{
+  public:
+    VirtualSensor() = delete;
+    virtual ~VirtualSensor() = default;
+
+    /** @brief Constructs VirtualSensor
+     *
+     * @param[in] bus          - Handle to system dbus
+     * @param[in] objPath      - The Dbus path of sensor
+     * @param[in] sensorConfig - Json object for sensor config
+     * @param[in] name         - Sensor name
+     * @param[in] type         - sensor type/unit
+     */
+    VirtualSensor(sdbusplus::bus_t& bus, const char* objPath,
+                  const Json& sensorConfig, const std::string& name,
+                  const std::string& type) :
+        ValueObject(bus, objPath, action::defer_emit), bus(bus), name(name),
+        objPath(objPath)
+    {
+        initVirtualSensor(sensorConfig, objPath, type);
+    }
+
+    /** @brief Constructs VirtualSensor
+     *
+     * @param[in] bus          - Handle to system dbus
+     * @param[in] objPath      - The Dbus path of sensor
+     * @param[in] ifacemap     - All the sensor information
+     * @param[in] name         - Virtual sensor name
+     * @param[in] type         - Virtual sensor type/unit
+     * @param[in] calcType     - Calculation used to calculate sensor value
+     * @param[in] entityPath   - Virtual sensor path in entityManager Dbus
+     *
+     */
+    VirtualSensor(sdbusplus::bus_t& bus, const char* objPath,
+                  const InterfaceMap& ifacemap, const std::string& name,
+                  const std::string& type, const std::string& calculationType,
+                  const std::string& entityPath) :
+        ValueObject(bus, objPath, action::defer_emit), bus(bus), name(name),
+        objPath(objPath), entityPath(entityPath)
+    {
+        initVirtualSensor(ifacemap, objPath, type, calculationType);
+    }
+
+    /** @brief Set sensor value */
+    void setSensorValue(double value);
+
+    /** @brief Update sensor at regular intrval */
+    void updateVirtualSensor();
+
+    /** @brief Check if sensor value is in valid range */
+    bool sensorInRange(double value);
+
+    /** @brief Map of list of parameters */
+    using ParamMap =
+        std::unordered_map<std::string, std::unique_ptr<SensorParam>>;
+    ParamMap paramMap;
+
+  private:
+    /** @brief sdbusplus bus client connection. */
+    sdbusplus::bus_t& bus;
+    /** @brief name of sensor */
+    std::string name;
+    /** @brief unit of sensor */
+    ValueIface::Unit units;
+    /** @brief object path of this sensor */
+    std::string objPath;
+
+    /** @brief Virtual sensor path in entityManager Dbus.
+     * This value is used to set thresholds/create association
+     */
+    std::string entityPath;
+    /** @brief Expression string for virtual sensor value calculations */
+    std::string exprStr;
+    /** @brief symbol table from exprtk */
+    exprtk::symbol_table<double> symbols{};
+    /** @brief expression from exprtk to calculate sensor value */
+    exprtk::expression<double> expression{};
+    /** @brief The vecops package so the expression can use vectors */
+    exprtk::rtl::vecops::package<double> vecopsPackage;
+    /** @brief The maximum valid value for an input sensor **/
+    double maxValidInput = std::numeric_limits<double>::infinity();
+    /** @brief The minimum valid value for an input sensor **/
+    double minValidInput = -std::numeric_limits<double>::infinity();
+
+    /** @brief The critical threshold interface object */
+    std::unique_ptr<Threshold<CriticalObject>> criticalIface;
+    /** @brief The warning threshold interface object */
+    std::unique_ptr<Threshold<WarningObject>> warningIface;
+    /** @brief The soft shutdown threshold interface object */
+    std::unique_ptr<Threshold<SoftShutdownObject>> softShutdownIface;
+    /** @brief The hard shutdown threshold interface object */
+    std::unique_ptr<Threshold<HardShutdownObject>> hardShutdownIface;
+    /** @brief The performance loss threshold interface object */
+    std::unique_ptr<Threshold<PerformanceLossObject>> perfLossIface;
+
+    /** @brief The association interface object */
+    std::unique_ptr<AssociationObject> associationIface;
+
+    static FuncMaxIgnoreNaN<double> funcMaxIgnoreNaN;
+    static FuncSumIgnoreNaN<double> funcSumIgnoreNaN;
+    static FuncIfNan<double> funcIfNan;
+
+    /** @brief Read config from json object and initialize sensor data
+     * for each virtual sensor
+     */
+    void initVirtualSensor(const Json& sensorConfig, const std::string& objPath,
+                           const std::string& type);
+
+    /** @brief Read config from interface map and initialize sensor data
+     * for each virtual sensor
+     */
+    void initVirtualSensor(const InterfaceMap& interfaceMap,
+                           const std::string& objPath,
+                           const std::string& sensorType,
+                           const std::string& calculationType);
+
+    /** @brief Returns which calculation function or expression to use */
+    double calculateValue(const std::string& sensortype,
+                          const VirtualSensor::ParamMap& paramMap);
+    /** @brief create threshold objects from json config */
+    void createThresholds(const Json& threshold, const std::string& objPath,
+                          ValueIface::Unit units);
+    /** @brief parse config from entity manager **/
+    void parseConfigInterface(const PropertyMap& propertyMap,
+                              const std::string& sensorType,
+                              const std::string& interface);
+
+    /** @brief Check Sensor threshold and update alarm and log. Returns
+     * true if the threshold range has no alarms set. change will be
+     * set if a change to the alarms were detected, else will be left
+     * unchanged */
+    template <typename V, typename T>
+    bool checkThresholds(V value, T& threshold, bool& change)
+    {
+        if (!threshold)
+            return true;
+
+        static constexpr auto tname = T::element_type::name;
+
+        auto alarmHigh = threshold->alarmHigh();
+        auto highHysteresis = threshold->getHighHysteresis();
+        if ((!alarmHigh && value >= threshold->high()) ||
+            (alarmHigh && value < (threshold->high() - highHysteresis)))
+        {
+            change = true;
+            if (!alarmHigh)
+            {
+                error("ASSERT: sensor {SENSOR} is above the upper threshold "
+                      "{THRESHOLD}.",
+                      "SENSOR", name, "THRESHOLD", tname);
+                threshold->alarmHighSignalAsserted(value);
+            }
+            else
+            {
+                info("DEASSERT: sensor {SENSOR} is under the upper threshold "
+                     "{THRESHOLD}.",
+                     "SENSOR", name, "THRESHOLD", tname);
+                threshold->alarmHighSignalDeasserted(value);
+            }
+            alarmHigh = !alarmHigh;
+            threshold->alarmHigh(alarmHigh);
+        }
+
+        auto alarmLow = threshold->alarmLow();
+        auto lowHysteresis = threshold->getLowHysteresis();
+        if ((!alarmLow && value <= threshold->low()) ||
+            (alarmLow && value > (threshold->low() + lowHysteresis)))
+        {
+            change = true;
+            if (!alarmLow)
+            {
+                error("ASSERT: sensor {SENSOR} is below the lower threshold "
+                      "{THRESHOLD}.",
+                      "SENSOR", name, "THRESHOLD", tname);
+                threshold->alarmLowSignalAsserted(value);
+            }
+            else
+            {
+                info("DEASSERT: sensor {SENSOR} is above the lower threshold "
+                     "{THRESHOLD}.",
+                     "SENSOR", name, "THRESHOLD", tname);
+                threshold->alarmLowSignalDeasserted(value);
+            }
+            alarmLow = !alarmLow;
+            threshold->alarmLow(alarmLow);
+        }
+        return !alarmHigh && !alarmLow;
+    }
+
+    /** @brief Create Association from entityPath*/
+    void createAssociation(const std::string& objPath,
+                           const std::string& entityPath);
+};
+
+class VirtualSensors
+{
+  public:
+    VirtualSensors() = delete;
+    virtual ~VirtualSensors() = default;
+
+    /** @brief Constructs VirtualSensors
+     *
+     * @param[in] bus     - Handle to system dbus
+     */
+    explicit VirtualSensors(sdbusplus::bus_t& bus) : bus(bus)
+    {
+        createVirtualSensors();
+    }
+    /** @brief Calls createVirtualSensor when interface added */
+    void propertiesChanged(sdbusplus::message_t& msg);
+
+  private:
+    /** @brief sdbusplus bus client connection. */
+    sdbusplus::bus_t& bus;
+    /** @brief Get virtual sensor config from DBus**/
+    ManagedObjectType getObjectsFromDBus();
+    /** @brief Parsing virtual sensor config JSON file  */
+    Json parseConfigFile();
+
+    /** @brief Matches for virtual sensors */
+    std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches;
+    /** @brief Map of the object VirtualSensor */
+    std::unordered_map<std::string, std::unique_ptr<VirtualSensor>>
+        virtualSensorsMap;
+
+    /** @brief Create list of virtual sensors from JSON config*/
+    void createVirtualSensors();
+    /** @brief Create list of virtual sensors from DBus config */
+    void createVirtualSensorsFromDBus(const std::string& calculationType);
+    /** @brief Setup matches for virtual sensors */
+    void setupMatches();
+};
+
+} // namespace phosphor::virtual_sensor