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{"Warning", "Critical", 133 "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 = getNumberFromConfig<uint64_t>(propertyMap, "Severity", 152 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 = getNumberFromConfig<double>(propertyMap, "Hysteresis", 184 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 = std::make_unique<SensorParam>(bus, sensorObjPath, 210 *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 = std::make_unique<SensorParam>(bus, path, 333 *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 = std::make_unique<AssociationObject>(bus, 384 objPath.c_str()); 385 associationIface->associations(assocsDbus); 386 } 387 388 void VirtualSensor::initVirtualSensor(const InterfaceMap& interfaceMap, 389 const std::string& objPath, 390 const std::string& sensorType, 391 const std::string& calculationIface) 392 { 393 Json thresholds; 394 const std::string vsThresholdsIntf = calculationIface + 395 vsThresholdsIfaceSuffix; 396 397 for (const auto& [interface, propertyMap] : interfaceMap) 398 { 399 /* Each threshold is on it's own interface with a number as a suffix 400 * eg xyz.openbmc_project.Configuration.ModifiedMedian.Thresholds1 */ 401 if (interface.find(vsThresholdsIntf) != std::string::npos) 402 { 403 parseThresholds(thresholds, propertyMap, interface); 404 } 405 else if (interface == calculationIface) 406 { 407 parseConfigInterface(propertyMap, sensorType, interface); 408 } 409 } 410 411 createThresholds(thresholds, objPath); 412 symbols.add_constants(); 413 symbols.add_package(vecopsPackage); 414 expression.register_symbol_table(symbols); 415 416 createAssociation(objPath, entityPath); 417 /* Print all parameters for debug purpose only */ 418 if (DEBUG) 419 { 420 printParams(paramMap); 421 } 422 } 423 424 void VirtualSensor::setSensorValue(double value) 425 { 426 value = std::clamp(value, ValueIface::minValue(), ValueIface::maxValue()); 427 ValueIface::value(value); 428 } 429 430 double VirtualSensor::calculateValue(const std::string& calculation, 431 const VirtualSensor::ParamMap& paramMap) 432 { 433 auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(), 434 calculation); 435 if (itr == calculationIfaces.end()) 436 { 437 return std::numeric_limits<double>::quiet_NaN(); 438 } 439 else if (calculation == "xyz.openbmc_project.Configuration.ModifiedMedian") 440 { 441 return calculateModifiedMedianValue(paramMap); 442 } 443 else if (calculation == "xyz.openbmc_project.Configuration.Maximum") 444 { 445 return calculateMaximumValue(paramMap); 446 } 447 return std::numeric_limits<double>::quiet_NaN(); 448 } 449 450 bool VirtualSensor::sensorInRange(double value) 451 { 452 if (value <= this->maxValidInput && value >= this->minValidInput) 453 { 454 return true; 455 } 456 return false; 457 } 458 459 void VirtualSensor::updateVirtualSensor() 460 { 461 for (auto& param : paramMap) 462 { 463 auto& name = param.first; 464 auto& data = param.second; 465 if (auto var = symbols.get_variable(name)) 466 { 467 var->ref() = data->getParamValue(); 468 } 469 else 470 { 471 /* Invalid parameter */ 472 throw std::invalid_argument("ParamName not found in symbols"); 473 } 474 } 475 auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(), 476 exprStr); 477 auto val = (itr == calculationIfaces.end()) 478 ? expression.value() 479 : calculateValue(exprStr, paramMap); 480 481 /* Set sensor value to dbus interface */ 482 setSensorValue(val); 483 484 if (DEBUG) 485 { 486 debug("Sensor {NAME} = {VALUE}", "NAME", this->name, "VALUE", val); 487 } 488 489 /* Check sensor thresholds and log required message */ 490 checkThresholds(val, perfLossIface); 491 checkThresholds(val, warningIface); 492 checkThresholds(val, criticalIface); 493 checkThresholds(val, softShutdownIface); 494 checkThresholds(val, hardShutdownIface); 495 } 496 497 double VirtualSensor::calculateModifiedMedianValue( 498 const VirtualSensor::ParamMap& paramMap) 499 { 500 std::vector<double> values; 501 502 for (auto& param : paramMap) 503 { 504 auto& name = param.first; 505 if (auto var = symbols.get_variable(name)) 506 { 507 if (!sensorInRange(var->ref())) 508 { 509 continue; 510 } 511 values.push_back(var->ref()); 512 } 513 } 514 515 size_t size = values.size(); 516 std::sort(values.begin(), values.end()); 517 switch (size) 518 { 519 case 2: 520 /* Choose biggest value */ 521 return values.at(1); 522 case 0: 523 return std::numeric_limits<double>::quiet_NaN(); 524 default: 525 /* Choose median value */ 526 if (size % 2 == 0) 527 { 528 // Average of the two middle values 529 return (values.at(size / 2) + values.at(size / 2 - 1)) / 2; 530 } 531 else 532 { 533 return values.at((size - 1) / 2); 534 } 535 } 536 } 537 538 double VirtualSensor::calculateMaximumValue( 539 const VirtualSensor::ParamMap& paramMap) 540 { 541 std::vector<double> values; 542 543 for (auto& param : paramMap) 544 { 545 auto& name = param.first; 546 if (auto var = symbols.get_variable(name)) 547 { 548 if (!sensorInRange(var->ref())) 549 { 550 continue; 551 } 552 values.push_back(var->ref()); 553 } 554 } 555 auto maxIt = std::max_element(values.begin(), values.end()); 556 if (maxIt == values.end()) 557 { 558 return std::numeric_limits<double>::quiet_NaN(); 559 } 560 return *maxIt; 561 } 562 563 void VirtualSensor::createThresholds(const Json& threshold, 564 const std::string& objPath) 565 { 566 if (threshold.empty()) 567 { 568 return; 569 } 570 // Only create the threshold interfaces if 571 // at least one of their values is present. 572 if (threshold.contains("CriticalHigh") || threshold.contains("CriticalLow")) 573 { 574 criticalIface = 575 std::make_unique<Threshold<CriticalObject>>(bus, objPath.c_str()); 576 577 if (threshold.contains("CriticalHigh")) 578 { 579 criticalIface->setEntityInterfaceHigh( 580 threshold.value("CriticalHighDirection", "")); 581 if (DEBUG) 582 { 583 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath, 584 "INTF", threshold.value("CriticalHighDirection", "")); 585 } 586 } 587 if (threshold.contains("CriticalLow")) 588 { 589 criticalIface->setEntityInterfaceLow( 590 threshold.value("CriticalLowDirection", "")); 591 if (DEBUG) 592 { 593 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath, 594 "INTF", threshold.value("CriticalLowDirection", "")); 595 } 596 } 597 598 criticalIface->setEntityPath(entityPath); 599 if (DEBUG) 600 { 601 debug("Sensor Threshold:{NAME} = path:{PATH}", "NAME", objPath, 602 "PATH", entityPath); 603 } 604 605 criticalIface->criticalHigh(threshold.value( 606 "CriticalHigh", std::numeric_limits<double>::quiet_NaN())); 607 criticalIface->criticalLow(threshold.value( 608 "CriticalLow", std::numeric_limits<double>::quiet_NaN())); 609 criticalIface->setHighHysteresis( 610 threshold.value("CriticalHighHysteresis", defaultHysteresis)); 611 criticalIface->setLowHysteresis( 612 threshold.value("CriticalLowHysteresis", defaultHysteresis)); 613 } 614 615 if (threshold.contains("WarningHigh") || threshold.contains("WarningLow")) 616 { 617 warningIface = 618 std::make_unique<Threshold<WarningObject>>(bus, objPath.c_str()); 619 620 if (threshold.contains("WarningHigh")) 621 { 622 warningIface->setEntityInterfaceHigh( 623 threshold.value("WarningHighDirection", "")); 624 if (DEBUG) 625 { 626 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath, 627 "INTF", threshold.value("WarningHighDirection", "")); 628 } 629 } 630 if (threshold.contains("WarningLow")) 631 { 632 warningIface->setEntityInterfaceLow( 633 threshold.value("WarningLowDirection", "")); 634 if (DEBUG) 635 { 636 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath, 637 "INTF", threshold.value("WarningLowDirection", "")); 638 } 639 } 640 641 warningIface->setEntityPath(entityPath); 642 if (DEBUG) 643 { 644 debug("Sensor Threshold:{NAME} = path:{PATH}", "NAME", objPath, 645 "PATH", entityPath); 646 } 647 648 warningIface->warningHigh(threshold.value( 649 "WarningHigh", std::numeric_limits<double>::quiet_NaN())); 650 warningIface->warningLow(threshold.value( 651 "WarningLow", std::numeric_limits<double>::quiet_NaN())); 652 warningIface->setHighHysteresis( 653 threshold.value("WarningHighHysteresis", defaultHysteresis)); 654 warningIface->setLowHysteresis( 655 threshold.value("WarningLowHysteresis", defaultHysteresis)); 656 } 657 658 if (threshold.contains("HardShutdownHigh") || 659 threshold.contains("HardShutdownLow")) 660 { 661 hardShutdownIface = std::make_unique<Threshold<HardShutdownObject>>( 662 bus, objPath.c_str()); 663 664 hardShutdownIface->hardShutdownHigh(threshold.value( 665 "HardShutdownHigh", std::numeric_limits<double>::quiet_NaN())); 666 hardShutdownIface->hardShutdownLow(threshold.value( 667 "HardShutdownLow", std::numeric_limits<double>::quiet_NaN())); 668 hardShutdownIface->setHighHysteresis( 669 threshold.value("HardShutdownHighHysteresis", defaultHysteresis)); 670 hardShutdownIface->setLowHysteresis( 671 threshold.value("HardShutdownLowHysteresis", defaultHysteresis)); 672 } 673 674 if (threshold.contains("SoftShutdownHigh") || 675 threshold.contains("SoftShutdownLow")) 676 { 677 softShutdownIface = std::make_unique<Threshold<SoftShutdownObject>>( 678 bus, objPath.c_str()); 679 680 softShutdownIface->softShutdownHigh(threshold.value( 681 "SoftShutdownHigh", std::numeric_limits<double>::quiet_NaN())); 682 softShutdownIface->softShutdownLow(threshold.value( 683 "SoftShutdownLow", std::numeric_limits<double>::quiet_NaN())); 684 softShutdownIface->setHighHysteresis( 685 threshold.value("SoftShutdownHighHysteresis", defaultHysteresis)); 686 softShutdownIface->setLowHysteresis( 687 threshold.value("SoftShutdownLowHysteresis", defaultHysteresis)); 688 } 689 690 if (threshold.contains("PerformanceLossHigh") || 691 threshold.contains("PerformanceLossLow")) 692 { 693 perfLossIface = std::make_unique<Threshold<PerformanceLossObject>>( 694 bus, objPath.c_str()); 695 696 perfLossIface->performanceLossHigh(threshold.value( 697 "PerformanceLossHigh", std::numeric_limits<double>::quiet_NaN())); 698 perfLossIface->performanceLossLow(threshold.value( 699 "PerformanceLossLow", std::numeric_limits<double>::quiet_NaN())); 700 perfLossIface->setHighHysteresis(threshold.value( 701 "PerformanceLossHighHysteresis", defaultHysteresis)); 702 perfLossIface->setLowHysteresis( 703 threshold.value("PerformanceLossLowHysteresis", defaultHysteresis)); 704 } 705 } 706 707 ManagedObjectType VirtualSensors::getObjectsFromDBus() 708 { 709 ManagedObjectType objects; 710 711 try 712 { 713 auto method = bus.new_method_call("xyz.openbmc_project.EntityManager", 714 "/xyz/openbmc_project/inventory", 715 "org.freedesktop.DBus.ObjectManager", 716 "GetManagedObjects"); 717 auto reply = bus.call(method); 718 reply.read(objects); 719 } 720 catch (const sdbusplus::exception_t& ex) 721 { 722 // If entity manager isn't running yet, keep going. 723 if (std::string("org.freedesktop.DBus.Error.ServiceUnknown") != 724 ex.name()) 725 { 726 error("Could not reach entity-manager: {ERROR}", "ERROR", ex); 727 throw; 728 } 729 } 730 731 return objects; 732 } 733 734 void VirtualSensors::propertiesChanged(sdbusplus::message_t& msg) 735 { 736 std::string path; 737 PropertyMap properties; 738 739 msg.read(path, properties); 740 741 /* We get multiple callbacks for one sensor. 'Type' is a required field and 742 * is a unique label so use to to only proceed once per sensor */ 743 if (properties.contains("Type")) 744 { 745 if (isCalculationType(path)) 746 { 747 createVirtualSensorsFromDBus(path); 748 } 749 } 750 } 751 752 /** @brief Parsing Virtual Sensor config JSON file */ 753 Json VirtualSensors::parseConfigFile() 754 { 755 using path = std::filesystem::path; 756 auto configFile = []() -> path { 757 static constexpr auto name = "virtual_sensor_config.json"; 758 759 for (auto pathSeg : {std::filesystem::current_path(), 760 path{"/var/lib/phosphor-virtual-sensor"}, 761 path{"/usr/share/phosphor-virtual-sensor"}}) 762 { 763 auto file = pathSeg / name; 764 if (std::filesystem::exists(file)) 765 { 766 return file; 767 } 768 } 769 return name; 770 }(); 771 772 std::ifstream jsonFile(configFile); 773 if (!jsonFile.is_open()) 774 { 775 error("config JSON file {FILENAME} not found", "FILENAME", configFile); 776 return {}; 777 } 778 779 auto data = Json::parse(jsonFile, nullptr, false); 780 if (data.is_discarded()) 781 { 782 error("config readings JSON parser failure with {FILENAME}", "FILENAME", 783 configFile); 784 throw std::exception{}; 785 } 786 787 return data; 788 } 789 790 std::map<std::string, ValueIface::Unit> unitMap = { 791 {"temperature", ValueIface::Unit::DegreesC}, 792 {"fan_tach", ValueIface::Unit::RPMS}, 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