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