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