blob: e8bba1431e64d4e55f46b4571bd621bd3e7b3061 [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 Feist9a25ed42019-10-15 15:43:44 -070054static std::vector<std::shared_ptr<CFMSensor>> cfmSensors;
55
James Feistb2eb3f52018-12-04 16:17:50 -080056static void setupSensorMatch(
57 std::vector<sdbusplus::bus::match::match>& matches,
58 sdbusplus::bus::bus& connection, const std::string& type,
59 std::function<void(const double&, sdbusplus::message::message&)>&& callback)
60{
61
62 std::function<void(sdbusplus::message::message & message)> eventHandler =
63 [callback{std::move(callback)}](sdbusplus::message::message& message) {
64 std::string objectName;
James Feist3eb82622019-02-08 13:10:22 -080065 boost::container::flat_map<std::string,
66 std::variant<double, int64_t>>
James Feistb2eb3f52018-12-04 16:17:50 -080067 values;
68 message.read(objectName, values);
69 auto findValue = values.find("Value");
70 if (findValue == values.end())
71 {
72 return;
73 }
James Feist3eb82622019-02-08 13:10:22 -080074 double value =
75 std::visit(VariantToDoubleVisitor(), findValue->second);
James Feist9566bfa2019-01-29 15:31:23 -080076 if (std::isnan(value))
77 {
78 return;
79 }
80
James Feistb2eb3f52018-12-04 16:17:50 -080081 callback(value, message);
82 };
83 matches.emplace_back(connection,
84 "type='signal',"
85 "member='PropertiesChanged',interface='org."
86 "freedesktop.DBus.Properties',path_"
87 "namespace='/xyz/openbmc_project/sensors/" +
88 std::string(type) +
89 "',arg0='xyz.openbmc_project.Sensor.Value'",
90 std::move(eventHandler));
91}
92
James Feist13452092019-03-07 16:38:12 -080093static void setMaxPWM(const std::shared_ptr<sdbusplus::asio::connection>& conn,
94 double value)
95{
96 using GetSubTreeType = std::vector<std::pair<
97 std::string,
98 std::vector<std::pair<std::string, std::vector<std::string>>>>>;
99
100 conn->async_method_call(
101 [conn, value](const boost::system::error_code ec,
102 const GetSubTreeType& ret) {
103 if (ec)
104 {
105 std::cerr << "Error calling mapper\n";
106 return;
107 }
108 for (const auto& [path, objDict] : ret)
109 {
110 if (objDict.empty())
111 {
112 return;
113 }
114 const std::string& owner = objDict.begin()->first;
115
116 conn->async_method_call(
117 [conn, value, owner,
118 path](const boost::system::error_code ec,
119 const std::variant<std::string>& classType) {
120 if (ec)
121 {
122 std::cerr << "Error getting pid class\n";
123 return;
124 }
125 auto classStr = std::get_if<std::string>(&classType);
126 if (classStr == nullptr || *classStr != "fan")
127 {
128 return;
129 }
130 conn->async_method_call(
131 [](boost::system::error_code& ec) {
132 if (ec)
133 {
134 std::cerr << "Error setting pid class\n";
135 return;
136 }
137 },
138 owner, path, "org.freedesktop.DBus.Properties",
139 "Set", pidConfigurationType, "OutLimitMax",
140 std::variant<double>(value));
141 },
142 owner, path, "org.freedesktop.DBus.Properties", "Get",
143 pidConfigurationType, "Class");
144 }
145 },
James Feista5e58722019-04-22 14:43:11 -0700146 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
147 0, std::array<std::string, 1>{pidConfigurationType});
James Feist13452092019-03-07 16:38:12 -0800148}
149
James Feistb2eb3f52018-12-04 16:17:50 -0800150CFMSensor::CFMSensor(std::shared_ptr<sdbusplus::asio::connection>& conn,
151 const std::string& sensorName,
152 const std::string& sensorConfiguration,
153 sdbusplus::asio::object_server& objectServer,
James Feistb839c052019-05-15 10:25:24 -0700154 std::vector<thresholds::Threshold>&& thresholdData,
James Feistb2eb3f52018-12-04 16:17:50 -0800155 std::shared_ptr<ExitAirTempSensor>& parent) :
156 Sensor(boost::replace_all_copy(sensorName, " ", "_"),
James Feist930fcde2019-05-28 12:58:43 -0700157 std::move(thresholdData), sensorConfiguration,
158 "xyz.openbmc_project.Configuration.ExitAirTemp", cfmMaxReading,
159 cfmMinReading),
James Feist9a25ed42019-10-15 15:43:44 -0700160 std::enable_shared_from_this<CFMSensor>(), dbusConnection(conn),
161 parent(parent), objServer(objectServer)
James Feistb2eb3f52018-12-04 16:17:50 -0800162{
163 sensorInterface =
164 objectServer.add_interface("/xyz/openbmc_project/sensors/cfm/" + name,
165 "xyz.openbmc_project.Sensor.Value");
166
167 if (thresholds::hasWarningInterface(thresholds))
168 {
169 thresholdInterfaceWarning = objectServer.add_interface(
170 "/xyz/openbmc_project/sensors/cfm/" + name,
171 "xyz.openbmc_project.Sensor.Threshold.Warning");
172 }
173 if (thresholds::hasCriticalInterface(thresholds))
174 {
175 thresholdInterfaceCritical = objectServer.add_interface(
176 "/xyz/openbmc_project/sensors/cfm/" + name,
177 "xyz.openbmc_project.Sensor.Threshold.Critical");
178 }
James Feist078f2322019-03-08 11:09:05 -0800179
180 association = objectServer.add_interface(
James Feist2adc95c2019-09-30 14:55:28 -0700181 "/xyz/openbmc_project/sensors/cfm/" + name, association::interface);
James Feist078f2322019-03-08 11:09:05 -0800182
James Feistb2eb3f52018-12-04 16:17:50 -0800183 setInitialProperties(conn);
James Feist9a25ed42019-10-15 15:43:44 -0700184
James Feist13452092019-03-07 16:38:12 -0800185 pwmLimitIface =
186 objectServer.add_interface("/xyz/openbmc_project/control/pwm_limit",
187 "xyz.openbmc_project.Control.PWMLimit");
188 cfmLimitIface =
189 objectServer.add_interface("/xyz/openbmc_project/control/MaxCFM",
190 "xyz.openbmc_project.Control.CFMLimit");
James Feist9a25ed42019-10-15 15:43:44 -0700191}
James Feist13452092019-03-07 16:38:12 -0800192
James Feist9a25ed42019-10-15 15:43:44 -0700193void CFMSensor::setupMatches()
194{
195
196 std::shared_ptr<CFMSensor> self = shared_from_this();
197 setupSensorMatch(matches, *dbusConnection, "fan_tach",
198 std::move([self](const double& value,
199 sdbusplus::message::message& message) {
200 self->tachReadings[message.get_path()] = value;
201 if (self->tachRanges.find(message.get_path()) ==
202 self->tachRanges.end())
203 {
204 // calls update reading after updating ranges
205 self->addTachRanges(message.get_sender(),
206 message.get_path());
207 }
208 else
209 {
210 self->updateReading();
211 }
212 }));
213
214 dbusConnection->async_method_call(
215 [self](const boost::system::error_code ec,
216 const std::variant<double> cfmVariant) {
James Feist13452092019-03-07 16:38:12 -0800217 uint64_t maxRpm = 100;
218 if (!ec)
219 {
220
221 auto cfm = std::get_if<double>(&cfmVariant);
222 if (cfm != nullptr || *cfm >= minSystemCfm)
223 {
James Feist9a25ed42019-10-15 15:43:44 -0700224 maxRpm = self->getMaxRpm(*cfm);
James Feist13452092019-03-07 16:38:12 -0800225 }
226 }
James Feist9a25ed42019-10-15 15:43:44 -0700227 self->pwmLimitIface->register_property("Limit", maxRpm);
228 self->pwmLimitIface->initialize();
229 setMaxPWM(self->dbusConnection, maxRpm);
James Feist13452092019-03-07 16:38:12 -0800230 },
231 settingsDaemon, cfmSettingPath, "org.freedesktop.DBus.Properties",
232 "Get", cfmSettingIface, "Limit");
233
234 matches.emplace_back(
James Feist9a25ed42019-10-15 15:43:44 -0700235 *dbusConnection,
James Feist13452092019-03-07 16:38:12 -0800236 "type='signal',"
237 "member='PropertiesChanged',interface='org."
238 "freedesktop.DBus.Properties',path='" +
239 std::string(cfmSettingPath) + "',arg0='" +
240 std::string(cfmSettingIface) + "'",
James Feist9a25ed42019-10-15 15:43:44 -0700241 [self](sdbusplus::message::message& message) {
James Feist13452092019-03-07 16:38:12 -0800242 boost::container::flat_map<std::string, std::variant<double>>
243 values;
244 std::string objectName;
245 message.read(objectName, values);
246 const auto findValue = values.find("Limit");
247 if (findValue == values.end())
248 {
249 return;
250 }
251 const auto reading = std::get_if<double>(&(findValue->second));
252 if (reading == nullptr)
253 {
254 std::cerr << "Got CFM Limit of wrong type\n";
255 return;
256 }
257 if (*reading < minSystemCfm && *reading != 0)
258 {
259 std::cerr << "Illegal CFM setting detected\n";
260 return;
261 }
James Feist9a25ed42019-10-15 15:43:44 -0700262 uint64_t maxRpm = self->getMaxRpm(*reading);
263 self->pwmLimitIface->set_property("Limit", maxRpm);
264 setMaxPWM(self->dbusConnection, maxRpm);
James Feist13452092019-03-07 16:38:12 -0800265 });
James Feistb2eb3f52018-12-04 16:17:50 -0800266}
267
James Feist9566bfa2019-01-29 15:31:23 -0800268CFMSensor::~CFMSensor()
269{
270 objServer.remove_interface(thresholdInterfaceWarning);
271 objServer.remove_interface(thresholdInterfaceCritical);
272 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800273 objServer.remove_interface(association);
James Feist13452092019-03-07 16:38:12 -0800274 objServer.remove_interface(cfmLimitIface);
275 objServer.remove_interface(pwmLimitIface);
276}
277
278void CFMSensor::createMaxCFMIface(void)
279{
James Feistb6c0b912019-07-09 12:21:44 -0700280 cfmLimitIface->register_property("Limit", c2 * maxCFM * tachs.size());
James Feist13452092019-03-07 16:38:12 -0800281 cfmLimitIface->initialize();
James Feist9566bfa2019-01-29 15:31:23 -0800282}
283
James Feistb2eb3f52018-12-04 16:17:50 -0800284void CFMSensor::addTachRanges(const std::string& serviceName,
285 const std::string& path)
286{
James Feist9a25ed42019-10-15 15:43:44 -0700287 std::shared_ptr<CFMSensor> self = shared_from_this();
James Feistb2eb3f52018-12-04 16:17:50 -0800288 dbusConnection->async_method_call(
James Feist9a25ed42019-10-15 15:43:44 -0700289 [self, path](const boost::system::error_code ec,
James Feistb2eb3f52018-12-04 16:17:50 -0800290 const boost::container::flat_map<std::string,
291 BasicVariantType>& data) {
292 if (ec)
293 {
294 std::cerr << "Error getting properties from " << path << "\n";
James Feist1ccdb5e2019-01-24 09:44:01 -0800295 return;
James Feistb2eb3f52018-12-04 16:17:50 -0800296 }
297
298 double max = loadVariant<double>(data, "MaxValue");
299 double min = loadVariant<double>(data, "MinValue");
James Feist9a25ed42019-10-15 15:43:44 -0700300 self->tachRanges[path] = std::make_pair(min, max);
301 self->updateReading();
James Feistb2eb3f52018-12-04 16:17:50 -0800302 },
303 serviceName, path, "org.freedesktop.DBus.Properties", "GetAll",
304 "xyz.openbmc_project.Sensor.Value");
305}
306
307void CFMSensor::checkThresholds(void)
308{
309 thresholds::checkThresholds(this);
310}
311
312void CFMSensor::updateReading(void)
313{
314 double val = 0.0;
315 if (calculate(val))
316 {
317 if (value != val && parent)
318 {
319 parent->updateReading();
320 }
321 updateValue(val);
322 }
323 else
324 {
325 updateValue(std::numeric_limits<double>::quiet_NaN());
326 }
327}
328
James Feist13452092019-03-07 16:38:12 -0800329uint64_t CFMSensor::getMaxRpm(uint64_t cfmMaxSetting)
330{
331 uint64_t pwmPercent = 100;
332 double totalCFM = std::numeric_limits<double>::max();
333 if (cfmMaxSetting == 0)
334 {
335 return pwmPercent;
336 }
337
James Feist52427952019-04-05 14:23:35 -0700338 bool firstLoop = true;
James Feist13452092019-03-07 16:38:12 -0800339 while (totalCFM > cfmMaxSetting)
340 {
James Feist52427952019-04-05 14:23:35 -0700341 if (firstLoop)
342 {
343 firstLoop = false;
344 }
345 else
346 {
347 pwmPercent--;
348 }
349
James Feist13452092019-03-07 16:38:12 -0800350 double ci = 0;
351 if (pwmPercent == 0)
352 {
353 ci = 0;
354 }
355 else if (pwmPercent < tachMinPercent)
356 {
357 ci = c1;
358 }
359 else if (pwmPercent > tachMaxPercent)
360 {
361 ci = c2;
362 }
363 else
364 {
365 ci = c1 + (((c2 - c1) * (pwmPercent - tachMinPercent)) /
366 (tachMaxPercent - tachMinPercent));
367 }
368
369 // Now calculate the CFM for this tach
370 // CFMi = Ci * Qmaxi * TACHi
371 totalCFM = ci * maxCFM * pwmPercent;
372 totalCFM *= tachs.size();
373 // divide by 100 since pwm is in percent
374 totalCFM /= 100;
375
James Feist13452092019-03-07 16:38:12 -0800376 if (pwmPercent <= 0)
377 {
378 break;
379 }
380 }
James Feist52427952019-04-05 14:23:35 -0700381
James Feist13452092019-03-07 16:38:12 -0800382 return pwmPercent;
383}
384
James Feistb2eb3f52018-12-04 16:17:50 -0800385bool CFMSensor::calculate(double& value)
386{
387 double totalCFM = 0;
388 for (const std::string& tachName : tachs)
389 {
James Feist9566bfa2019-01-29 15:31:23 -0800390
James Feistb2eb3f52018-12-04 16:17:50 -0800391 auto findReading = std::find_if(
392 tachReadings.begin(), tachReadings.end(), [&](const auto& item) {
393 return boost::ends_with(item.first, tachName);
394 });
395 auto findRange = std::find_if(
396 tachRanges.begin(), tachRanges.end(), [&](const auto& item) {
397 return boost::ends_with(item.first, tachName);
398 });
399 if (findReading == tachReadings.end())
400 {
James Feista5e58722019-04-22 14:43:11 -0700401 if constexpr (DEBUG)
James Feista96329f2019-01-24 10:08:27 -0800402 {
403 std::cerr << "Can't find " << tachName << "in readings\n";
404 }
James Feist9566bfa2019-01-29 15:31:23 -0800405 continue; // haven't gotten a reading
James Feistb2eb3f52018-12-04 16:17:50 -0800406 }
407
408 if (findRange == tachRanges.end())
409 {
James Feist523828e2019-03-04 14:38:37 -0800410 std::cerr << "Can't find " << tachName << " in ranges\n";
James Feistb2eb3f52018-12-04 16:17:50 -0800411 return false; // haven't gotten a max / min
412 }
413
414 // avoid divide by 0
415 if (findRange->second.second == 0)
416 {
417 std::cerr << "Tach Max Set to 0 " << tachName << "\n";
418 return false;
419 }
420
421 double rpm = findReading->second;
422
423 // for now assume the min for a fan is always 0, divide by max to get
424 // percent and mult by 100
425 rpm /= findRange->second.second;
426 rpm *= 100;
427
428 if constexpr (DEBUG)
429 {
430 std::cout << "Tach " << tachName << "at " << rpm << "\n";
431 }
432
433 // Do a linear interpolation to get Ci
434 // Ci = C1 + (C2 - C1)/(RPM2 - RPM1) * (TACHi - TACH1)
435
436 double ci = 0;
437 if (rpm == 0)
438 {
439 ci = 0;
440 }
441 else if (rpm < tachMinPercent)
442 {
443 ci = c1;
444 }
445 else if (rpm > tachMaxPercent)
446 {
447 ci = c2;
448 }
449 else
450 {
451 ci = c1 + (((c2 - c1) * (rpm - tachMinPercent)) /
452 (tachMaxPercent - tachMinPercent));
453 }
454
455 // Now calculate the CFM for this tach
456 // CFMi = Ci * Qmaxi * TACHi
457 totalCFM += ci * maxCFM * rpm;
James Feista5e58722019-04-22 14:43:11 -0700458 if constexpr (DEBUG)
459 {
460 std::cerr << "totalCFM = " << totalCFM << "\n";
461 std::cerr << "Ci " << ci << " MaxCFM " << maxCFM << " rpm " << rpm
462 << "\n";
463 std::cerr << "c1 " << c1 << " c2 " << c2 << " max "
464 << tachMaxPercent << " min " << tachMinPercent << "\n";
465 }
James Feistb2eb3f52018-12-04 16:17:50 -0800466 }
467
468 // divide by 100 since rpm is in percent
469 value = totalCFM / 100;
James Feista5e58722019-04-22 14:43:11 -0700470 if constexpr (DEBUG)
471 {
472 std::cerr << "cfm value = " << value << "\n";
473 }
James Feist9566bfa2019-01-29 15:31:23 -0800474 return true;
James Feistb2eb3f52018-12-04 16:17:50 -0800475}
476
477static constexpr double exitAirMaxReading = 127;
478static constexpr double exitAirMinReading = -128;
James Feistbc896df2018-11-26 16:28:17 -0800479ExitAirTempSensor::ExitAirTempSensor(
480 std::shared_ptr<sdbusplus::asio::connection>& conn,
James Feistb2eb3f52018-12-04 16:17:50 -0800481 const std::string& sensorName, const std::string& sensorConfiguration,
James Feistbc896df2018-11-26 16:28:17 -0800482 sdbusplus::asio::object_server& objectServer,
James Feistb839c052019-05-15 10:25:24 -0700483 std::vector<thresholds::Threshold>&& thresholdData) :
James Feistb2eb3f52018-12-04 16:17:50 -0800484 Sensor(boost::replace_all_copy(sensorName, " ", "_"),
James Feist930fcde2019-05-28 12:58:43 -0700485 std::move(thresholdData), sensorConfiguration,
486 "xyz.openbmc_project.Configuration.ExitAirTemp", exitAirMaxReading,
487 exitAirMinReading),
James Feist9a25ed42019-10-15 15:43:44 -0700488 std::enable_shared_from_this<ExitAirTempSensor>(), dbusConnection(conn),
489 objServer(objectServer)
James Feistbc896df2018-11-26 16:28:17 -0800490{
491 sensorInterface = objectServer.add_interface(
492 "/xyz/openbmc_project/sensors/temperature/" + name,
493 "xyz.openbmc_project.Sensor.Value");
494
495 if (thresholds::hasWarningInterface(thresholds))
496 {
497 thresholdInterfaceWarning = objectServer.add_interface(
498 "/xyz/openbmc_project/sensors/temperature/" + name,
499 "xyz.openbmc_project.Sensor.Threshold.Warning");
500 }
501 if (thresholds::hasCriticalInterface(thresholds))
502 {
503 thresholdInterfaceCritical = objectServer.add_interface(
504 "/xyz/openbmc_project/sensors/temperature/" + name,
505 "xyz.openbmc_project.Sensor.Threshold.Critical");
506 }
James Feist078f2322019-03-08 11:09:05 -0800507 association = objectServer.add_interface(
508 "/xyz/openbmc_project/sensors/temperature/" + name,
James Feist2adc95c2019-09-30 14:55:28 -0700509 association::interface);
James Feistbc896df2018-11-26 16:28:17 -0800510 setInitialProperties(conn);
James Feist71d31b22019-01-02 16:57:54 -0800511 setupPowerMatch(conn);
James Feistbc896df2018-11-26 16:28:17 -0800512}
513
514ExitAirTempSensor::~ExitAirTempSensor()
515{
James Feist523828e2019-03-04 14:38:37 -0800516 objServer.remove_interface(thresholdInterfaceWarning);
517 objServer.remove_interface(thresholdInterfaceCritical);
518 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800519 objServer.remove_interface(association);
James Feistbc896df2018-11-26 16:28:17 -0800520}
521
522void ExitAirTempSensor::setupMatches(void)
523{
James Feistb2eb3f52018-12-04 16:17:50 -0800524 constexpr const std::array<const char*, 2> matchTypes = {
525 "power", inletTemperatureSensor};
James Feistbc896df2018-11-26 16:28:17 -0800526
James Feist9a25ed42019-10-15 15:43:44 -0700527 std::shared_ptr<ExitAirTempSensor> self = shared_from_this();
James Feistb2eb3f52018-12-04 16:17:50 -0800528 for (const std::string& type : matchTypes)
James Feistbc896df2018-11-26 16:28:17 -0800529 {
James Feistb2eb3f52018-12-04 16:17:50 -0800530 setupSensorMatch(matches, *dbusConnection, type,
James Feist9a25ed42019-10-15 15:43:44 -0700531 [self, type](const double& value,
James Feistb2eb3f52018-12-04 16:17:50 -0800532 sdbusplus::message::message& message) {
533 if (type == "power")
534 {
James Feista5e58722019-04-22 14:43:11 -0700535 std::string path = message.get_path();
536 if (path.find("PS") != std::string::npos &&
537 boost::ends_with(path, "Input_Power"))
538 {
James Feist9a25ed42019-10-15 15:43:44 -0700539 self->powerReadings[message.get_path()] =
540 value;
James Feista5e58722019-04-22 14:43:11 -0700541 }
James Feistb2eb3f52018-12-04 16:17:50 -0800542 }
543 else if (type == inletTemperatureSensor)
544 {
James Feist9a25ed42019-10-15 15:43:44 -0700545 self->inletTemp = value;
James Feistb2eb3f52018-12-04 16:17:50 -0800546 }
James Feist9a25ed42019-10-15 15:43:44 -0700547 self->updateReading();
James Feistb2eb3f52018-12-04 16:17:50 -0800548 });
James Feistbc896df2018-11-26 16:28:17 -0800549 }
James Feist9566bfa2019-01-29 15:31:23 -0800550 dbusConnection->async_method_call(
James Feist9a25ed42019-10-15 15:43:44 -0700551 [self](boost::system::error_code ec,
James Feist9566bfa2019-01-29 15:31:23 -0800552 const std::variant<double>& value) {
553 if (ec)
554 {
555 // sensor not ready yet
556 return;
557 }
558
James Feist9a25ed42019-10-15 15:43:44 -0700559 self->inletTemp = std::visit(VariantToDoubleVisitor(), value);
James Feist9566bfa2019-01-29 15:31:23 -0800560 },
561 "xyz.openbmc_project.HwmonTempSensor",
562 std::string("/xyz/openbmc_project/sensors/") + inletTemperatureSensor,
James Feista5e58722019-04-22 14:43:11 -0700563 properties::interface, properties::get, sensorValueInterface, "Value");
564 dbusConnection->async_method_call(
James Feist9a25ed42019-10-15 15:43:44 -0700565 [self](boost::system::error_code ec, const GetSubTreeType& subtree) {
James Feista5e58722019-04-22 14:43:11 -0700566 if (ec)
567 {
568 std::cerr << "Error contacting mapper\n";
569 return;
570 }
571 for (const auto& item : subtree)
572 {
573 size_t lastSlash = item.first.rfind("/");
574 if (lastSlash == std::string::npos ||
575 lastSlash == item.first.size() || !item.second.size())
576 {
577 continue;
578 }
579 std::string sensorName = item.first.substr(lastSlash + 1);
580 if (boost::starts_with(sensorName, "PS") &&
581 boost::ends_with(sensorName, "Input_Power"))
582 {
583 const std::string& path = item.first;
James Feist9a25ed42019-10-15 15:43:44 -0700584 self->dbusConnection->async_method_call(
585 [self, path](boost::system::error_code ec,
James Feista5e58722019-04-22 14:43:11 -0700586 const std::variant<double>& value) {
587 if (ec)
588 {
589 std::cerr << "Error getting value from " << path
590 << "\n";
591 }
592
593 double reading =
594 std::visit(VariantToDoubleVisitor(), value);
595 if constexpr (DEBUG)
596 {
597 std::cerr << path << "Reading " << reading
598 << "\n";
599 }
James Feist9a25ed42019-10-15 15:43:44 -0700600 self->powerReadings[path] = reading;
James Feista5e58722019-04-22 14:43:11 -0700601 },
602 item.second[0].first, item.first, properties::interface,
603 properties::get, sensorValueInterface, "Value");
604 }
605 }
606 },
607 mapper::busName, mapper::path, mapper::interface, mapper::subtree,
608 "/xyz/openbmc_project/sensors/power", 0,
609 std::array<const char*, 1>{sensorValueInterface});
James Feistbc896df2018-11-26 16:28:17 -0800610}
611
612void ExitAirTempSensor::updateReading(void)
613{
614
615 double val = 0.0;
616 if (calculate(val))
617 {
James Feist18af4232019-03-13 11:14:00 -0700618 val = std::floor(val + 0.5);
James Feistbc896df2018-11-26 16:28:17 -0800619 updateValue(val);
620 }
621 else
622 {
623 updateValue(std::numeric_limits<double>::quiet_NaN());
624 }
625}
626
James Feistb2eb3f52018-12-04 16:17:50 -0800627double ExitAirTempSensor::getTotalCFM(void)
James Feistbc896df2018-11-26 16:28:17 -0800628{
James Feistb2eb3f52018-12-04 16:17:50 -0800629 double sum = 0;
630 for (auto& sensor : cfmSensors)
James Feistbc896df2018-11-26 16:28:17 -0800631 {
James Feistb2eb3f52018-12-04 16:17:50 -0800632 double reading = 0;
633 if (!sensor->calculate(reading))
James Feistbc896df2018-11-26 16:28:17 -0800634 {
James Feistbc896df2018-11-26 16:28:17 -0800635 return -1;
636 }
James Feistb2eb3f52018-12-04 16:17:50 -0800637 sum += reading;
James Feistbc896df2018-11-26 16:28:17 -0800638 }
James Feistb2eb3f52018-12-04 16:17:50 -0800639
640 return sum;
James Feistbc896df2018-11-26 16:28:17 -0800641}
642
643bool ExitAirTempSensor::calculate(double& val)
644{
James Feistae11cfc2019-05-07 15:01:20 -0700645 constexpr size_t maxErrorPrint = 1;
James Feistbc896df2018-11-26 16:28:17 -0800646 static bool firstRead = false;
James Feistae11cfc2019-05-07 15:01:20 -0700647 static size_t errorPrint = maxErrorPrint;
648
James Feistbc896df2018-11-26 16:28:17 -0800649 double cfm = getTotalCFM();
650 if (cfm <= 0)
651 {
652 std::cerr << "Error getting cfm\n";
653 return false;
654 }
655
656 // if there is an error getting inlet temp, return error
657 if (std::isnan(inletTemp))
658 {
James Feistae11cfc2019-05-07 15:01:20 -0700659 if (errorPrint > 0)
660 {
661 errorPrint--;
662 std::cerr << "Cannot get inlet temp\n";
663 }
James Feistbc896df2018-11-26 16:28:17 -0800664 val = 0;
665 return false;
666 }
667
668 // if fans are off, just make the exit temp equal to inlet
James Feist71d31b22019-01-02 16:57:54 -0800669 if (!isPowerOn())
James Feistbc896df2018-11-26 16:28:17 -0800670 {
671 val = inletTemp;
672 return true;
673 }
674
675 double totalPower = 0;
676 for (const auto& reading : powerReadings)
677 {
678 if (std::isnan(reading.second))
679 {
680 continue;
681 }
682 totalPower += reading.second;
683 }
684
685 // Calculate power correction factor
686 // Ci = CL + (CH - CL)/(QMax - QMin) * (CFM - QMin)
687 float powerFactor = 0.0;
688 if (cfm <= qMin)
689 {
690 powerFactor = powerFactorMin;
691 }
692 else if (cfm >= qMax)
693 {
694 powerFactor = powerFactorMax;
695 }
696 else
697 {
698 powerFactor = powerFactorMin + ((powerFactorMax - powerFactorMin) /
699 (qMax - qMin) * (cfm - qMin));
700 }
701
James Feistb6c0b912019-07-09 12:21:44 -0700702 totalPower *= static_cast<double>(powerFactor);
James Feistbc896df2018-11-26 16:28:17 -0800703 totalPower += pOffset;
704
705 if (totalPower == 0)
706 {
James Feistae11cfc2019-05-07 15:01:20 -0700707 if (errorPrint > 0)
708 {
709 errorPrint--;
710 std::cerr << "total power 0\n";
711 }
James Feistbc896df2018-11-26 16:28:17 -0800712 val = 0;
713 return false;
714 }
715
716 if constexpr (DEBUG)
717 {
718 std::cout << "Power Factor " << powerFactor << "\n";
719 std::cout << "Inlet Temp " << inletTemp << "\n";
720 std::cout << "Total Power" << totalPower << "\n";
721 }
722
723 // Calculate the exit air temp
724 // Texit = Tfp + (1.76 * TotalPower / CFM * Faltitude)
James Feistb6c0b912019-07-09 12:21:44 -0700725 double reading = 1.76 * totalPower * static_cast<double>(altitudeFactor);
James Feistbc896df2018-11-26 16:28:17 -0800726 reading /= cfm;
727 reading += inletTemp;
728
729 if constexpr (DEBUG)
730 {
731 std::cout << "Reading 1: " << reading << "\n";
732 }
733
734 // Now perform the exponential average
735 // Calculate alpha based on SDR values and CFM
736 // Ai = As + (Af - As)/(QMax - QMin) * (CFM - QMin)
737
738 double alpha = 0.0;
739 if (cfm < qMin)
740 {
741 alpha = alphaS;
742 }
743 else if (cfm >= qMax)
744 {
745 alpha = alphaF;
746 }
747 else
748 {
749 alpha = alphaS + ((alphaF - alphaS) * (cfm - qMin) / (qMax - qMin));
750 }
751
752 auto time = std::chrono::system_clock::now();
753 if (!firstRead)
754 {
755 firstRead = true;
756 lastTime = time;
757 lastReading = reading;
758 }
759 double alphaDT =
760 std::chrono::duration_cast<std::chrono::seconds>(time - lastTime)
761 .count() *
762 alpha;
763
764 // cap at 1.0 or the below fails
765 if (alphaDT > 1.0)
766 {
767 alphaDT = 1.0;
768 }
769
770 if constexpr (DEBUG)
771 {
772 std::cout << "AlphaDT: " << alphaDT << "\n";
773 }
774
775 reading = ((reading * alphaDT) + (lastReading * (1.0 - alphaDT)));
776
777 if constexpr (DEBUG)
778 {
779 std::cout << "Reading 2: " << reading << "\n";
780 }
781
782 val = reading;
783 lastReading = reading;
784 lastTime = time;
James Feistae11cfc2019-05-07 15:01:20 -0700785 errorPrint = maxErrorPrint;
James Feistbc896df2018-11-26 16:28:17 -0800786 return true;
787}
788
789void ExitAirTempSensor::checkThresholds(void)
790{
791 thresholds::checkThresholds(this);
792}
793
James Feistbc896df2018-11-26 16:28:17 -0800794static void loadVariantPathArray(
795 const boost::container::flat_map<std::string, BasicVariantType>& data,
796 const std::string& key, std::vector<std::string>& resp)
797{
798 auto it = data.find(key);
799 if (it == data.end())
800 {
801 std::cerr << "Configuration missing " << key << "\n";
802 throw std::invalid_argument("Key Missing");
803 }
804 BasicVariantType copy = it->second;
James Feist3eb82622019-02-08 13:10:22 -0800805 std::vector<std::string> config = std::get<std::vector<std::string>>(copy);
James Feistbc896df2018-11-26 16:28:17 -0800806 for (auto& str : config)
807 {
808 boost::replace_all(str, " ", "_");
809 }
810 resp = std::move(config);
811}
812
813void createSensor(sdbusplus::asio::object_server& objectServer,
James Feistb2eb3f52018-12-04 16:17:50 -0800814 std::shared_ptr<ExitAirTempSensor>& exitAirSensor,
James Feistbc896df2018-11-26 16:28:17 -0800815 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
816{
817 if (!dbusConnection)
818 {
819 std::cerr << "Connection not created\n";
820 return;
821 }
822 dbusConnection->async_method_call(
823 [&](boost::system::error_code ec, const ManagedObjectType& resp) {
824 if (ec)
825 {
826 std::cerr << "Error contacting entity manager\n";
827 return;
828 }
James Feist9a25ed42019-10-15 15:43:44 -0700829
830 cfmSensors.clear();
James Feistbc896df2018-11-26 16:28:17 -0800831 for (const auto& pathPair : resp)
832 {
833 for (const auto& entry : pathPair.second)
834 {
835 if (entry.first == exitAirIface)
836 {
James Feistbc896df2018-11-26 16:28:17 -0800837 // thresholds should be under the same path
838 std::vector<thresholds::Threshold> sensorThresholds;
839 parseThresholdsFromConfig(pathPair.second,
840 sensorThresholds);
James Feistbc896df2018-11-26 16:28:17 -0800841
James Feist523828e2019-03-04 14:38:37 -0800842 std::string name =
843 loadVariant<std::string>(entry.second, "Name");
844 exitAirSensor = std::make_shared<ExitAirTempSensor>(
845 dbusConnection, name, pathPair.first.str,
846 objectServer, std::move(sensorThresholds));
James Feistb2eb3f52018-12-04 16:17:50 -0800847 exitAirSensor->powerFactorMin =
848 loadVariant<double>(entry.second, "PowerFactorMin");
849 exitAirSensor->powerFactorMax =
850 loadVariant<double>(entry.second, "PowerFactorMax");
851 exitAirSensor->qMin =
852 loadVariant<double>(entry.second, "QMin");
853 exitAirSensor->qMax =
854 loadVariant<double>(entry.second, "QMax");
855 exitAirSensor->alphaS =
856 loadVariant<double>(entry.second, "AlphaS");
857 exitAirSensor->alphaF =
858 loadVariant<double>(entry.second, "AlphaF");
James Feistbc896df2018-11-26 16:28:17 -0800859 }
860 else if (entry.first == cfmIface)
861
862 {
James Feistb2eb3f52018-12-04 16:17:50 -0800863 // thresholds should be under the same path
864 std::vector<thresholds::Threshold> sensorThresholds;
865 parseThresholdsFromConfig(pathPair.second,
866 sensorThresholds);
867 std::string name =
868 loadVariant<std::string>(entry.second, "Name");
James Feist9a25ed42019-10-15 15:43:44 -0700869 auto sensor = std::make_shared<CFMSensor>(
James Feistb2eb3f52018-12-04 16:17:50 -0800870 dbusConnection, name, pathPair.first.str,
871 objectServer, std::move(sensorThresholds),
872 exitAirSensor);
873 loadVariantPathArray(entry.second, "Tachs",
874 sensor->tachs);
875 sensor->maxCFM =
876 loadVariant<double>(entry.second, "MaxCFM");
James Feistbc896df2018-11-26 16:28:17 -0800877
878 // change these into percent upon getting the data
James Feistb2eb3f52018-12-04 16:17:50 -0800879 sensor->c1 =
880 loadVariant<double>(entry.second, "C1") / 100;
881 sensor->c2 =
882 loadVariant<double>(entry.second, "C2") / 100;
883 sensor->tachMinPercent =
884 loadVariant<double>(entry.second,
885 "TachMinPercent") /
James Feistbc896df2018-11-26 16:28:17 -0800886 100;
James Feistb2eb3f52018-12-04 16:17:50 -0800887 sensor->tachMaxPercent =
888 loadVariant<double>(entry.second,
889 "TachMaxPercent") /
James Feistbc896df2018-11-26 16:28:17 -0800890 100;
James Feist13452092019-03-07 16:38:12 -0800891 sensor->createMaxCFMIface();
James Feist9a25ed42019-10-15 15:43:44 -0700892 sensor->setupMatches();
James Feistbc896df2018-11-26 16:28:17 -0800893
James Feistb2eb3f52018-12-04 16:17:50 -0800894 cfmSensors.emplace_back(std::move(sensor));
James Feistbc896df2018-11-26 16:28:17 -0800895 }
896 }
897 }
James Feistb2eb3f52018-12-04 16:17:50 -0800898 if (exitAirSensor)
James Feistbc896df2018-11-26 16:28:17 -0800899 {
James Feist9a25ed42019-10-15 15:43:44 -0700900 exitAirSensor->setupMatches();
James Feistb2eb3f52018-12-04 16:17:50 -0800901 exitAirSensor->updateReading();
James Feistbc896df2018-11-26 16:28:17 -0800902 }
903 },
904 entityManagerName, "/", "org.freedesktop.DBus.ObjectManager",
905 "GetManagedObjects");
906}
907
James Feistb6c0b912019-07-09 12:21:44 -0700908int main()
James Feistbc896df2018-11-26 16:28:17 -0800909{
910
911 boost::asio::io_service io;
912 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
913 systemBus->request_name("xyz.openbmc_project.ExitAirTempSensor");
914 sdbusplus::asio::object_server objectServer(systemBus);
915 std::shared_ptr<ExitAirTempSensor> sensor =
916 nullptr; // wait until we find the config
917 std::vector<std::unique_ptr<sdbusplus::bus::match::match>> matches;
918
919 io.post([&]() { createSensor(objectServer, sensor, systemBus); });
920
921 boost::asio::deadline_timer configTimer(io);
922
923 std::function<void(sdbusplus::message::message&)> eventHandler =
James Feistb6c0b912019-07-09 12:21:44 -0700924 [&](sdbusplus::message::message&) {
James Feistbc896df2018-11-26 16:28:17 -0800925 configTimer.expires_from_now(boost::posix_time::seconds(1));
926 // create a timer because normally multiple properties change
927 configTimer.async_wait([&](const boost::system::error_code& ec) {
928 if (ec == boost::asio::error::operation_aborted)
929 {
930 return; // we're being canceled
931 }
932 createSensor(objectServer, sensor, systemBus);
933 if (!sensor)
934 {
935 std::cout << "Configuration not detected\n";
936 }
937 });
938 };
939 constexpr const std::array<const char*, 2> monitorIfaces = {exitAirIface,
940 cfmIface};
941 for (const char* type : monitorIfaces)
942 {
943 auto match = std::make_unique<sdbusplus::bus::match::match>(
944 static_cast<sdbusplus::bus::bus&>(*systemBus),
945 "type='signal',member='PropertiesChanged',path_namespace='" +
946 std::string(inventoryPath) + "',arg0namespace='" + type + "'",
947 eventHandler);
948 matches.emplace_back(std::move(match));
949 }
950
951 io.run();
952}