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