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