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