blob: c66745721449d6336ffa3bab9f3271e1db3a0f76 [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 Feist655f3762020-10-05 15:28:15 -070064constexpr const std::array<const char*, 2> monitorIfaces = {exitAirIface,
65 cfmIface};
66
James Feist9a25ed42019-10-15 15:43:44 -070067static std::vector<std::shared_ptr<CFMSensor>> cfmSensors;
68
James Feistb2eb3f52018-12-04 16:17:50 -080069static void setupSensorMatch(
70 std::vector<sdbusplus::bus::match::match>& matches,
71 sdbusplus::bus::bus& connection, const std::string& type,
72 std::function<void(const double&, sdbusplus::message::message&)>&& callback)
73{
74
75 std::function<void(sdbusplus::message::message & message)> eventHandler =
76 [callback{std::move(callback)}](sdbusplus::message::message& message) {
77 std::string objectName;
James Feist3eb82622019-02-08 13:10:22 -080078 boost::container::flat_map<std::string,
79 std::variant<double, int64_t>>
James Feistb2eb3f52018-12-04 16:17:50 -080080 values;
81 message.read(objectName, values);
82 auto findValue = values.find("Value");
83 if (findValue == values.end())
84 {
85 return;
86 }
James Feist3eb82622019-02-08 13:10:22 -080087 double value =
88 std::visit(VariantToDoubleVisitor(), findValue->second);
James Feist9566bfa2019-01-29 15:31:23 -080089 if (std::isnan(value))
90 {
91 return;
92 }
93
James Feistb2eb3f52018-12-04 16:17:50 -080094 callback(value, message);
95 };
96 matches.emplace_back(connection,
97 "type='signal',"
98 "member='PropertiesChanged',interface='org."
99 "freedesktop.DBus.Properties',path_"
100 "namespace='/xyz/openbmc_project/sensors/" +
101 std::string(type) +
102 "',arg0='xyz.openbmc_project.Sensor.Value'",
103 std::move(eventHandler));
104}
105
James Feist13452092019-03-07 16:38:12 -0800106static void setMaxPWM(const std::shared_ptr<sdbusplus::asio::connection>& conn,
107 double value)
108{
109 using GetSubTreeType = std::vector<std::pair<
110 std::string,
111 std::vector<std::pair<std::string, std::vector<std::string>>>>>;
112
113 conn->async_method_call(
114 [conn, value](const boost::system::error_code ec,
115 const GetSubTreeType& ret) {
116 if (ec)
117 {
118 std::cerr << "Error calling mapper\n";
119 return;
120 }
121 for (const auto& [path, objDict] : ret)
122 {
123 if (objDict.empty())
124 {
125 return;
126 }
127 const std::string& owner = objDict.begin()->first;
128
129 conn->async_method_call(
130 [conn, value, owner,
131 path](const boost::system::error_code ec,
132 const std::variant<std::string>& classType) {
133 if (ec)
134 {
135 std::cerr << "Error getting pid class\n";
136 return;
137 }
138 auto classStr = std::get_if<std::string>(&classType);
139 if (classStr == nullptr || *classStr != "fan")
140 {
141 return;
142 }
143 conn->async_method_call(
144 [](boost::system::error_code& ec) {
145 if (ec)
146 {
147 std::cerr << "Error setting pid class\n";
148 return;
149 }
150 },
151 owner, path, "org.freedesktop.DBus.Properties",
152 "Set", pidConfigurationType, "OutLimitMax",
153 std::variant<double>(value));
154 },
155 owner, path, "org.freedesktop.DBus.Properties", "Get",
156 pidConfigurationType, "Class");
157 }
158 },
James Feista5e58722019-04-22 14:43:11 -0700159 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
160 0, std::array<std::string, 1>{pidConfigurationType});
James Feist13452092019-03-07 16:38:12 -0800161}
162
James Feistb2eb3f52018-12-04 16:17:50 -0800163CFMSensor::CFMSensor(std::shared_ptr<sdbusplus::asio::connection>& conn,
164 const std::string& sensorName,
165 const std::string& sensorConfiguration,
166 sdbusplus::asio::object_server& objectServer,
James Feistb839c052019-05-15 10:25:24 -0700167 std::vector<thresholds::Threshold>&& thresholdData,
James Feistb2eb3f52018-12-04 16:17:50 -0800168 std::shared_ptr<ExitAirTempSensor>& parent) :
169 Sensor(boost::replace_all_copy(sensorName, " ", "_"),
James Feist930fcde2019-05-28 12:58:43 -0700170 std::move(thresholdData), sensorConfiguration,
171 "xyz.openbmc_project.Configuration.ExitAirTemp", cfmMaxReading,
James Feiste3338522020-09-15 15:40:30 -0700172 cfmMinReading, conn, PowerState::on),
Brad Bishopfbb44ad2019-11-08 09:42:37 -0500173 std::enable_shared_from_this<CFMSensor>(), parent(parent),
James Feiste3338522020-09-15 15:40:30 -0700174 objServer(objectServer)
James Feistb2eb3f52018-12-04 16:17:50 -0800175{
176 sensorInterface =
177 objectServer.add_interface("/xyz/openbmc_project/sensors/cfm/" + name,
178 "xyz.openbmc_project.Sensor.Value");
179
180 if (thresholds::hasWarningInterface(thresholds))
181 {
182 thresholdInterfaceWarning = objectServer.add_interface(
183 "/xyz/openbmc_project/sensors/cfm/" + name,
184 "xyz.openbmc_project.Sensor.Threshold.Warning");
185 }
186 if (thresholds::hasCriticalInterface(thresholds))
187 {
188 thresholdInterfaceCritical = objectServer.add_interface(
189 "/xyz/openbmc_project/sensors/cfm/" + name,
190 "xyz.openbmc_project.Sensor.Threshold.Critical");
191 }
James Feist078f2322019-03-08 11:09:05 -0800192
193 association = objectServer.add_interface(
James Feist2adc95c2019-09-30 14:55:28 -0700194 "/xyz/openbmc_project/sensors/cfm/" + name, association::interface);
James Feist078f2322019-03-08 11:09:05 -0800195
James Feistb2eb3f52018-12-04 16:17:50 -0800196 setInitialProperties(conn);
James Feist9a25ed42019-10-15 15:43:44 -0700197
James Feist13452092019-03-07 16:38:12 -0800198 pwmLimitIface =
199 objectServer.add_interface("/xyz/openbmc_project/control/pwm_limit",
200 "xyz.openbmc_project.Control.PWMLimit");
201 cfmLimitIface =
202 objectServer.add_interface("/xyz/openbmc_project/control/MaxCFM",
203 "xyz.openbmc_project.Control.CFMLimit");
James Feist9a25ed42019-10-15 15:43:44 -0700204}
James Feist13452092019-03-07 16:38:12 -0800205
James Feist9a25ed42019-10-15 15:43:44 -0700206void CFMSensor::setupMatches()
207{
208
209 std::shared_ptr<CFMSensor> self = shared_from_this();
210 setupSensorMatch(matches, *dbusConnection, "fan_tach",
211 std::move([self](const double& value,
212 sdbusplus::message::message& message) {
213 self->tachReadings[message.get_path()] = value;
214 if (self->tachRanges.find(message.get_path()) ==
215 self->tachRanges.end())
216 {
217 // calls update reading after updating ranges
218 self->addTachRanges(message.get_sender(),
219 message.get_path());
220 }
221 else
222 {
223 self->updateReading();
224 }
225 }));
226
227 dbusConnection->async_method_call(
228 [self](const boost::system::error_code ec,
229 const std::variant<double> cfmVariant) {
James Feist13452092019-03-07 16:38:12 -0800230 uint64_t maxRpm = 100;
231 if (!ec)
232 {
233
234 auto cfm = std::get_if<double>(&cfmVariant);
Josh Lehanff336992020-02-26 11:13:19 -0800235 if (cfm != nullptr && *cfm >= minSystemCfm)
James Feist13452092019-03-07 16:38:12 -0800236 {
James Feist9a25ed42019-10-15 15:43:44 -0700237 maxRpm = self->getMaxRpm(*cfm);
James Feist13452092019-03-07 16:38:12 -0800238 }
239 }
James Feist9a25ed42019-10-15 15:43:44 -0700240 self->pwmLimitIface->register_property("Limit", maxRpm);
241 self->pwmLimitIface->initialize();
242 setMaxPWM(self->dbusConnection, maxRpm);
James Feist13452092019-03-07 16:38:12 -0800243 },
244 settingsDaemon, cfmSettingPath, "org.freedesktop.DBus.Properties",
245 "Get", cfmSettingIface, "Limit");
246
247 matches.emplace_back(
James Feist9a25ed42019-10-15 15:43:44 -0700248 *dbusConnection,
James Feist13452092019-03-07 16:38:12 -0800249 "type='signal',"
250 "member='PropertiesChanged',interface='org."
251 "freedesktop.DBus.Properties',path='" +
252 std::string(cfmSettingPath) + "',arg0='" +
253 std::string(cfmSettingIface) + "'",
James Feist9a25ed42019-10-15 15:43:44 -0700254 [self](sdbusplus::message::message& message) {
James Feist13452092019-03-07 16:38:12 -0800255 boost::container::flat_map<std::string, std::variant<double>>
256 values;
257 std::string objectName;
258 message.read(objectName, values);
259 const auto findValue = values.find("Limit");
260 if (findValue == values.end())
261 {
262 return;
263 }
264 const auto reading = std::get_if<double>(&(findValue->second));
265 if (reading == nullptr)
266 {
267 std::cerr << "Got CFM Limit of wrong type\n";
268 return;
269 }
270 if (*reading < minSystemCfm && *reading != 0)
271 {
272 std::cerr << "Illegal CFM setting detected\n";
273 return;
274 }
James Feist9a25ed42019-10-15 15:43:44 -0700275 uint64_t maxRpm = self->getMaxRpm(*reading);
276 self->pwmLimitIface->set_property("Limit", maxRpm);
277 setMaxPWM(self->dbusConnection, maxRpm);
James Feist13452092019-03-07 16:38:12 -0800278 });
James Feistb2eb3f52018-12-04 16:17:50 -0800279}
280
James Feist9566bfa2019-01-29 15:31:23 -0800281CFMSensor::~CFMSensor()
282{
283 objServer.remove_interface(thresholdInterfaceWarning);
284 objServer.remove_interface(thresholdInterfaceCritical);
285 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800286 objServer.remove_interface(association);
James Feist13452092019-03-07 16:38:12 -0800287 objServer.remove_interface(cfmLimitIface);
288 objServer.remove_interface(pwmLimitIface);
289}
290
291void CFMSensor::createMaxCFMIface(void)
292{
James Feistb6c0b912019-07-09 12:21:44 -0700293 cfmLimitIface->register_property("Limit", c2 * maxCFM * tachs.size());
James Feist13452092019-03-07 16:38:12 -0800294 cfmLimitIface->initialize();
James Feist9566bfa2019-01-29 15:31:23 -0800295}
296
James Feistb2eb3f52018-12-04 16:17:50 -0800297void CFMSensor::addTachRanges(const std::string& serviceName,
298 const std::string& path)
299{
James Feist9a25ed42019-10-15 15:43:44 -0700300 std::shared_ptr<CFMSensor> self = shared_from_this();
James Feistb2eb3f52018-12-04 16:17:50 -0800301 dbusConnection->async_method_call(
James Feist9a25ed42019-10-15 15:43:44 -0700302 [self, path](const boost::system::error_code ec,
James Feistb2eb3f52018-12-04 16:17:50 -0800303 const boost::container::flat_map<std::string,
304 BasicVariantType>& data) {
305 if (ec)
306 {
307 std::cerr << "Error getting properties from " << path << "\n";
James Feist1ccdb5e2019-01-24 09:44:01 -0800308 return;
James Feistb2eb3f52018-12-04 16:17:50 -0800309 }
310
311 double max = loadVariant<double>(data, "MaxValue");
312 double min = loadVariant<double>(data, "MinValue");
James Feist9a25ed42019-10-15 15:43:44 -0700313 self->tachRanges[path] = std::make_pair(min, max);
314 self->updateReading();
James Feistb2eb3f52018-12-04 16:17:50 -0800315 },
316 serviceName, path, "org.freedesktop.DBus.Properties", "GetAll",
317 "xyz.openbmc_project.Sensor.Value");
318}
319
320void CFMSensor::checkThresholds(void)
321{
322 thresholds::checkThresholds(this);
323}
324
325void CFMSensor::updateReading(void)
326{
327 double val = 0.0;
328 if (calculate(val))
329 {
330 if (value != val && parent)
331 {
332 parent->updateReading();
333 }
334 updateValue(val);
335 }
336 else
337 {
338 updateValue(std::numeric_limits<double>::quiet_NaN());
339 }
340}
341
James Feist13452092019-03-07 16:38:12 -0800342uint64_t CFMSensor::getMaxRpm(uint64_t cfmMaxSetting)
343{
344 uint64_t pwmPercent = 100;
345 double totalCFM = std::numeric_limits<double>::max();
346 if (cfmMaxSetting == 0)
347 {
348 return pwmPercent;
349 }
350
James Feist52427952019-04-05 14:23:35 -0700351 bool firstLoop = true;
James Feist13452092019-03-07 16:38:12 -0800352 while (totalCFM > cfmMaxSetting)
353 {
James Feist52427952019-04-05 14:23:35 -0700354 if (firstLoop)
355 {
356 firstLoop = false;
357 }
358 else
359 {
360 pwmPercent--;
361 }
362
James Feist13452092019-03-07 16:38:12 -0800363 double ci = 0;
364 if (pwmPercent == 0)
365 {
366 ci = 0;
367 }
368 else if (pwmPercent < tachMinPercent)
369 {
370 ci = c1;
371 }
372 else if (pwmPercent > tachMaxPercent)
373 {
374 ci = c2;
375 }
376 else
377 {
378 ci = c1 + (((c2 - c1) * (pwmPercent - tachMinPercent)) /
379 (tachMaxPercent - tachMinPercent));
380 }
381
382 // Now calculate the CFM for this tach
383 // CFMi = Ci * Qmaxi * TACHi
384 totalCFM = ci * maxCFM * pwmPercent;
385 totalCFM *= tachs.size();
386 // divide by 100 since pwm is in percent
387 totalCFM /= 100;
388
James Feist13452092019-03-07 16:38:12 -0800389 if (pwmPercent <= 0)
390 {
391 break;
392 }
393 }
James Feist52427952019-04-05 14:23:35 -0700394
James Feist13452092019-03-07 16:38:12 -0800395 return pwmPercent;
396}
397
James Feistb2eb3f52018-12-04 16:17:50 -0800398bool CFMSensor::calculate(double& value)
399{
400 double totalCFM = 0;
401 for (const std::string& tachName : tachs)
402 {
James Feist9566bfa2019-01-29 15:31:23 -0800403
James Feistb2eb3f52018-12-04 16:17:50 -0800404 auto findReading = std::find_if(
405 tachReadings.begin(), tachReadings.end(), [&](const auto& item) {
406 return boost::ends_with(item.first, tachName);
407 });
408 auto findRange = std::find_if(
409 tachRanges.begin(), tachRanges.end(), [&](const auto& item) {
410 return boost::ends_with(item.first, tachName);
411 });
412 if (findReading == tachReadings.end())
413 {
James Feista5e58722019-04-22 14:43:11 -0700414 if constexpr (DEBUG)
James Feista96329f2019-01-24 10:08:27 -0800415 {
416 std::cerr << "Can't find " << tachName << "in readings\n";
417 }
James Feist9566bfa2019-01-29 15:31:23 -0800418 continue; // haven't gotten a reading
James Feistb2eb3f52018-12-04 16:17:50 -0800419 }
420
421 if (findRange == tachRanges.end())
422 {
James Feist523828e2019-03-04 14:38:37 -0800423 std::cerr << "Can't find " << tachName << " in ranges\n";
James Feistb2eb3f52018-12-04 16:17:50 -0800424 return false; // haven't gotten a max / min
425 }
426
427 // avoid divide by 0
428 if (findRange->second.second == 0)
429 {
430 std::cerr << "Tach Max Set to 0 " << tachName << "\n";
431 return false;
432 }
433
434 double rpm = findReading->second;
435
436 // for now assume the min for a fan is always 0, divide by max to get
437 // percent and mult by 100
438 rpm /= findRange->second.second;
439 rpm *= 100;
440
441 if constexpr (DEBUG)
442 {
443 std::cout << "Tach " << tachName << "at " << rpm << "\n";
444 }
445
446 // Do a linear interpolation to get Ci
447 // Ci = C1 + (C2 - C1)/(RPM2 - RPM1) * (TACHi - TACH1)
448
449 double ci = 0;
450 if (rpm == 0)
451 {
452 ci = 0;
453 }
454 else if (rpm < tachMinPercent)
455 {
456 ci = c1;
457 }
458 else if (rpm > tachMaxPercent)
459 {
460 ci = c2;
461 }
462 else
463 {
464 ci = c1 + (((c2 - c1) * (rpm - tachMinPercent)) /
465 (tachMaxPercent - tachMinPercent));
466 }
467
468 // Now calculate the CFM for this tach
469 // CFMi = Ci * Qmaxi * TACHi
470 totalCFM += ci * maxCFM * rpm;
James Feista5e58722019-04-22 14:43:11 -0700471 if constexpr (DEBUG)
472 {
473 std::cerr << "totalCFM = " << totalCFM << "\n";
474 std::cerr << "Ci " << ci << " MaxCFM " << maxCFM << " rpm " << rpm
475 << "\n";
476 std::cerr << "c1 " << c1 << " c2 " << c2 << " max "
477 << tachMaxPercent << " min " << tachMinPercent << "\n";
478 }
James Feistb2eb3f52018-12-04 16:17:50 -0800479 }
480
481 // divide by 100 since rpm is in percent
482 value = totalCFM / 100;
James Feista5e58722019-04-22 14:43:11 -0700483 if constexpr (DEBUG)
484 {
485 std::cerr << "cfm value = " << value << "\n";
486 }
James Feist9566bfa2019-01-29 15:31:23 -0800487 return true;
James Feistb2eb3f52018-12-04 16:17:50 -0800488}
489
490static constexpr double exitAirMaxReading = 127;
491static constexpr double exitAirMinReading = -128;
James Feistbc896df2018-11-26 16:28:17 -0800492ExitAirTempSensor::ExitAirTempSensor(
493 std::shared_ptr<sdbusplus::asio::connection>& conn,
James Feistb2eb3f52018-12-04 16:17:50 -0800494 const std::string& sensorName, const std::string& sensorConfiguration,
James Feistbc896df2018-11-26 16:28:17 -0800495 sdbusplus::asio::object_server& objectServer,
James Feistb839c052019-05-15 10:25:24 -0700496 std::vector<thresholds::Threshold>&& thresholdData) :
James Feistb2eb3f52018-12-04 16:17:50 -0800497 Sensor(boost::replace_all_copy(sensorName, " ", "_"),
James Feist930fcde2019-05-28 12:58:43 -0700498 std::move(thresholdData), sensorConfiguration,
499 "xyz.openbmc_project.Configuration.ExitAirTemp", exitAirMaxReading,
James Feiste3338522020-09-15 15:40:30 -0700500 exitAirMinReading, conn, PowerState::on),
501 std::enable_shared_from_this<ExitAirTempSensor>(), objServer(objectServer)
James Feistbc896df2018-11-26 16:28:17 -0800502{
503 sensorInterface = objectServer.add_interface(
504 "/xyz/openbmc_project/sensors/temperature/" + name,
505 "xyz.openbmc_project.Sensor.Value");
506
507 if (thresholds::hasWarningInterface(thresholds))
508 {
509 thresholdInterfaceWarning = objectServer.add_interface(
510 "/xyz/openbmc_project/sensors/temperature/" + name,
511 "xyz.openbmc_project.Sensor.Threshold.Warning");
512 }
513 if (thresholds::hasCriticalInterface(thresholds))
514 {
515 thresholdInterfaceCritical = objectServer.add_interface(
516 "/xyz/openbmc_project/sensors/temperature/" + name,
517 "xyz.openbmc_project.Sensor.Threshold.Critical");
518 }
James Feist078f2322019-03-08 11:09:05 -0800519 association = objectServer.add_interface(
520 "/xyz/openbmc_project/sensors/temperature/" + name,
James Feist2adc95c2019-09-30 14:55:28 -0700521 association::interface);
James Feistbc896df2018-11-26 16:28:17 -0800522 setInitialProperties(conn);
James Feistbc896df2018-11-26 16:28:17 -0800523}
524
525ExitAirTempSensor::~ExitAirTempSensor()
526{
James Feist523828e2019-03-04 14:38:37 -0800527 objServer.remove_interface(thresholdInterfaceWarning);
528 objServer.remove_interface(thresholdInterfaceCritical);
529 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800530 objServer.remove_interface(association);
James Feistbc896df2018-11-26 16:28:17 -0800531}
532
533void ExitAirTempSensor::setupMatches(void)
534{
James Feistb2eb3f52018-12-04 16:17:50 -0800535 constexpr const std::array<const char*, 2> matchTypes = {
536 "power", inletTemperatureSensor};
James Feistbc896df2018-11-26 16:28:17 -0800537
James Feist9a25ed42019-10-15 15:43:44 -0700538 std::shared_ptr<ExitAirTempSensor> self = shared_from_this();
James Feistb2eb3f52018-12-04 16:17:50 -0800539 for (const std::string& type : matchTypes)
James Feistbc896df2018-11-26 16:28:17 -0800540 {
James Feistb2eb3f52018-12-04 16:17:50 -0800541 setupSensorMatch(matches, *dbusConnection, type,
James Feist9a25ed42019-10-15 15:43:44 -0700542 [self, type](const double& value,
James Feistb2eb3f52018-12-04 16:17:50 -0800543 sdbusplus::message::message& message) {
544 if (type == "power")
545 {
James Feista5e58722019-04-22 14:43:11 -0700546 std::string path = message.get_path();
547 if (path.find("PS") != std::string::npos &&
548 boost::ends_with(path, "Input_Power"))
549 {
James Feist9a25ed42019-10-15 15:43:44 -0700550 self->powerReadings[message.get_path()] =
551 value;
James Feista5e58722019-04-22 14:43:11 -0700552 }
James Feistb2eb3f52018-12-04 16:17:50 -0800553 }
554 else if (type == inletTemperatureSensor)
555 {
James Feist9a25ed42019-10-15 15:43:44 -0700556 self->inletTemp = value;
James Feistb2eb3f52018-12-04 16:17:50 -0800557 }
James Feist9a25ed42019-10-15 15:43:44 -0700558 self->updateReading();
James Feistb2eb3f52018-12-04 16:17:50 -0800559 });
James Feistbc896df2018-11-26 16:28:17 -0800560 }
James Feist9566bfa2019-01-29 15:31:23 -0800561 dbusConnection->async_method_call(
James Feist9a25ed42019-10-15 15:43:44 -0700562 [self](boost::system::error_code ec,
James Feist9566bfa2019-01-29 15:31:23 -0800563 const std::variant<double>& value) {
564 if (ec)
565 {
566 // sensor not ready yet
567 return;
568 }
569
James Feist9a25ed42019-10-15 15:43:44 -0700570 self->inletTemp = std::visit(VariantToDoubleVisitor(), value);
James Feist9566bfa2019-01-29 15:31:23 -0800571 },
572 "xyz.openbmc_project.HwmonTempSensor",
573 std::string("/xyz/openbmc_project/sensors/") + inletTemperatureSensor,
James Feista5e58722019-04-22 14:43:11 -0700574 properties::interface, properties::get, sensorValueInterface, "Value");
575 dbusConnection->async_method_call(
James Feist9a25ed42019-10-15 15:43:44 -0700576 [self](boost::system::error_code ec, const GetSubTreeType& subtree) {
James Feista5e58722019-04-22 14:43:11 -0700577 if (ec)
578 {
579 std::cerr << "Error contacting mapper\n";
580 return;
581 }
582 for (const auto& item : subtree)
583 {
584 size_t lastSlash = item.first.rfind("/");
585 if (lastSlash == std::string::npos ||
586 lastSlash == item.first.size() || !item.second.size())
587 {
588 continue;
589 }
590 std::string sensorName = item.first.substr(lastSlash + 1);
591 if (boost::starts_with(sensorName, "PS") &&
592 boost::ends_with(sensorName, "Input_Power"))
593 {
594 const std::string& path = item.first;
James Feist9a25ed42019-10-15 15:43:44 -0700595 self->dbusConnection->async_method_call(
596 [self, path](boost::system::error_code ec,
James Feista5e58722019-04-22 14:43:11 -0700597 const std::variant<double>& value) {
598 if (ec)
599 {
600 std::cerr << "Error getting value from " << path
601 << "\n";
602 }
603
604 double reading =
605 std::visit(VariantToDoubleVisitor(), value);
606 if constexpr (DEBUG)
607 {
608 std::cerr << path << "Reading " << reading
609 << "\n";
610 }
James Feist9a25ed42019-10-15 15:43:44 -0700611 self->powerReadings[path] = reading;
James Feista5e58722019-04-22 14:43:11 -0700612 },
613 item.second[0].first, item.first, properties::interface,
614 properties::get, sensorValueInterface, "Value");
615 }
616 }
617 },
618 mapper::busName, mapper::path, mapper::interface, mapper::subtree,
619 "/xyz/openbmc_project/sensors/power", 0,
620 std::array<const char*, 1>{sensorValueInterface});
James Feistbc896df2018-11-26 16:28:17 -0800621}
622
623void ExitAirTempSensor::updateReading(void)
624{
625
626 double val = 0.0;
627 if (calculate(val))
628 {
James Feist18af4232019-03-13 11:14:00 -0700629 val = std::floor(val + 0.5);
James Feistbc896df2018-11-26 16:28:17 -0800630 updateValue(val);
631 }
632 else
633 {
634 updateValue(std::numeric_limits<double>::quiet_NaN());
635 }
636}
637
James Feistb2eb3f52018-12-04 16:17:50 -0800638double ExitAirTempSensor::getTotalCFM(void)
James Feistbc896df2018-11-26 16:28:17 -0800639{
James Feistb2eb3f52018-12-04 16:17:50 -0800640 double sum = 0;
641 for (auto& sensor : cfmSensors)
James Feistbc896df2018-11-26 16:28:17 -0800642 {
James Feistb2eb3f52018-12-04 16:17:50 -0800643 double reading = 0;
644 if (!sensor->calculate(reading))
James Feistbc896df2018-11-26 16:28:17 -0800645 {
James Feistbc896df2018-11-26 16:28:17 -0800646 return -1;
647 }
James Feistb2eb3f52018-12-04 16:17:50 -0800648 sum += reading;
James Feistbc896df2018-11-26 16:28:17 -0800649 }
James Feistb2eb3f52018-12-04 16:17:50 -0800650
651 return sum;
James Feistbc896df2018-11-26 16:28:17 -0800652}
653
654bool ExitAirTempSensor::calculate(double& val)
655{
James Feistae11cfc2019-05-07 15:01:20 -0700656 constexpr size_t maxErrorPrint = 1;
James Feistbc896df2018-11-26 16:28:17 -0800657 static bool firstRead = false;
James Feistae11cfc2019-05-07 15:01:20 -0700658 static size_t errorPrint = maxErrorPrint;
659
James Feistbc896df2018-11-26 16:28:17 -0800660 double cfm = getTotalCFM();
661 if (cfm <= 0)
662 {
663 std::cerr << "Error getting cfm\n";
664 return false;
665 }
666
667 // if there is an error getting inlet temp, return error
668 if (std::isnan(inletTemp))
669 {
James Feistae11cfc2019-05-07 15:01:20 -0700670 if (errorPrint > 0)
671 {
672 errorPrint--;
673 std::cerr << "Cannot get inlet temp\n";
674 }
James Feistbc896df2018-11-26 16:28:17 -0800675 val = 0;
676 return false;
677 }
678
679 // if fans are off, just make the exit temp equal to inlet
James Feist71d31b22019-01-02 16:57:54 -0800680 if (!isPowerOn())
James Feistbc896df2018-11-26 16:28:17 -0800681 {
682 val = inletTemp;
683 return true;
684 }
685
686 double totalPower = 0;
687 for (const auto& reading : powerReadings)
688 {
689 if (std::isnan(reading.second))
690 {
691 continue;
692 }
693 totalPower += reading.second;
694 }
695
696 // Calculate power correction factor
697 // Ci = CL + (CH - CL)/(QMax - QMin) * (CFM - QMin)
698 float powerFactor = 0.0;
699 if (cfm <= qMin)
700 {
701 powerFactor = powerFactorMin;
702 }
703 else if (cfm >= qMax)
704 {
705 powerFactor = powerFactorMax;
706 }
707 else
708 {
709 powerFactor = powerFactorMin + ((powerFactorMax - powerFactorMin) /
710 (qMax - qMin) * (cfm - qMin));
711 }
712
James Feistb6c0b912019-07-09 12:21:44 -0700713 totalPower *= static_cast<double>(powerFactor);
James Feistbc896df2018-11-26 16:28:17 -0800714 totalPower += pOffset;
715
716 if (totalPower == 0)
717 {
James Feistae11cfc2019-05-07 15:01:20 -0700718 if (errorPrint > 0)
719 {
720 errorPrint--;
721 std::cerr << "total power 0\n";
722 }
James Feistbc896df2018-11-26 16:28:17 -0800723 val = 0;
724 return false;
725 }
726
727 if constexpr (DEBUG)
728 {
729 std::cout << "Power Factor " << powerFactor << "\n";
730 std::cout << "Inlet Temp " << inletTemp << "\n";
731 std::cout << "Total Power" << totalPower << "\n";
732 }
733
734 // Calculate the exit air temp
735 // Texit = Tfp + (1.76 * TotalPower / CFM * Faltitude)
James Feistb6c0b912019-07-09 12:21:44 -0700736 double reading = 1.76 * totalPower * static_cast<double>(altitudeFactor);
James Feistbc896df2018-11-26 16:28:17 -0800737 reading /= cfm;
738 reading += inletTemp;
739
740 if constexpr (DEBUG)
741 {
742 std::cout << "Reading 1: " << reading << "\n";
743 }
744
745 // Now perform the exponential average
746 // Calculate alpha based on SDR values and CFM
747 // Ai = As + (Af - As)/(QMax - QMin) * (CFM - QMin)
748
749 double alpha = 0.0;
750 if (cfm < qMin)
751 {
752 alpha = alphaS;
753 }
754 else if (cfm >= qMax)
755 {
756 alpha = alphaF;
757 }
758 else
759 {
760 alpha = alphaS + ((alphaF - alphaS) * (cfm - qMin) / (qMax - qMin));
761 }
762
763 auto time = std::chrono::system_clock::now();
764 if (!firstRead)
765 {
766 firstRead = true;
767 lastTime = time;
768 lastReading = reading;
769 }
770 double alphaDT =
771 std::chrono::duration_cast<std::chrono::seconds>(time - lastTime)
772 .count() *
773 alpha;
774
775 // cap at 1.0 or the below fails
776 if (alphaDT > 1.0)
777 {
778 alphaDT = 1.0;
779 }
780
781 if constexpr (DEBUG)
782 {
783 std::cout << "AlphaDT: " << alphaDT << "\n";
784 }
785
786 reading = ((reading * alphaDT) + (lastReading * (1.0 - alphaDT)));
787
788 if constexpr (DEBUG)
789 {
790 std::cout << "Reading 2: " << reading << "\n";
791 }
792
793 val = reading;
794 lastReading = reading;
795 lastTime = time;
James Feistae11cfc2019-05-07 15:01:20 -0700796 errorPrint = maxErrorPrint;
James Feistbc896df2018-11-26 16:28:17 -0800797 return true;
798}
799
800void ExitAirTempSensor::checkThresholds(void)
801{
802 thresholds::checkThresholds(this);
803}
804
James Feistbc896df2018-11-26 16:28:17 -0800805static void loadVariantPathArray(
806 const boost::container::flat_map<std::string, BasicVariantType>& data,
807 const std::string& key, std::vector<std::string>& resp)
808{
809 auto it = data.find(key);
810 if (it == data.end())
811 {
812 std::cerr << "Configuration missing " << key << "\n";
813 throw std::invalid_argument("Key Missing");
814 }
815 BasicVariantType copy = it->second;
James Feist3eb82622019-02-08 13:10:22 -0800816 std::vector<std::string> config = std::get<std::vector<std::string>>(copy);
James Feistbc896df2018-11-26 16:28:17 -0800817 for (auto& str : config)
818 {
819 boost::replace_all(str, " ", "_");
820 }
821 resp = std::move(config);
822}
823
824void createSensor(sdbusplus::asio::object_server& objectServer,
James Feistb2eb3f52018-12-04 16:17:50 -0800825 std::shared_ptr<ExitAirTempSensor>& exitAirSensor,
James Feistbc896df2018-11-26 16:28:17 -0800826 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
827{
828 if (!dbusConnection)
829 {
830 std::cerr << "Connection not created\n";
831 return;
832 }
James Feist655f3762020-10-05 15:28:15 -0700833 auto getter = std::make_shared<GetSensorConfiguration>(
834 dbusConnection,
835 std::move([&objectServer, &dbusConnection,
836 &exitAirSensor](const ManagedObjectType& resp) {
James Feist9a25ed42019-10-15 15:43:44 -0700837 cfmSensors.clear();
James Feistbc896df2018-11-26 16:28:17 -0800838 for (const auto& pathPair : resp)
839 {
840 for (const auto& entry : pathPair.second)
841 {
842 if (entry.first == exitAirIface)
843 {
James Feistbc896df2018-11-26 16:28:17 -0800844 // thresholds should be under the same path
845 std::vector<thresholds::Threshold> sensorThresholds;
846 parseThresholdsFromConfig(pathPair.second,
847 sensorThresholds);
James Feistbc896df2018-11-26 16:28:17 -0800848
James Feist523828e2019-03-04 14:38:37 -0800849 std::string name =
850 loadVariant<std::string>(entry.second, "Name");
851 exitAirSensor = std::make_shared<ExitAirTempSensor>(
852 dbusConnection, name, pathPair.first.str,
853 objectServer, std::move(sensorThresholds));
James Feistb2eb3f52018-12-04 16:17:50 -0800854 exitAirSensor->powerFactorMin =
855 loadVariant<double>(entry.second, "PowerFactorMin");
856 exitAirSensor->powerFactorMax =
857 loadVariant<double>(entry.second, "PowerFactorMax");
858 exitAirSensor->qMin =
859 loadVariant<double>(entry.second, "QMin");
860 exitAirSensor->qMax =
861 loadVariant<double>(entry.second, "QMax");
862 exitAirSensor->alphaS =
863 loadVariant<double>(entry.second, "AlphaS");
864 exitAirSensor->alphaF =
865 loadVariant<double>(entry.second, "AlphaF");
James Feistbc896df2018-11-26 16:28:17 -0800866 }
867 else if (entry.first == cfmIface)
868
869 {
James Feistb2eb3f52018-12-04 16:17:50 -0800870 // thresholds should be under the same path
871 std::vector<thresholds::Threshold> sensorThresholds;
872 parseThresholdsFromConfig(pathPair.second,
873 sensorThresholds);
874 std::string name =
875 loadVariant<std::string>(entry.second, "Name");
James Feist9a25ed42019-10-15 15:43:44 -0700876 auto sensor = std::make_shared<CFMSensor>(
James Feistb2eb3f52018-12-04 16:17:50 -0800877 dbusConnection, name, pathPair.first.str,
878 objectServer, std::move(sensorThresholds),
879 exitAirSensor);
880 loadVariantPathArray(entry.second, "Tachs",
881 sensor->tachs);
882 sensor->maxCFM =
883 loadVariant<double>(entry.second, "MaxCFM");
James Feistbc896df2018-11-26 16:28:17 -0800884
885 // change these into percent upon getting the data
James Feistb2eb3f52018-12-04 16:17:50 -0800886 sensor->c1 =
887 loadVariant<double>(entry.second, "C1") / 100;
888 sensor->c2 =
889 loadVariant<double>(entry.second, "C2") / 100;
890 sensor->tachMinPercent =
891 loadVariant<double>(entry.second,
892 "TachMinPercent") /
James Feistbc896df2018-11-26 16:28:17 -0800893 100;
James Feistb2eb3f52018-12-04 16:17:50 -0800894 sensor->tachMaxPercent =
895 loadVariant<double>(entry.second,
896 "TachMaxPercent") /
James Feistbc896df2018-11-26 16:28:17 -0800897 100;
James Feist13452092019-03-07 16:38:12 -0800898 sensor->createMaxCFMIface();
James Feist9a25ed42019-10-15 15:43:44 -0700899 sensor->setupMatches();
James Feistbc896df2018-11-26 16:28:17 -0800900
James Feistb2eb3f52018-12-04 16:17:50 -0800901 cfmSensors.emplace_back(std::move(sensor));
James Feistbc896df2018-11-26 16:28:17 -0800902 }
903 }
904 }
James Feistb2eb3f52018-12-04 16:17:50 -0800905 if (exitAirSensor)
James Feistbc896df2018-11-26 16:28:17 -0800906 {
James Feist9a25ed42019-10-15 15:43:44 -0700907 exitAirSensor->setupMatches();
James Feistb2eb3f52018-12-04 16:17:50 -0800908 exitAirSensor->updateReading();
James Feistbc896df2018-11-26 16:28:17 -0800909 }
James Feist655f3762020-10-05 15:28:15 -0700910 }));
911 getter->getConfiguration(
912 std::vector<std::string>(monitorIfaces.begin(), monitorIfaces.end()));
James Feistbc896df2018-11-26 16:28:17 -0800913}
914
James Feistb6c0b912019-07-09 12:21:44 -0700915int main()
James Feistbc896df2018-11-26 16:28:17 -0800916{
917
918 boost::asio::io_service io;
919 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
920 systemBus->request_name("xyz.openbmc_project.ExitAirTempSensor");
921 sdbusplus::asio::object_server objectServer(systemBus);
922 std::shared_ptr<ExitAirTempSensor> sensor =
923 nullptr; // wait until we find the config
924 std::vector<std::unique_ptr<sdbusplus::bus::match::match>> matches;
925
926 io.post([&]() { createSensor(objectServer, sensor, systemBus); });
927
928 boost::asio::deadline_timer configTimer(io);
929
930 std::function<void(sdbusplus::message::message&)> eventHandler =
James Feistb6c0b912019-07-09 12:21:44 -0700931 [&](sdbusplus::message::message&) {
James Feistbc896df2018-11-26 16:28:17 -0800932 configTimer.expires_from_now(boost::posix_time::seconds(1));
933 // create a timer because normally multiple properties change
934 configTimer.async_wait([&](const boost::system::error_code& ec) {
935 if (ec == boost::asio::error::operation_aborted)
936 {
937 return; // we're being canceled
938 }
939 createSensor(objectServer, sensor, systemBus);
940 if (!sensor)
941 {
942 std::cout << "Configuration not detected\n";
943 }
944 });
945 };
James Feistbc896df2018-11-26 16:28:17 -0800946 for (const char* type : monitorIfaces)
947 {
948 auto match = std::make_unique<sdbusplus::bus::match::match>(
949 static_cast<sdbusplus::bus::bus&>(*systemBus),
950 "type='signal',member='PropertiesChanged',path_namespace='" +
951 std::string(inventoryPath) + "',arg0namespace='" + type + "'",
952 eventHandler);
953 matches.emplace_back(std::move(match));
954 }
955
956 io.run();
957}