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