blob: 9b682cbfaf9ae6e591517f4853419d5668e4bfc1 [file] [log] [blame]
#include "virtualSensor.hpp"
#include <phosphor-logging/lg2.hpp>
#include <fstream>
static constexpr bool DEBUG = false;
static constexpr auto sensorDbusPath = "/xyz/openbmc_project/sensors/";
static constexpr auto vsThresholdsIfaceSuffix = ".Thresholds";
static constexpr std::array<const char*, 2> calculationIfaces = {
"xyz.openbmc_project.Configuration.ModifiedMedian",
"xyz.openbmc_project.Configuration.Maximum"};
static constexpr auto defaultHysteresis = 0;
PHOSPHOR_LOG2_USING_WITH_FLAGS;
int handleDbusSignal(sd_bus_message* msg, void* usrData, sd_bus_error*)
{
if (usrData == nullptr)
{
throw std::runtime_error("Invalid match");
}
auto sdbpMsg = sdbusplus::message_t(msg);
std::string msgIfce;
std::map<std::string, std::variant<int64_t, double, bool>> msgData;
sdbpMsg.read(msgIfce, msgData);
if (msgData.find("Value") != msgData.end())
{
using namespace phosphor::virtualSensor;
VirtualSensor* obj = static_cast<VirtualSensor*>(usrData);
// TODO(openbmc/phosphor-virtual-sensor#1): updateVirtualSensor should
// be changed to take the information we got from the signal, to avoid
// having to do numerous dbus queries.
obj->updateVirtualSensor();
}
return 0;
}
namespace phosphor
{
namespace virtualSensor
{
FuncMaxIgnoreNaN<double> VirtualSensor::funcMaxIgnoreNaN;
FuncSumIgnoreNaN<double> VirtualSensor::funcSumIgnoreNaN;
FuncIfNan<double> VirtualSensor::funcIfNan;
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;
}
bool isCalculationType(const std::string& interface)
{
auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(),
interface);
if (itr != calculationIfaces.end())
{
return true;
}
return false;
}
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 (!isCalculationType(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)
{
static const Json empty{};
/* Get threshold values if defined in config */
auto threshold = sensorConfig.value("Threshold", empty);
createThresholds(threshold, objPath);
/* 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 */
if (DEBUG)
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;
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);
symbols.add_constants();
symbols.add_package(vecopsPackage);
expression.register_symbol_table(symbols);
createAssociation(objPath, entityPath);
/* Print all parameters for debug purpose only */
if (DEBUG)
{
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 itr = std::find(calculationIfaces.begin(), calculationIfaces.end(),
calculation);
if (itr == calculationIfaces.end())
{
return std::numeric_limits<double>::quiet_NaN();
}
else if (calculation == "xyz.openbmc_project.Configuration.ModifiedMedian")
{
return calculateModifiedMedianValue(paramMap);
}
else if (calculation == "xyz.openbmc_project.Configuration.Maximum")
{
return calculateMaximumValue(paramMap);
}
return std::numeric_limits<double>::quiet_NaN();
}
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 itr = std::find(calculationIfaces.begin(), calculationIfaces.end(),
exprStr);
auto val = (itr == calculationIfaces.end())
? expression.value()
: calculateValue(exprStr, paramMap);
/* Set sensor value to dbus interface */
setSensorValue(val);
if (DEBUG)
{
debug("Sensor {NAME} = {VALUE}", "NAME", this->name, "VALUE", val);
}
/* Check sensor thresholds and log required message */
checkThresholds(val, perfLossIface);
checkThresholds(val, warningIface);
checkThresholds(val, criticalIface);
checkThresholds(val, softShutdownIface);
checkThresholds(val, hardShutdownIface);
}
double VirtualSensor::calculateModifiedMedianValue(
const VirtualSensor::ParamMap& paramMap)
{
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());
}
}
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 VirtualSensor::calculateMaximumValue(
const VirtualSensor::ParamMap& paramMap)
{
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());
}
}
auto maxIt = std::max_element(values.begin(), values.end());
if (maxIt == values.end())
{
return std::numeric_limits<double>::quiet_NaN();
}
return *maxIt;
}
void VirtualSensor::createThresholds(const Json& threshold,
const std::string& objPath)
{
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());
if (threshold.contains("CriticalHigh"))
{
criticalIface->setEntityInterfaceHigh(
threshold.value("CriticalHighDirection", ""));
if (DEBUG)
{
debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
"INTF", threshold.value("CriticalHighDirection", ""));
}
}
if (threshold.contains("CriticalLow"))
{
criticalIface->setEntityInterfaceLow(
threshold.value("CriticalLowDirection", ""));
if (DEBUG)
{
debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
"INTF", threshold.value("CriticalLowDirection", ""));
}
}
criticalIface->setEntityPath(entityPath);
if (DEBUG)
{
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());
if (threshold.contains("WarningHigh"))
{
warningIface->setEntityInterfaceHigh(
threshold.value("WarningHighDirection", ""));
if (DEBUG)
{
debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
"INTF", threshold.value("WarningHighDirection", ""));
}
}
if (threshold.contains("WarningLow"))
{
warningIface->setEntityInterfaceLow(
threshold.value("WarningLowDirection", ""));
if (DEBUG)
{
debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
"INTF", threshold.value("WarningLowDirection", ""));
}
}
warningIface->setEntityPath(entityPath);
if (DEBUG)
{
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());
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());
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());
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 path;
PropertyMap properties;
msg.read(path, 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 (isCalculationType(path))
{
createVirtualSensorsFromDBus(path);
}
}
}
/** @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;
}
std::map<std::string, ValueIface::Unit> unitMap = {
{"temperature", ValueIface::Unit::DegreesC},
{"fan_tach", ValueIface::Unit::RPMS},
{"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}};
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 char* 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
if (DEBUG)
{
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();
if (desc.contains("Type"))
{
auto type = desc.value("Type", "");
auto path = "xyz.openbmc_project.Configuration." + type;
if (!isCalculationType(path))
{
error("Invalid calculation type {TYPE} supplied.",
"TYPE", type);
continue;
}
createVirtualSensorsFromDBus(path);
}
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);
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 virtualSensor
} // namespace phosphor
/**
* @brief Main
*/
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::virtualSensor::VirtualSensors virtualSensors(bus);
// Request service bus name
bus.request_name("xyz.openbmc_project.VirtualSensor");
// Run the dbus loop.
bus.process_loop();
return 0;
}