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