1 /* 2 // Copyright (c) 2018 Intel Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 17 #include "ExitAirTempSensor.hpp" 18 19 #include "Utils.hpp" 20 #include "VariantVisitors.hpp" 21 22 #include <boost/algorithm/string/replace.hpp> 23 #include <boost/container/flat_map.hpp> 24 #include <sdbusplus/asio/connection.hpp> 25 #include <sdbusplus/asio/object_server.hpp> 26 #include <sdbusplus/bus/match.hpp> 27 28 #include <array> 29 #include <chrono> 30 #include <cmath> 31 #include <functional> 32 #include <iostream> 33 #include <limits> 34 #include <memory> 35 #include <numeric> 36 #include <stdexcept> 37 #include <utility> 38 #include <variant> 39 #include <vector> 40 41 constexpr const double altitudeFactor = 1.14; 42 constexpr const char* exitAirType = "ExitAirTempSensor"; 43 constexpr const char* cfmType = "CFMSensor"; 44 45 // todo: this *might* need to be configurable 46 constexpr const char* inletTemperatureSensor = "temperature/Front_Panel_Temp"; 47 constexpr const char* pidConfigurationType = 48 "xyz.openbmc_project.Configuration.Pid"; 49 constexpr const char* settingsDaemon = "xyz.openbmc_project.Settings"; 50 constexpr const char* cfmSettingPath = "/xyz/openbmc_project/control/cfm_limit"; 51 constexpr const char* cfmSettingIface = "xyz.openbmc_project.Control.CFMLimit"; 52 53 static constexpr bool debug = false; 54 55 static constexpr double cfmMaxReading = 255; 56 static constexpr double cfmMinReading = 0; 57 58 static constexpr size_t minSystemCfm = 50; 59 60 constexpr const auto monitorTypes{ 61 std::to_array<const char*>({exitAirType, cfmType})}; 62 63 static std::vector<std::shared_ptr<CFMSensor>> cfmSensors; 64 65 static void setupSensorMatch( 66 std::vector<sdbusplus::bus::match_t>& matches, sdbusplus::bus_t& connection, 67 const std::string& type, 68 std::function<void(const double&, sdbusplus::message_t&)>&& callback) 69 { 70 std::function<void(sdbusplus::message_t & message)> eventHandler = 71 [callback{std::move(callback)}](sdbusplus::message_t& message) { 72 std::string objectName; 73 boost::container::flat_map<std::string, std::variant<double, int64_t>> 74 values; 75 message.read(objectName, values); 76 auto findValue = values.find("Value"); 77 if (findValue == values.end()) 78 { 79 return; 80 } 81 double value = std::visit(VariantToDoubleVisitor(), findValue->second); 82 if (std::isnan(value)) 83 { 84 return; 85 } 86 87 callback(value, message); 88 }; 89 matches.emplace_back(connection, 90 "type='signal'," 91 "member='PropertiesChanged',interface='org." 92 "freedesktop.DBus.Properties',path_" 93 "namespace='/xyz/openbmc_project/sensors/" + 94 std::string(type) + 95 "',arg0='xyz.openbmc_project.Sensor.Value'", 96 std::move(eventHandler)); 97 } 98 99 static void setMaxPWM(const std::shared_ptr<sdbusplus::asio::connection>& conn, 100 double value) 101 { 102 using GetSubTreeType = std::vector<std::pair< 103 std::string, 104 std::vector<std::pair<std::string, std::vector<std::string>>>>>; 105 106 conn->async_method_call( 107 [conn, value](const boost::system::error_code ec, 108 const GetSubTreeType& ret) { 109 if (ec) 110 { 111 std::cerr << "Error calling mapper\n"; 112 return; 113 } 114 for (const auto& [path, objDict] : ret) 115 { 116 if (objDict.empty()) 117 { 118 return; 119 } 120 const std::string& owner = objDict.begin()->first; 121 122 conn->async_method_call( 123 [conn, value, owner, 124 path{path}](const boost::system::error_code ec, 125 const std::variant<std::string>& classType) { 126 if (ec) 127 { 128 std::cerr << "Error getting pid class\n"; 129 return; 130 } 131 const auto* classStr = std::get_if<std::string>(&classType); 132 if (classStr == nullptr || *classStr != "fan") 133 { 134 return; 135 } 136 conn->async_method_call( 137 [](boost::system::error_code& ec) { 138 if (ec) 139 { 140 std::cerr << "Error setting pid class\n"; 141 return; 142 } 143 }, 144 owner, path, "org.freedesktop.DBus.Properties", "Set", 145 pidConfigurationType, "OutLimitMax", 146 std::variant<double>(value)); 147 }, 148 owner, path, "org.freedesktop.DBus.Properties", "Get", 149 pidConfigurationType, "Class"); 150 } 151 }, 152 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/", 153 0, std::array<std::string, 1>{pidConfigurationType}); 154 } 155 156 CFMSensor::CFMSensor(std::shared_ptr<sdbusplus::asio::connection>& conn, 157 const std::string& sensorName, 158 const std::string& sensorConfiguration, 159 sdbusplus::asio::object_server& objectServer, 160 std::vector<thresholds::Threshold>&& thresholdData, 161 std::shared_ptr<ExitAirTempSensor>& parent) : 162 Sensor(escapeName(sensorName), std::move(thresholdData), 163 sensorConfiguration, "CFMSensor", false, false, cfmMaxReading, 164 cfmMinReading, conn, PowerState::on), 165 parent(parent), objServer(objectServer) 166 { 167 sensorInterface = objectServer.add_interface( 168 "/xyz/openbmc_project/sensors/airflow/" + name, 169 "xyz.openbmc_project.Sensor.Value"); 170 171 for (const auto& threshold : thresholds) 172 { 173 std::string interface = thresholds::getInterface(threshold.level); 174 thresholdInterfaces[static_cast<size_t>(threshold.level)] = 175 objectServer.add_interface( 176 "/xyz/openbmc_project/sensors/airflow/" + name, interface); 177 } 178 179 association = objectServer.add_interface( 180 "/xyz/openbmc_project/sensors/airflow/" + name, association::interface); 181 182 setInitialProperties(sensor_paths::unitCFM); 183 184 pwmLimitIface = 185 objectServer.add_interface("/xyz/openbmc_project/control/pwm_limit", 186 "xyz.openbmc_project.Control.PWMLimit"); 187 cfmLimitIface = 188 objectServer.add_interface("/xyz/openbmc_project/control/MaxCFM", 189 "xyz.openbmc_project.Control.CFMLimit"); 190 } 191 192 void CFMSensor::setupMatches() 193 { 194 std::weak_ptr<CFMSensor> weakRef = weak_from_this(); 195 setupSensorMatch( 196 matches, *dbusConnection, "fan_tach", 197 [weakRef](const double& value, sdbusplus::message_t& message) { 198 auto self = weakRef.lock(); 199 if (!self) 200 { 201 return; 202 } 203 self->tachReadings[message.get_path()] = value; 204 if (self->tachRanges.find(message.get_path()) == self->tachRanges.end()) 205 { 206 // calls update reading after updating ranges 207 self->addTachRanges(message.get_sender(), message.get_path()); 208 } 209 else 210 { 211 self->updateReading(); 212 } 213 }); 214 215 dbusConnection->async_method_call( 216 [weakRef](const boost::system::error_code ec, 217 const std::variant<double> cfmVariant) { 218 auto self = weakRef.lock(); 219 if (!self) 220 { 221 return; 222 } 223 224 uint64_t maxRpm = 100; 225 if (!ec) 226 { 227 const auto* cfm = std::get_if<double>(&cfmVariant); 228 if (cfm != nullptr && *cfm >= minSystemCfm) 229 { 230 maxRpm = self->getMaxRpm(*cfm); 231 } 232 } 233 self->pwmLimitIface->register_property("Limit", maxRpm); 234 self->pwmLimitIface->initialize(); 235 setMaxPWM(self->dbusConnection, maxRpm); 236 }, 237 settingsDaemon, cfmSettingPath, "org.freedesktop.DBus.Properties", 238 "Get", cfmSettingIface, "Limit"); 239 240 matches.emplace_back(*dbusConnection, 241 "type='signal'," 242 "member='PropertiesChanged',interface='org." 243 "freedesktop.DBus.Properties',path='" + 244 std::string(cfmSettingPath) + "',arg0='" + 245 std::string(cfmSettingIface) + "'", 246 [weakRef](sdbusplus::message_t& message) { 247 auto self = weakRef.lock(); 248 if (!self) 249 { 250 return; 251 } 252 boost::container::flat_map<std::string, std::variant<double>> values; 253 std::string objectName; 254 message.read(objectName, values); 255 const auto findValue = values.find("Limit"); 256 if (findValue == values.end()) 257 { 258 return; 259 } 260 auto* const reading = std::get_if<double>(&(findValue->second)); 261 if (reading == nullptr) 262 { 263 std::cerr << "Got CFM Limit of wrong type\n"; 264 return; 265 } 266 if (*reading < minSystemCfm && *reading != 0) 267 { 268 std::cerr << "Illegal CFM setting detected\n"; 269 return; 270 } 271 uint64_t maxRpm = self->getMaxRpm(*reading); 272 self->pwmLimitIface->set_property("Limit", maxRpm); 273 setMaxPWM(self->dbusConnection, maxRpm); 274 }); 275 } 276 277 CFMSensor::~CFMSensor() 278 { 279 for (const auto& iface : thresholdInterfaces) 280 { 281 objServer.remove_interface(iface); 282 } 283 objServer.remove_interface(sensorInterface); 284 objServer.remove_interface(association); 285 objServer.remove_interface(cfmLimitIface); 286 objServer.remove_interface(pwmLimitIface); 287 } 288 289 void CFMSensor::createMaxCFMIface(void) 290 { 291 cfmLimitIface->register_property("Limit", c2 * maxCFM * tachs.size()); 292 cfmLimitIface->initialize(); 293 } 294 295 void CFMSensor::addTachRanges(const std::string& serviceName, 296 const std::string& path) 297 { 298 std::weak_ptr<CFMSensor> weakRef = weak_from_this(); 299 dbusConnection->async_method_call( 300 [weakRef, path](const boost::system::error_code ec, 301 const SensorBaseConfigMap& data) { 302 if (ec) 303 { 304 std::cerr << "Error getting properties from " << path << "\n"; 305 return; 306 } 307 auto self = weakRef.lock(); 308 if (!self) 309 { 310 return; 311 } 312 double max = loadVariant<double>(data, "MaxValue"); 313 double min = loadVariant<double>(data, "MinValue"); 314 self->tachRanges[path] = std::make_pair(min, max); 315 self->updateReading(); 316 }, 317 serviceName, path, "org.freedesktop.DBus.Properties", "GetAll", 318 "xyz.openbmc_project.Sensor.Value"); 319 } 320 321 void CFMSensor::checkThresholds(void) 322 { 323 thresholds::checkThresholds(this); 324 } 325 326 void CFMSensor::updateReading(void) 327 { 328 double val = 0.0; 329 if (calculate(val)) 330 { 331 if (value != val && parent) 332 { 333 parent->updateReading(); 334 } 335 updateValue(val); 336 } 337 else 338 { 339 updateValue(std::numeric_limits<double>::quiet_NaN()); 340 } 341 } 342 343 uint64_t CFMSensor::getMaxRpm(uint64_t cfmMaxSetting) const 344 { 345 uint64_t pwmPercent = 100; 346 double totalCFM = std::numeric_limits<double>::max(); 347 if (cfmMaxSetting == 0) 348 { 349 return pwmPercent; 350 } 351 352 bool firstLoop = true; 353 while (totalCFM > cfmMaxSetting) 354 { 355 if (firstLoop) 356 { 357 firstLoop = false; 358 } 359 else 360 { 361 pwmPercent--; 362 } 363 364 double ci = 0; 365 if (pwmPercent == 0) 366 { 367 ci = 0; 368 } 369 else if (pwmPercent < tachMinPercent) 370 { 371 ci = c1; 372 } 373 else if (pwmPercent > tachMaxPercent) 374 { 375 ci = c2; 376 } 377 else 378 { 379 ci = c1 + (((c2 - c1) * (pwmPercent - tachMinPercent)) / 380 (tachMaxPercent - tachMinPercent)); 381 } 382 383 // Now calculate the CFM for this tach 384 // CFMi = Ci * Qmaxi * TACHi 385 totalCFM = ci * maxCFM * pwmPercent; 386 totalCFM *= tachs.size(); 387 // divide by 100 since pwm is in percent 388 totalCFM /= 100; 389 390 if (pwmPercent <= 0) 391 { 392 break; 393 } 394 } 395 396 return pwmPercent; 397 } 398 399 bool CFMSensor::calculate(double& value) 400 { 401 double totalCFM = 0; 402 for (const std::string& tachName : tachs) 403 { 404 auto findReading = std::find_if( 405 tachReadings.begin(), tachReadings.end(), 406 [&](const auto& item) { return item.first.ends_with(tachName); }); 407 auto findRange = std::find_if( 408 tachRanges.begin(), tachRanges.end(), 409 [&](const auto& item) { return item.first.ends_with(tachName); }); 410 if (findReading == tachReadings.end()) 411 { 412 if constexpr (debug) 413 { 414 std::cerr << "Can't find " << tachName << "in readings\n"; 415 } 416 continue; // haven't gotten a reading 417 } 418 419 if (findRange == tachRanges.end()) 420 { 421 std::cerr << "Can't find " << tachName << " in ranges\n"; 422 return false; // haven't gotten a max / min 423 } 424 425 // avoid divide by 0 426 if (findRange->second.second == 0) 427 { 428 std::cerr << "Tach Max Set to 0 " << tachName << "\n"; 429 return false; 430 } 431 432 double rpm = findReading->second; 433 434 // for now assume the min for a fan is always 0, divide by max to get 435 // percent and mult by 100 436 rpm /= findRange->second.second; 437 rpm *= 100; 438 439 if constexpr (debug) 440 { 441 std::cout << "Tach " << tachName << "at " << rpm << "\n"; 442 } 443 444 // Do a linear interpolation to get Ci 445 // Ci = C1 + (C2 - C1)/(RPM2 - RPM1) * (TACHi - TACH1) 446 447 double ci = 0; 448 if (rpm == 0) 449 { 450 ci = 0; 451 } 452 else if (rpm < tachMinPercent) 453 { 454 ci = c1; 455 } 456 else if (rpm > tachMaxPercent) 457 { 458 ci = c2; 459 } 460 else 461 { 462 ci = c1 + (((c2 - c1) * (rpm - tachMinPercent)) / 463 (tachMaxPercent - tachMinPercent)); 464 } 465 466 // Now calculate the CFM for this tach 467 // CFMi = Ci * Qmaxi * TACHi 468 totalCFM += ci * maxCFM * rpm; 469 if constexpr (debug) 470 { 471 std::cerr << "totalCFM = " << totalCFM << "\n"; 472 std::cerr << "Ci " << ci << " MaxCFM " << maxCFM << " rpm " << rpm 473 << "\n"; 474 std::cerr << "c1 " << c1 << " c2 " << c2 << " max " 475 << tachMaxPercent << " min " << tachMinPercent << "\n"; 476 } 477 } 478 479 // divide by 100 since rpm is in percent 480 value = totalCFM / 100; 481 if constexpr (debug) 482 { 483 std::cerr << "cfm value = " << value << "\n"; 484 } 485 return true; 486 } 487 488 static constexpr double exitAirMaxReading = 127; 489 static constexpr double exitAirMinReading = -128; 490 ExitAirTempSensor::ExitAirTempSensor( 491 std::shared_ptr<sdbusplus::asio::connection>& conn, 492 const std::string& sensorName, const std::string& sensorConfiguration, 493 sdbusplus::asio::object_server& objectServer, 494 std::vector<thresholds::Threshold>&& thresholdData) : 495 Sensor(escapeName(sensorName), std::move(thresholdData), 496 sensorConfiguration, "ExitAirTemp", false, false, exitAirMaxReading, 497 exitAirMinReading, conn, PowerState::on), 498 objServer(objectServer) 499 { 500 sensorInterface = objectServer.add_interface( 501 "/xyz/openbmc_project/sensors/temperature/" + name, 502 "xyz.openbmc_project.Sensor.Value"); 503 504 for (const auto& threshold : thresholds) 505 { 506 std::string interface = thresholds::getInterface(threshold.level); 507 thresholdInterfaces[static_cast<size_t>(threshold.level)] = 508 objectServer.add_interface( 509 "/xyz/openbmc_project/sensors/temperature/" + name, interface); 510 } 511 association = objectServer.add_interface( 512 "/xyz/openbmc_project/sensors/temperature/" + name, 513 association::interface); 514 setInitialProperties(sensor_paths::unitDegreesC); 515 } 516 517 ExitAirTempSensor::~ExitAirTempSensor() 518 { 519 for (const auto& iface : thresholdInterfaces) 520 { 521 objServer.remove_interface(iface); 522 } 523 objServer.remove_interface(sensorInterface); 524 objServer.remove_interface(association); 525 } 526 527 void ExitAirTempSensor::setupMatches(void) 528 { 529 constexpr const auto matchTypes{ 530 std::to_array<const char*>({"power", inletTemperatureSensor})}; 531 532 std::weak_ptr<ExitAirTempSensor> weakRef = weak_from_this(); 533 for (const std::string type : matchTypes) 534 { 535 setupSensorMatch(matches, *dbusConnection, type, 536 [weakRef, type](const double& value, 537 sdbusplus::message_t& message) { 538 auto self = weakRef.lock(); 539 if (!self) 540 { 541 return; 542 } 543 if (type == "power") 544 { 545 std::string path = message.get_path(); 546 if (path.find("PS") != std::string::npos && 547 path.ends_with("Input_Power")) 548 { 549 self->powerReadings[message.get_path()] = value; 550 } 551 } 552 else if (type == inletTemperatureSensor) 553 { 554 self->inletTemp = value; 555 } 556 self->updateReading(); 557 }); 558 } 559 dbusConnection->async_method_call( 560 [weakRef](boost::system::error_code ec, 561 const std::variant<double>& value) { 562 if (ec) 563 { 564 // sensor not ready yet 565 return; 566 } 567 auto self = weakRef.lock(); 568 if (!self) 569 { 570 return; 571 } 572 self->inletTemp = std::visit(VariantToDoubleVisitor(), value); 573 }, 574 "xyz.openbmc_project.HwmonTempSensor", 575 std::string("/xyz/openbmc_project/sensors/") + inletTemperatureSensor, 576 properties::interface, properties::get, sensorValueInterface, "Value"); 577 dbusConnection->async_method_call( 578 [weakRef](boost::system::error_code ec, const GetSubTreeType& subtree) { 579 if (ec) 580 { 581 std::cerr << "Error contacting mapper\n"; 582 return; 583 } 584 auto self = weakRef.lock(); 585 if (!self) 586 { 587 return; 588 } 589 for (const auto& [path, matches] : subtree) 590 { 591 size_t lastSlash = path.rfind('/'); 592 if (lastSlash == std::string::npos || lastSlash == path.size() || 593 matches.empty()) 594 { 595 continue; 596 } 597 std::string sensorName = path.substr(lastSlash + 1); 598 if (sensorName.starts_with("PS") && 599 sensorName.ends_with("Input_Power")) 600 { 601 // lambda capture requires a proper variable (not a structured 602 // binding) 603 const std::string& cbPath = path; 604 self->dbusConnection->async_method_call( 605 [weakRef, cbPath](boost::system::error_code ec, 606 const std::variant<double>& value) { 607 if (ec) 608 { 609 std::cerr << "Error getting value from " << cbPath 610 << "\n"; 611 } 612 auto self = weakRef.lock(); 613 if (!self) 614 { 615 return; 616 } 617 double reading = std::visit(VariantToDoubleVisitor(), 618 value); 619 if constexpr (debug) 620 { 621 std::cerr << cbPath << "Reading " << reading << "\n"; 622 } 623 self->powerReadings[cbPath] = reading; 624 }, 625 matches[0].first, cbPath, properties::interface, 626 properties::get, sensorValueInterface, "Value"); 627 } 628 } 629 }, 630 mapper::busName, mapper::path, mapper::interface, mapper::subtree, 631 "/xyz/openbmc_project/sensors/power", 0, 632 std::array<const char*, 1>{sensorValueInterface}); 633 } 634 635 void ExitAirTempSensor::updateReading(void) 636 { 637 double val = 0.0; 638 if (calculate(val)) 639 { 640 val = std::floor(val + 0.5); 641 updateValue(val); 642 } 643 else 644 { 645 updateValue(std::numeric_limits<double>::quiet_NaN()); 646 } 647 } 648 649 double ExitAirTempSensor::getTotalCFM(void) 650 { 651 double sum = 0; 652 for (auto& sensor : cfmSensors) 653 { 654 double reading = 0; 655 if (!sensor->calculate(reading)) 656 { 657 return -1; 658 } 659 sum += reading; 660 } 661 662 return sum; 663 } 664 665 bool ExitAirTempSensor::calculate(double& val) 666 { 667 constexpr size_t maxErrorPrint = 5; 668 static bool firstRead = false; 669 static size_t errorPrint = maxErrorPrint; 670 671 double cfm = getTotalCFM(); 672 if (cfm <= 0) 673 { 674 std::cerr << "Error getting cfm\n"; 675 return false; 676 } 677 678 // Though cfm is not expected to be less than qMin normally, 679 // it is not a hard limit for exit air temp calculation. 680 // 50% qMin is chosen as a generic limit between providing 681 // a valid derived exit air temp and reporting exit air temp not available. 682 constexpr const double cfmLimitFactor = 0.5; 683 if (cfm < (qMin * cfmLimitFactor)) 684 { 685 if (errorPrint > 0) 686 { 687 errorPrint--; 688 std::cerr << "cfm " << cfm << " is too low, expected qMin " << qMin 689 << "\n"; 690 } 691 val = 0; 692 return false; 693 } 694 695 // if there is an error getting inlet temp, return error 696 if (std::isnan(inletTemp)) 697 { 698 if (errorPrint > 0) 699 { 700 errorPrint--; 701 std::cerr << "Cannot get inlet temp\n"; 702 } 703 val = 0; 704 return false; 705 } 706 707 // if fans are off, just make the exit temp equal to inlet 708 if (!isPowerOn()) 709 { 710 val = inletTemp; 711 return true; 712 } 713 714 double totalPower = 0; 715 for (const auto& [path, reading] : powerReadings) 716 { 717 if (std::isnan(reading)) 718 { 719 continue; 720 } 721 totalPower += reading; 722 } 723 724 // Calculate power correction factor 725 // Ci = CL + (CH - CL)/(QMax - QMin) * (CFM - QMin) 726 double powerFactor = 0.0; 727 if (cfm <= qMin) 728 { 729 powerFactor = powerFactorMin; 730 } 731 else if (cfm >= qMax) 732 { 733 powerFactor = powerFactorMax; 734 } 735 else 736 { 737 powerFactor = powerFactorMin + ((powerFactorMax - powerFactorMin) / 738 (qMax - qMin) * (cfm - qMin)); 739 } 740 741 totalPower *= powerFactor; 742 totalPower += pOffset; 743 744 if (totalPower == 0) 745 { 746 if (errorPrint > 0) 747 { 748 errorPrint--; 749 std::cerr << "total power 0\n"; 750 } 751 val = 0; 752 return false; 753 } 754 755 if constexpr (debug) 756 { 757 std::cout << "Power Factor " << powerFactor << "\n"; 758 std::cout << "Inlet Temp " << inletTemp << "\n"; 759 std::cout << "Total Power" << totalPower << "\n"; 760 } 761 762 // Calculate the exit air temp 763 // Texit = Tfp + (1.76 * TotalPower / CFM * Faltitude) 764 double reading = 1.76 * totalPower * altitudeFactor; 765 reading /= cfm; 766 reading += inletTemp; 767 768 if constexpr (debug) 769 { 770 std::cout << "Reading 1: " << reading << "\n"; 771 } 772 773 // Now perform the exponential average 774 // Calculate alpha based on SDR values and CFM 775 // Ai = As + (Af - As)/(QMax - QMin) * (CFM - QMin) 776 777 double alpha = 0.0; 778 if (cfm < qMin) 779 { 780 alpha = alphaS; 781 } 782 else if (cfm >= qMax) 783 { 784 alpha = alphaF; 785 } 786 else 787 { 788 alpha = alphaS + ((alphaF - alphaS) * (cfm - qMin) / (qMax - qMin)); 789 } 790 791 auto time = std::chrono::steady_clock::now(); 792 if (!firstRead) 793 { 794 firstRead = true; 795 lastTime = time; 796 lastReading = reading; 797 } 798 double alphaDT = 799 std::chrono::duration_cast<std::chrono::seconds>(time - lastTime) 800 .count() * 801 alpha; 802 803 // cap at 1.0 or the below fails 804 if (alphaDT > 1.0) 805 { 806 alphaDT = 1.0; 807 } 808 809 if constexpr (debug) 810 { 811 std::cout << "AlphaDT: " << alphaDT << "\n"; 812 } 813 814 reading = ((reading * alphaDT) + (lastReading * (1.0 - alphaDT))); 815 816 if constexpr (debug) 817 { 818 std::cout << "Reading 2: " << reading << "\n"; 819 } 820 821 val = reading; 822 lastReading = reading; 823 lastTime = time; 824 errorPrint = maxErrorPrint; 825 return true; 826 } 827 828 void ExitAirTempSensor::checkThresholds(void) 829 { 830 thresholds::checkThresholds(this); 831 } 832 833 static void loadVariantPathArray(const SensorBaseConfigMap& data, 834 const std::string& key, 835 std::vector<std::string>& resp) 836 { 837 auto it = data.find(key); 838 if (it == data.end()) 839 { 840 std::cerr << "Configuration missing " << key << "\n"; 841 throw std::invalid_argument("Key Missing"); 842 } 843 BasicVariantType copy = it->second; 844 std::vector<std::string> config = std::get<std::vector<std::string>>(copy); 845 for (auto& str : config) 846 { 847 boost::replace_all(str, " ", "_"); 848 } 849 resp = std::move(config); 850 } 851 852 void createSensor(sdbusplus::asio::object_server& objectServer, 853 std::shared_ptr<ExitAirTempSensor>& exitAirSensor, 854 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection) 855 { 856 if (!dbusConnection) 857 { 858 std::cerr << "Connection not created\n"; 859 return; 860 } 861 auto getter = std::make_shared<GetSensorConfiguration>( 862 dbusConnection, [&objectServer, &dbusConnection, 863 &exitAirSensor](const ManagedObjectType& resp) { 864 cfmSensors.clear(); 865 for (const auto& [path, interfaces] : resp) 866 { 867 for (const auto& [intf, cfg] : interfaces) 868 { 869 if (intf == configInterfaceName(exitAirType)) 870 { 871 // thresholds should be under the same path 872 std::vector<thresholds::Threshold> sensorThresholds; 873 parseThresholdsFromConfig(interfaces, sensorThresholds); 874 875 std::string name = loadVariant<std::string>(cfg, "Name"); 876 exitAirSensor = nullptr; 877 exitAirSensor = std::make_shared<ExitAirTempSensor>( 878 dbusConnection, name, path.str, objectServer, 879 std::move(sensorThresholds)); 880 exitAirSensor->powerFactorMin = 881 loadVariant<double>(cfg, "PowerFactorMin"); 882 exitAirSensor->powerFactorMax = 883 loadVariant<double>(cfg, "PowerFactorMax"); 884 exitAirSensor->qMin = loadVariant<double>(cfg, "QMin"); 885 exitAirSensor->qMax = loadVariant<double>(cfg, "QMax"); 886 exitAirSensor->alphaS = loadVariant<double>(cfg, "AlphaS"); 887 exitAirSensor->alphaF = loadVariant<double>(cfg, "AlphaF"); 888 } 889 else if (intf == configInterfaceName(cfmType)) 890 { 891 // thresholds should be under the same path 892 std::vector<thresholds::Threshold> sensorThresholds; 893 parseThresholdsFromConfig(interfaces, sensorThresholds); 894 std::string name = loadVariant<std::string>(cfg, "Name"); 895 auto sensor = std::make_shared<CFMSensor>( 896 dbusConnection, name, path.str, objectServer, 897 std::move(sensorThresholds), exitAirSensor); 898 loadVariantPathArray(cfg, "Tachs", sensor->tachs); 899 sensor->maxCFM = loadVariant<double>(cfg, "MaxCFM"); 900 901 // change these into percent upon getting the data 902 sensor->c1 = loadVariant<double>(cfg, "C1") / 100; 903 sensor->c2 = loadVariant<double>(cfg, "C2") / 100; 904 sensor->tachMinPercent = 905 loadVariant<double>(cfg, "TachMinPercent"); 906 sensor->tachMaxPercent = 907 loadVariant<double>(cfg, "TachMaxPercent"); 908 sensor->createMaxCFMIface(); 909 sensor->setupMatches(); 910 911 cfmSensors.emplace_back(std::move(sensor)); 912 } 913 } 914 } 915 if (exitAirSensor) 916 { 917 exitAirSensor->setupMatches(); 918 exitAirSensor->updateReading(); 919 } 920 }); 921 getter->getConfiguration( 922 std::vector<std::string>(monitorTypes.begin(), monitorTypes.end())); 923 } 924 925 int main() 926 { 927 boost::asio::io_context io; 928 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io); 929 sdbusplus::asio::object_server objectServer(systemBus, true); 930 objectServer.add_manager("/xyz/openbmc_project/sensors"); 931 systemBus->request_name("xyz.openbmc_project.ExitAirTempSensor"); 932 std::shared_ptr<ExitAirTempSensor> sensor = 933 nullptr; // wait until we find the config 934 935 boost::asio::post(io, 936 [&]() { createSensor(objectServer, sensor, systemBus); }); 937 938 boost::asio::steady_timer configTimer(io); 939 940 std::function<void(sdbusplus::message_t&)> eventHandler = 941 [&](sdbusplus::message_t&) { 942 configTimer.expires_after(std::chrono::seconds(1)); 943 // create a timer because normally multiple properties change 944 configTimer.async_wait([&](const boost::system::error_code& ec) { 945 if (ec == boost::asio::error::operation_aborted) 946 { 947 return; // we're being canceled 948 } 949 createSensor(objectServer, sensor, systemBus); 950 if (!sensor) 951 { 952 std::cout << "Configuration not detected\n"; 953 } 954 }); 955 }; 956 std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches = 957 setupPropertiesChangedMatches(*systemBus, monitorTypes, eventHandler); 958 959 setupManufacturingModeMatch(*systemBus); 960 io.run(); 961 return 0; 962 } 963