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