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