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