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 #include <Utils.hpp> 19 #include <VariantVisitors.hpp> 20 #include <boost/algorithm/string/replace.hpp> 21 #include <boost/container/flat_map.hpp> 22 #include <sdbusplus/asio/connection.hpp> 23 #include <sdbusplus/asio/object_server.hpp> 24 #include <sdbusplus/bus/match.hpp> 25 26 #include <array> 27 #include <chrono> 28 #include <cmath> 29 #include <functional> 30 #include <iostream> 31 #include <limits> 32 #include <memory> 33 #include <numeric> 34 #include <stdexcept> 35 #include <utility> 36 #include <variant> 37 #include <vector> 38 39 constexpr const double altitudeFactor = 1.14; 40 constexpr const char* exitAirIface = 41 "xyz.openbmc_project.Configuration.ExitAirTempSensor"; 42 constexpr const char* cfmIface = "xyz.openbmc_project.Configuration.CFMSensor"; 43 44 // todo: this *might* need to be configurable 45 constexpr const char* inletTemperatureSensor = "temperature/Front_Panel_Temp"; 46 constexpr const char* pidConfigurationType = 47 "xyz.openbmc_project.Configuration.Pid"; 48 constexpr const char* settingsDaemon = "xyz.openbmc_project.Settings"; 49 constexpr const char* cfmSettingPath = "/xyz/openbmc_project/control/cfm_limit"; 50 constexpr const char* cfmSettingIface = "xyz.openbmc_project.Control.CFMLimit"; 51 52 static constexpr bool debug = false; 53 54 static constexpr double cfmMaxReading = 255; 55 static constexpr double cfmMinReading = 0; 56 57 static constexpr size_t minSystemCfm = 50; 58 59 constexpr const auto monitorIfaces{ 60 std::to_array<const char*>({exitAirIface, cfmIface})}; 61 62 static std::vector<std::shared_ptr<CFMSensor>> cfmSensors; 63 64 static void setupSensorMatch( 65 std::vector<sdbusplus::bus::match_t>& matches, sdbusplus::bus_t& connection, 66 const std::string& type, 67 std::function<void(const double&, sdbusplus::message_t&)>&& callback) 68 { 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, "xyz.openbmc_project.Configuration.CFMSensor", 164 false, false, cfmMaxReading, 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 195 std::weak_ptr<CFMSensor> weakRef = weak_from_this(); 196 setupSensorMatch( 197 matches, *dbusConnection, "fan_tach", 198 [weakRef](const double& value, sdbusplus::message_t& message) { 199 auto self = weakRef.lock(); 200 if (!self) 201 { 202 return; 203 } 204 self->tachReadings[message.get_path()] = value; 205 if (self->tachRanges.find(message.get_path()) == self->tachRanges.end()) 206 { 207 // calls update reading after updating ranges 208 self->addTachRanges(message.get_sender(), message.get_path()); 209 } 210 else 211 { 212 self->updateReading(); 213 } 214 }); 215 216 dbusConnection->async_method_call( 217 [weakRef](const boost::system::error_code ec, 218 const std::variant<double> cfmVariant) { 219 auto self = weakRef.lock(); 220 if (!self) 221 { 222 return; 223 } 224 225 uint64_t maxRpm = 100; 226 if (!ec) 227 { 228 229 const auto* cfm = std::get_if<double>(&cfmVariant); 230 if (cfm != nullptr && *cfm >= minSystemCfm) 231 { 232 maxRpm = self->getMaxRpm(*cfm); 233 } 234 } 235 self->pwmLimitIface->register_property("Limit", maxRpm); 236 self->pwmLimitIface->initialize(); 237 setMaxPWM(self->dbusConnection, maxRpm); 238 }, 239 settingsDaemon, cfmSettingPath, "org.freedesktop.DBus.Properties", 240 "Get", cfmSettingIface, "Limit"); 241 242 matches.emplace_back(*dbusConnection, 243 "type='signal'," 244 "member='PropertiesChanged',interface='org." 245 "freedesktop.DBus.Properties',path='" + 246 std::string(cfmSettingPath) + "',arg0='" + 247 std::string(cfmSettingIface) + "'", 248 [weakRef](sdbusplus::message_t& message) { 249 auto self = weakRef.lock(); 250 if (!self) 251 { 252 return; 253 } 254 boost::container::flat_map<std::string, std::variant<double>> values; 255 std::string objectName; 256 message.read(objectName, values); 257 const auto findValue = values.find("Limit"); 258 if (findValue == values.end()) 259 { 260 return; 261 } 262 auto* const reading = std::get_if<double>(&(findValue->second)); 263 if (reading == nullptr) 264 { 265 std::cerr << "Got CFM Limit of wrong type\n"; 266 return; 267 } 268 if (*reading < minSystemCfm && *reading != 0) 269 { 270 std::cerr << "Illegal CFM setting detected\n"; 271 return; 272 } 273 uint64_t maxRpm = self->getMaxRpm(*reading); 274 self->pwmLimitIface->set_property("Limit", maxRpm); 275 setMaxPWM(self->dbusConnection, maxRpm); 276 }); 277 } 278 279 CFMSensor::~CFMSensor() 280 { 281 for (const auto& iface : thresholdInterfaces) 282 { 283 objServer.remove_interface(iface); 284 } 285 objServer.remove_interface(sensorInterface); 286 objServer.remove_interface(association); 287 objServer.remove_interface(cfmLimitIface); 288 objServer.remove_interface(pwmLimitIface); 289 } 290 291 void CFMSensor::createMaxCFMIface(void) 292 { 293 cfmLimitIface->register_property("Limit", c2 * maxCFM * tachs.size()); 294 cfmLimitIface->initialize(); 295 } 296 297 void CFMSensor::addTachRanges(const std::string& serviceName, 298 const std::string& path) 299 { 300 std::weak_ptr<CFMSensor> weakRef = weak_from_this(); 301 dbusConnection->async_method_call( 302 [weakRef, path](const boost::system::error_code ec, 303 const SensorBaseConfigMap& data) { 304 if (ec) 305 { 306 std::cerr << "Error getting properties from " << path << "\n"; 307 return; 308 } 309 auto self = weakRef.lock(); 310 if (!self) 311 { 312 return; 313 } 314 double max = loadVariant<double>(data, "MaxValue"); 315 double min = loadVariant<double>(data, "MinValue"); 316 self->tachRanges[path] = std::make_pair(min, max); 317 self->updateReading(); 318 }, 319 serviceName, path, "org.freedesktop.DBus.Properties", "GetAll", 320 "xyz.openbmc_project.Sensor.Value"); 321 } 322 323 void CFMSensor::checkThresholds(void) 324 { 325 thresholds::checkThresholds(this); 326 } 327 328 void CFMSensor::updateReading(void) 329 { 330 double val = 0.0; 331 if (calculate(val)) 332 { 333 if (value != val && parent) 334 { 335 parent->updateReading(); 336 } 337 updateValue(val); 338 } 339 else 340 { 341 updateValue(std::numeric_limits<double>::quiet_NaN()); 342 } 343 } 344 345 uint64_t CFMSensor::getMaxRpm(uint64_t cfmMaxSetting) const 346 { 347 uint64_t pwmPercent = 100; 348 double totalCFM = std::numeric_limits<double>::max(); 349 if (cfmMaxSetting == 0) 350 { 351 return pwmPercent; 352 } 353 354 bool firstLoop = true; 355 while (totalCFM > cfmMaxSetting) 356 { 357 if (firstLoop) 358 { 359 firstLoop = false; 360 } 361 else 362 { 363 pwmPercent--; 364 } 365 366 double ci = 0; 367 if (pwmPercent == 0) 368 { 369 ci = 0; 370 } 371 else if (pwmPercent < tachMinPercent) 372 { 373 ci = c1; 374 } 375 else if (pwmPercent > tachMaxPercent) 376 { 377 ci = c2; 378 } 379 else 380 { 381 ci = c1 + (((c2 - c1) * (pwmPercent - tachMinPercent)) / 382 (tachMaxPercent - tachMinPercent)); 383 } 384 385 // Now calculate the CFM for this tach 386 // CFMi = Ci * Qmaxi * TACHi 387 totalCFM = ci * maxCFM * pwmPercent; 388 totalCFM *= tachs.size(); 389 // divide by 100 since pwm is in percent 390 totalCFM /= 100; 391 392 if (pwmPercent <= 0) 393 { 394 break; 395 } 396 } 397 398 return pwmPercent; 399 } 400 401 bool CFMSensor::calculate(double& value) 402 { 403 double totalCFM = 0; 404 for (const std::string& tachName : tachs) 405 { 406 407 auto findReading = std::find_if( 408 tachReadings.begin(), tachReadings.end(), 409 [&](const auto& item) { return item.first.ends_with(tachName); }); 410 auto findRange = std::find_if(tachRanges.begin(), tachRanges.end(), 411 [&](const auto& item) { 412 return item.first.ends_with(tachName); 413 }); 414 if (findReading == tachReadings.end()) 415 { 416 if constexpr (debug) 417 { 418 std::cerr << "Can't find " << tachName << "in readings\n"; 419 } 420 continue; // haven't gotten a reading 421 } 422 423 if (findRange == tachRanges.end()) 424 { 425 std::cerr << "Can't find " << tachName << " in ranges\n"; 426 return false; // haven't gotten a max / min 427 } 428 429 // avoid divide by 0 430 if (findRange->second.second == 0) 431 { 432 std::cerr << "Tach Max Set to 0 " << tachName << "\n"; 433 return false; 434 } 435 436 double rpm = findReading->second; 437 438 // for now assume the min for a fan is always 0, divide by max to get 439 // percent and mult by 100 440 rpm /= findRange->second.second; 441 rpm *= 100; 442 443 if constexpr (debug) 444 { 445 std::cout << "Tach " << tachName << "at " << rpm << "\n"; 446 } 447 448 // Do a linear interpolation to get Ci 449 // Ci = C1 + (C2 - C1)/(RPM2 - RPM1) * (TACHi - TACH1) 450 451 double ci = 0; 452 if (rpm == 0) 453 { 454 ci = 0; 455 } 456 else if (rpm < tachMinPercent) 457 { 458 ci = c1; 459 } 460 else if (rpm > tachMaxPercent) 461 { 462 ci = c2; 463 } 464 else 465 { 466 ci = c1 + (((c2 - c1) * (rpm - tachMinPercent)) / 467 (tachMaxPercent - tachMinPercent)); 468 } 469 470 // Now calculate the CFM for this tach 471 // CFMi = Ci * Qmaxi * TACHi 472 totalCFM += ci * maxCFM * rpm; 473 if constexpr (debug) 474 { 475 std::cerr << "totalCFM = " << totalCFM << "\n"; 476 std::cerr << "Ci " << ci << " MaxCFM " << maxCFM << " rpm " << rpm 477 << "\n"; 478 std::cerr << "c1 " << c1 << " c2 " << c2 << " max " 479 << tachMaxPercent << " min " << tachMinPercent << "\n"; 480 } 481 } 482 483 // divide by 100 since rpm is in percent 484 value = totalCFM / 100; 485 if constexpr (debug) 486 { 487 std::cerr << "cfm value = " << value << "\n"; 488 } 489 return true; 490 } 491 492 static constexpr double exitAirMaxReading = 127; 493 static constexpr double exitAirMinReading = -128; 494 ExitAirTempSensor::ExitAirTempSensor( 495 std::shared_ptr<sdbusplus::asio::connection>& conn, 496 const std::string& sensorName, const std::string& sensorConfiguration, 497 sdbusplus::asio::object_server& objectServer, 498 std::vector<thresholds::Threshold>&& thresholdData) : 499 Sensor(escapeName(sensorName), std::move(thresholdData), 500 sensorConfiguration, "xyz.openbmc_project.Configuration.ExitAirTemp", 501 false, false, exitAirMaxReading, exitAirMinReading, conn, 502 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 == exitAirIface) 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 == cfmIface) 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>(monitorIfaces.begin(), monitorIfaces.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 systemBus->request_name("xyz.openbmc_project.ExitAirTempSensor"); 937 sdbusplus::asio::object_server objectServer(systemBus); 938 std::shared_ptr<ExitAirTempSensor> sensor = 939 nullptr; // wait until we find the config 940 941 io.post([&]() { createSensor(objectServer, sensor, systemBus); }); 942 943 boost::asio::deadline_timer configTimer(io); 944 945 std::function<void(sdbusplus::message_t&)> eventHandler = 946 [&](sdbusplus::message_t&) { 947 configTimer.expires_from_now(boost::posix_time::seconds(1)); 948 // create a timer because normally multiple properties change 949 configTimer.async_wait([&](const boost::system::error_code& ec) { 950 if (ec == boost::asio::error::operation_aborted) 951 { 952 return; // we're being canceled 953 } 954 createSensor(objectServer, sensor, systemBus); 955 if (!sensor) 956 { 957 std::cout << "Configuration not detected\n"; 958 } 959 }); 960 }; 961 std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches = 962 setupPropertiesChangedMatches(*systemBus, monitorIfaces, eventHandler); 963 964 setupManufacturingModeMatch(*systemBus); 965 io.run(); 966 return 0; 967 } 968