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