blob: 41d9a198d5cb7ec247e0dccbc9f56d71f5c88dc7 [file] [log] [blame]
James Feistbc896df2018-11-26 16:28:17 -08001/*
2// Copyright (c) 2018 Intel Corporation
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15*/
16
17#include "ExitAirTempSensor.hpp"
18
19#include "Utils.hpp"
20#include "VariantVisitors.hpp"
21
22#include <math.h>
23
24#include <boost/algorithm/string/predicate.hpp>
25#include <boost/algorithm/string/replace.hpp>
Patrick Venture96e97db2019-10-31 13:44:38 -070026#include <boost/container/flat_map.hpp>
James Feist38fb5982020-05-28 10:09:54 -070027#include <sdbusplus/asio/connection.hpp>
28#include <sdbusplus/asio/object_server.hpp>
29#include <sdbusplus/bus/match.hpp>
30
31#include <array>
James Feistbc896df2018-11-26 16:28:17 -080032#include <chrono>
Patrick Venture96e97db2019-10-31 13:44:38 -070033#include <cmath>
34#include <functional>
James Feistbc896df2018-11-26 16:28:17 -080035#include <iostream>
36#include <limits>
Patrick Venture96e97db2019-10-31 13:44:38 -070037#include <memory>
James Feistbc896df2018-11-26 16:28:17 -080038#include <numeric>
Patrick Venture96e97db2019-10-31 13:44:38 -070039#include <stdexcept>
40#include <utility>
41#include <variant>
James Feistbc896df2018-11-26 16:28:17 -080042#include <vector>
43
44constexpr const float altitudeFactor = 1.14;
45constexpr const char* exitAirIface =
46 "xyz.openbmc_project.Configuration.ExitAirTempSensor";
47constexpr const char* cfmIface = "xyz.openbmc_project.Configuration.CFMSensor";
48
49// todo: this *might* need to be configurable
50constexpr const char* inletTemperatureSensor = "temperature/Front_Panel_Temp";
James Feist13452092019-03-07 16:38:12 -080051constexpr const char* pidConfigurationType =
52 "xyz.openbmc_project.Configuration.Pid";
53constexpr const char* settingsDaemon = "xyz.openbmc_project.Settings";
54constexpr const char* cfmSettingPath = "/xyz/openbmc_project/control/cfm_limit";
55constexpr const char* cfmSettingIface = "xyz.openbmc_project.Control.CFMLimit";
James Feistbc896df2018-11-26 16:28:17 -080056
57static constexpr bool DEBUG = false;
58
James Feistb2eb3f52018-12-04 16:17:50 -080059static constexpr double cfmMaxReading = 255;
60static constexpr double cfmMinReading = 0;
61
James Feist13452092019-03-07 16:38:12 -080062static constexpr size_t minSystemCfm = 50;
63
James Feist9a25ed42019-10-15 15:43:44 -070064static std::vector<std::shared_ptr<CFMSensor>> cfmSensors;
65
James Feistb2eb3f52018-12-04 16:17:50 -080066static void setupSensorMatch(
67 std::vector<sdbusplus::bus::match::match>& matches,
68 sdbusplus::bus::bus& connection, const std::string& type,
69 std::function<void(const double&, sdbusplus::message::message&)>&& callback)
70{
71
72 std::function<void(sdbusplus::message::message & message)> eventHandler =
73 [callback{std::move(callback)}](sdbusplus::message::message& message) {
74 std::string objectName;
James Feist3eb82622019-02-08 13:10:22 -080075 boost::container::flat_map<std::string,
76 std::variant<double, int64_t>>
James Feistb2eb3f52018-12-04 16:17:50 -080077 values;
78 message.read(objectName, values);
79 auto findValue = values.find("Value");
80 if (findValue == values.end())
81 {
82 return;
83 }
James Feist3eb82622019-02-08 13:10:22 -080084 double value =
85 std::visit(VariantToDoubleVisitor(), findValue->second);
James Feist9566bfa2019-01-29 15:31:23 -080086 if (std::isnan(value))
87 {
88 return;
89 }
90
James Feistb2eb3f52018-12-04 16:17:50 -080091 callback(value, message);
92 };
93 matches.emplace_back(connection,
94 "type='signal',"
95 "member='PropertiesChanged',interface='org."
96 "freedesktop.DBus.Properties',path_"
97 "namespace='/xyz/openbmc_project/sensors/" +
98 std::string(type) +
99 "',arg0='xyz.openbmc_project.Sensor.Value'",
100 std::move(eventHandler));
101}
102
James Feist13452092019-03-07 16:38:12 -0800103static void setMaxPWM(const std::shared_ptr<sdbusplus::asio::connection>& conn,
104 double value)
105{
106 using GetSubTreeType = std::vector<std::pair<
107 std::string,
108 std::vector<std::pair<std::string, std::vector<std::string>>>>>;
109
110 conn->async_method_call(
111 [conn, value](const boost::system::error_code ec,
112 const GetSubTreeType& ret) {
113 if (ec)
114 {
115 std::cerr << "Error calling mapper\n";
116 return;
117 }
118 for (const auto& [path, objDict] : ret)
119 {
120 if (objDict.empty())
121 {
122 return;
123 }
124 const std::string& owner = objDict.begin()->first;
125
126 conn->async_method_call(
127 [conn, value, owner,
128 path](const boost::system::error_code ec,
129 const std::variant<std::string>& classType) {
130 if (ec)
131 {
132 std::cerr << "Error getting pid class\n";
133 return;
134 }
135 auto classStr = std::get_if<std::string>(&classType);
136 if (classStr == nullptr || *classStr != "fan")
137 {
138 return;
139 }
140 conn->async_method_call(
141 [](boost::system::error_code& ec) {
142 if (ec)
143 {
144 std::cerr << "Error setting pid class\n";
145 return;
146 }
147 },
148 owner, path, "org.freedesktop.DBus.Properties",
149 "Set", pidConfigurationType, "OutLimitMax",
150 std::variant<double>(value));
151 },
152 owner, path, "org.freedesktop.DBus.Properties", "Get",
153 pidConfigurationType, "Class");
154 }
155 },
James Feista5e58722019-04-22 14:43:11 -0700156 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
157 0, std::array<std::string, 1>{pidConfigurationType});
James Feist13452092019-03-07 16:38:12 -0800158}
159
James Feistb2eb3f52018-12-04 16:17:50 -0800160CFMSensor::CFMSensor(std::shared_ptr<sdbusplus::asio::connection>& conn,
161 const std::string& sensorName,
162 const std::string& sensorConfiguration,
163 sdbusplus::asio::object_server& objectServer,
James Feistb839c052019-05-15 10:25:24 -0700164 std::vector<thresholds::Threshold>&& thresholdData,
James Feistb2eb3f52018-12-04 16:17:50 -0800165 std::shared_ptr<ExitAirTempSensor>& parent) :
166 Sensor(boost::replace_all_copy(sensorName, " ", "_"),
James Feist930fcde2019-05-28 12:58:43 -0700167 std::move(thresholdData), sensorConfiguration,
168 "xyz.openbmc_project.Configuration.ExitAirTemp", cfmMaxReading,
James Feiste3338522020-09-15 15:40:30 -0700169 cfmMinReading, conn, PowerState::on),
Brad Bishopfbb44ad2019-11-08 09:42:37 -0500170 std::enable_shared_from_this<CFMSensor>(), parent(parent),
James Feiste3338522020-09-15 15:40:30 -0700171 objServer(objectServer)
James Feistb2eb3f52018-12-04 16:17:50 -0800172{
173 sensorInterface =
174 objectServer.add_interface("/xyz/openbmc_project/sensors/cfm/" + name,
175 "xyz.openbmc_project.Sensor.Value");
176
177 if (thresholds::hasWarningInterface(thresholds))
178 {
179 thresholdInterfaceWarning = objectServer.add_interface(
180 "/xyz/openbmc_project/sensors/cfm/" + name,
181 "xyz.openbmc_project.Sensor.Threshold.Warning");
182 }
183 if (thresholds::hasCriticalInterface(thresholds))
184 {
185 thresholdInterfaceCritical = objectServer.add_interface(
186 "/xyz/openbmc_project/sensors/cfm/" + name,
187 "xyz.openbmc_project.Sensor.Threshold.Critical");
188 }
James Feist078f2322019-03-08 11:09:05 -0800189
190 association = objectServer.add_interface(
James Feist2adc95c2019-09-30 14:55:28 -0700191 "/xyz/openbmc_project/sensors/cfm/" + name, association::interface);
James Feist078f2322019-03-08 11:09:05 -0800192
James Feistb2eb3f52018-12-04 16:17:50 -0800193 setInitialProperties(conn);
James Feist9a25ed42019-10-15 15:43:44 -0700194
James Feist13452092019-03-07 16:38:12 -0800195 pwmLimitIface =
196 objectServer.add_interface("/xyz/openbmc_project/control/pwm_limit",
197 "xyz.openbmc_project.Control.PWMLimit");
198 cfmLimitIface =
199 objectServer.add_interface("/xyz/openbmc_project/control/MaxCFM",
200 "xyz.openbmc_project.Control.CFMLimit");
James Feist9a25ed42019-10-15 15:43:44 -0700201}
James Feist13452092019-03-07 16:38:12 -0800202
James Feist9a25ed42019-10-15 15:43:44 -0700203void CFMSensor::setupMatches()
204{
205
206 std::shared_ptr<CFMSensor> self = shared_from_this();
207 setupSensorMatch(matches, *dbusConnection, "fan_tach",
208 std::move([self](const double& value,
209 sdbusplus::message::message& message) {
210 self->tachReadings[message.get_path()] = value;
211 if (self->tachRanges.find(message.get_path()) ==
212 self->tachRanges.end())
213 {
214 // calls update reading after updating ranges
215 self->addTachRanges(message.get_sender(),
216 message.get_path());
217 }
218 else
219 {
220 self->updateReading();
221 }
222 }));
223
224 dbusConnection->async_method_call(
225 [self](const boost::system::error_code ec,
226 const std::variant<double> cfmVariant) {
James Feist13452092019-03-07 16:38:12 -0800227 uint64_t maxRpm = 100;
228 if (!ec)
229 {
230
231 auto cfm = std::get_if<double>(&cfmVariant);
Josh Lehanff336992020-02-26 11:13:19 -0800232 if (cfm != nullptr && *cfm >= minSystemCfm)
James Feist13452092019-03-07 16:38:12 -0800233 {
James Feist9a25ed42019-10-15 15:43:44 -0700234 maxRpm = self->getMaxRpm(*cfm);
James Feist13452092019-03-07 16:38:12 -0800235 }
236 }
James Feist9a25ed42019-10-15 15:43:44 -0700237 self->pwmLimitIface->register_property("Limit", maxRpm);
238 self->pwmLimitIface->initialize();
239 setMaxPWM(self->dbusConnection, maxRpm);
James Feist13452092019-03-07 16:38:12 -0800240 },
241 settingsDaemon, cfmSettingPath, "org.freedesktop.DBus.Properties",
242 "Get", cfmSettingIface, "Limit");
243
244 matches.emplace_back(
James Feist9a25ed42019-10-15 15:43:44 -0700245 *dbusConnection,
James Feist13452092019-03-07 16:38:12 -0800246 "type='signal',"
247 "member='PropertiesChanged',interface='org."
248 "freedesktop.DBus.Properties',path='" +
249 std::string(cfmSettingPath) + "',arg0='" +
250 std::string(cfmSettingIface) + "'",
James Feist9a25ed42019-10-15 15:43:44 -0700251 [self](sdbusplus::message::message& message) {
James Feist13452092019-03-07 16:38:12 -0800252 boost::container::flat_map<std::string, std::variant<double>>
253 values;
254 std::string objectName;
255 message.read(objectName, values);
256 const auto findValue = values.find("Limit");
257 if (findValue == values.end())
258 {
259 return;
260 }
261 const auto reading = std::get_if<double>(&(findValue->second));
262 if (reading == nullptr)
263 {
264 std::cerr << "Got CFM Limit of wrong type\n";
265 return;
266 }
267 if (*reading < minSystemCfm && *reading != 0)
268 {
269 std::cerr << "Illegal CFM setting detected\n";
270 return;
271 }
James Feist9a25ed42019-10-15 15:43:44 -0700272 uint64_t maxRpm = self->getMaxRpm(*reading);
273 self->pwmLimitIface->set_property("Limit", maxRpm);
274 setMaxPWM(self->dbusConnection, maxRpm);
James Feist13452092019-03-07 16:38:12 -0800275 });
James Feistb2eb3f52018-12-04 16:17:50 -0800276}
277
James Feist9566bfa2019-01-29 15:31:23 -0800278CFMSensor::~CFMSensor()
279{
280 objServer.remove_interface(thresholdInterfaceWarning);
281 objServer.remove_interface(thresholdInterfaceCritical);
282 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800283 objServer.remove_interface(association);
James Feist13452092019-03-07 16:38:12 -0800284 objServer.remove_interface(cfmLimitIface);
285 objServer.remove_interface(pwmLimitIface);
286}
287
288void CFMSensor::createMaxCFMIface(void)
289{
James Feistb6c0b912019-07-09 12:21:44 -0700290 cfmLimitIface->register_property("Limit", c2 * maxCFM * tachs.size());
James Feist13452092019-03-07 16:38:12 -0800291 cfmLimitIface->initialize();
James Feist9566bfa2019-01-29 15:31:23 -0800292}
293
James Feistb2eb3f52018-12-04 16:17:50 -0800294void CFMSensor::addTachRanges(const std::string& serviceName,
295 const std::string& path)
296{
James Feist9a25ed42019-10-15 15:43:44 -0700297 std::shared_ptr<CFMSensor> self = shared_from_this();
James Feistb2eb3f52018-12-04 16:17:50 -0800298 dbusConnection->async_method_call(
James Feist9a25ed42019-10-15 15:43:44 -0700299 [self, path](const boost::system::error_code ec,
James Feistb2eb3f52018-12-04 16:17:50 -0800300 const boost::container::flat_map<std::string,
301 BasicVariantType>& data) {
302 if (ec)
303 {
304 std::cerr << "Error getting properties from " << path << "\n";
James Feist1ccdb5e2019-01-24 09:44:01 -0800305 return;
James Feistb2eb3f52018-12-04 16:17:50 -0800306 }
307
308 double max = loadVariant<double>(data, "MaxValue");
309 double min = loadVariant<double>(data, "MinValue");
James Feist9a25ed42019-10-15 15:43:44 -0700310 self->tachRanges[path] = std::make_pair(min, max);
311 self->updateReading();
James Feistb2eb3f52018-12-04 16:17:50 -0800312 },
313 serviceName, path, "org.freedesktop.DBus.Properties", "GetAll",
314 "xyz.openbmc_project.Sensor.Value");
315}
316
317void CFMSensor::checkThresholds(void)
318{
319 thresholds::checkThresholds(this);
320}
321
322void CFMSensor::updateReading(void)
323{
324 double val = 0.0;
325 if (calculate(val))
326 {
327 if (value != val && parent)
328 {
329 parent->updateReading();
330 }
331 updateValue(val);
332 }
333 else
334 {
335 updateValue(std::numeric_limits<double>::quiet_NaN());
336 }
337}
338
James Feist13452092019-03-07 16:38:12 -0800339uint64_t CFMSensor::getMaxRpm(uint64_t cfmMaxSetting)
340{
341 uint64_t pwmPercent = 100;
342 double totalCFM = std::numeric_limits<double>::max();
343 if (cfmMaxSetting == 0)
344 {
345 return pwmPercent;
346 }
347
James Feist52427952019-04-05 14:23:35 -0700348 bool firstLoop = true;
James Feist13452092019-03-07 16:38:12 -0800349 while (totalCFM > cfmMaxSetting)
350 {
James Feist52427952019-04-05 14:23:35 -0700351 if (firstLoop)
352 {
353 firstLoop = false;
354 }
355 else
356 {
357 pwmPercent--;
358 }
359
James Feist13452092019-03-07 16:38:12 -0800360 double ci = 0;
361 if (pwmPercent == 0)
362 {
363 ci = 0;
364 }
365 else if (pwmPercent < tachMinPercent)
366 {
367 ci = c1;
368 }
369 else if (pwmPercent > tachMaxPercent)
370 {
371 ci = c2;
372 }
373 else
374 {
375 ci = c1 + (((c2 - c1) * (pwmPercent - tachMinPercent)) /
376 (tachMaxPercent - tachMinPercent));
377 }
378
379 // Now calculate the CFM for this tach
380 // CFMi = Ci * Qmaxi * TACHi
381 totalCFM = ci * maxCFM * pwmPercent;
382 totalCFM *= tachs.size();
383 // divide by 100 since pwm is in percent
384 totalCFM /= 100;
385
James Feist13452092019-03-07 16:38:12 -0800386 if (pwmPercent <= 0)
387 {
388 break;
389 }
390 }
James Feist52427952019-04-05 14:23:35 -0700391
James Feist13452092019-03-07 16:38:12 -0800392 return pwmPercent;
393}
394
James Feistb2eb3f52018-12-04 16:17:50 -0800395bool CFMSensor::calculate(double& value)
396{
397 double totalCFM = 0;
398 for (const std::string& tachName : tachs)
399 {
James Feist9566bfa2019-01-29 15:31:23 -0800400
James Feistb2eb3f52018-12-04 16:17:50 -0800401 auto findReading = std::find_if(
402 tachReadings.begin(), tachReadings.end(), [&](const auto& item) {
403 return boost::ends_with(item.first, tachName);
404 });
405 auto findRange = std::find_if(
406 tachRanges.begin(), tachRanges.end(), [&](const auto& item) {
407 return boost::ends_with(item.first, tachName);
408 });
409 if (findReading == tachReadings.end())
410 {
James Feista5e58722019-04-22 14:43:11 -0700411 if constexpr (DEBUG)
James Feista96329f2019-01-24 10:08:27 -0800412 {
413 std::cerr << "Can't find " << tachName << "in readings\n";
414 }
James Feist9566bfa2019-01-29 15:31:23 -0800415 continue; // haven't gotten a reading
James Feistb2eb3f52018-12-04 16:17:50 -0800416 }
417
418 if (findRange == tachRanges.end())
419 {
James Feist523828e2019-03-04 14:38:37 -0800420 std::cerr << "Can't find " << tachName << " in ranges\n";
James Feistb2eb3f52018-12-04 16:17:50 -0800421 return false; // haven't gotten a max / min
422 }
423
424 // avoid divide by 0
425 if (findRange->second.second == 0)
426 {
427 std::cerr << "Tach Max Set to 0 " << tachName << "\n";
428 return false;
429 }
430
431 double rpm = findReading->second;
432
433 // for now assume the min for a fan is always 0, divide by max to get
434 // percent and mult by 100
435 rpm /= findRange->second.second;
436 rpm *= 100;
437
438 if constexpr (DEBUG)
439 {
440 std::cout << "Tach " << tachName << "at " << rpm << "\n";
441 }
442
443 // Do a linear interpolation to get Ci
444 // Ci = C1 + (C2 - C1)/(RPM2 - RPM1) * (TACHi - TACH1)
445
446 double ci = 0;
447 if (rpm == 0)
448 {
449 ci = 0;
450 }
451 else if (rpm < tachMinPercent)
452 {
453 ci = c1;
454 }
455 else if (rpm > tachMaxPercent)
456 {
457 ci = c2;
458 }
459 else
460 {
461 ci = c1 + (((c2 - c1) * (rpm - tachMinPercent)) /
462 (tachMaxPercent - tachMinPercent));
463 }
464
465 // Now calculate the CFM for this tach
466 // CFMi = Ci * Qmaxi * TACHi
467 totalCFM += ci * maxCFM * rpm;
James Feista5e58722019-04-22 14:43:11 -0700468 if constexpr (DEBUG)
469 {
470 std::cerr << "totalCFM = " << totalCFM << "\n";
471 std::cerr << "Ci " << ci << " MaxCFM " << maxCFM << " rpm " << rpm
472 << "\n";
473 std::cerr << "c1 " << c1 << " c2 " << c2 << " max "
474 << tachMaxPercent << " min " << tachMinPercent << "\n";
475 }
James Feistb2eb3f52018-12-04 16:17:50 -0800476 }
477
478 // divide by 100 since rpm is in percent
479 value = totalCFM / 100;
James Feista5e58722019-04-22 14:43:11 -0700480 if constexpr (DEBUG)
481 {
482 std::cerr << "cfm value = " << value << "\n";
483 }
James Feist9566bfa2019-01-29 15:31:23 -0800484 return true;
James Feistb2eb3f52018-12-04 16:17:50 -0800485}
486
487static constexpr double exitAirMaxReading = 127;
488static constexpr double exitAirMinReading = -128;
James Feistbc896df2018-11-26 16:28:17 -0800489ExitAirTempSensor::ExitAirTempSensor(
490 std::shared_ptr<sdbusplus::asio::connection>& conn,
James Feistb2eb3f52018-12-04 16:17:50 -0800491 const std::string& sensorName, const std::string& sensorConfiguration,
James Feistbc896df2018-11-26 16:28:17 -0800492 sdbusplus::asio::object_server& objectServer,
James Feistb839c052019-05-15 10:25:24 -0700493 std::vector<thresholds::Threshold>&& thresholdData) :
James Feistb2eb3f52018-12-04 16:17:50 -0800494 Sensor(boost::replace_all_copy(sensorName, " ", "_"),
James Feist930fcde2019-05-28 12:58:43 -0700495 std::move(thresholdData), sensorConfiguration,
496 "xyz.openbmc_project.Configuration.ExitAirTemp", exitAirMaxReading,
James Feiste3338522020-09-15 15:40:30 -0700497 exitAirMinReading, conn, PowerState::on),
498 std::enable_shared_from_this<ExitAirTempSensor>(), objServer(objectServer)
James Feistbc896df2018-11-26 16:28:17 -0800499{
500 sensorInterface = objectServer.add_interface(
501 "/xyz/openbmc_project/sensors/temperature/" + name,
502 "xyz.openbmc_project.Sensor.Value");
503
504 if (thresholds::hasWarningInterface(thresholds))
505 {
506 thresholdInterfaceWarning = objectServer.add_interface(
507 "/xyz/openbmc_project/sensors/temperature/" + name,
508 "xyz.openbmc_project.Sensor.Threshold.Warning");
509 }
510 if (thresholds::hasCriticalInterface(thresholds))
511 {
512 thresholdInterfaceCritical = objectServer.add_interface(
513 "/xyz/openbmc_project/sensors/temperature/" + name,
514 "xyz.openbmc_project.Sensor.Threshold.Critical");
515 }
James Feist078f2322019-03-08 11:09:05 -0800516 association = objectServer.add_interface(
517 "/xyz/openbmc_project/sensors/temperature/" + name,
James Feist2adc95c2019-09-30 14:55:28 -0700518 association::interface);
James Feistbc896df2018-11-26 16:28:17 -0800519 setInitialProperties(conn);
James Feistbc896df2018-11-26 16:28:17 -0800520}
521
522ExitAirTempSensor::~ExitAirTempSensor()
523{
James Feist523828e2019-03-04 14:38:37 -0800524 objServer.remove_interface(thresholdInterfaceWarning);
525 objServer.remove_interface(thresholdInterfaceCritical);
526 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800527 objServer.remove_interface(association);
James Feistbc896df2018-11-26 16:28:17 -0800528}
529
530void ExitAirTempSensor::setupMatches(void)
531{
James Feistb2eb3f52018-12-04 16:17:50 -0800532 constexpr const std::array<const char*, 2> matchTypes = {
533 "power", inletTemperatureSensor};
James Feistbc896df2018-11-26 16:28:17 -0800534
James Feist9a25ed42019-10-15 15:43:44 -0700535 std::shared_ptr<ExitAirTempSensor> self = shared_from_this();
James Feistb2eb3f52018-12-04 16:17:50 -0800536 for (const std::string& type : matchTypes)
James Feistbc896df2018-11-26 16:28:17 -0800537 {
James Feistb2eb3f52018-12-04 16:17:50 -0800538 setupSensorMatch(matches, *dbusConnection, type,
James Feist9a25ed42019-10-15 15:43:44 -0700539 [self, type](const double& value,
James Feistb2eb3f52018-12-04 16:17:50 -0800540 sdbusplus::message::message& message) {
541 if (type == "power")
542 {
James Feista5e58722019-04-22 14:43:11 -0700543 std::string path = message.get_path();
544 if (path.find("PS") != std::string::npos &&
545 boost::ends_with(path, "Input_Power"))
546 {
James Feist9a25ed42019-10-15 15:43:44 -0700547 self->powerReadings[message.get_path()] =
548 value;
James Feista5e58722019-04-22 14:43:11 -0700549 }
James Feistb2eb3f52018-12-04 16:17:50 -0800550 }
551 else if (type == inletTemperatureSensor)
552 {
James Feist9a25ed42019-10-15 15:43:44 -0700553 self->inletTemp = value;
James Feistb2eb3f52018-12-04 16:17:50 -0800554 }
James Feist9a25ed42019-10-15 15:43:44 -0700555 self->updateReading();
James Feistb2eb3f52018-12-04 16:17:50 -0800556 });
James Feistbc896df2018-11-26 16:28:17 -0800557 }
James Feist9566bfa2019-01-29 15:31:23 -0800558 dbusConnection->async_method_call(
James Feist9a25ed42019-10-15 15:43:44 -0700559 [self](boost::system::error_code ec,
James Feist9566bfa2019-01-29 15:31:23 -0800560 const std::variant<double>& value) {
561 if (ec)
562 {
563 // sensor not ready yet
564 return;
565 }
566
James Feist9a25ed42019-10-15 15:43:44 -0700567 self->inletTemp = std::visit(VariantToDoubleVisitor(), value);
James Feist9566bfa2019-01-29 15:31:23 -0800568 },
569 "xyz.openbmc_project.HwmonTempSensor",
570 std::string("/xyz/openbmc_project/sensors/") + inletTemperatureSensor,
James Feista5e58722019-04-22 14:43:11 -0700571 properties::interface, properties::get, sensorValueInterface, "Value");
572 dbusConnection->async_method_call(
James Feist9a25ed42019-10-15 15:43:44 -0700573 [self](boost::system::error_code ec, const GetSubTreeType& subtree) {
James Feista5e58722019-04-22 14:43:11 -0700574 if (ec)
575 {
576 std::cerr << "Error contacting mapper\n";
577 return;
578 }
579 for (const auto& item : subtree)
580 {
581 size_t lastSlash = item.first.rfind("/");
582 if (lastSlash == std::string::npos ||
583 lastSlash == item.first.size() || !item.second.size())
584 {
585 continue;
586 }
587 std::string sensorName = item.first.substr(lastSlash + 1);
588 if (boost::starts_with(sensorName, "PS") &&
589 boost::ends_with(sensorName, "Input_Power"))
590 {
591 const std::string& path = item.first;
James Feist9a25ed42019-10-15 15:43:44 -0700592 self->dbusConnection->async_method_call(
593 [self, path](boost::system::error_code ec,
James Feista5e58722019-04-22 14:43:11 -0700594 const std::variant<double>& value) {
595 if (ec)
596 {
597 std::cerr << "Error getting value from " << path
598 << "\n";
599 }
600
601 double reading =
602 std::visit(VariantToDoubleVisitor(), value);
603 if constexpr (DEBUG)
604 {
605 std::cerr << path << "Reading " << reading
606 << "\n";
607 }
James Feist9a25ed42019-10-15 15:43:44 -0700608 self->powerReadings[path] = reading;
James Feista5e58722019-04-22 14:43:11 -0700609 },
610 item.second[0].first, item.first, properties::interface,
611 properties::get, sensorValueInterface, "Value");
612 }
613 }
614 },
615 mapper::busName, mapper::path, mapper::interface, mapper::subtree,
616 "/xyz/openbmc_project/sensors/power", 0,
617 std::array<const char*, 1>{sensorValueInterface});
James Feistbc896df2018-11-26 16:28:17 -0800618}
619
620void ExitAirTempSensor::updateReading(void)
621{
622
623 double val = 0.0;
624 if (calculate(val))
625 {
James Feist18af4232019-03-13 11:14:00 -0700626 val = std::floor(val + 0.5);
James Feistbc896df2018-11-26 16:28:17 -0800627 updateValue(val);
628 }
629 else
630 {
631 updateValue(std::numeric_limits<double>::quiet_NaN());
632 }
633}
634
James Feistb2eb3f52018-12-04 16:17:50 -0800635double ExitAirTempSensor::getTotalCFM(void)
James Feistbc896df2018-11-26 16:28:17 -0800636{
James Feistb2eb3f52018-12-04 16:17:50 -0800637 double sum = 0;
638 for (auto& sensor : cfmSensors)
James Feistbc896df2018-11-26 16:28:17 -0800639 {
James Feistb2eb3f52018-12-04 16:17:50 -0800640 double reading = 0;
641 if (!sensor->calculate(reading))
James Feistbc896df2018-11-26 16:28:17 -0800642 {
James Feistbc896df2018-11-26 16:28:17 -0800643 return -1;
644 }
James Feistb2eb3f52018-12-04 16:17:50 -0800645 sum += reading;
James Feistbc896df2018-11-26 16:28:17 -0800646 }
James Feistb2eb3f52018-12-04 16:17:50 -0800647
648 return sum;
James Feistbc896df2018-11-26 16:28:17 -0800649}
650
651bool ExitAirTempSensor::calculate(double& val)
652{
James Feistae11cfc2019-05-07 15:01:20 -0700653 constexpr size_t maxErrorPrint = 1;
James Feistbc896df2018-11-26 16:28:17 -0800654 static bool firstRead = false;
James Feistae11cfc2019-05-07 15:01:20 -0700655 static size_t errorPrint = maxErrorPrint;
656
James Feistbc896df2018-11-26 16:28:17 -0800657 double cfm = getTotalCFM();
658 if (cfm <= 0)
659 {
660 std::cerr << "Error getting cfm\n";
661 return false;
662 }
663
664 // if there is an error getting inlet temp, return error
665 if (std::isnan(inletTemp))
666 {
James Feistae11cfc2019-05-07 15:01:20 -0700667 if (errorPrint > 0)
668 {
669 errorPrint--;
670 std::cerr << "Cannot get inlet temp\n";
671 }
James Feistbc896df2018-11-26 16:28:17 -0800672 val = 0;
673 return false;
674 }
675
676 // if fans are off, just make the exit temp equal to inlet
James Feist71d31b22019-01-02 16:57:54 -0800677 if (!isPowerOn())
James Feistbc896df2018-11-26 16:28:17 -0800678 {
679 val = inletTemp;
680 return true;
681 }
682
683 double totalPower = 0;
684 for (const auto& reading : powerReadings)
685 {
686 if (std::isnan(reading.second))
687 {
688 continue;
689 }
690 totalPower += reading.second;
691 }
692
693 // Calculate power correction factor
694 // Ci = CL + (CH - CL)/(QMax - QMin) * (CFM - QMin)
695 float powerFactor = 0.0;
696 if (cfm <= qMin)
697 {
698 powerFactor = powerFactorMin;
699 }
700 else if (cfm >= qMax)
701 {
702 powerFactor = powerFactorMax;
703 }
704 else
705 {
706 powerFactor = powerFactorMin + ((powerFactorMax - powerFactorMin) /
707 (qMax - qMin) * (cfm - qMin));
708 }
709
James Feistb6c0b912019-07-09 12:21:44 -0700710 totalPower *= static_cast<double>(powerFactor);
James Feistbc896df2018-11-26 16:28:17 -0800711 totalPower += pOffset;
712
713 if (totalPower == 0)
714 {
James Feistae11cfc2019-05-07 15:01:20 -0700715 if (errorPrint > 0)
716 {
717 errorPrint--;
718 std::cerr << "total power 0\n";
719 }
James Feistbc896df2018-11-26 16:28:17 -0800720 val = 0;
721 return false;
722 }
723
724 if constexpr (DEBUG)
725 {
726 std::cout << "Power Factor " << powerFactor << "\n";
727 std::cout << "Inlet Temp " << inletTemp << "\n";
728 std::cout << "Total Power" << totalPower << "\n";
729 }
730
731 // Calculate the exit air temp
732 // Texit = Tfp + (1.76 * TotalPower / CFM * Faltitude)
James Feistb6c0b912019-07-09 12:21:44 -0700733 double reading = 1.76 * totalPower * static_cast<double>(altitudeFactor);
James Feistbc896df2018-11-26 16:28:17 -0800734 reading /= cfm;
735 reading += inletTemp;
736
737 if constexpr (DEBUG)
738 {
739 std::cout << "Reading 1: " << reading << "\n";
740 }
741
742 // Now perform the exponential average
743 // Calculate alpha based on SDR values and CFM
744 // Ai = As + (Af - As)/(QMax - QMin) * (CFM - QMin)
745
746 double alpha = 0.0;
747 if (cfm < qMin)
748 {
749 alpha = alphaS;
750 }
751 else if (cfm >= qMax)
752 {
753 alpha = alphaF;
754 }
755 else
756 {
757 alpha = alphaS + ((alphaF - alphaS) * (cfm - qMin) / (qMax - qMin));
758 }
759
760 auto time = std::chrono::system_clock::now();
761 if (!firstRead)
762 {
763 firstRead = true;
764 lastTime = time;
765 lastReading = reading;
766 }
767 double alphaDT =
768 std::chrono::duration_cast<std::chrono::seconds>(time - lastTime)
769 .count() *
770 alpha;
771
772 // cap at 1.0 or the below fails
773 if (alphaDT > 1.0)
774 {
775 alphaDT = 1.0;
776 }
777
778 if constexpr (DEBUG)
779 {
780 std::cout << "AlphaDT: " << alphaDT << "\n";
781 }
782
783 reading = ((reading * alphaDT) + (lastReading * (1.0 - alphaDT)));
784
785 if constexpr (DEBUG)
786 {
787 std::cout << "Reading 2: " << reading << "\n";
788 }
789
790 val = reading;
791 lastReading = reading;
792 lastTime = time;
James Feistae11cfc2019-05-07 15:01:20 -0700793 errorPrint = maxErrorPrint;
James Feistbc896df2018-11-26 16:28:17 -0800794 return true;
795}
796
797void ExitAirTempSensor::checkThresholds(void)
798{
799 thresholds::checkThresholds(this);
800}
801
James Feistbc896df2018-11-26 16:28:17 -0800802static void loadVariantPathArray(
803 const boost::container::flat_map<std::string, BasicVariantType>& data,
804 const std::string& key, std::vector<std::string>& resp)
805{
806 auto it = data.find(key);
807 if (it == data.end())
808 {
809 std::cerr << "Configuration missing " << key << "\n";
810 throw std::invalid_argument("Key Missing");
811 }
812 BasicVariantType copy = it->second;
James Feist3eb82622019-02-08 13:10:22 -0800813 std::vector<std::string> config = std::get<std::vector<std::string>>(copy);
James Feistbc896df2018-11-26 16:28:17 -0800814 for (auto& str : config)
815 {
816 boost::replace_all(str, " ", "_");
817 }
818 resp = std::move(config);
819}
820
821void createSensor(sdbusplus::asio::object_server& objectServer,
James Feistb2eb3f52018-12-04 16:17:50 -0800822 std::shared_ptr<ExitAirTempSensor>& exitAirSensor,
James Feistbc896df2018-11-26 16:28:17 -0800823 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
824{
825 if (!dbusConnection)
826 {
827 std::cerr << "Connection not created\n";
828 return;
829 }
830 dbusConnection->async_method_call(
831 [&](boost::system::error_code ec, const ManagedObjectType& resp) {
832 if (ec)
833 {
834 std::cerr << "Error contacting entity manager\n";
835 return;
836 }
James Feist9a25ed42019-10-15 15:43:44 -0700837
838 cfmSensors.clear();
James Feistbc896df2018-11-26 16:28:17 -0800839 for (const auto& pathPair : resp)
840 {
841 for (const auto& entry : pathPair.second)
842 {
843 if (entry.first == exitAirIface)
844 {
James Feistbc896df2018-11-26 16:28:17 -0800845 // thresholds should be under the same path
846 std::vector<thresholds::Threshold> sensorThresholds;
847 parseThresholdsFromConfig(pathPair.second,
848 sensorThresholds);
James Feistbc896df2018-11-26 16:28:17 -0800849
James Feist523828e2019-03-04 14:38:37 -0800850 std::string name =
851 loadVariant<std::string>(entry.second, "Name");
852 exitAirSensor = std::make_shared<ExitAirTempSensor>(
853 dbusConnection, name, pathPair.first.str,
854 objectServer, std::move(sensorThresholds));
James Feistb2eb3f52018-12-04 16:17:50 -0800855 exitAirSensor->powerFactorMin =
856 loadVariant<double>(entry.second, "PowerFactorMin");
857 exitAirSensor->powerFactorMax =
858 loadVariant<double>(entry.second, "PowerFactorMax");
859 exitAirSensor->qMin =
860 loadVariant<double>(entry.second, "QMin");
861 exitAirSensor->qMax =
862 loadVariant<double>(entry.second, "QMax");
863 exitAirSensor->alphaS =
864 loadVariant<double>(entry.second, "AlphaS");
865 exitAirSensor->alphaF =
866 loadVariant<double>(entry.second, "AlphaF");
James Feistbc896df2018-11-26 16:28:17 -0800867 }
868 else if (entry.first == cfmIface)
869
870 {
James Feistb2eb3f52018-12-04 16:17:50 -0800871 // thresholds should be under the same path
872 std::vector<thresholds::Threshold> sensorThresholds;
873 parseThresholdsFromConfig(pathPair.second,
874 sensorThresholds);
875 std::string name =
876 loadVariant<std::string>(entry.second, "Name");
James Feist9a25ed42019-10-15 15:43:44 -0700877 auto sensor = std::make_shared<CFMSensor>(
James Feistb2eb3f52018-12-04 16:17:50 -0800878 dbusConnection, name, pathPair.first.str,
879 objectServer, std::move(sensorThresholds),
880 exitAirSensor);
881 loadVariantPathArray(entry.second, "Tachs",
882 sensor->tachs);
883 sensor->maxCFM =
884 loadVariant<double>(entry.second, "MaxCFM");
James Feistbc896df2018-11-26 16:28:17 -0800885
886 // change these into percent upon getting the data
James Feistb2eb3f52018-12-04 16:17:50 -0800887 sensor->c1 =
888 loadVariant<double>(entry.second, "C1") / 100;
889 sensor->c2 =
890 loadVariant<double>(entry.second, "C2") / 100;
891 sensor->tachMinPercent =
892 loadVariant<double>(entry.second,
893 "TachMinPercent") /
James Feistbc896df2018-11-26 16:28:17 -0800894 100;
James Feistb2eb3f52018-12-04 16:17:50 -0800895 sensor->tachMaxPercent =
896 loadVariant<double>(entry.second,
897 "TachMaxPercent") /
James Feistbc896df2018-11-26 16:28:17 -0800898 100;
James Feist13452092019-03-07 16:38:12 -0800899 sensor->createMaxCFMIface();
James Feist9a25ed42019-10-15 15:43:44 -0700900 sensor->setupMatches();
James Feistbc896df2018-11-26 16:28:17 -0800901
James Feistb2eb3f52018-12-04 16:17:50 -0800902 cfmSensors.emplace_back(std::move(sensor));
James Feistbc896df2018-11-26 16:28:17 -0800903 }
904 }
905 }
James Feistb2eb3f52018-12-04 16:17:50 -0800906 if (exitAirSensor)
James Feistbc896df2018-11-26 16:28:17 -0800907 {
James Feist9a25ed42019-10-15 15:43:44 -0700908 exitAirSensor->setupMatches();
James Feistb2eb3f52018-12-04 16:17:50 -0800909 exitAirSensor->updateReading();
James Feistbc896df2018-11-26 16:28:17 -0800910 }
911 },
912 entityManagerName, "/", "org.freedesktop.DBus.ObjectManager",
913 "GetManagedObjects");
914}
915
James Feistb6c0b912019-07-09 12:21:44 -0700916int main()
James Feistbc896df2018-11-26 16:28:17 -0800917{
918
919 boost::asio::io_service io;
920 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
921 systemBus->request_name("xyz.openbmc_project.ExitAirTempSensor");
922 sdbusplus::asio::object_server objectServer(systemBus);
923 std::shared_ptr<ExitAirTempSensor> sensor =
924 nullptr; // wait until we find the config
925 std::vector<std::unique_ptr<sdbusplus::bus::match::match>> matches;
926
927 io.post([&]() { createSensor(objectServer, sensor, systemBus); });
928
929 boost::asio::deadline_timer configTimer(io);
930
931 std::function<void(sdbusplus::message::message&)> eventHandler =
James Feistb6c0b912019-07-09 12:21:44 -0700932 [&](sdbusplus::message::message&) {
James Feistbc896df2018-11-26 16:28:17 -0800933 configTimer.expires_from_now(boost::posix_time::seconds(1));
934 // create a timer because normally multiple properties change
935 configTimer.async_wait([&](const boost::system::error_code& ec) {
936 if (ec == boost::asio::error::operation_aborted)
937 {
938 return; // we're being canceled
939 }
940 createSensor(objectServer, sensor, systemBus);
941 if (!sensor)
942 {
943 std::cout << "Configuration not detected\n";
944 }
945 });
946 };
947 constexpr const std::array<const char*, 2> monitorIfaces = {exitAirIface,
948 cfmIface};
949 for (const char* type : monitorIfaces)
950 {
951 auto match = std::make_unique<sdbusplus::bus::match::match>(
952 static_cast<sdbusplus::bus::bus&>(*systemBus),
953 "type='signal',member='PropertiesChanged',path_namespace='" +
954 std::string(inventoryPath) + "',arg0namespace='" + type + "'",
955 eventHandler);
956 matches.emplace_back(std::move(match));
957 }
958
959 io.run();
960}