#include "virtualSensor.hpp" #include "config.hpp" #include #include #include #include #include static constexpr bool DEBUG = false; static constexpr auto busName = "xyz.openbmc_project.VirtualSensor"; static constexpr auto sensorDbusPath = "/xyz/openbmc_project/sensors/"; static constexpr uint8_t defaultHighThreshold = 100; static constexpr uint8_t defaultLowThreshold = 0; using namespace phosphor::logging; int handleDbusSignal(sd_bus_message* msg, void* usrData, sd_bus_error*) { if (usrData == nullptr) { throw std::runtime_error("Invalid match"); } auto sdbpMsg = sdbusplus::message::message(msg); std::string msgIfce; std::map> msgData; sdbpMsg.read(msgIfce, msgData); if (msgData.find("Value") != msgData.end()) { using namespace phosphor::virtualSensor; VirtualSensor* obj = static_cast(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 { void printParams(const VirtualSensor::ParamMap& paramMap) { for (const auto& p : paramMap) { const auto& p1 = p.first; const auto& p2 = p.second; auto val = p2->getParamValue(); std::cout << p1 << " = " << val << "\n"; } } double SensorParam::getParamValue() { switch (paramType) { case constParam: return value; break; case dbusParam: return dbusSensor->getSensorValue(); break; default: throw std::invalid_argument("param type not supported"); } } 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); if (!threshold.empty()) { criticalIface = std::make_unique(bus, objPath.c_str()); criticalIface->criticalHigh( threshold.value("CriticalHigh", defaultHighThreshold)); criticalIface->criticalLow( threshold.value("CriticalLow", defaultLowThreshold)); warningIface = std::make_unique(bus, objPath.c_str()); warningIface->warningHigh( threshold.value("WarningHigh", defaultHighThreshold)); warningIface->warningLow( threshold.value("WarningLow", defaultLowThreshold)); // Only create the high and low shutdown interfaces if // at least one of their values is present. if (threshold.contains("HardShutdownHigh") || threshold.contains("HardShutdownLow")) { hardShutdownIface = std::make_unique(bus, objPath.c_str()); hardShutdownIface->hardShutdownHigh(threshold.value( "HardShutdownHigh", std::numeric_limits::quiet_NaN())); hardShutdownIface->hardShutdownLow(threshold.value( "HardShutdownLow", std::numeric_limits::quiet_NaN())); } if (threshold.contains("SoftShutdownHigh") || threshold.contains("SoftShutdownLow")) { softShutdownIface = std::make_unique(bus, objPath.c_str()); softShutdownIface->softShutdownHigh(threshold.value( "SoftShutdownHigh", std::numeric_limits::quiet_NaN())); softShutdownIface->softShutdownLow(threshold.value( "SoftShutdownLow", std::numeric_limits::quiet_NaN())); } } /* Get expression string */ exprStr = sensorConfig.value("Expression", ""); /* Get all the parameter listed in configuration */ auto params = sensorConfig.value("Params", empty); /* Check for constant parameter */ const auto& consParams = params.value("ConstParam", empty); if (!consParams.empty()) { for (auto& j : consParams) { if (j.find("ParamName") != j.end()) { auto paramPtr = std::make_unique(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()) { std::string objPath(sensorDbusPath); objPath += sensorType + "/" + name; auto paramPtr = std::make_unique(bus, objPath, this); std::string name = j["ParamName"]; symbols.create_variable(name); paramMap.emplace(std::move(name), std::move(paramPtr)); } } } } symbols.add_constants(); symbols.add_package(vecopsPackage); expression.register_symbol_table(symbols); /* parser from exprtk */ exprtk::parser parser{}; if (!parser.compile(exprStr, expression)) { log("Expression compilation failed"); for (std::size_t i = 0; i < parser.error_count(); ++i) { auto error = parser.get_error(i); log( fmt::format( "Position: {} Type: {} Message: {}", error.token.position, exprtk::parser_error::to_str(error.mode), error.diagnostic) .c_str()); } throw std::runtime_error("Expression compilation failed"); } /* Print all parameters for debug purpose only */ if (DEBUG) printParams(paramMap); } void VirtualSensor::setSensorValue(double value) { ValueIface::value(value); } void VirtualSensor::checkSensorThreshold(const double value) { if (warningIface) { if (value >= warningIface->warningHigh()) { if (!warningIface->warningAlarmHigh()) { warningIface->warningAlarmHigh(true); log("ASSERT: Virtual Sensor has exceeded " "warning high threshold", entry("NAME = %s", name.c_str())); } } else if (warningIface->warningAlarmHigh()) { warningIface->warningAlarmHigh(false); log("DEASSERT: Virtual Sensor is under " "warning high threshold", entry("NAME = %s", name.c_str())); } if (value <= warningIface->warningLow()) { if (!warningIface->warningAlarmLow()) { warningIface->warningAlarmLow(true); log("ASSERT: Virtual Sensor is under " "warning low threshold", entry("NAME = %s", name.c_str())); } } else if (warningIface->warningAlarmLow()) { warningIface->warningAlarmLow(false); log("DEASSERT: Virtual Sensor is above " "warning low threshold", entry("NAME = %s", name.c_str())); } } if (criticalIface) { if (value >= criticalIface->criticalHigh()) { if (!criticalIface->criticalAlarmHigh()) { criticalIface->criticalAlarmHigh(true); log("ASSERT: Virtual Sensor has exceeded " "critical high threshold", entry("NAME = %s", name.c_str())); } } else if (criticalIface->criticalAlarmHigh()) { criticalIface->criticalAlarmHigh(false); log("DEASSERT: Virtual Sensor is under " "critical high threshold", entry("NAME = %s", name.c_str())); } if (value <= criticalIface->criticalLow()) { if (!criticalIface->criticalAlarmLow()) { criticalIface->criticalAlarmLow(true); log("ASSERT: Virtual Sensor is under " "critical low threshold", entry("NAME = %s", name.c_str())); } } else if (criticalIface->criticalAlarmLow()) { criticalIface->criticalAlarmLow(false); log("DEASSERT: Virtual Sensor is above " "critical low threshold", entry("NAME = %s", name.c_str())); } } if (softShutdownIface) { if (value >= softShutdownIface->softShutdownHigh()) { if (!softShutdownIface->softShutdownAlarmHigh()) { softShutdownIface->softShutdownAlarmHigh(true); log("ASSERT: Virtual Sensor has exceeded " "softShutdown high threshold", entry("NAME = %s", name.c_str())); } } else if (softShutdownIface->softShutdownAlarmHigh()) { softShutdownIface->softShutdownAlarmHigh(false); log("DEASSERT: Virtual Sensor is under " "softShutdown high threshold", entry("NAME = %s", name.c_str())); } if (value <= softShutdownIface->softShutdownLow()) { if (!softShutdownIface->softShutdownAlarmLow()) { softShutdownIface->softShutdownAlarmLow(true); log("ASSERT: Virtual Sensor is under " "softShutdown low threshold", entry("NAME = %s", name.c_str())); } } else if (softShutdownIface->softShutdownAlarmLow()) { softShutdownIface->softShutdownAlarmLow(false); log("DEASSERT: Virtual Sensor is above " "softShutdown low threshold", entry("NAME = %s", name.c_str())); } } if (hardShutdownIface) { if (value >= hardShutdownIface->hardShutdownHigh()) { if (!hardShutdownIface->hardShutdownAlarmHigh()) { hardShutdownIface->hardShutdownAlarmHigh(true); log("ASSERT: Virtual Sensor has exceeded " "hardShutdown high threshold", entry("NAME = %s", name.c_str())); } } else if (hardShutdownIface->hardShutdownAlarmHigh()) { hardShutdownIface->hardShutdownAlarmHigh(false); log("DEASSERT: Virtual Sensor is under " "hardShutdown high threshold", entry("NAME = %s", name.c_str())); } if (value <= hardShutdownIface->hardShutdownLow()) { if (!hardShutdownIface->hardShutdownAlarmLow()) { hardShutdownIface->hardShutdownAlarmLow(true); log("ASSERT: Virtual Sensor is under " "hardShutdown low threshold", entry("NAME = %s", name.c_str())); } } else if (hardShutdownIface->hardShutdownAlarmLow()) { hardShutdownIface->hardShutdownAlarmLow(false); log("DEASSERT: Virtual Sensor is above " "hardShutdown low threshold", entry("NAME = %s", name.c_str())); } } } 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"); } } double val = expression.value(); /* Set sensor value to dbus interface */ setSensorValue(val); if (DEBUG) std::cout << "Sensor value is " << val << "\n"; /* Check sensor threshold and log required message */ checkSensorThreshold(val); } /** @brief Parsing Virtual Sensor config JSON file */ Json VirtualSensors::parseConfigFile(const std::string configFile) { std::ifstream jsonFile(configFile); if (!jsonFile.is_open()) { log("config JSON file not found", entry("FILENAME = %s", configFile.c_str())); throw std::exception{}; } auto data = Json::parse(jsonFile, nullptr, false); if (data.is_discarded()) { log("config readings JSON parser failure", entry("FILENAME = %s", configFile.c_str())); throw std::exception{}; } return data; } std::map 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}}; void VirtualSensors::createVirtualSensors() { static const Json empty{}; auto data = parseConfigFile(VIRTUAL_SENSOR_CONFIG_FILE); // print values if (DEBUG) std::cout << "Config json data:\n" << data << "\n\n"; /* Get virtual sensors config data */ for (const auto& j : data) { auto desc = j.value("Desc", empty); if (!desc.empty()) { std::string sensorType = desc.value("SensorType", ""); std::string name = desc.value("Name", ""); if (!name.empty() && !sensorType.empty()) { if (unitMap.find(sensorType) == unitMap.end()) { log("Sensor type is not supported", entry("TYPE = %s", sensorType.c_str())); } else { std::string objPath(sensorDbusPath); objPath += sensorType + "/" + name; auto virtualSensorPtr = std::make_unique( bus, objPath.c_str(), j, name); log("Added a new virtual sensor", entry("NAME = %s", name.c_str())); virtualSensorPtr->updateVirtualSensor(); /* Initialize unit value for virtual sensor */ virtualSensorPtr->ValueIface::unit(unitMap[sensorType]); virtualSensorsMap.emplace(std::move(name), std::move(virtualSensorPtr)); } } else { log("Sensor type or name not found in config file"); } } else { log( "Descriptor for new virtual sensor not found in config file"); } } } } // namespace virtualSensor } // namespace phosphor /** * @brief Main */ int main() { // Get a default event loop auto event = sdeventplus::Event::get_default(); // Get a handle to system dbus auto bus = sdbusplus::bus::new_default(); // Add the ObjectManager interface sdbusplus::server::manager::manager objManager(bus, "/"); // Create an virtual sensors object phosphor::virtualSensor::VirtualSensors virtualSensors(bus); // Request service bus name bus.request_name(busName); // Attach the bus to sd_event to service user requests bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL); event.loop(); return 0; }