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