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