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