1 #include "virtualSensor.hpp" 2 3 #include <phosphor-logging/lg2.hpp> 4 5 #include <fstream> 6 7 static constexpr auto sensorDbusPath = "/xyz/openbmc_project/sensors/"; 8 static constexpr auto vsThresholdsIfaceSuffix = ".Thresholds"; 9 static constexpr std::array<const char*, 2> calculationIfaces = { 10 "xyz.openbmc_project.Configuration.ModifiedMedian", 11 "xyz.openbmc_project.Configuration.Maximum"}; 12 static constexpr auto defaultHysteresis = 0; 13 14 PHOSPHOR_LOG2_USING_WITH_FLAGS; 15 16 namespace phosphor::virtual_sensor 17 { 18 19 FuncMaxIgnoreNaN<double> VirtualSensor::funcMaxIgnoreNaN; 20 FuncSumIgnoreNaN<double> VirtualSensor::funcSumIgnoreNaN; 21 FuncIfNan<double> VirtualSensor::funcIfNan; 22 23 void printParams(const VirtualSensor::ParamMap& paramMap) 24 { 25 for (const auto& p : paramMap) 26 { 27 const auto& p1 = p.first; 28 const auto& p2 = p.second; 29 auto val = p2->getParamValue(); 30 debug("Parameter: {PARAM} = {VALUE}", "PARAM", p1, "VALUE", val); 31 } 32 } 33 34 double SensorParam::getParamValue() 35 { 36 switch (paramType) 37 { 38 case constParam: 39 return value; 40 break; 41 case dbusParam: 42 return dbusSensor->getSensorValue(); 43 break; 44 default: 45 throw std::invalid_argument("param type not supported"); 46 } 47 } 48 49 using AssociationList = 50 std::vector<std::tuple<std::string, std::string, std::string>>; 51 52 AssociationList getAssociationsFromJson(const Json& j) 53 { 54 AssociationList assocs{}; 55 try 56 { 57 j.get_to(assocs); 58 } 59 catch (const std::exception& ex) 60 { 61 error("Failed to parse association: {ERROR}", "ERROR", ex); 62 } 63 return assocs; 64 } 65 66 template <typename U> 67 struct VariantToNumber 68 { 69 template <typename T> 70 U operator()(const T& t) const 71 { 72 if constexpr (std::is_convertible<T, U>::value) 73 { 74 return static_cast<U>(t); 75 } 76 throw std::invalid_argument("Invalid number type in config\n"); 77 } 78 }; 79 80 template <typename U> 81 U getNumberFromConfig(const PropertyMap& map, const std::string& name, 82 bool required, 83 U defaultValue = std::numeric_limits<U>::quiet_NaN()) 84 { 85 if (auto itr = map.find(name); itr != map.end()) 86 { 87 return std::visit(VariantToNumber<U>(), itr->second); 88 } 89 else if (required) 90 { 91 error("Required field {NAME} missing in config", "NAME", name); 92 throw std::invalid_argument("Required field missing in config"); 93 } 94 return defaultValue; 95 } 96 97 bool isCalculationType(const std::string& interface) 98 { 99 auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(), 100 interface); 101 if (itr != calculationIfaces.end()) 102 { 103 return true; 104 } 105 return false; 106 } 107 108 const std::string getThresholdType(const std::string& direction, 109 const std::string& severity) 110 { 111 std::string suffix; 112 113 if (direction == "less than") 114 { 115 suffix = "Low"; 116 } 117 else if (direction == "greater than") 118 { 119 suffix = "High"; 120 } 121 else 122 { 123 throw std::invalid_argument( 124 "Invalid threshold direction specified in entity manager"); 125 } 126 return severity + suffix; 127 } 128 129 std::string getSeverityField(const PropertyMap& propertyMap) 130 { 131 static const std::array thresholdTypes{ 132 "Warning", "Critical", "PerformanceLoss", "SoftShutdown", 133 "HardShutdown"}; 134 135 std::string severity; 136 if (auto itr = propertyMap.find("Severity"); itr != propertyMap.end()) 137 { 138 /* Severity should be a string, but can be an unsigned int */ 139 if (std::holds_alternative<std::string>(itr->second)) 140 { 141 severity = std::get<std::string>(itr->second); 142 if (0 == std::ranges::count(thresholdTypes, severity)) 143 { 144 throw std::invalid_argument( 145 "Invalid threshold severity specified in entity manager"); 146 } 147 } 148 else 149 { 150 auto sev = 151 getNumberFromConfig<uint64_t>(propertyMap, "Severity", true); 152 /* Checking bounds ourselves so we throw invalid argument on 153 * invalid user input */ 154 if (sev >= thresholdTypes.size()) 155 { 156 throw std::invalid_argument( 157 "Invalid threshold severity specified in entity manager"); 158 } 159 severity = thresholdTypes.at(sev); 160 } 161 } 162 return severity; 163 } 164 165 void parseThresholds(Json& thresholds, const PropertyMap& propertyMap, 166 const std::string& entityInterface = "") 167 { 168 std::string direction; 169 170 auto value = getNumberFromConfig<double>(propertyMap, "Value", true); 171 172 auto severity = getSeverityField(propertyMap); 173 174 if (auto itr = propertyMap.find("Direction"); itr != propertyMap.end()) 175 { 176 direction = std::get<std::string>(itr->second); 177 } 178 179 auto threshold = getThresholdType(direction, severity); 180 thresholds[threshold] = value; 181 182 auto hysteresis = 183 getNumberFromConfig<double>(propertyMap, "Hysteresis", false); 184 if (hysteresis != std::numeric_limits<double>::quiet_NaN()) 185 { 186 thresholds[threshold + "Hysteresis"] = hysteresis; 187 } 188 189 if (!entityInterface.empty()) 190 { 191 thresholds[threshold + "Direction"] = entityInterface; 192 } 193 } 194 195 void VirtualSensor::parseConfigInterface(const PropertyMap& propertyMap, 196 const std::string& sensorType, 197 const std::string& interface) 198 { 199 /* Parse sensors / DBus params */ 200 if (auto itr = propertyMap.find("Sensors"); itr != propertyMap.end()) 201 { 202 auto sensors = std::get<std::vector<std::string>>(itr->second); 203 for (auto sensor : sensors) 204 { 205 std::replace(sensor.begin(), sensor.end(), ' ', '_'); 206 auto sensorObjPath = sensorDbusPath + sensorType + "/" + sensor; 207 208 auto paramPtr = 209 std::make_unique<SensorParam>(bus, sensorObjPath, *this); 210 symbols.create_variable(sensor); 211 paramMap.emplace(std::move(sensor), std::move(paramPtr)); 212 } 213 } 214 /* Get expression string */ 215 if (!isCalculationType(interface)) 216 { 217 throw std::invalid_argument("Invalid expression in interface"); 218 } 219 exprStr = interface; 220 221 /* Get optional min and max input and output values */ 222 ValueIface::maxValue( 223 getNumberFromConfig<double>(propertyMap, "MaxValue", false)); 224 ValueIface::minValue( 225 getNumberFromConfig<double>(propertyMap, "MinValue", false)); 226 maxValidInput = 227 getNumberFromConfig<double>(propertyMap, "MaxValidInput", false, 228 std::numeric_limits<double>::infinity()); 229 minValidInput = 230 getNumberFromConfig<double>(propertyMap, "MinValidInput", false, 231 -std::numeric_limits<double>::infinity()); 232 } 233 234 void VirtualSensor::initVirtualSensor(const Json& sensorConfig, 235 const std::string& objPath) 236 { 237 static const Json empty{}; 238 239 /* Get threshold values if defined in config */ 240 auto threshold = sensorConfig.value("Threshold", empty); 241 242 createThresholds(threshold, objPath); 243 244 /* Get MaxValue, MinValue setting if defined in config */ 245 auto confDesc = sensorConfig.value("Desc", empty); 246 if (auto maxConf = confDesc.find("MaxValue"); 247 maxConf != confDesc.end() && maxConf->is_number()) 248 { 249 ValueIface::maxValue(maxConf->get<double>()); 250 } 251 if (auto minConf = confDesc.find("MinValue"); 252 minConf != confDesc.end() && minConf->is_number()) 253 { 254 ValueIface::minValue(minConf->get<double>()); 255 } 256 257 /* Get optional association */ 258 auto assocJson = sensorConfig.value("Associations", empty); 259 if (!assocJson.empty()) 260 { 261 auto assocs = getAssociationsFromJson(assocJson); 262 if (!assocs.empty()) 263 { 264 associationIface = 265 std::make_unique<AssociationObject>(bus, objPath.c_str()); 266 associationIface->associations(assocs); 267 } 268 } 269 270 /* Get expression string */ 271 static constexpr auto exprKey = "Expression"; 272 if (sensorConfig.contains(exprKey)) 273 { 274 auto& ref = sensorConfig.at(exprKey); 275 if (ref.is_array()) 276 { 277 exprStr = std::string{}; 278 for (auto& s : ref) 279 { 280 exprStr += s; 281 } 282 } 283 else if (ref.is_string()) 284 { 285 exprStr = std::string{ref}; 286 } 287 } 288 289 /* Get all the parameter listed in configuration */ 290 auto params = sensorConfig.value("Params", empty); 291 292 /* Check for constant parameter */ 293 const auto& consParams = params.value("ConstParam", empty); 294 if (!consParams.empty()) 295 { 296 for (auto& j : consParams) 297 { 298 if (j.find("ParamName") != j.end()) 299 { 300 auto paramPtr = std::make_unique<SensorParam>(j["Value"]); 301 std::string name = j["ParamName"]; 302 symbols.create_variable(name); 303 paramMap.emplace(std::move(name), std::move(paramPtr)); 304 } 305 else 306 { 307 /* Invalid configuration */ 308 throw std::invalid_argument( 309 "ParamName not found in configuration"); 310 } 311 } 312 } 313 314 /* Check for dbus parameter */ 315 auto dbusParams = params.value("DbusParam", empty); 316 if (!dbusParams.empty()) 317 { 318 for (auto& j : dbusParams) 319 { 320 /* Get parameter dbus sensor descriptor */ 321 auto desc = j.value("Desc", empty); 322 if ((!desc.empty()) && (j.find("ParamName") != j.end())) 323 { 324 std::string sensorType = desc.value("SensorType", ""); 325 std::string name = desc.value("Name", ""); 326 327 if (!sensorType.empty() && !name.empty()) 328 { 329 auto path = sensorDbusPath + sensorType + "/" + name; 330 331 auto paramPtr = 332 std::make_unique<SensorParam>(bus, path, *this); 333 std::string paramName = j["ParamName"]; 334 symbols.create_variable(paramName); 335 paramMap.emplace(std::move(paramName), std::move(paramPtr)); 336 } 337 } 338 } 339 } 340 341 symbols.add_constants(); 342 symbols.add_package(vecopsPackage); 343 symbols.add_function("maxIgnoreNaN", funcMaxIgnoreNaN); 344 symbols.add_function("sumIgnoreNaN", funcSumIgnoreNaN); 345 symbols.add_function("ifNan", funcIfNan); 346 347 expression.register_symbol_table(symbols); 348 349 /* parser from exprtk */ 350 exprtk::parser<double> parser{}; 351 if (!parser.compile(exprStr, expression)) 352 { 353 error("Expression compilation failed"); 354 355 for (std::size_t i = 0; i < parser.error_count(); ++i) 356 { 357 auto err = parser.get_error(i); 358 error("Error parsing token at {POSITION}: {ERROR}", "POSITION", 359 err.token.position, "TYPE", 360 exprtk::parser_error::to_str(err.mode), "ERROR", 361 err.diagnostic); 362 } 363 throw std::runtime_error("Expression compilation failed"); 364 } 365 366 /* Print all parameters for debug purpose only */ 367 printParams(paramMap); 368 } 369 370 void VirtualSensor::createAssociation(const std::string& objPath, 371 const std::string& entityPath) 372 { 373 if (objPath.empty() || entityPath.empty()) 374 { 375 return; 376 } 377 378 std::filesystem::path p(entityPath); 379 auto assocsDbus = 380 AssociationList{{"chassis", "all_sensors", p.parent_path().string()}}; 381 associationIface = 382 std::make_unique<AssociationObject>(bus, objPath.c_str()); 383 associationIface->associations(assocsDbus); 384 } 385 386 void VirtualSensor::initVirtualSensor( 387 const InterfaceMap& interfaceMap, const std::string& objPath, 388 const std::string& sensorType, const std::string& calculationIface) 389 { 390 Json thresholds; 391 const std::string vsThresholdsIntf = 392 calculationIface + vsThresholdsIfaceSuffix; 393 394 for (const auto& [interface, propertyMap] : interfaceMap) 395 { 396 /* Each threshold is on it's own interface with a number as a suffix 397 * eg xyz.openbmc_project.Configuration.ModifiedMedian.Thresholds1 */ 398 if (interface.find(vsThresholdsIntf) != std::string::npos) 399 { 400 parseThresholds(thresholds, propertyMap, interface); 401 } 402 else if (interface == calculationIface) 403 { 404 parseConfigInterface(propertyMap, sensorType, interface); 405 } 406 } 407 408 createThresholds(thresholds, objPath); 409 symbols.add_constants(); 410 symbols.add_package(vecopsPackage); 411 expression.register_symbol_table(symbols); 412 413 createAssociation(objPath, entityPath); 414 /* Print all parameters for debug purpose only */ 415 printParams(paramMap); 416 } 417 418 void VirtualSensor::setSensorValue(double value) 419 { 420 value = std::clamp(value, ValueIface::minValue(), ValueIface::maxValue()); 421 ValueIface::value(value); 422 } 423 424 double VirtualSensor::calculateValue(const std::string& calculation, 425 const VirtualSensor::ParamMap& paramMap) 426 { 427 auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(), 428 calculation); 429 if (itr == calculationIfaces.end()) 430 { 431 return std::numeric_limits<double>::quiet_NaN(); 432 } 433 else if (calculation == "xyz.openbmc_project.Configuration.ModifiedMedian") 434 { 435 return calculateModifiedMedianValue(paramMap); 436 } 437 else if (calculation == "xyz.openbmc_project.Configuration.Maximum") 438 { 439 return calculateMaximumValue(paramMap); 440 } 441 return std::numeric_limits<double>::quiet_NaN(); 442 } 443 444 bool VirtualSensor::sensorInRange(double value) 445 { 446 if (value <= this->maxValidInput && value >= this->minValidInput) 447 { 448 return true; 449 } 450 return false; 451 } 452 453 void VirtualSensor::updateVirtualSensor() 454 { 455 for (auto& param : paramMap) 456 { 457 auto& name = param.first; 458 auto& data = param.second; 459 if (auto var = symbols.get_variable(name)) 460 { 461 var->ref() = data->getParamValue(); 462 } 463 else 464 { 465 /* Invalid parameter */ 466 throw std::invalid_argument("ParamName not found in symbols"); 467 } 468 } 469 auto itr = 470 std::find(calculationIfaces.begin(), calculationIfaces.end(), exprStr); 471 auto val = (itr == calculationIfaces.end()) 472 ? expression.value() 473 : calculateValue(exprStr, paramMap); 474 475 /* Set sensor value to dbus interface */ 476 setSensorValue(val); 477 debug("Sensor {NAME} = {VALUE}", "NAME", this->name, "VALUE", val); 478 479 /* Check sensor thresholds and log required message */ 480 checkThresholds(val, perfLossIface); 481 checkThresholds(val, warningIface); 482 checkThresholds(val, criticalIface); 483 checkThresholds(val, softShutdownIface); 484 checkThresholds(val, hardShutdownIface); 485 } 486 487 double VirtualSensor::calculateModifiedMedianValue( 488 const VirtualSensor::ParamMap& paramMap) 489 { 490 std::vector<double> values; 491 492 for (auto& param : paramMap) 493 { 494 auto& name = param.first; 495 if (auto var = symbols.get_variable(name)) 496 { 497 if (!sensorInRange(var->ref())) 498 { 499 continue; 500 } 501 values.push_back(var->ref()); 502 } 503 } 504 505 size_t size = values.size(); 506 std::sort(values.begin(), values.end()); 507 switch (size) 508 { 509 case 2: 510 /* Choose biggest value */ 511 return values.at(1); 512 case 0: 513 return std::numeric_limits<double>::quiet_NaN(); 514 default: 515 /* Choose median value */ 516 if (size % 2 == 0) 517 { 518 // Average of the two middle values 519 return (values.at(size / 2) + values.at(size / 2 - 1)) / 2; 520 } 521 else 522 { 523 return values.at((size - 1) / 2); 524 } 525 } 526 } 527 528 double VirtualSensor::calculateMaximumValue( 529 const VirtualSensor::ParamMap& paramMap) 530 { 531 std::vector<double> values; 532 533 for (auto& param : paramMap) 534 { 535 auto& name = param.first; 536 if (auto var = symbols.get_variable(name)) 537 { 538 if (!sensorInRange(var->ref())) 539 { 540 continue; 541 } 542 values.push_back(var->ref()); 543 } 544 } 545 auto maxIt = std::max_element(values.begin(), values.end()); 546 if (maxIt == values.end()) 547 { 548 return std::numeric_limits<double>::quiet_NaN(); 549 } 550 return *maxIt; 551 } 552 553 void VirtualSensor::createThresholds(const Json& threshold, 554 const std::string& objPath) 555 { 556 if (threshold.empty()) 557 { 558 return; 559 } 560 // Only create the threshold interfaces if 561 // at least one of their values is present. 562 if (threshold.contains("CriticalHigh") || threshold.contains("CriticalLow")) 563 { 564 criticalIface = 565 std::make_unique<Threshold<CriticalObject>>(bus, objPath.c_str()); 566 567 if (threshold.contains("CriticalHigh")) 568 { 569 criticalIface->setEntityInterfaceHigh( 570 threshold.value("CriticalHighDirection", "")); 571 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath, 572 "INTF", threshold.value("CriticalHighDirection", "")); 573 } 574 if (threshold.contains("CriticalLow")) 575 { 576 criticalIface->setEntityInterfaceLow( 577 threshold.value("CriticalLowDirection", "")); 578 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath, 579 "INTF", threshold.value("CriticalLowDirection", "")); 580 } 581 582 criticalIface->setEntityPath(entityPath); 583 debug("Sensor Threshold:{NAME} = path:{PATH}", "NAME", objPath, "PATH", 584 entityPath); 585 586 criticalIface->criticalHigh(threshold.value( 587 "CriticalHigh", std::numeric_limits<double>::quiet_NaN())); 588 criticalIface->criticalLow(threshold.value( 589 "CriticalLow", std::numeric_limits<double>::quiet_NaN())); 590 criticalIface->setHighHysteresis( 591 threshold.value("CriticalHighHysteresis", defaultHysteresis)); 592 criticalIface->setLowHysteresis( 593 threshold.value("CriticalLowHysteresis", defaultHysteresis)); 594 } 595 596 if (threshold.contains("WarningHigh") || threshold.contains("WarningLow")) 597 { 598 warningIface = 599 std::make_unique<Threshold<WarningObject>>(bus, objPath.c_str()); 600 601 if (threshold.contains("WarningHigh")) 602 { 603 warningIface->setEntityInterfaceHigh( 604 threshold.value("WarningHighDirection", "")); 605 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath, 606 "INTF", threshold.value("WarningHighDirection", "")); 607 } 608 if (threshold.contains("WarningLow")) 609 { 610 warningIface->setEntityInterfaceLow( 611 threshold.value("WarningLowDirection", "")); 612 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath, 613 "INTF", threshold.value("WarningLowDirection", "")); 614 } 615 616 warningIface->setEntityPath(entityPath); 617 debug("Sensor Threshold:{NAME} = path:{PATH}", "NAME", objPath, "PATH", 618 entityPath); 619 620 warningIface->warningHigh(threshold.value( 621 "WarningHigh", std::numeric_limits<double>::quiet_NaN())); 622 warningIface->warningLow(threshold.value( 623 "WarningLow", std::numeric_limits<double>::quiet_NaN())); 624 warningIface->setHighHysteresis( 625 threshold.value("WarningHighHysteresis", defaultHysteresis)); 626 warningIface->setLowHysteresis( 627 threshold.value("WarningLowHysteresis", defaultHysteresis)); 628 } 629 630 if (threshold.contains("HardShutdownHigh") || 631 threshold.contains("HardShutdownLow")) 632 { 633 hardShutdownIface = std::make_unique<Threshold<HardShutdownObject>>( 634 bus, objPath.c_str()); 635 636 hardShutdownIface->hardShutdownHigh(threshold.value( 637 "HardShutdownHigh", std::numeric_limits<double>::quiet_NaN())); 638 hardShutdownIface->hardShutdownLow(threshold.value( 639 "HardShutdownLow", std::numeric_limits<double>::quiet_NaN())); 640 hardShutdownIface->setHighHysteresis( 641 threshold.value("HardShutdownHighHysteresis", defaultHysteresis)); 642 hardShutdownIface->setLowHysteresis( 643 threshold.value("HardShutdownLowHysteresis", defaultHysteresis)); 644 } 645 646 if (threshold.contains("SoftShutdownHigh") || 647 threshold.contains("SoftShutdownLow")) 648 { 649 softShutdownIface = std::make_unique<Threshold<SoftShutdownObject>>( 650 bus, objPath.c_str()); 651 652 softShutdownIface->softShutdownHigh(threshold.value( 653 "SoftShutdownHigh", std::numeric_limits<double>::quiet_NaN())); 654 softShutdownIface->softShutdownLow(threshold.value( 655 "SoftShutdownLow", std::numeric_limits<double>::quiet_NaN())); 656 softShutdownIface->setHighHysteresis( 657 threshold.value("SoftShutdownHighHysteresis", defaultHysteresis)); 658 softShutdownIface->setLowHysteresis( 659 threshold.value("SoftShutdownLowHysteresis", defaultHysteresis)); 660 } 661 662 if (threshold.contains("PerformanceLossHigh") || 663 threshold.contains("PerformanceLossLow")) 664 { 665 perfLossIface = std::make_unique<Threshold<PerformanceLossObject>>( 666 bus, objPath.c_str()); 667 668 perfLossIface->performanceLossHigh(threshold.value( 669 "PerformanceLossHigh", std::numeric_limits<double>::quiet_NaN())); 670 perfLossIface->performanceLossLow(threshold.value( 671 "PerformanceLossLow", std::numeric_limits<double>::quiet_NaN())); 672 perfLossIface->setHighHysteresis(threshold.value( 673 "PerformanceLossHighHysteresis", defaultHysteresis)); 674 perfLossIface->setLowHysteresis( 675 threshold.value("PerformanceLossLowHysteresis", defaultHysteresis)); 676 } 677 } 678 679 ManagedObjectType VirtualSensors::getObjectsFromDBus() 680 { 681 ManagedObjectType objects; 682 683 try 684 { 685 auto method = bus.new_method_call( 686 "xyz.openbmc_project.EntityManager", 687 "/xyz/openbmc_project/inventory", 688 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 689 auto reply = bus.call(method); 690 reply.read(objects); 691 } 692 catch (const sdbusplus::exception_t& ex) 693 { 694 // If entity manager isn't running yet, keep going. 695 if (std::string("org.freedesktop.DBus.Error.ServiceUnknown") != 696 ex.name()) 697 { 698 error("Could not reach entity-manager: {ERROR}", "ERROR", ex); 699 throw; 700 } 701 } 702 703 return objects; 704 } 705 706 void VirtualSensors::propertiesChanged(sdbusplus::message_t& msg) 707 { 708 std::string path; 709 PropertyMap properties; 710 711 msg.read(path, properties); 712 713 /* We get multiple callbacks for one sensor. 'Type' is a required field and 714 * is a unique label so use to to only proceed once per sensor */ 715 if (properties.contains("Type")) 716 { 717 if (isCalculationType(path)) 718 { 719 createVirtualSensorsFromDBus(path); 720 } 721 } 722 } 723 724 /** @brief Parsing Virtual Sensor config JSON file */ 725 Json VirtualSensors::parseConfigFile() 726 { 727 using path = std::filesystem::path; 728 auto configFile = []() -> path { 729 static constexpr auto name = "virtual_sensor_config.json"; 730 731 for (auto pathSeg : {std::filesystem::current_path(), 732 path{"/var/lib/phosphor-virtual-sensor"}, 733 path{"/usr/share/phosphor-virtual-sensor"}}) 734 { 735 auto file = pathSeg / name; 736 if (std::filesystem::exists(file)) 737 { 738 return file; 739 } 740 } 741 return name; 742 }(); 743 744 std::ifstream jsonFile(configFile); 745 if (!jsonFile.is_open()) 746 { 747 error("config JSON file {FILENAME} not found", "FILENAME", configFile); 748 return {}; 749 } 750 751 auto data = Json::parse(jsonFile, nullptr, false); 752 if (data.is_discarded()) 753 { 754 error("config readings JSON parser failure with {FILENAME}", "FILENAME", 755 configFile); 756 throw std::exception{}; 757 } 758 759 return data; 760 } 761 762 std::map<std::string, ValueIface::Unit> unitMap = { 763 {"temperature", ValueIface::Unit::DegreesC}, 764 {"fan_tach", ValueIface::Unit::RPMS}, 765 {"fan_pwm", ValueIface::Unit::Percent}, 766 {"voltage", ValueIface::Unit::Volts}, 767 {"altitude", ValueIface::Unit::Meters}, 768 {"current", ValueIface::Unit::Amperes}, 769 {"power", ValueIface::Unit::Watts}, 770 {"energy", ValueIface::Unit::Joules}, 771 {"utilization", ValueIface::Unit::Percent}, 772 {"airflow", ValueIface::Unit::CFM}, 773 {"pressure", ValueIface::Unit::Pascals}}; 774 775 const std::string getSensorTypeFromUnit(const std::string& unit) 776 { 777 std::string unitPrefix = "xyz.openbmc_project.Sensor.Value.Unit."; 778 for (auto [type, unitObj] : unitMap) 779 { 780 auto unitPath = ValueIface::convertUnitToString(unitObj); 781 if (unitPath == (unitPrefix + unit)) 782 { 783 return type; 784 } 785 } 786 return ""; 787 } 788 789 void VirtualSensors::setupMatches() 790 { 791 /* Already setup */ 792 if (!this->matches.empty()) 793 { 794 return; 795 } 796 797 /* Setup matches */ 798 auto eventHandler = [this](sdbusplus::message_t& message) { 799 if (message.is_method_error()) 800 { 801 error("Callback method error"); 802 return; 803 } 804 this->propertiesChanged(message); 805 }; 806 807 for (const char* iface : calculationIfaces) 808 { 809 auto match = std::make_unique<sdbusplus::bus::match_t>( 810 bus, 811 sdbusplus::bus::match::rules::propertiesChangedNamespace( 812 "/xyz/openbmc_project/inventory", iface), 813 eventHandler); 814 this->matches.emplace_back(std::move(match)); 815 } 816 } 817 818 void VirtualSensors::createVirtualSensorsFromDBus( 819 const std::string& calculationIface) 820 { 821 if (calculationIface.empty()) 822 { 823 error("No calculation type supplied"); 824 return; 825 } 826 auto objects = getObjectsFromDBus(); 827 828 /* Get virtual sensors config data */ 829 for (const auto& [path, interfaceMap] : objects) 830 { 831 /* Find Virtual Sensor interfaces */ 832 auto intfIter = interfaceMap.find(calculationIface); 833 if (intfIter == interfaceMap.end()) 834 { 835 continue; 836 } 837 838 std::string name = path.filename(); 839 if (name.empty()) 840 { 841 error("Virtual Sensor name not found in entity manager config"); 842 continue; 843 } 844 if (virtualSensorsMap.contains(name)) 845 { 846 error("A virtual sensor named {NAME} already exists", "NAME", name); 847 continue; 848 } 849 850 /* Extract the virtual sensor type as we need this to initialize the 851 * sensor */ 852 std::string sensorType, sensorUnit; 853 auto propertyMap = intfIter->second; 854 auto proIter = propertyMap.find("Units"); 855 if (proIter != propertyMap.end()) 856 { 857 sensorUnit = std::get<std::string>(proIter->second); 858 } 859 sensorType = getSensorTypeFromUnit(sensorUnit); 860 if (sensorType.empty()) 861 { 862 error("Sensor unit type {TYPE} is not supported", "TYPE", 863 sensorUnit); 864 continue; 865 } 866 867 try 868 { 869 auto objpath = static_cast<std::string>(path); 870 auto virtObjPath = sensorDbusPath + sensorType + "/" + name; 871 872 auto virtualSensorPtr = std::make_unique<VirtualSensor>( 873 bus, virtObjPath.c_str(), interfaceMap, name, sensorType, 874 calculationIface, objpath); 875 info("Added a new virtual sensor: {NAME} {TYPE}", "NAME", name, 876 "TYPE", sensorType); 877 virtualSensorPtr->updateVirtualSensor(); 878 879 /* Initialize unit value for virtual sensor */ 880 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]); 881 virtualSensorPtr->emit_object_added(); 882 883 virtualSensorsMap.emplace(name, std::move(virtualSensorPtr)); 884 885 /* Setup match for interfaces removed */ 886 auto intfRemoved = [this, objpath, 887 name](sdbusplus::message_t& message) { 888 if (!virtualSensorsMap.contains(name)) 889 { 890 return; 891 } 892 sdbusplus::message::object_path path; 893 message.read(path); 894 if (static_cast<const std::string&>(path) == objpath) 895 { 896 info("Removed a virtual sensor: {NAME}", "NAME", name); 897 virtualSensorsMap.erase(name); 898 } 899 }; 900 auto matchOnRemove = std::make_unique<sdbusplus::bus::match_t>( 901 bus, 902 sdbusplus::bus::match::rules::interfacesRemoved() + 903 sdbusplus::bus::match::rules::argNpath(0, objpath), 904 intfRemoved); 905 /* TODO: slight race condition here. Check that the config still 906 * exists */ 907 this->matches.emplace_back(std::move(matchOnRemove)); 908 } 909 catch (const std::invalid_argument& ia) 910 { 911 error("Failed to set up virtual sensor: {ERROR}", "ERROR", ia); 912 } 913 } 914 } 915 916 void VirtualSensors::createVirtualSensors() 917 { 918 static const Json empty{}; 919 920 auto data = parseConfigFile(); 921 922 // print values 923 debug("JSON: {JSON}", "JSON", data.dump()); 924 925 /* Get virtual sensors config data */ 926 for (const auto& j : data) 927 { 928 auto desc = j.value("Desc", empty); 929 if (!desc.empty()) 930 { 931 if (desc.value("Config", "") == "D-Bus") 932 { 933 /* Look on D-Bus for a virtual sensor config. Set up matches 934 * first because the configs may not be on D-Bus yet and we 935 * don't want to miss them */ 936 setupMatches(); 937 938 if (desc.contains("Type")) 939 { 940 auto type = desc.value("Type", ""); 941 auto path = "xyz.openbmc_project.Configuration." + type; 942 943 if (!isCalculationType(path)) 944 { 945 error("Invalid calculation type {TYPE} supplied.", 946 "TYPE", type); 947 continue; 948 } 949 createVirtualSensorsFromDBus(path); 950 } 951 continue; 952 } 953 954 std::string sensorType = desc.value("SensorType", ""); 955 std::string name = desc.value("Name", ""); 956 std::replace(name.begin(), name.end(), ' ', '_'); 957 958 if (!name.empty() && !sensorType.empty()) 959 { 960 if (unitMap.find(sensorType) == unitMap.end()) 961 { 962 error("Sensor type {TYPE} is not supported", "TYPE", 963 sensorType); 964 } 965 else 966 { 967 if (virtualSensorsMap.find(name) != virtualSensorsMap.end()) 968 { 969 error("A virtual sensor named {NAME} already exists", 970 "NAME", name); 971 continue; 972 } 973 auto objPath = sensorDbusPath + sensorType + "/" + name; 974 975 auto virtualSensorPtr = std::make_unique<VirtualSensor>( 976 bus, objPath.c_str(), j, name); 977 978 info("Added a new virtual sensor: {NAME}", "NAME", name); 979 virtualSensorPtr->updateVirtualSensor(); 980 981 /* Initialize unit value for virtual sensor */ 982 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]); 983 virtualSensorPtr->emit_object_added(); 984 985 virtualSensorsMap.emplace(std::move(name), 986 std::move(virtualSensorPtr)); 987 } 988 } 989 else 990 { 991 error( 992 "Sensor type ({TYPE}) or name ({NAME}) not found in config file", 993 "NAME", name, "TYPE", sensorType); 994 } 995 } 996 else 997 { 998 error("Descriptor for new virtual sensor not found in config file"); 999 } 1000 } 1001 } 1002 1003 } // namespace phosphor::virtual_sensor 1004 1005 /** 1006 * @brief Main 1007 */ 1008 int main() 1009 { 1010 // Get a handle to system dbus 1011 auto bus = sdbusplus::bus::new_default(); 1012 1013 // Add the ObjectManager interface 1014 sdbusplus::server::manager_t objManager(bus, 1015 "/xyz/openbmc_project/sensors"); 1016 1017 // Create an virtual sensors object 1018 phosphor::virtual_sensor::VirtualSensors virtualSensors(bus); 1019 1020 // Request service bus name 1021 bus.request_name("xyz.openbmc_project.VirtualSensor"); 1022 1023 // Run the dbus loop. 1024 bus.process_loop(); 1025 1026 return 0; 1027 } 1028