1 #include "virtualSensor.hpp" 2 3 #include "config.hpp" 4 5 #include <phosphor-logging/lg2.hpp> 6 #include <sdeventplus/event.hpp> 7 8 #include <fstream> 9 10 static constexpr bool DEBUG = false; 11 static constexpr auto busName = "xyz.openbmc_project.VirtualSensor"; 12 static constexpr auto sensorDbusPath = "/xyz/openbmc_project/sensors/"; 13 static constexpr auto entityManagerBusName = 14 "xyz.openbmc_project.EntityManager"; 15 static constexpr auto vsThresholdsIfaceSuffix = ".Thresholds"; 16 static constexpr std::array<const char*, 1> calculationIfaces = { 17 "xyz.openbmc_project.Configuration.ModifiedMedian"}; 18 static constexpr auto defaultHysteresis = 0; 19 20 PHOSPHOR_LOG2_USING_WITH_FLAGS; 21 22 int handleDbusSignal(sd_bus_message* msg, void* usrData, sd_bus_error*) 23 { 24 if (usrData == nullptr) 25 { 26 throw std::runtime_error("Invalid match"); 27 } 28 29 auto sdbpMsg = sdbusplus::message::message(msg); 30 std::string msgIfce; 31 std::map<std::string, std::variant<int64_t, double, bool>> msgData; 32 33 sdbpMsg.read(msgIfce, msgData); 34 35 if (msgData.find("Value") != msgData.end()) 36 { 37 using namespace phosphor::virtualSensor; 38 VirtualSensor* obj = static_cast<VirtualSensor*>(usrData); 39 // TODO(openbmc/phosphor-virtual-sensor#1): updateVirtualSensor should 40 // be changed to take the information we got from the signal, to avoid 41 // having to do numerous dbus queries. 42 obj->updateVirtualSensor(); 43 } 44 return 0; 45 } 46 47 namespace phosphor 48 { 49 namespace virtualSensor 50 { 51 52 void printParams(const VirtualSensor::ParamMap& paramMap) 53 { 54 for (const auto& p : paramMap) 55 { 56 const auto& p1 = p.first; 57 const auto& p2 = p.second; 58 auto val = p2->getParamValue(); 59 debug("Parameter: {PARAM} = {VALUE}", "PARAM", p1, "VALUE", val); 60 } 61 } 62 63 double SensorParam::getParamValue() 64 { 65 switch (paramType) 66 { 67 case constParam: 68 return value; 69 break; 70 case dbusParam: 71 return dbusSensor->getSensorValue(); 72 break; 73 default: 74 throw std::invalid_argument("param type not supported"); 75 } 76 } 77 78 using AssociationList = 79 std::vector<std::tuple<std::string, std::string, std::string>>; 80 81 AssociationList getAssociationsFromJson(const Json& j) 82 { 83 AssociationList assocs{}; 84 try 85 { 86 j.get_to(assocs); 87 } 88 catch (const std::exception& ex) 89 { 90 error("Failed to parse association: {ERROR}", "ERROR", ex); 91 } 92 return assocs; 93 } 94 95 template <typename U> 96 struct VariantToNumber 97 { 98 template <typename T> 99 U operator()(const T& t) const 100 { 101 if constexpr (std::is_convertible<T, U>::value) 102 { 103 return static_cast<U>(t); 104 } 105 throw std::invalid_argument("Invalid number type in config\n"); 106 } 107 }; 108 109 template <typename U> 110 U getNumberFromConfig(const PropertyMap& map, const std::string& name, 111 bool required) 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 std::numeric_limits<U>::quiet_NaN(); 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 = 179 getNumberFromConfig<uint64_t>(propertyMap, "Severity", 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 { 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 = 210 getNumberFromConfig<double>(propertyMap, "Hysteresis", false); 211 if (hysteresis != std::numeric_limits<double>::quiet_NaN()) 212 { 213 thresholds[threshold + "Hysteresis"] = hysteresis; 214 } 215 } 216 217 void VirtualSensor::parseConfigInterface(const PropertyMap& propertyMap, 218 const std::string& sensorType, 219 const std::string& interface) 220 { 221 /* Parse sensors / DBus params */ 222 if (auto itr = propertyMap.find("Sensors"); itr != propertyMap.end()) 223 { 224 auto sensors = std::get<std::vector<std::string>>(itr->second); 225 for (auto sensor : sensors) 226 { 227 std::replace(sensor.begin(), sensor.end(), ' ', '_'); 228 auto sensorObjPath = sensorDbusPath + sensorType + "/" + sensor; 229 230 auto paramPtr = 231 std::make_unique<SensorParam>(bus, sensorObjPath, this); 232 symbols.create_variable(sensor); 233 paramMap.emplace(std::move(sensor), std::move(paramPtr)); 234 } 235 } 236 /* Get expression string */ 237 if (!isCalculationType(interface)) 238 { 239 throw std::invalid_argument("Invalid expression in interface"); 240 } 241 exprStr = interface; 242 243 /* Get optional min and max input and output values */ 244 ValueIface::maxValue( 245 getNumberFromConfig<double>(propertyMap, "MaxValue", false)); 246 ValueIface::minValue( 247 getNumberFromConfig<double>(propertyMap, "MinValue", false)); 248 maxValidInput = 249 getNumberFromConfig<double>(propertyMap, "MaxValidInput", false); 250 minValidInput = 251 getNumberFromConfig<double>(propertyMap, "MinValidInput", false); 252 } 253 254 void VirtualSensor::initVirtualSensor(const Json& sensorConfig, 255 const std::string& objPath) 256 { 257 258 static const Json empty{}; 259 260 /* Get threshold values if defined in config */ 261 auto threshold = sensorConfig.value("Threshold", empty); 262 263 createThresholds(threshold, objPath); 264 265 /* Get MaxValue, MinValue setting if defined in config */ 266 auto confDesc = sensorConfig.value("Desc", empty); 267 if (auto maxConf = confDesc.find("MaxValue"); 268 maxConf != confDesc.end() && maxConf->is_number()) 269 { 270 ValueIface::maxValue(maxConf->get<double>()); 271 } 272 if (auto minConf = confDesc.find("MinValue"); 273 minConf != confDesc.end() && minConf->is_number()) 274 { 275 ValueIface::minValue(minConf->get<double>()); 276 } 277 278 /* Get optional association */ 279 auto assocJson = sensorConfig.value("Associations", empty); 280 if (!assocJson.empty()) 281 { 282 auto assocs = getAssociationsFromJson(assocJson); 283 if (!assocs.empty()) 284 { 285 associationIface = 286 std::make_unique<AssociationObject>(bus, objPath.c_str()); 287 associationIface->associations(assocs); 288 } 289 } 290 291 /* Get expression string */ 292 exprStr = sensorConfig.value("Expression", ""); 293 294 /* Get all the parameter listed in configuration */ 295 auto params = sensorConfig.value("Params", empty); 296 297 /* Check for constant parameter */ 298 const auto& consParams = params.value("ConstParam", empty); 299 if (!consParams.empty()) 300 { 301 for (auto& j : consParams) 302 { 303 if (j.find("ParamName") != j.end()) 304 { 305 auto paramPtr = std::make_unique<SensorParam>(j["Value"]); 306 std::string name = j["ParamName"]; 307 symbols.create_variable(name); 308 paramMap.emplace(std::move(name), std::move(paramPtr)); 309 } 310 else 311 { 312 /* Invalid configuration */ 313 throw std::invalid_argument( 314 "ParamName not found in configuration"); 315 } 316 } 317 } 318 319 /* Check for dbus parameter */ 320 auto dbusParams = params.value("DbusParam", empty); 321 if (!dbusParams.empty()) 322 { 323 for (auto& j : dbusParams) 324 { 325 /* Get parameter dbus sensor descriptor */ 326 auto desc = j.value("Desc", empty); 327 if ((!desc.empty()) && (j.find("ParamName") != j.end())) 328 { 329 std::string sensorType = desc.value("SensorType", ""); 330 std::string name = desc.value("Name", ""); 331 332 if (!sensorType.empty() && !name.empty()) 333 { 334 auto path = sensorDbusPath + sensorType + "/" + name; 335 336 auto paramPtr = 337 std::make_unique<SensorParam>(bus, path, this); 338 std::string paramName = j["ParamName"]; 339 symbols.create_variable(paramName); 340 paramMap.emplace(std::move(paramName), std::move(paramPtr)); 341 } 342 } 343 } 344 } 345 346 symbols.add_constants(); 347 symbols.add_package(vecopsPackage); 348 expression.register_symbol_table(symbols); 349 350 /* parser from exprtk */ 351 exprtk::parser<double> parser{}; 352 if (!parser.compile(exprStr, expression)) 353 { 354 error("Expression compilation failed"); 355 356 for (std::size_t i = 0; i < parser.error_count(); ++i) 357 { 358 auto err = parser.get_error(i); 359 error("Error parsing token at {POSITION}: {ERROR}", "POSITION", 360 err.token.position, "TYPE", 361 exprtk::parser_error::to_str(err.mode), "ERROR", 362 err.diagnostic); 363 } 364 throw std::runtime_error("Expression compilation failed"); 365 } 366 367 /* Print all parameters for debug purpose only */ 368 if (DEBUG) 369 printParams(paramMap); 370 } 371 372 void VirtualSensor::initVirtualSensor(const InterfaceMap& interfaceMap, 373 const std::string& objPath, 374 const std::string& sensorType, 375 const std::string& calculationIface) 376 { 377 Json thresholds; 378 const std::string vsThresholdsIntf = 379 calculationIface + vsThresholdsIfaceSuffix; 380 381 for (const auto& [interface, propertyMap] : interfaceMap) 382 { 383 /* Each threshold is on it's own interface with a number as a suffix 384 * eg xyz.openbmc_project.Configuration.ModifiedMedian.Thresholds1 */ 385 if (interface.find(vsThresholdsIntf) != std::string::npos) 386 { 387 parseThresholds(thresholds, propertyMap); 388 } 389 else if (interface == calculationIface) 390 { 391 parseConfigInterface(propertyMap, sensorType, interface); 392 } 393 } 394 395 createThresholds(thresholds, objPath); 396 symbols.add_constants(); 397 symbols.add_package(vecopsPackage); 398 expression.register_symbol_table(symbols); 399 400 /* Print all parameters for debug purpose only */ 401 if (DEBUG) 402 { 403 printParams(paramMap); 404 } 405 } 406 407 void VirtualSensor::setSensorValue(double value) 408 { 409 value = std::clamp(value, ValueIface::minValue(), ValueIface::maxValue()); 410 ValueIface::value(value); 411 } 412 413 double VirtualSensor::calculateValue(const std::string& calculation, 414 const VirtualSensor::ParamMap& paramMap) 415 { 416 auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(), 417 calculation); 418 if (itr == calculationIfaces.end()) 419 { 420 return std::numeric_limits<double>::quiet_NaN(); 421 } 422 else if (calculation == "xyz.openbmc_project.Configuration.ModifiedMedian") 423 { 424 return calculateModifiedMedianValue(paramMap); 425 } 426 return std::numeric_limits<double>::quiet_NaN(); 427 } 428 429 bool VirtualSensor::sensorInRange(double value) 430 { 431 if (value <= this->maxValidInput && value >= this->minValidInput) 432 { 433 return true; 434 } 435 return false; 436 } 437 438 void VirtualSensor::updateVirtualSensor() 439 { 440 for (auto& param : paramMap) 441 { 442 auto& name = param.first; 443 auto& data = param.second; 444 if (auto var = symbols.get_variable(name)) 445 { 446 var->ref() = data->getParamValue(); 447 } 448 else 449 { 450 /* Invalid parameter */ 451 throw std::invalid_argument("ParamName not found in symbols"); 452 } 453 } 454 auto itr = 455 std::find(calculationIfaces.begin(), calculationIfaces.end(), exprStr); 456 auto val = (itr == calculationIfaces.end()) 457 ? expression.value() 458 : calculateValue(exprStr, paramMap); 459 460 /* Set sensor value to dbus interface */ 461 setSensorValue(val); 462 463 if (DEBUG) 464 { 465 debug("Sensor {NAME} = {VALUE}", "NAME", this->name, "VALUE", val); 466 } 467 468 /* Check sensor thresholds and log required message */ 469 checkThresholds(val, perfLossIface); 470 checkThresholds(val, warningIface); 471 checkThresholds(val, criticalIface); 472 checkThresholds(val, softShutdownIface); 473 checkThresholds(val, hardShutdownIface); 474 } 475 476 double VirtualSensor::calculateModifiedMedianValue( 477 const VirtualSensor::ParamMap& paramMap) 478 { 479 std::vector<double> values; 480 481 for (auto& param : paramMap) 482 { 483 auto& name = param.first; 484 if (auto var = symbols.get_variable(name)) 485 { 486 if (!sensorInRange(var->ref())) 487 { 488 continue; 489 } 490 values.push_back(var->ref()); 491 } 492 } 493 494 size_t size = values.size(); 495 std::sort(values.begin(), values.end()); 496 switch (size) 497 { 498 case 2: 499 /* Choose biggest value */ 500 return values.at(1); 501 case 0: 502 return std::numeric_limits<double>::quiet_NaN(); 503 default: 504 /* Choose median value */ 505 if (size % 2 == 0) 506 { 507 // Average of the two middle values 508 return (values.at(size / 2) + values.at(size / 2 - 1)) / 2; 509 } 510 else 511 { 512 return values.at((size - 1) / 2); 513 } 514 } 515 } 516 517 void VirtualSensor::createThresholds(const Json& threshold, 518 const std::string& objPath) 519 { 520 if (threshold.empty()) 521 { 522 return; 523 } 524 // Only create the threshold interfaces if 525 // at least one of their values is present. 526 if (threshold.contains("CriticalHigh") || threshold.contains("CriticalLow")) 527 { 528 criticalIface = 529 std::make_unique<Threshold<CriticalObject>>(bus, objPath.c_str()); 530 531 criticalIface->criticalHigh(threshold.value( 532 "CriticalHigh", std::numeric_limits<double>::quiet_NaN())); 533 criticalIface->criticalLow(threshold.value( 534 "CriticalLow", std::numeric_limits<double>::quiet_NaN())); 535 criticalIface->setHighHysteresis( 536 threshold.value("CriticalHighHysteresis", defaultHysteresis)); 537 criticalIface->setLowHysteresis( 538 threshold.value("CriticalLowHysteresis", defaultHysteresis)); 539 } 540 541 if (threshold.contains("WarningHigh") || threshold.contains("WarningLow")) 542 { 543 warningIface = 544 std::make_unique<Threshold<WarningObject>>(bus, objPath.c_str()); 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(entityManagerBusName, "/", 612 "org.freedesktop.DBus.ObjectManager", 613 "GetManagedObjects"); 614 auto reply = bus.call(method); 615 reply.read(objects); 616 } 617 catch (const sdbusplus::exception::exception& ex) 618 { 619 // If entity manager isn't running yet, keep going. 620 if (std::string("org.freedesktop.DBus.Error.ServiceUnknown") != 621 ex.name()) 622 { 623 throw ex.name(); 624 } 625 } 626 627 return objects; 628 } 629 630 void VirtualSensors::propertiesChanged(sdbusplus::message::message& msg) 631 { 632 std::string path; 633 PropertyMap properties; 634 635 msg.read(path, properties); 636 637 /* We get multiple callbacks for one sensor. 'Type' is a required field and 638 * is a unique label so use to to only proceed once per sensor */ 639 if (properties.contains("Type")) 640 { 641 if (isCalculationType(path)) 642 { 643 createVirtualSensorsFromDBus(path); 644 } 645 } 646 } 647 648 /** @brief Parsing Virtual Sensor config JSON file */ 649 Json VirtualSensors::parseConfigFile(const std::string& configFile) 650 { 651 std::ifstream jsonFile(configFile); 652 if (!jsonFile.is_open()) 653 { 654 error("config JSON file {FILENAME} not found", "FILENAME", configFile); 655 return {}; 656 } 657 658 auto data = Json::parse(jsonFile, nullptr, false); 659 if (data.is_discarded()) 660 { 661 error("config readings JSON parser failure with {FILENAME}", "FILENAME", 662 configFile); 663 throw std::exception{}; 664 } 665 666 return data; 667 } 668 669 std::map<std::string, ValueIface::Unit> unitMap = { 670 {"temperature", ValueIface::Unit::DegreesC}, 671 {"fan_tach", ValueIface::Unit::RPMS}, 672 {"voltage", ValueIface::Unit::Volts}, 673 {"altitude", ValueIface::Unit::Meters}, 674 {"current", ValueIface::Unit::Amperes}, 675 {"power", ValueIface::Unit::Watts}, 676 {"energy", ValueIface::Unit::Joules}, 677 {"utilization", ValueIface::Unit::Percent}, 678 {"airflow", ValueIface::Unit::CFM}, 679 {"pressure", ValueIface::Unit::Pascals}}; 680 681 const std::string getSensorTypeFromUnit(const std::string& unit) 682 { 683 std::string unitPrefix = "xyz.openbmc_project.Sensor.Value.Unit."; 684 for (auto [type, unitObj] : unitMap) 685 { 686 auto unitPath = ValueIface::convertUnitToString(unitObj); 687 if (unitPath == (unitPrefix + unit)) 688 { 689 return type; 690 } 691 } 692 return ""; 693 } 694 695 void VirtualSensors::setupMatches() 696 { 697 /* Already setup */ 698 if (!this->matches.empty()) 699 { 700 return; 701 } 702 703 /* Setup matches */ 704 auto eventHandler = [this](sdbusplus::message::message& message) { 705 if (message.is_method_error()) 706 { 707 error("Callback method error"); 708 return; 709 } 710 this->propertiesChanged(message); 711 }; 712 713 for (const char* iface : calculationIfaces) 714 { 715 auto match = std::make_unique<sdbusplus::bus::match::match>( 716 bus, 717 sdbusplus::bus::match::rules::propertiesChangedNamespace( 718 "/xyz/openbmc_project/inventory", iface), 719 eventHandler); 720 this->matches.emplace_back(std::move(match)); 721 } 722 } 723 724 void VirtualSensors::createVirtualSensorsFromDBus( 725 const std::string& calculationIface) 726 { 727 if (calculationIface.empty()) 728 { 729 error("No calculation type supplied"); 730 return; 731 } 732 auto objects = getObjectsFromDBus(); 733 734 /* Get virtual sensors config data */ 735 for (const auto& [path, interfaceMap] : objects) 736 { 737 auto objpath = static_cast<std::string>(path); 738 std::string name = path.filename(); 739 std::string sensorType, sensorUnit; 740 741 /* Find Virtual Sensor interfaces */ 742 if (!interfaceMap.contains(calculationIface)) 743 { 744 continue; 745 } 746 if (name.empty()) 747 { 748 error("Virtual Sensor name not found in entity manager config"); 749 continue; 750 } 751 if (virtualSensorsMap.contains(name)) 752 { 753 error("A virtual sensor named {NAME} already exists", "NAME", name); 754 continue; 755 } 756 757 /* Extract the virtual sensor type as we need this to initialize the 758 * sensor */ 759 for (const auto& [interface, propertyMap] : interfaceMap) 760 { 761 if (interface != calculationIface) 762 { 763 continue; 764 } 765 auto itr = propertyMap.find("Units"); 766 if (itr != propertyMap.end()) 767 { 768 sensorUnit = std::get<std::string>(itr->second); 769 break; 770 } 771 } 772 sensorType = getSensorTypeFromUnit(sensorUnit); 773 if (sensorType.empty()) 774 { 775 error("Sensor unit type {TYPE} is not supported", "TYPE", 776 sensorUnit); 777 continue; 778 } 779 780 try 781 { 782 auto virtObjPath = sensorDbusPath + sensorType + "/" + name; 783 784 auto virtualSensorPtr = std::make_unique<VirtualSensor>( 785 bus, virtObjPath.c_str(), interfaceMap, name, sensorType, 786 calculationIface); 787 info("Added a new virtual sensor: {NAME} {TYPE}", "NAME", name, 788 "TYPE", sensorType); 789 virtualSensorPtr->updateVirtualSensor(); 790 791 /* Initialize unit value for virtual sensor */ 792 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]); 793 virtualSensorPtr->emit_object_added(); 794 795 virtualSensorsMap.emplace(name, std::move(virtualSensorPtr)); 796 797 /* Setup match for interfaces removed */ 798 auto intfRemoved = [this, objpath, 799 name](sdbusplus::message::message& message) { 800 if (!virtualSensorsMap.contains(name)) 801 { 802 return; 803 } 804 sdbusplus::message::object_path path; 805 message.read(path); 806 if (static_cast<const std::string&>(path) == objpath) 807 { 808 info("Removed a virtual sensor: {NAME}", "NAME", name); 809 virtualSensorsMap.erase(name); 810 } 811 }; 812 auto matchOnRemove = std::make_unique<sdbusplus::bus::match::match>( 813 bus, 814 sdbusplus::bus::match::rules::interfacesRemoved() + 815 sdbusplus::bus::match::rules::argNpath(0, objpath), 816 intfRemoved); 817 /* TODO: slight race condition here. Check that the config still 818 * exists */ 819 this->matches.emplace_back(std::move(matchOnRemove)); 820 } 821 catch (const std::invalid_argument& ia) 822 { 823 error("Failed to set up virtual sensor: {ERROR}", "ERROR", ia); 824 } 825 } 826 } 827 828 void VirtualSensors::createVirtualSensors() 829 { 830 static const Json empty{}; 831 832 auto data = parseConfigFile(VIRTUAL_SENSOR_CONFIG_FILE); 833 834 // print values 835 if (DEBUG) 836 { 837 debug("JSON: {JSON}", "JSON", data.dump()); 838 } 839 840 /* Get virtual sensors config data */ 841 for (const auto& j : data) 842 { 843 auto desc = j.value("Desc", empty); 844 if (!desc.empty()) 845 { 846 if (desc.value("Config", "") == "D-Bus") 847 { 848 /* Look on D-Bus for a virtual sensor config. Set up matches 849 * first because the configs may not be on D-Bus yet and we 850 * don't want to miss them */ 851 setupMatches(); 852 853 if (desc.contains("Type")) 854 { 855 auto type = desc.value("Type", ""); 856 auto path = "xyz.openbmc_project.Configuration." + type; 857 858 if (!isCalculationType(path)) 859 { 860 error("Invalid calculation type {TYPE} supplied.", 861 "TYPE", type); 862 continue; 863 } 864 createVirtualSensorsFromDBus(path); 865 } 866 continue; 867 } 868 869 std::string sensorType = desc.value("SensorType", ""); 870 std::string name = desc.value("Name", ""); 871 std::replace(name.begin(), name.end(), ' ', '_'); 872 873 if (!name.empty() && !sensorType.empty()) 874 { 875 if (unitMap.find(sensorType) == unitMap.end()) 876 { 877 error("Sensor type {TYPE} is not supported", "TYPE", 878 sensorType); 879 } 880 else 881 { 882 if (virtualSensorsMap.find(name) != virtualSensorsMap.end()) 883 { 884 error("A virtual sensor named {NAME} already exists", 885 "NAME", name); 886 continue; 887 } 888 auto objPath = sensorDbusPath + sensorType + "/" + name; 889 890 auto virtualSensorPtr = std::make_unique<VirtualSensor>( 891 bus, objPath.c_str(), j, name); 892 893 info("Added a new virtual sensor: {NAME}", "NAME", name); 894 virtualSensorPtr->updateVirtualSensor(); 895 896 /* Initialize unit value for virtual sensor */ 897 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]); 898 virtualSensorPtr->emit_object_added(); 899 900 virtualSensorsMap.emplace(std::move(name), 901 std::move(virtualSensorPtr)); 902 } 903 } 904 else 905 { 906 error( 907 "Sensor type ({TYPE}) or name ({NAME}) not found in config file", 908 "NAME", name, "TYPE", sensorType); 909 } 910 } 911 else 912 { 913 error("Descriptor for new virtual sensor not found in config file"); 914 } 915 } 916 } 917 918 } // namespace virtualSensor 919 } // namespace phosphor 920 921 /** 922 * @brief Main 923 */ 924 int main() 925 { 926 927 // Get a default event loop 928 auto event = sdeventplus::Event::get_default(); 929 930 // Get a handle to system dbus 931 auto bus = sdbusplus::bus::new_default(); 932 933 // Add the ObjectManager interface 934 sdbusplus::server::manager::manager objManager(bus, "/"); 935 936 // Create an virtual sensors object 937 phosphor::virtualSensor::VirtualSensors virtualSensors(bus); 938 939 // Request service bus name 940 bus.request_name(busName); 941 942 // Attach the bus to sd_event to service user requests 943 bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL); 944 event.loop(); 945 946 return 0; 947 } 948