Basic initial implementation

This is a base implementation which does following
1. create a daemon,
2. parse config file and create a virtual sensor object
3. create list of virtual sensors for given config.
4. Creates a systemd unit file

It currently supports constant params only and dbus params will be in
follow up patch.

Signed-off-by: Vijay Khemka <vijaykhemka@fb.com>
Change-Id: I89b2ffb8bff67bdbb3033071cba9f6e565a9af6e
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..4258e5e
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,53 @@
+project(
+    'phosphor-virtual-sensor',
+    'cpp',
+    version: '1.0',
+    default_options: [
+        'cpp_std=c++17',
+    ],
+)
+
+executable(
+    'virtual-sensor',
+    [
+        'virtualSensor.cpp',
+    ],
+    dependencies: [
+        dependency('phosphor-logging'),
+        dependency('sdbusplus'),
+        dependency('phosphor-dbus-interfaces'),
+        dependency('sdeventplus'),
+    ],
+    install: true,
+    install_dir: get_option('bindir')
+)
+
+packagedir = join_paths(
+    get_option('prefix'),
+    get_option('datadir'),
+    meson.project_name(),
+)
+
+configfile = 'virtual_sensor_config.json'
+confpath = '"' + join_paths(
+    packagedir,
+    configfile,
+) + '"'
+
+install_data(sources : configfile, install_dir : packagedir)
+
+conf_data = configuration_data()
+conf_data.set('VIRTUAL_SENSOR_CONFIG_FILE', confpath)
+
+configure_file(output : 'config.hpp',
+               configuration : conf_data)
+
+systemd = dependency('systemd')
+conf_data = configuration_data()
+conf_data.set('bindir', get_option('prefix') / get_option('bindir'))
+configure_file(
+  input: 'phosphor-virtual-sensor.service.in',
+  output: 'phosphor-virtual-sensor.service',
+  configuration: conf_data,
+  install: true,
+  install_dir: systemd.get_pkgconfig_variable('systemdsystemunitdir'))
diff --git a/phosphor-virtual-sensor.service.in b/phosphor-virtual-sensor.service.in
new file mode 100644
index 0000000..4bba079
--- /dev/null
+++ b/phosphor-virtual-sensor.service.in
@@ -0,0 +1,11 @@
+[Unit]
+Description=Virtual sensors
+
+[Service]
+ExecStart=@bindir@/virtual-sensor
+Type=dbus
+BusName=xyz.openbmc_project.VirtualSensor
+SyslogIdentifier=phosphor-virtual-sensor
+
+[Install]
+WantedBy=multi-user.target
diff --git a/virtualSensor.cpp b/virtualSensor.cpp
new file mode 100644
index 0000000..ef8543f
--- /dev/null
+++ b/virtualSensor.cpp
@@ -0,0 +1,210 @@
+#include "virtualSensor.hpp"
+
+#include "config.hpp"
+
+#include <phosphor-logging/log.hpp>
+#include <sdeventplus/event.hpp>
+
+#include <fstream>
+#include <iostream>
+
+static constexpr bool DEBUG = false;
+static constexpr auto busName = "xyz.openbmc_project.VirtualSensor";
+static constexpr auto sensorDbusPath = "/xyz/openbmc_project/sensors/";
+static constexpr uint8_t defaultHighThreshold = 100;
+static constexpr uint8_t defaultLowThreshold = 0;
+
+using namespace phosphor::logging;
+
+namespace phosphor
+{
+namespace virtualSensor
+{
+
+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();
+        std::cout << p1 << " = " << val << "\n";
+    }
+}
+
+double SensorParam::getParamValue()
+{
+    switch (paramType)
+    {
+        case constParam:
+            return value;
+            break;
+        default:
+            throw std::invalid_argument("param type not supported");
+    }
+}
+
+void VirtualSensor::initVirtualSensor(const Json& sensorConfig)
+{
+
+    static const Json empty{};
+
+    /* Get threshold values if defined in config */
+    auto threshold = sensorConfig.value("Threshold", empty);
+    if (!threshold.empty())
+    {
+        sensorThreshold.criticalHigh =
+            threshold.value("CriticalHigh", defaultHighThreshold);
+        sensorThreshold.criticalLow =
+            threshold.value("CriticalLow", defaultLowThreshold);
+        sensorThreshold.warningHigh =
+            threshold.value("WarningHigh", defaultHighThreshold);
+        sensorThreshold.warningLow =
+            threshold.value("WarningLow", defaultLowThreshold);
+    }
+
+    /* Set threshold value to dbus */
+    setSensorThreshold();
+
+    /* Get expression string */
+    exprStr = sensorConfig.value("Expression", "");
+
+    /* 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"]);
+                paramMap.emplace(j["ParamName"], std::move(paramPtr));
+            }
+            else
+            {
+                /* Invalid configuration */
+                throw std::invalid_argument(
+                    "ParamName not found in configuration");
+            }
+        }
+    }
+
+    /* TODO: Check for dbus parameter */
+
+    /* Print all parameters for debug purpose only */
+    if (DEBUG)
+        printParams(paramMap);
+}
+
+void VirtualSensor::setSensorValue(double value)
+{
+    ValueIface::value(value);
+}
+
+void VirtualSensor::setSensorThreshold()
+{
+    CriticalInterface::criticalHigh(sensorThreshold.criticalHigh);
+    CriticalInterface::criticalLow(sensorThreshold.criticalLow);
+    WarningInterface::warningHigh(sensorThreshold.warningHigh);
+    WarningInterface::warningLow(sensorThreshold.warningLow);
+}
+
+/* TBD */
+void VirtualSensor::updateVirtualSensor()
+{}
+
+/** @brief Parsing Virtual Sensor config JSON file  */
+Json VirtualSensors::parseConfigFile(const std::string configFile)
+{
+    std::ifstream jsonFile(configFile);
+    if (!jsonFile.is_open())
+    {
+        log<level::ERR>("config JSON file not found",
+                        entry("FILENAME = %s", configFile.c_str()));
+        throw std::exception{};
+    }
+
+    auto data = Json::parse(jsonFile, nullptr, false);
+    if (data.is_discarded())
+    {
+        log<level::ERR>("config readings JSON parser failure",
+                        entry("FILENAME = %s", configFile.c_str()));
+        throw std::exception{};
+    }
+
+    return data;
+}
+
+void VirtualSensors::createVirtualSensors()
+{
+    static const Json empty{};
+
+    auto data = parseConfigFile(VIRTUAL_SENSOR_CONFIG_FILE);
+    // print values
+    if (DEBUG)
+        std::cout << "Config json data:\n" << data << "\n\n";
+
+    /* Get virtual sensors  config data */
+    for (const auto& j : data)
+    {
+        auto desc = j.value("Desc", empty);
+        if (!desc.empty())
+        {
+            std::string sensorType = desc.value("SensorType", "");
+            std::string name = desc.value("Name", "");
+
+            if (!name.empty() && !sensorType.empty())
+            {
+                std::string objPath(sensorDbusPath);
+                objPath += sensorType + "/" + name;
+
+                auto virtualSensorPtr =
+                    std::make_unique<VirtualSensor>(bus, objPath.c_str(), j);
+                virtualSensorsMap.emplace(name, std::move(virtualSensorPtr));
+
+                log<level::INFO>("Added a new virtual sensor",
+                                 entry("NAME = %s", name.c_str()));
+            }
+            else
+            {
+                log<level::ERR>("Sensor type or name not found in config file");
+            }
+        }
+        else
+        {
+            log<level::ERR>(
+                "Descriptor for new virtual sensor not found in config file");
+        }
+    }
+}
+
+} // namespace virtualSensor
+} // namespace phosphor
+
+/**
+ * @brief Main
+ */
+int main()
+{
+
+    // Get a default event loop
+    auto event = sdeventplus::Event::get_default();
+
+    // Get a handle to system dbus
+    auto bus = sdbusplus::bus::new_default();
+
+    // Create an virtual sensors object
+    phosphor::virtualSensor::VirtualSensors virtualSensors(bus);
+
+    // Request service bus name
+    bus.request_name(busName);
+
+    // Attach the bus to sd_event to service user requests
+    bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
+    event.loop();
+
+    return 0;
+}
diff --git a/virtualSensor.hpp b/virtualSensor.hpp
new file mode 100644
index 0000000..a49c94f
--- /dev/null
+++ b/virtualSensor.hpp
@@ -0,0 +1,139 @@
+#include <nlohmann/json.hpp>
+#include <sdbusplus/bus.hpp>
+#include <xyz/openbmc_project/Sensor/Threshold/Critical/server.hpp>
+#include <xyz/openbmc_project/Sensor/Threshold/Warning/server.hpp>
+#include <xyz/openbmc_project/Sensor/Value/server.hpp>
+
+#include <map>
+#include <string>
+
+namespace phosphor
+{
+namespace virtualSensor
+{
+
+using Json = nlohmann::json;
+using ValueIface = sdbusplus::xyz::openbmc_project::Sensor::server::Value;
+
+using CriticalInterface =
+    sdbusplus::xyz::openbmc_project::Sensor::Threshold::server::Critical;
+
+using WarningInterface =
+    sdbusplus::xyz::openbmc_project::Sensor::Threshold::server::Warning;
+
+using sensorIfaces =
+    sdbusplus::server::object::object<ValueIface, CriticalInterface,
+                                      WarningInterface>;
+
+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 Get sensor value property from D-bus interface */
+    double getParamValue();
+
+  private:
+    double value;
+    ParamType paramType;
+};
+
+class VirtualSensor : public sensorIfaces
+{
+  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
+     */
+    VirtualSensor(sdbusplus::bus::bus& bus, const char* objPath,
+                  const Json& sensorConfig) :
+        sensorIfaces(bus, objPath),
+        bus(bus)
+    {
+        initVirtualSensor(sensorConfig);
+    }
+
+    struct Threshold
+    {
+        double criticalHigh;
+        double criticalLow;
+        double warningHigh;
+        double warningLow;
+    };
+
+    /** @brief Set sensor value */
+    void setSensorValue(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::bus& bus;
+    /** @brief Expression string for virtual sensor value calculations */
+    std::string exprStr;
+    /** @brief Sensor Threshold config values */
+    struct Threshold sensorThreshold;
+
+    /** @brief Read config from json object and initialize sensor data
+     * for each virtual sensor
+     */
+    void initVirtualSensor(const Json& sensorConfig);
+    /** @brief Set Sensor Threshold to D-bus at beginning */
+    void setSensorThreshold();
+    /** @brief Update sensor at regular intrval */
+    void updateVirtualSensor();
+};
+
+class VirtualSensors
+{
+  public:
+    VirtualSensors() = delete;
+    virtual ~VirtualSensors() = default;
+
+    /** @brief Constructs VirtualSensors
+     *
+     * @param[in] bus     - Handle to system dbus
+     */
+    explicit VirtualSensors(sdbusplus::bus::bus& bus) : bus(bus)
+    {
+        createVirtualSensors();
+    }
+
+  private:
+    /** @brief sdbusplus bus client connection. */
+    sdbusplus::bus::bus& bus;
+    /** @brief Parsing virtual sensor config JSON file  */
+    Json parseConfigFile(const std::string configFile);
+
+    /** @brief Map of the object VirtualSensor */
+    std::unordered_map<std::string, std::unique_ptr<VirtualSensor>>
+        virtualSensorsMap;
+
+    /** @brief Create list of virtual sensors */
+    void createVirtualSensors();
+};
+
+} // namespace virtualSensor
+} // namespace phosphor
diff --git a/virtual_sensor_config.json b/virtual_sensor_config.json
new file mode 100644
index 0000000..a394fae
--- /dev/null
+++ b/virtual_sensor_config.json
@@ -0,0 +1,46 @@
+[
+	{
+		"Desc" :
+		{
+			"Name" : "Virtual_Inlet_Temp",
+			"SensorType" : "temperature"
+		},
+		"Threshold" :
+		{
+			"CriticalHigh": 90,
+			"CriticalLow": 20,
+			"WarningHigh": 70,
+			"WarningLow": 30
+		},
+		"Params":
+		{
+			"ConstParam" :
+			[
+				{
+					"ParamName" : "P1",
+					"Value" : 1.1
+				}
+			],
+			"DbusParam" :
+			[
+				{
+					"ParamName" : "P2",
+					"Desc" :
+					{
+						"Name" : "MB_INLET_TEMP",
+						"SensorType" : "temperature"
+					}
+				},
+				{
+					"ParamName" : "P3",
+					"Desc" :
+					{
+						"Name" : "MB_FAN0_TACH",
+						"SensorType" : "fan_tach"
+					}
+				}
+			]
+		},
+		"Expression" : "P1 * (P2 + 5 - P3 * 0.01)"
+	}
+]