blob: 972b42b2f1e87c2f1b0b290839662a5b9999375a [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>
26#include <chrono>
27#include <iostream>
28#include <limits>
29#include <numeric>
30#include <sdbusplus/asio/connection.hpp>
31#include <sdbusplus/asio/object_server.hpp>
32#include <vector>
33
34constexpr const float altitudeFactor = 1.14;
35constexpr const char* exitAirIface =
36 "xyz.openbmc_project.Configuration.ExitAirTempSensor";
37constexpr const char* cfmIface = "xyz.openbmc_project.Configuration.CFMSensor";
38
39// todo: this *might* need to be configurable
40constexpr const char* inletTemperatureSensor = "temperature/Front_Panel_Temp";
James Feist13452092019-03-07 16:38:12 -080041constexpr const char* pidConfigurationType =
42 "xyz.openbmc_project.Configuration.Pid";
43constexpr const char* settingsDaemon = "xyz.openbmc_project.Settings";
44constexpr const char* cfmSettingPath = "/xyz/openbmc_project/control/cfm_limit";
45constexpr const char* cfmSettingIface = "xyz.openbmc_project.Control.CFMLimit";
James Feistbc896df2018-11-26 16:28:17 -080046
47static constexpr bool DEBUG = false;
48
James Feistb2eb3f52018-12-04 16:17:50 -080049static constexpr double cfmMaxReading = 255;
50static constexpr double cfmMinReading = 0;
51
James Feist13452092019-03-07 16:38:12 -080052static constexpr size_t minSystemCfm = 50;
53
James Feistb2eb3f52018-12-04 16:17:50 -080054static void setupSensorMatch(
55 std::vector<sdbusplus::bus::match::match>& matches,
56 sdbusplus::bus::bus& connection, const std::string& type,
57 std::function<void(const double&, sdbusplus::message::message&)>&& callback)
58{
59
60 std::function<void(sdbusplus::message::message & message)> eventHandler =
61 [callback{std::move(callback)}](sdbusplus::message::message& message) {
62 std::string objectName;
James Feist3eb82622019-02-08 13:10:22 -080063 boost::container::flat_map<std::string,
64 std::variant<double, int64_t>>
James Feistb2eb3f52018-12-04 16:17:50 -080065 values;
66 message.read(objectName, values);
67 auto findValue = values.find("Value");
68 if (findValue == values.end())
69 {
70 return;
71 }
James Feist3eb82622019-02-08 13:10:22 -080072 double value =
73 std::visit(VariantToDoubleVisitor(), findValue->second);
James Feist9566bfa2019-01-29 15:31:23 -080074 if (std::isnan(value))
75 {
76 return;
77 }
78
James Feistb2eb3f52018-12-04 16:17:50 -080079 callback(value, message);
80 };
81 matches.emplace_back(connection,
82 "type='signal',"
83 "member='PropertiesChanged',interface='org."
84 "freedesktop.DBus.Properties',path_"
85 "namespace='/xyz/openbmc_project/sensors/" +
86 std::string(type) +
87 "',arg0='xyz.openbmc_project.Sensor.Value'",
88 std::move(eventHandler));
89}
90
James Feist13452092019-03-07 16:38:12 -080091static void setMaxPWM(const std::shared_ptr<sdbusplus::asio::connection>& conn,
92 double value)
93{
94 using GetSubTreeType = std::vector<std::pair<
95 std::string,
96 std::vector<std::pair<std::string, std::vector<std::string>>>>>;
97
98 conn->async_method_call(
99 [conn, value](const boost::system::error_code ec,
100 const GetSubTreeType& ret) {
101 if (ec)
102 {
103 std::cerr << "Error calling mapper\n";
104 return;
105 }
106 for (const auto& [path, objDict] : ret)
107 {
108 if (objDict.empty())
109 {
110 return;
111 }
112 const std::string& owner = objDict.begin()->first;
113
114 conn->async_method_call(
115 [conn, value, owner,
116 path](const boost::system::error_code ec,
117 const std::variant<std::string>& classType) {
118 if (ec)
119 {
120 std::cerr << "Error getting pid class\n";
121 return;
122 }
123 auto classStr = std::get_if<std::string>(&classType);
124 if (classStr == nullptr || *classStr != "fan")
125 {
126 return;
127 }
128 conn->async_method_call(
129 [](boost::system::error_code& ec) {
130 if (ec)
131 {
132 std::cerr << "Error setting pid class\n";
133 return;
134 }
135 },
136 owner, path, "org.freedesktop.DBus.Properties",
137 "Set", pidConfigurationType, "OutLimitMax",
138 std::variant<double>(value));
139 },
140 owner, path, "org.freedesktop.DBus.Properties", "Get",
141 pidConfigurationType, "Class");
142 }
143 },
James Feista5e58722019-04-22 14:43:11 -0700144 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
145 0, std::array<std::string, 1>{pidConfigurationType});
James Feist13452092019-03-07 16:38:12 -0800146}
147
James Feistb2eb3f52018-12-04 16:17:50 -0800148CFMSensor::CFMSensor(std::shared_ptr<sdbusplus::asio::connection>& conn,
149 const std::string& sensorName,
150 const std::string& sensorConfiguration,
151 sdbusplus::asio::object_server& objectServer,
James Feistb839c052019-05-15 10:25:24 -0700152 std::vector<thresholds::Threshold>&& thresholdData,
James Feistb2eb3f52018-12-04 16:17:50 -0800153 std::shared_ptr<ExitAirTempSensor>& parent) :
154 Sensor(boost::replace_all_copy(sensorName, " ", "_"),
James Feist930fcde2019-05-28 12:58:43 -0700155 std::move(thresholdData), sensorConfiguration,
156 "xyz.openbmc_project.Configuration.ExitAirTemp", cfmMaxReading,
157 cfmMinReading),
James Feist9566bfa2019-01-29 15:31:23 -0800158 dbusConnection(conn), parent(parent), objServer(objectServer)
James Feistb2eb3f52018-12-04 16:17:50 -0800159{
160 sensorInterface =
161 objectServer.add_interface("/xyz/openbmc_project/sensors/cfm/" + name,
162 "xyz.openbmc_project.Sensor.Value");
163
164 if (thresholds::hasWarningInterface(thresholds))
165 {
166 thresholdInterfaceWarning = objectServer.add_interface(
167 "/xyz/openbmc_project/sensors/cfm/" + name,
168 "xyz.openbmc_project.Sensor.Threshold.Warning");
169 }
170 if (thresholds::hasCriticalInterface(thresholds))
171 {
172 thresholdInterfaceCritical = objectServer.add_interface(
173 "/xyz/openbmc_project/sensors/cfm/" + name,
174 "xyz.openbmc_project.Sensor.Threshold.Critical");
175 }
James Feist078f2322019-03-08 11:09:05 -0800176
177 association = objectServer.add_interface(
James Feista5e58722019-04-22 14:43:11 -0700178 "/xyz/openbmc_project/sensors/cfm/" + name, "org.openbmc.Associations");
James Feist078f2322019-03-08 11:09:05 -0800179
James Feistb2eb3f52018-12-04 16:17:50 -0800180 setInitialProperties(conn);
181 setupSensorMatch(
182 matches, *dbusConnection, "fan_tach",
183 std::move(
184 [this](const double& value, sdbusplus::message::message& message) {
185 tachReadings[message.get_path()] = value;
186 if (tachRanges.find(message.get_path()) == tachRanges.end())
187 {
188 // calls update reading after updating ranges
189 addTachRanges(message.get_sender(), message.get_path());
190 }
191 else
192 {
193 updateReading();
194 }
195 }));
James Feist13452092019-03-07 16:38:12 -0800196 pwmLimitIface =
197 objectServer.add_interface("/xyz/openbmc_project/control/pwm_limit",
198 "xyz.openbmc_project.Control.PWMLimit");
199 cfmLimitIface =
200 objectServer.add_interface("/xyz/openbmc_project/control/MaxCFM",
201 "xyz.openbmc_project.Control.CFMLimit");
202
203 conn->async_method_call(
204 [this, conn](const boost::system::error_code ec,
205 const std::variant<double> cfmVariant) {
206 uint64_t maxRpm = 100;
207 if (!ec)
208 {
209
210 auto cfm = std::get_if<double>(&cfmVariant);
211 if (cfm != nullptr || *cfm >= minSystemCfm)
212 {
213 maxRpm = getMaxRpm(*cfm);
214 }
215 }
216 pwmLimitIface->register_property("Limit", maxRpm);
217 pwmLimitIface->initialize();
218 setMaxPWM(conn, maxRpm);
219 },
220 settingsDaemon, cfmSettingPath, "org.freedesktop.DBus.Properties",
221 "Get", cfmSettingIface, "Limit");
222
223 matches.emplace_back(
224 *conn,
225 "type='signal',"
226 "member='PropertiesChanged',interface='org."
227 "freedesktop.DBus.Properties',path='" +
228 std::string(cfmSettingPath) + "',arg0='" +
229 std::string(cfmSettingIface) + "'",
230 [this, conn](sdbusplus::message::message& message) {
231 boost::container::flat_map<std::string, std::variant<double>>
232 values;
233 std::string objectName;
234 message.read(objectName, values);
235 const auto findValue = values.find("Limit");
236 if (findValue == values.end())
237 {
238 return;
239 }
240 const auto reading = std::get_if<double>(&(findValue->second));
241 if (reading == nullptr)
242 {
243 std::cerr << "Got CFM Limit of wrong type\n";
244 return;
245 }
246 if (*reading < minSystemCfm && *reading != 0)
247 {
248 std::cerr << "Illegal CFM setting detected\n";
249 return;
250 }
251 uint64_t maxRpm = getMaxRpm(*reading);
252 pwmLimitIface->set_property("Limit", maxRpm);
253 setMaxPWM(conn, maxRpm);
254 });
James Feistb2eb3f52018-12-04 16:17:50 -0800255}
256
James Feist9566bfa2019-01-29 15:31:23 -0800257CFMSensor::~CFMSensor()
258{
259 objServer.remove_interface(thresholdInterfaceWarning);
260 objServer.remove_interface(thresholdInterfaceCritical);
261 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800262 objServer.remove_interface(association);
James Feist13452092019-03-07 16:38:12 -0800263 objServer.remove_interface(cfmLimitIface);
264 objServer.remove_interface(pwmLimitIface);
265}
266
267void CFMSensor::createMaxCFMIface(void)
268{
James Feistb6c0b912019-07-09 12:21:44 -0700269 cfmLimitIface->register_property("Limit", c2 * maxCFM * tachs.size());
James Feist13452092019-03-07 16:38:12 -0800270 cfmLimitIface->initialize();
James Feist9566bfa2019-01-29 15:31:23 -0800271}
272
James Feistb2eb3f52018-12-04 16:17:50 -0800273void CFMSensor::addTachRanges(const std::string& serviceName,
274 const std::string& path)
275{
276 dbusConnection->async_method_call(
277 [this, path](const boost::system::error_code ec,
278 const boost::container::flat_map<std::string,
279 BasicVariantType>& data) {
280 if (ec)
281 {
282 std::cerr << "Error getting properties from " << path << "\n";
James Feist1ccdb5e2019-01-24 09:44:01 -0800283 return;
James Feistb2eb3f52018-12-04 16:17:50 -0800284 }
285
286 double max = loadVariant<double>(data, "MaxValue");
287 double min = loadVariant<double>(data, "MinValue");
288 tachRanges[path] = std::make_pair(min, max);
289 updateReading();
290 },
291 serviceName, path, "org.freedesktop.DBus.Properties", "GetAll",
292 "xyz.openbmc_project.Sensor.Value");
293}
294
295void CFMSensor::checkThresholds(void)
296{
297 thresholds::checkThresholds(this);
298}
299
300void CFMSensor::updateReading(void)
301{
302 double val = 0.0;
303 if (calculate(val))
304 {
305 if (value != val && parent)
306 {
307 parent->updateReading();
308 }
309 updateValue(val);
310 }
311 else
312 {
313 updateValue(std::numeric_limits<double>::quiet_NaN());
314 }
315}
316
James Feist13452092019-03-07 16:38:12 -0800317uint64_t CFMSensor::getMaxRpm(uint64_t cfmMaxSetting)
318{
319 uint64_t pwmPercent = 100;
320 double totalCFM = std::numeric_limits<double>::max();
321 if (cfmMaxSetting == 0)
322 {
323 return pwmPercent;
324 }
325
James Feist52427952019-04-05 14:23:35 -0700326 bool firstLoop = true;
James Feist13452092019-03-07 16:38:12 -0800327 while (totalCFM > cfmMaxSetting)
328 {
James Feist52427952019-04-05 14:23:35 -0700329 if (firstLoop)
330 {
331 firstLoop = false;
332 }
333 else
334 {
335 pwmPercent--;
336 }
337
James Feist13452092019-03-07 16:38:12 -0800338 double ci = 0;
339 if (pwmPercent == 0)
340 {
341 ci = 0;
342 }
343 else if (pwmPercent < tachMinPercent)
344 {
345 ci = c1;
346 }
347 else if (pwmPercent > tachMaxPercent)
348 {
349 ci = c2;
350 }
351 else
352 {
353 ci = c1 + (((c2 - c1) * (pwmPercent - tachMinPercent)) /
354 (tachMaxPercent - tachMinPercent));
355 }
356
357 // Now calculate the CFM for this tach
358 // CFMi = Ci * Qmaxi * TACHi
359 totalCFM = ci * maxCFM * pwmPercent;
360 totalCFM *= tachs.size();
361 // divide by 100 since pwm is in percent
362 totalCFM /= 100;
363
James Feist13452092019-03-07 16:38:12 -0800364 if (pwmPercent <= 0)
365 {
366 break;
367 }
368 }
James Feist52427952019-04-05 14:23:35 -0700369
James Feist13452092019-03-07 16:38:12 -0800370 return pwmPercent;
371}
372
James Feistb2eb3f52018-12-04 16:17:50 -0800373bool CFMSensor::calculate(double& value)
374{
375 double totalCFM = 0;
376 for (const std::string& tachName : tachs)
377 {
James Feist9566bfa2019-01-29 15:31:23 -0800378
James Feistb2eb3f52018-12-04 16:17:50 -0800379 auto findReading = std::find_if(
380 tachReadings.begin(), tachReadings.end(), [&](const auto& item) {
381 return boost::ends_with(item.first, tachName);
382 });
383 auto findRange = std::find_if(
384 tachRanges.begin(), tachRanges.end(), [&](const auto& item) {
385 return boost::ends_with(item.first, tachName);
386 });
387 if (findReading == tachReadings.end())
388 {
James Feista5e58722019-04-22 14:43:11 -0700389 if constexpr (DEBUG)
James Feista96329f2019-01-24 10:08:27 -0800390 {
391 std::cerr << "Can't find " << tachName << "in readings\n";
392 }
James Feist9566bfa2019-01-29 15:31:23 -0800393 continue; // haven't gotten a reading
James Feistb2eb3f52018-12-04 16:17:50 -0800394 }
395
396 if (findRange == tachRanges.end())
397 {
James Feist523828e2019-03-04 14:38:37 -0800398 std::cerr << "Can't find " << tachName << " in ranges\n";
James Feistb2eb3f52018-12-04 16:17:50 -0800399 return false; // haven't gotten a max / min
400 }
401
402 // avoid divide by 0
403 if (findRange->second.second == 0)
404 {
405 std::cerr << "Tach Max Set to 0 " << tachName << "\n";
406 return false;
407 }
408
409 double rpm = findReading->second;
410
411 // for now assume the min for a fan is always 0, divide by max to get
412 // percent and mult by 100
413 rpm /= findRange->second.second;
414 rpm *= 100;
415
416 if constexpr (DEBUG)
417 {
418 std::cout << "Tach " << tachName << "at " << rpm << "\n";
419 }
420
421 // Do a linear interpolation to get Ci
422 // Ci = C1 + (C2 - C1)/(RPM2 - RPM1) * (TACHi - TACH1)
423
424 double ci = 0;
425 if (rpm == 0)
426 {
427 ci = 0;
428 }
429 else if (rpm < tachMinPercent)
430 {
431 ci = c1;
432 }
433 else if (rpm > tachMaxPercent)
434 {
435 ci = c2;
436 }
437 else
438 {
439 ci = c1 + (((c2 - c1) * (rpm - tachMinPercent)) /
440 (tachMaxPercent - tachMinPercent));
441 }
442
443 // Now calculate the CFM for this tach
444 // CFMi = Ci * Qmaxi * TACHi
445 totalCFM += ci * maxCFM * rpm;
James Feista5e58722019-04-22 14:43:11 -0700446 if constexpr (DEBUG)
447 {
448 std::cerr << "totalCFM = " << totalCFM << "\n";
449 std::cerr << "Ci " << ci << " MaxCFM " << maxCFM << " rpm " << rpm
450 << "\n";
451 std::cerr << "c1 " << c1 << " c2 " << c2 << " max "
452 << tachMaxPercent << " min " << tachMinPercent << "\n";
453 }
James Feistb2eb3f52018-12-04 16:17:50 -0800454 }
455
456 // divide by 100 since rpm is in percent
457 value = totalCFM / 100;
James Feista5e58722019-04-22 14:43:11 -0700458 if constexpr (DEBUG)
459 {
460 std::cerr << "cfm value = " << value << "\n";
461 }
James Feist9566bfa2019-01-29 15:31:23 -0800462 return true;
James Feistb2eb3f52018-12-04 16:17:50 -0800463}
464
465static constexpr double exitAirMaxReading = 127;
466static constexpr double exitAirMinReading = -128;
James Feistbc896df2018-11-26 16:28:17 -0800467ExitAirTempSensor::ExitAirTempSensor(
468 std::shared_ptr<sdbusplus::asio::connection>& conn,
James Feistb2eb3f52018-12-04 16:17:50 -0800469 const std::string& sensorName, const std::string& sensorConfiguration,
James Feistbc896df2018-11-26 16:28:17 -0800470 sdbusplus::asio::object_server& objectServer,
James Feistb839c052019-05-15 10:25:24 -0700471 std::vector<thresholds::Threshold>&& thresholdData) :
James Feistb2eb3f52018-12-04 16:17:50 -0800472 Sensor(boost::replace_all_copy(sensorName, " ", "_"),
James Feist930fcde2019-05-28 12:58:43 -0700473 std::move(thresholdData), sensorConfiguration,
474 "xyz.openbmc_project.Configuration.ExitAirTemp", exitAirMaxReading,
475 exitAirMinReading),
James Feist523828e2019-03-04 14:38:37 -0800476 dbusConnection(conn), objServer(objectServer)
James Feistbc896df2018-11-26 16:28:17 -0800477{
478 sensorInterface = objectServer.add_interface(
479 "/xyz/openbmc_project/sensors/temperature/" + name,
480 "xyz.openbmc_project.Sensor.Value");
481
482 if (thresholds::hasWarningInterface(thresholds))
483 {
484 thresholdInterfaceWarning = objectServer.add_interface(
485 "/xyz/openbmc_project/sensors/temperature/" + name,
486 "xyz.openbmc_project.Sensor.Threshold.Warning");
487 }
488 if (thresholds::hasCriticalInterface(thresholds))
489 {
490 thresholdInterfaceCritical = objectServer.add_interface(
491 "/xyz/openbmc_project/sensors/temperature/" + name,
492 "xyz.openbmc_project.Sensor.Threshold.Critical");
493 }
James Feist078f2322019-03-08 11:09:05 -0800494 association = objectServer.add_interface(
495 "/xyz/openbmc_project/sensors/temperature/" + name,
496 "org.openbmc.Associations");
James Feistbc896df2018-11-26 16:28:17 -0800497 setInitialProperties(conn);
498 setupMatches();
James Feist71d31b22019-01-02 16:57:54 -0800499 setupPowerMatch(conn);
James Feistbc896df2018-11-26 16:28:17 -0800500}
501
502ExitAirTempSensor::~ExitAirTempSensor()
503{
James Feist523828e2019-03-04 14:38:37 -0800504 objServer.remove_interface(thresholdInterfaceWarning);
505 objServer.remove_interface(thresholdInterfaceCritical);
506 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800507 objServer.remove_interface(association);
James Feistbc896df2018-11-26 16:28:17 -0800508}
509
510void ExitAirTempSensor::setupMatches(void)
511{
James Feistb2eb3f52018-12-04 16:17:50 -0800512 constexpr const std::array<const char*, 2> matchTypes = {
513 "power", inletTemperatureSensor};
James Feistbc896df2018-11-26 16:28:17 -0800514
James Feistb2eb3f52018-12-04 16:17:50 -0800515 for (const std::string& type : matchTypes)
James Feistbc896df2018-11-26 16:28:17 -0800516 {
James Feistb2eb3f52018-12-04 16:17:50 -0800517 setupSensorMatch(matches, *dbusConnection, type,
518 [this, type](const double& value,
519 sdbusplus::message::message& message) {
520 if (type == "power")
521 {
James Feista5e58722019-04-22 14:43:11 -0700522 std::string path = message.get_path();
523 if (path.find("PS") != std::string::npos &&
524 boost::ends_with(path, "Input_Power"))
525 {
526 powerReadings[message.get_path()] = value;
527 }
James Feistb2eb3f52018-12-04 16:17:50 -0800528 }
529 else if (type == inletTemperatureSensor)
530 {
531 inletTemp = value;
532 }
533 updateReading();
534 });
James Feistbc896df2018-11-26 16:28:17 -0800535 }
James Feist9566bfa2019-01-29 15:31:23 -0800536 dbusConnection->async_method_call(
537 [this](boost::system::error_code ec,
538 const std::variant<double>& value) {
539 if (ec)
540 {
541 // sensor not ready yet
542 return;
543 }
544
James Feist3eb82622019-02-08 13:10:22 -0800545 inletTemp = std::visit(VariantToDoubleVisitor(), value);
James Feist9566bfa2019-01-29 15:31:23 -0800546 },
547 "xyz.openbmc_project.HwmonTempSensor",
548 std::string("/xyz/openbmc_project/sensors/") + inletTemperatureSensor,
James Feista5e58722019-04-22 14:43:11 -0700549 properties::interface, properties::get, sensorValueInterface, "Value");
550 dbusConnection->async_method_call(
551 [this](boost::system::error_code ec, const GetSubTreeType& subtree) {
552 if (ec)
553 {
554 std::cerr << "Error contacting mapper\n";
555 return;
556 }
557 for (const auto& item : subtree)
558 {
559 size_t lastSlash = item.first.rfind("/");
560 if (lastSlash == std::string::npos ||
561 lastSlash == item.first.size() || !item.second.size())
562 {
563 continue;
564 }
565 std::string sensorName = item.first.substr(lastSlash + 1);
566 if (boost::starts_with(sensorName, "PS") &&
567 boost::ends_with(sensorName, "Input_Power"))
568 {
569 const std::string& path = item.first;
570 dbusConnection->async_method_call(
571 [this, path](boost::system::error_code ec,
572 const std::variant<double>& value) {
573 if (ec)
574 {
575 std::cerr << "Error getting value from " << path
576 << "\n";
577 }
578
579 double reading =
580 std::visit(VariantToDoubleVisitor(), value);
581 if constexpr (DEBUG)
582 {
583 std::cerr << path << "Reading " << reading
584 << "\n";
585 }
586 powerReadings[path] = reading;
587 },
588 item.second[0].first, item.first, properties::interface,
589 properties::get, sensorValueInterface, "Value");
590 }
591 }
592 },
593 mapper::busName, mapper::path, mapper::interface, mapper::subtree,
594 "/xyz/openbmc_project/sensors/power", 0,
595 std::array<const char*, 1>{sensorValueInterface});
James Feistbc896df2018-11-26 16:28:17 -0800596}
597
598void ExitAirTempSensor::updateReading(void)
599{
600
601 double val = 0.0;
602 if (calculate(val))
603 {
James Feist18af4232019-03-13 11:14:00 -0700604 val = std::floor(val + 0.5);
James Feistbc896df2018-11-26 16:28:17 -0800605 updateValue(val);
606 }
607 else
608 {
609 updateValue(std::numeric_limits<double>::quiet_NaN());
610 }
611}
612
James Feistb2eb3f52018-12-04 16:17:50 -0800613double ExitAirTempSensor::getTotalCFM(void)
James Feistbc896df2018-11-26 16:28:17 -0800614{
James Feistb2eb3f52018-12-04 16:17:50 -0800615 double sum = 0;
616 for (auto& sensor : cfmSensors)
James Feistbc896df2018-11-26 16:28:17 -0800617 {
James Feistb2eb3f52018-12-04 16:17:50 -0800618 double reading = 0;
619 if (!sensor->calculate(reading))
James Feistbc896df2018-11-26 16:28:17 -0800620 {
James Feistbc896df2018-11-26 16:28:17 -0800621 return -1;
622 }
James Feistb2eb3f52018-12-04 16:17:50 -0800623 sum += reading;
James Feistbc896df2018-11-26 16:28:17 -0800624 }
James Feistb2eb3f52018-12-04 16:17:50 -0800625
626 return sum;
James Feistbc896df2018-11-26 16:28:17 -0800627}
628
629bool ExitAirTempSensor::calculate(double& val)
630{
James Feistae11cfc2019-05-07 15:01:20 -0700631 constexpr size_t maxErrorPrint = 1;
James Feistbc896df2018-11-26 16:28:17 -0800632 static bool firstRead = false;
James Feistae11cfc2019-05-07 15:01:20 -0700633 static size_t errorPrint = maxErrorPrint;
634
James Feistbc896df2018-11-26 16:28:17 -0800635 double cfm = getTotalCFM();
636 if (cfm <= 0)
637 {
638 std::cerr << "Error getting cfm\n";
639 return false;
640 }
641
642 // if there is an error getting inlet temp, return error
643 if (std::isnan(inletTemp))
644 {
James Feistae11cfc2019-05-07 15:01:20 -0700645 if (errorPrint > 0)
646 {
647 errorPrint--;
648 std::cerr << "Cannot get inlet temp\n";
649 }
James Feistbc896df2018-11-26 16:28:17 -0800650 val = 0;
651 return false;
652 }
653
654 // if fans are off, just make the exit temp equal to inlet
James Feist71d31b22019-01-02 16:57:54 -0800655 if (!isPowerOn())
James Feistbc896df2018-11-26 16:28:17 -0800656 {
657 val = inletTemp;
658 return true;
659 }
660
661 double totalPower = 0;
662 for (const auto& reading : powerReadings)
663 {
664 if (std::isnan(reading.second))
665 {
666 continue;
667 }
668 totalPower += reading.second;
669 }
670
671 // Calculate power correction factor
672 // Ci = CL + (CH - CL)/(QMax - QMin) * (CFM - QMin)
673 float powerFactor = 0.0;
674 if (cfm <= qMin)
675 {
676 powerFactor = powerFactorMin;
677 }
678 else if (cfm >= qMax)
679 {
680 powerFactor = powerFactorMax;
681 }
682 else
683 {
684 powerFactor = powerFactorMin + ((powerFactorMax - powerFactorMin) /
685 (qMax - qMin) * (cfm - qMin));
686 }
687
James Feistb6c0b912019-07-09 12:21:44 -0700688 totalPower *= static_cast<double>(powerFactor);
James Feistbc896df2018-11-26 16:28:17 -0800689 totalPower += pOffset;
690
691 if (totalPower == 0)
692 {
James Feistae11cfc2019-05-07 15:01:20 -0700693 if (errorPrint > 0)
694 {
695 errorPrint--;
696 std::cerr << "total power 0\n";
697 }
James Feistbc896df2018-11-26 16:28:17 -0800698 val = 0;
699 return false;
700 }
701
702 if constexpr (DEBUG)
703 {
704 std::cout << "Power Factor " << powerFactor << "\n";
705 std::cout << "Inlet Temp " << inletTemp << "\n";
706 std::cout << "Total Power" << totalPower << "\n";
707 }
708
709 // Calculate the exit air temp
710 // Texit = Tfp + (1.76 * TotalPower / CFM * Faltitude)
James Feistb6c0b912019-07-09 12:21:44 -0700711 double reading = 1.76 * totalPower * static_cast<double>(altitudeFactor);
James Feistbc896df2018-11-26 16:28:17 -0800712 reading /= cfm;
713 reading += inletTemp;
714
715 if constexpr (DEBUG)
716 {
717 std::cout << "Reading 1: " << reading << "\n";
718 }
719
720 // Now perform the exponential average
721 // Calculate alpha based on SDR values and CFM
722 // Ai = As + (Af - As)/(QMax - QMin) * (CFM - QMin)
723
724 double alpha = 0.0;
725 if (cfm < qMin)
726 {
727 alpha = alphaS;
728 }
729 else if (cfm >= qMax)
730 {
731 alpha = alphaF;
732 }
733 else
734 {
735 alpha = alphaS + ((alphaF - alphaS) * (cfm - qMin) / (qMax - qMin));
736 }
737
738 auto time = std::chrono::system_clock::now();
739 if (!firstRead)
740 {
741 firstRead = true;
742 lastTime = time;
743 lastReading = reading;
744 }
745 double alphaDT =
746 std::chrono::duration_cast<std::chrono::seconds>(time - lastTime)
747 .count() *
748 alpha;
749
750 // cap at 1.0 or the below fails
751 if (alphaDT > 1.0)
752 {
753 alphaDT = 1.0;
754 }
755
756 if constexpr (DEBUG)
757 {
758 std::cout << "AlphaDT: " << alphaDT << "\n";
759 }
760
761 reading = ((reading * alphaDT) + (lastReading * (1.0 - alphaDT)));
762
763 if constexpr (DEBUG)
764 {
765 std::cout << "Reading 2: " << reading << "\n";
766 }
767
768 val = reading;
769 lastReading = reading;
770 lastTime = time;
James Feistae11cfc2019-05-07 15:01:20 -0700771 errorPrint = maxErrorPrint;
James Feistbc896df2018-11-26 16:28:17 -0800772 return true;
773}
774
775void ExitAirTempSensor::checkThresholds(void)
776{
777 thresholds::checkThresholds(this);
778}
779
James Feistbc896df2018-11-26 16:28:17 -0800780static void loadVariantPathArray(
781 const boost::container::flat_map<std::string, BasicVariantType>& data,
782 const std::string& key, std::vector<std::string>& resp)
783{
784 auto it = data.find(key);
785 if (it == data.end())
786 {
787 std::cerr << "Configuration missing " << key << "\n";
788 throw std::invalid_argument("Key Missing");
789 }
790 BasicVariantType copy = it->second;
James Feist3eb82622019-02-08 13:10:22 -0800791 std::vector<std::string> config = std::get<std::vector<std::string>>(copy);
James Feistbc896df2018-11-26 16:28:17 -0800792 for (auto& str : config)
793 {
794 boost::replace_all(str, " ", "_");
795 }
796 resp = std::move(config);
797}
798
799void createSensor(sdbusplus::asio::object_server& objectServer,
James Feistb2eb3f52018-12-04 16:17:50 -0800800 std::shared_ptr<ExitAirTempSensor>& exitAirSensor,
James Feistbc896df2018-11-26 16:28:17 -0800801 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
802{
803 if (!dbusConnection)
804 {
805 std::cerr << "Connection not created\n";
806 return;
807 }
808 dbusConnection->async_method_call(
809 [&](boost::system::error_code ec, const ManagedObjectType& resp) {
810 if (ec)
811 {
812 std::cerr << "Error contacting entity manager\n";
813 return;
814 }
James Feistb2eb3f52018-12-04 16:17:50 -0800815 std::vector<std::unique_ptr<CFMSensor>> cfmSensors;
James Feistbc896df2018-11-26 16:28:17 -0800816 for (const auto& pathPair : resp)
817 {
818 for (const auto& entry : pathPair.second)
819 {
820 if (entry.first == exitAirIface)
821 {
James Feistbc896df2018-11-26 16:28:17 -0800822 // thresholds should be under the same path
823 std::vector<thresholds::Threshold> sensorThresholds;
824 parseThresholdsFromConfig(pathPair.second,
825 sensorThresholds);
James Feistbc896df2018-11-26 16:28:17 -0800826
James Feist523828e2019-03-04 14:38:37 -0800827 std::string name =
828 loadVariant<std::string>(entry.second, "Name");
829 exitAirSensor = std::make_shared<ExitAirTempSensor>(
830 dbusConnection, name, pathPair.first.str,
831 objectServer, std::move(sensorThresholds));
James Feistb2eb3f52018-12-04 16:17:50 -0800832 exitAirSensor->powerFactorMin =
833 loadVariant<double>(entry.second, "PowerFactorMin");
834 exitAirSensor->powerFactorMax =
835 loadVariant<double>(entry.second, "PowerFactorMax");
836 exitAirSensor->qMin =
837 loadVariant<double>(entry.second, "QMin");
838 exitAirSensor->qMax =
839 loadVariant<double>(entry.second, "QMax");
840 exitAirSensor->alphaS =
841 loadVariant<double>(entry.second, "AlphaS");
842 exitAirSensor->alphaF =
843 loadVariant<double>(entry.second, "AlphaF");
James Feistbc896df2018-11-26 16:28:17 -0800844 }
845 else if (entry.first == cfmIface)
846
847 {
James Feistb2eb3f52018-12-04 16:17:50 -0800848 // thresholds should be under the same path
849 std::vector<thresholds::Threshold> sensorThresholds;
850 parseThresholdsFromConfig(pathPair.second,
851 sensorThresholds);
852 std::string name =
853 loadVariant<std::string>(entry.second, "Name");
854 auto sensor = std::make_unique<CFMSensor>(
855 dbusConnection, name, pathPair.first.str,
856 objectServer, std::move(sensorThresholds),
857 exitAirSensor);
858 loadVariantPathArray(entry.second, "Tachs",
859 sensor->tachs);
860 sensor->maxCFM =
861 loadVariant<double>(entry.second, "MaxCFM");
James Feistbc896df2018-11-26 16:28:17 -0800862
863 // change these into percent upon getting the data
James Feistb2eb3f52018-12-04 16:17:50 -0800864 sensor->c1 =
865 loadVariant<double>(entry.second, "C1") / 100;
866 sensor->c2 =
867 loadVariant<double>(entry.second, "C2") / 100;
868 sensor->tachMinPercent =
869 loadVariant<double>(entry.second,
870 "TachMinPercent") /
James Feistbc896df2018-11-26 16:28:17 -0800871 100;
James Feistb2eb3f52018-12-04 16:17:50 -0800872 sensor->tachMaxPercent =
873 loadVariant<double>(entry.second,
874 "TachMaxPercent") /
James Feistbc896df2018-11-26 16:28:17 -0800875 100;
James Feist13452092019-03-07 16:38:12 -0800876 sensor->createMaxCFMIface();
James Feistbc896df2018-11-26 16:28:17 -0800877
James Feistb2eb3f52018-12-04 16:17:50 -0800878 cfmSensors.emplace_back(std::move(sensor));
James Feistbc896df2018-11-26 16:28:17 -0800879 }
880 }
881 }
James Feistb2eb3f52018-12-04 16:17:50 -0800882 if (exitAirSensor)
James Feistbc896df2018-11-26 16:28:17 -0800883 {
James Feistb2eb3f52018-12-04 16:17:50 -0800884 exitAirSensor->cfmSensors = std::move(cfmSensors);
James Feistb2eb3f52018-12-04 16:17:50 -0800885 exitAirSensor->updateReading();
James Feistbc896df2018-11-26 16:28:17 -0800886 }
887 },
888 entityManagerName, "/", "org.freedesktop.DBus.ObjectManager",
889 "GetManagedObjects");
890}
891
James Feistb6c0b912019-07-09 12:21:44 -0700892int main()
James Feistbc896df2018-11-26 16:28:17 -0800893{
894
895 boost::asio::io_service io;
896 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
897 systemBus->request_name("xyz.openbmc_project.ExitAirTempSensor");
898 sdbusplus::asio::object_server objectServer(systemBus);
899 std::shared_ptr<ExitAirTempSensor> sensor =
900 nullptr; // wait until we find the config
901 std::vector<std::unique_ptr<sdbusplus::bus::match::match>> matches;
902
903 io.post([&]() { createSensor(objectServer, sensor, systemBus); });
904
905 boost::asio::deadline_timer configTimer(io);
906
907 std::function<void(sdbusplus::message::message&)> eventHandler =
James Feistb6c0b912019-07-09 12:21:44 -0700908 [&](sdbusplus::message::message&) {
James Feistbc896df2018-11-26 16:28:17 -0800909 configTimer.expires_from_now(boost::posix_time::seconds(1));
910 // create a timer because normally multiple properties change
911 configTimer.async_wait([&](const boost::system::error_code& ec) {
912 if (ec == boost::asio::error::operation_aborted)
913 {
914 return; // we're being canceled
915 }
916 createSensor(objectServer, sensor, systemBus);
917 if (!sensor)
918 {
919 std::cout << "Configuration not detected\n";
920 }
921 });
922 };
923 constexpr const std::array<const char*, 2> monitorIfaces = {exitAirIface,
924 cfmIface};
925 for (const char* type : monitorIfaces)
926 {
927 auto match = std::make_unique<sdbusplus::bus::match::match>(
928 static_cast<sdbusplus::bus::bus&>(*systemBus),
929 "type='signal',member='PropertiesChanged',path_namespace='" +
930 std::string(inventoryPath) + "',arg0namespace='" + type + "'",
931 eventHandler);
932 matches.emplace_back(std::move(match));
933 }
934
935 io.run();
936}