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