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)"
+ }
+]