blob: 725cb12c0e74d40cc24f37e9d0e93186f710d602 [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
Ed Tanous8a57ec02020-10-09 12:46:52 -070017#include <ExitAirTempSensor.hpp>
18#include <Utils.hpp>
19#include <VariantVisitors.hpp>
James Feistbc896df2018-11-26 16:28:17 -080020#include <boost/algorithm/string/predicate.hpp>
21#include <boost/algorithm/string/replace.hpp>
Patrick Venture96e97db2019-10-31 13:44:38 -070022#include <boost/container/flat_map.hpp>
James Feist38fb5982020-05-28 10:09:54 -070023#include <sdbusplus/asio/connection.hpp>
24#include <sdbusplus/asio/object_server.hpp>
25#include <sdbusplus/bus/match.hpp>
26
27#include <array>
James Feistbc896df2018-11-26 16:28:17 -080028#include <chrono>
Patrick Venture96e97db2019-10-31 13:44:38 -070029#include <cmath>
30#include <functional>
James Feistbc896df2018-11-26 16:28:17 -080031#include <iostream>
32#include <limits>
Patrick Venture96e97db2019-10-31 13:44:38 -070033#include <memory>
James Feistbc896df2018-11-26 16:28:17 -080034#include <numeric>
Patrick Venture96e97db2019-10-31 13:44:38 -070035#include <stdexcept>
36#include <utility>
37#include <variant>
James Feistbc896df2018-11-26 16:28:17 -080038#include <vector>
39
Ed Tanous8a57ec02020-10-09 12:46:52 -070040constexpr const double altitudeFactor = 1.14;
James Feistbc896df2018-11-26 16:28:17 -080041constexpr const char* exitAirIface =
42 "xyz.openbmc_project.Configuration.ExitAirTempSensor";
43constexpr const char* cfmIface = "xyz.openbmc_project.Configuration.CFMSensor";
44
45// todo: this *might* need to be configurable
46constexpr const char* inletTemperatureSensor = "temperature/Front_Panel_Temp";
James Feist13452092019-03-07 16:38:12 -080047constexpr const char* pidConfigurationType =
48 "xyz.openbmc_project.Configuration.Pid";
49constexpr const char* settingsDaemon = "xyz.openbmc_project.Settings";
50constexpr const char* cfmSettingPath = "/xyz/openbmc_project/control/cfm_limit";
51constexpr const char* cfmSettingIface = "xyz.openbmc_project.Control.CFMLimit";
James Feistbc896df2018-11-26 16:28:17 -080052
Ed Tanous8a57ec02020-10-09 12:46:52 -070053static constexpr bool debug = false;
James Feistbc896df2018-11-26 16:28:17 -080054
James Feistb2eb3f52018-12-04 16:17:50 -080055static constexpr double cfmMaxReading = 255;
56static constexpr double cfmMinReading = 0;
57
James Feist13452092019-03-07 16:38:12 -080058static constexpr size_t minSystemCfm = 50;
59
James Feist655f3762020-10-05 15:28:15 -070060constexpr const std::array<const char*, 2> monitorIfaces = {exitAirIface,
61 cfmIface};
62
James Feist9a25ed42019-10-15 15:43:44 -070063static std::vector<std::shared_ptr<CFMSensor>> cfmSensors;
64
James Feistb2eb3f52018-12-04 16:17:50 -080065static void setupSensorMatch(
66 std::vector<sdbusplus::bus::match::match>& matches,
67 sdbusplus::bus::bus& connection, const std::string& type,
68 std::function<void(const double&, sdbusplus::message::message&)>&& callback)
69{
70
71 std::function<void(sdbusplus::message::message & message)> eventHandler =
72 [callback{std::move(callback)}](sdbusplus::message::message& message) {
73 std::string objectName;
James Feist3eb82622019-02-08 13:10:22 -080074 boost::container::flat_map<std::string,
75 std::variant<double, int64_t>>
James Feistb2eb3f52018-12-04 16:17:50 -080076 values;
77 message.read(objectName, values);
78 auto findValue = values.find("Value");
79 if (findValue == values.end())
80 {
81 return;
82 }
James Feist3eb82622019-02-08 13:10:22 -080083 double value =
84 std::visit(VariantToDoubleVisitor(), findValue->second);
James Feist9566bfa2019-01-29 15:31:23 -080085 if (std::isnan(value))
86 {
87 return;
88 }
89
James Feistb2eb3f52018-12-04 16:17:50 -080090 callback(value, message);
91 };
92 matches.emplace_back(connection,
93 "type='signal',"
94 "member='PropertiesChanged',interface='org."
95 "freedesktop.DBus.Properties',path_"
96 "namespace='/xyz/openbmc_project/sensors/" +
97 std::string(type) +
98 "',arg0='xyz.openbmc_project.Sensor.Value'",
99 std::move(eventHandler));
100}
101
James Feist13452092019-03-07 16:38:12 -0800102static void setMaxPWM(const std::shared_ptr<sdbusplus::asio::connection>& conn,
103 double value)
104{
105 using GetSubTreeType = std::vector<std::pair<
106 std::string,
107 std::vector<std::pair<std::string, std::vector<std::string>>>>>;
108
109 conn->async_method_call(
110 [conn, value](const boost::system::error_code ec,
111 const GetSubTreeType& ret) {
112 if (ec)
113 {
114 std::cerr << "Error calling mapper\n";
115 return;
116 }
117 for (const auto& [path, objDict] : ret)
118 {
119 if (objDict.empty())
120 {
121 return;
122 }
123 const std::string& owner = objDict.begin()->first;
124
125 conn->async_method_call(
126 [conn, value, owner,
Ed Tanous8a57ec02020-10-09 12:46:52 -0700127 path{path}](const boost::system::error_code ec,
128 const std::variant<std::string>& classType) {
James Feist13452092019-03-07 16:38:12 -0800129 if (ec)
130 {
131 std::cerr << "Error getting pid class\n";
132 return;
133 }
134 auto classStr = std::get_if<std::string>(&classType);
135 if (classStr == nullptr || *classStr != "fan")
136 {
137 return;
138 }
139 conn->async_method_call(
140 [](boost::system::error_code& ec) {
141 if (ec)
142 {
143 std::cerr << "Error setting pid class\n";
144 return;
145 }
146 },
147 owner, path, "org.freedesktop.DBus.Properties",
148 "Set", pidConfigurationType, "OutLimitMax",
149 std::variant<double>(value));
150 },
151 owner, path, "org.freedesktop.DBus.Properties", "Get",
152 pidConfigurationType, "Class");
153 }
154 },
James Feista5e58722019-04-22 14:43:11 -0700155 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
156 0, std::array<std::string, 1>{pidConfigurationType});
James Feist13452092019-03-07 16:38:12 -0800157}
158
James Feistb2eb3f52018-12-04 16:17:50 -0800159CFMSensor::CFMSensor(std::shared_ptr<sdbusplus::asio::connection>& conn,
160 const std::string& sensorName,
161 const std::string& sensorConfiguration,
162 sdbusplus::asio::object_server& objectServer,
James Feistb839c052019-05-15 10:25:24 -0700163 std::vector<thresholds::Threshold>&& thresholdData,
James Feistb2eb3f52018-12-04 16:17:50 -0800164 std::shared_ptr<ExitAirTempSensor>& parent) :
165 Sensor(boost::replace_all_copy(sensorName, " ", "_"),
James Feist930fcde2019-05-28 12:58:43 -0700166 std::move(thresholdData), sensorConfiguration,
167 "xyz.openbmc_project.Configuration.ExitAirTemp", cfmMaxReading,
James Feiste3338522020-09-15 15:40:30 -0700168 cfmMinReading, conn, PowerState::on),
Brad Bishopfbb44ad2019-11-08 09:42:37 -0500169 std::enable_shared_from_this<CFMSensor>(), parent(parent),
James Feiste3338522020-09-15 15:40:30 -0700170 objServer(objectServer)
James Feistb2eb3f52018-12-04 16:17:50 -0800171{
172 sensorInterface =
173 objectServer.add_interface("/xyz/openbmc_project/sensors/cfm/" + name,
174 "xyz.openbmc_project.Sensor.Value");
175
176 if (thresholds::hasWarningInterface(thresholds))
177 {
178 thresholdInterfaceWarning = objectServer.add_interface(
179 "/xyz/openbmc_project/sensors/cfm/" + name,
180 "xyz.openbmc_project.Sensor.Threshold.Warning");
181 }
182 if (thresholds::hasCriticalInterface(thresholds))
183 {
184 thresholdInterfaceCritical = objectServer.add_interface(
185 "/xyz/openbmc_project/sensors/cfm/" + name,
186 "xyz.openbmc_project.Sensor.Threshold.Critical");
187 }
James Feist078f2322019-03-08 11:09:05 -0800188
189 association = objectServer.add_interface(
James Feist2adc95c2019-09-30 14:55:28 -0700190 "/xyz/openbmc_project/sensors/cfm/" + name, association::interface);
James Feist078f2322019-03-08 11:09:05 -0800191
James Feistb2eb3f52018-12-04 16:17:50 -0800192 setInitialProperties(conn);
James Feist9a25ed42019-10-15 15:43:44 -0700193
James Feist13452092019-03-07 16:38:12 -0800194 pwmLimitIface =
195 objectServer.add_interface("/xyz/openbmc_project/control/pwm_limit",
196 "xyz.openbmc_project.Control.PWMLimit");
197 cfmLimitIface =
198 objectServer.add_interface("/xyz/openbmc_project/control/MaxCFM",
199 "xyz.openbmc_project.Control.CFMLimit");
James Feist9a25ed42019-10-15 15:43:44 -0700200}
James Feist13452092019-03-07 16:38:12 -0800201
James Feist9a25ed42019-10-15 15:43:44 -0700202void CFMSensor::setupMatches()
203{
204
205 std::shared_ptr<CFMSensor> self = shared_from_this();
206 setupSensorMatch(matches, *dbusConnection, "fan_tach",
207 std::move([self](const double& value,
208 sdbusplus::message::message& message) {
209 self->tachReadings[message.get_path()] = value;
210 if (self->tachRanges.find(message.get_path()) ==
211 self->tachRanges.end())
212 {
213 // calls update reading after updating ranges
214 self->addTachRanges(message.get_sender(),
215 message.get_path());
216 }
217 else
218 {
219 self->updateReading();
220 }
221 }));
222
223 dbusConnection->async_method_call(
224 [self](const boost::system::error_code ec,
225 const std::variant<double> cfmVariant) {
James Feist13452092019-03-07 16:38:12 -0800226 uint64_t maxRpm = 100;
227 if (!ec)
228 {
229
230 auto cfm = std::get_if<double>(&cfmVariant);
Josh Lehanff336992020-02-26 11:13:19 -0800231 if (cfm != nullptr && *cfm >= minSystemCfm)
James Feist13452092019-03-07 16:38:12 -0800232 {
James Feist9a25ed42019-10-15 15:43:44 -0700233 maxRpm = self->getMaxRpm(*cfm);
James Feist13452092019-03-07 16:38:12 -0800234 }
235 }
James Feist9a25ed42019-10-15 15:43:44 -0700236 self->pwmLimitIface->register_property("Limit", maxRpm);
237 self->pwmLimitIface->initialize();
238 setMaxPWM(self->dbusConnection, maxRpm);
James Feist13452092019-03-07 16:38:12 -0800239 },
240 settingsDaemon, cfmSettingPath, "org.freedesktop.DBus.Properties",
241 "Get", cfmSettingIface, "Limit");
242
243 matches.emplace_back(
James Feist9a25ed42019-10-15 15:43:44 -0700244 *dbusConnection,
James Feist13452092019-03-07 16:38:12 -0800245 "type='signal',"
246 "member='PropertiesChanged',interface='org."
247 "freedesktop.DBus.Properties',path='" +
248 std::string(cfmSettingPath) + "',arg0='" +
249 std::string(cfmSettingIface) + "'",
James Feist9a25ed42019-10-15 15:43:44 -0700250 [self](sdbusplus::message::message& message) {
James Feist13452092019-03-07 16:38:12 -0800251 boost::container::flat_map<std::string, std::variant<double>>
252 values;
253 std::string objectName;
254 message.read(objectName, values);
255 const auto findValue = values.find("Limit");
256 if (findValue == values.end())
257 {
258 return;
259 }
260 const auto reading = std::get_if<double>(&(findValue->second));
261 if (reading == nullptr)
262 {
263 std::cerr << "Got CFM Limit of wrong type\n";
264 return;
265 }
266 if (*reading < minSystemCfm && *reading != 0)
267 {
268 std::cerr << "Illegal CFM setting detected\n";
269 return;
270 }
James Feist9a25ed42019-10-15 15:43:44 -0700271 uint64_t maxRpm = self->getMaxRpm(*reading);
272 self->pwmLimitIface->set_property("Limit", maxRpm);
273 setMaxPWM(self->dbusConnection, maxRpm);
James Feist13452092019-03-07 16:38:12 -0800274 });
James Feistb2eb3f52018-12-04 16:17:50 -0800275}
276
James Feist9566bfa2019-01-29 15:31:23 -0800277CFMSensor::~CFMSensor()
278{
279 objServer.remove_interface(thresholdInterfaceWarning);
280 objServer.remove_interface(thresholdInterfaceCritical);
281 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800282 objServer.remove_interface(association);
James Feist13452092019-03-07 16:38:12 -0800283 objServer.remove_interface(cfmLimitIface);
284 objServer.remove_interface(pwmLimitIface);
285}
286
287void CFMSensor::createMaxCFMIface(void)
288{
James Feistb6c0b912019-07-09 12:21:44 -0700289 cfmLimitIface->register_property("Limit", c2 * maxCFM * tachs.size());
James Feist13452092019-03-07 16:38:12 -0800290 cfmLimitIface->initialize();
James Feist9566bfa2019-01-29 15:31:23 -0800291}
292
James Feistb2eb3f52018-12-04 16:17:50 -0800293void CFMSensor::addTachRanges(const std::string& serviceName,
294 const std::string& path)
295{
James Feist9a25ed42019-10-15 15:43:44 -0700296 std::shared_ptr<CFMSensor> self = shared_from_this();
James Feistb2eb3f52018-12-04 16:17:50 -0800297 dbusConnection->async_method_call(
James Feist9a25ed42019-10-15 15:43:44 -0700298 [self, path](const boost::system::error_code ec,
James Feistb2eb3f52018-12-04 16:17:50 -0800299 const boost::container::flat_map<std::string,
300 BasicVariantType>& data) {
301 if (ec)
302 {
303 std::cerr << "Error getting properties from " << path << "\n";
James Feist1ccdb5e2019-01-24 09:44:01 -0800304 return;
James Feistb2eb3f52018-12-04 16:17:50 -0800305 }
306
307 double max = loadVariant<double>(data, "MaxValue");
308 double min = loadVariant<double>(data, "MinValue");
James Feist9a25ed42019-10-15 15:43:44 -0700309 self->tachRanges[path] = std::make_pair(min, max);
310 self->updateReading();
James Feistb2eb3f52018-12-04 16:17:50 -0800311 },
312 serviceName, path, "org.freedesktop.DBus.Properties", "GetAll",
313 "xyz.openbmc_project.Sensor.Value");
314}
315
316void CFMSensor::checkThresholds(void)
317{
318 thresholds::checkThresholds(this);
319}
320
321void CFMSensor::updateReading(void)
322{
323 double val = 0.0;
324 if (calculate(val))
325 {
326 if (value != val && parent)
327 {
328 parent->updateReading();
329 }
330 updateValue(val);
331 }
332 else
333 {
334 updateValue(std::numeric_limits<double>::quiet_NaN());
335 }
336}
337
James Feist13452092019-03-07 16:38:12 -0800338uint64_t CFMSensor::getMaxRpm(uint64_t cfmMaxSetting)
339{
340 uint64_t pwmPercent = 100;
341 double totalCFM = std::numeric_limits<double>::max();
342 if (cfmMaxSetting == 0)
343 {
344 return pwmPercent;
345 }
346
James Feist52427952019-04-05 14:23:35 -0700347 bool firstLoop = true;
James Feist13452092019-03-07 16:38:12 -0800348 while (totalCFM > cfmMaxSetting)
349 {
James Feist52427952019-04-05 14:23:35 -0700350 if (firstLoop)
351 {
352 firstLoop = false;
353 }
354 else
355 {
356 pwmPercent--;
357 }
358
James Feist13452092019-03-07 16:38:12 -0800359 double ci = 0;
360 if (pwmPercent == 0)
361 {
362 ci = 0;
363 }
364 else if (pwmPercent < tachMinPercent)
365 {
366 ci = c1;
367 }
368 else if (pwmPercent > tachMaxPercent)
369 {
370 ci = c2;
371 }
372 else
373 {
374 ci = c1 + (((c2 - c1) * (pwmPercent - tachMinPercent)) /
375 (tachMaxPercent - tachMinPercent));
376 }
377
378 // Now calculate the CFM for this tach
379 // CFMi = Ci * Qmaxi * TACHi
380 totalCFM = ci * maxCFM * pwmPercent;
381 totalCFM *= tachs.size();
382 // divide by 100 since pwm is in percent
383 totalCFM /= 100;
384
James Feist13452092019-03-07 16:38:12 -0800385 if (pwmPercent <= 0)
386 {
387 break;
388 }
389 }
James Feist52427952019-04-05 14:23:35 -0700390
James Feist13452092019-03-07 16:38:12 -0800391 return pwmPercent;
392}
393
James Feistb2eb3f52018-12-04 16:17:50 -0800394bool CFMSensor::calculate(double& value)
395{
396 double totalCFM = 0;
397 for (const std::string& tachName : tachs)
398 {
James Feist9566bfa2019-01-29 15:31:23 -0800399
James Feistb2eb3f52018-12-04 16:17:50 -0800400 auto findReading = std::find_if(
401 tachReadings.begin(), tachReadings.end(), [&](const auto& item) {
402 return boost::ends_with(item.first, tachName);
403 });
404 auto findRange = std::find_if(
405 tachRanges.begin(), tachRanges.end(), [&](const auto& item) {
406 return boost::ends_with(item.first, tachName);
407 });
408 if (findReading == tachReadings.end())
409 {
Ed Tanous8a57ec02020-10-09 12:46:52 -0700410 if constexpr (debug)
James Feista96329f2019-01-24 10:08:27 -0800411 {
412 std::cerr << "Can't find " << tachName << "in readings\n";
413 }
James Feist9566bfa2019-01-29 15:31:23 -0800414 continue; // haven't gotten a reading
James Feistb2eb3f52018-12-04 16:17:50 -0800415 }
416
417 if (findRange == tachRanges.end())
418 {
James Feist523828e2019-03-04 14:38:37 -0800419 std::cerr << "Can't find " << tachName << " in ranges\n";
James Feistb2eb3f52018-12-04 16:17:50 -0800420 return false; // haven't gotten a max / min
421 }
422
423 // avoid divide by 0
424 if (findRange->second.second == 0)
425 {
426 std::cerr << "Tach Max Set to 0 " << tachName << "\n";
427 return false;
428 }
429
430 double rpm = findReading->second;
431
432 // for now assume the min for a fan is always 0, divide by max to get
433 // percent and mult by 100
434 rpm /= findRange->second.second;
435 rpm *= 100;
436
Ed Tanous8a57ec02020-10-09 12:46:52 -0700437 if constexpr (debug)
James Feistb2eb3f52018-12-04 16:17:50 -0800438 {
439 std::cout << "Tach " << tachName << "at " << rpm << "\n";
440 }
441
442 // Do a linear interpolation to get Ci
443 // Ci = C1 + (C2 - C1)/(RPM2 - RPM1) * (TACHi - TACH1)
444
445 double ci = 0;
446 if (rpm == 0)
447 {
448 ci = 0;
449 }
450 else if (rpm < tachMinPercent)
451 {
452 ci = c1;
453 }
454 else if (rpm > tachMaxPercent)
455 {
456 ci = c2;
457 }
458 else
459 {
460 ci = c1 + (((c2 - c1) * (rpm - tachMinPercent)) /
461 (tachMaxPercent - tachMinPercent));
462 }
463
464 // Now calculate the CFM for this tach
465 // CFMi = Ci * Qmaxi * TACHi
466 totalCFM += ci * maxCFM * rpm;
Ed Tanous8a57ec02020-10-09 12:46:52 -0700467 if constexpr (debug)
James Feista5e58722019-04-22 14:43:11 -0700468 {
469 std::cerr << "totalCFM = " << totalCFM << "\n";
470 std::cerr << "Ci " << ci << " MaxCFM " << maxCFM << " rpm " << rpm
471 << "\n";
472 std::cerr << "c1 " << c1 << " c2 " << c2 << " max "
473 << tachMaxPercent << " min " << tachMinPercent << "\n";
474 }
James Feistb2eb3f52018-12-04 16:17:50 -0800475 }
476
477 // divide by 100 since rpm is in percent
478 value = totalCFM / 100;
Ed Tanous8a57ec02020-10-09 12:46:52 -0700479 if constexpr (debug)
James Feista5e58722019-04-22 14:43:11 -0700480 {
481 std::cerr << "cfm value = " << value << "\n";
482 }
James Feist9566bfa2019-01-29 15:31:23 -0800483 return true;
James Feistb2eb3f52018-12-04 16:17:50 -0800484}
485
486static constexpr double exitAirMaxReading = 127;
487static constexpr double exitAirMinReading = -128;
James Feistbc896df2018-11-26 16:28:17 -0800488ExitAirTempSensor::ExitAirTempSensor(
489 std::shared_ptr<sdbusplus::asio::connection>& conn,
James Feistb2eb3f52018-12-04 16:17:50 -0800490 const std::string& sensorName, const std::string& sensorConfiguration,
James Feistbc896df2018-11-26 16:28:17 -0800491 sdbusplus::asio::object_server& objectServer,
James Feistb839c052019-05-15 10:25:24 -0700492 std::vector<thresholds::Threshold>&& thresholdData) :
James Feistb2eb3f52018-12-04 16:17:50 -0800493 Sensor(boost::replace_all_copy(sensorName, " ", "_"),
James Feist930fcde2019-05-28 12:58:43 -0700494 std::move(thresholdData), sensorConfiguration,
495 "xyz.openbmc_project.Configuration.ExitAirTemp", exitAirMaxReading,
James Feiste3338522020-09-15 15:40:30 -0700496 exitAirMinReading, conn, PowerState::on),
497 std::enable_shared_from_this<ExitAirTempSensor>(), objServer(objectServer)
James Feistbc896df2018-11-26 16:28:17 -0800498{
499 sensorInterface = objectServer.add_interface(
500 "/xyz/openbmc_project/sensors/temperature/" + name,
501 "xyz.openbmc_project.Sensor.Value");
502
503 if (thresholds::hasWarningInterface(thresholds))
504 {
505 thresholdInterfaceWarning = objectServer.add_interface(
506 "/xyz/openbmc_project/sensors/temperature/" + name,
507 "xyz.openbmc_project.Sensor.Threshold.Warning");
508 }
509 if (thresholds::hasCriticalInterface(thresholds))
510 {
511 thresholdInterfaceCritical = objectServer.add_interface(
512 "/xyz/openbmc_project/sensors/temperature/" + name,
513 "xyz.openbmc_project.Sensor.Threshold.Critical");
514 }
James Feist078f2322019-03-08 11:09:05 -0800515 association = objectServer.add_interface(
516 "/xyz/openbmc_project/sensors/temperature/" + name,
James Feist2adc95c2019-09-30 14:55:28 -0700517 association::interface);
James Feistbc896df2018-11-26 16:28:17 -0800518 setInitialProperties(conn);
James Feistbc896df2018-11-26 16:28:17 -0800519}
520
521ExitAirTempSensor::~ExitAirTempSensor()
522{
James Feist523828e2019-03-04 14:38:37 -0800523 objServer.remove_interface(thresholdInterfaceWarning);
524 objServer.remove_interface(thresholdInterfaceCritical);
525 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800526 objServer.remove_interface(association);
James Feistbc896df2018-11-26 16:28:17 -0800527}
528
529void ExitAirTempSensor::setupMatches(void)
530{
James Feistb2eb3f52018-12-04 16:17:50 -0800531 constexpr const std::array<const char*, 2> matchTypes = {
532 "power", inletTemperatureSensor};
James Feistbc896df2018-11-26 16:28:17 -0800533
James Feist9a25ed42019-10-15 15:43:44 -0700534 std::shared_ptr<ExitAirTempSensor> self = shared_from_this();
James Feistb2eb3f52018-12-04 16:17:50 -0800535 for (const std::string& type : matchTypes)
James Feistbc896df2018-11-26 16:28:17 -0800536 {
James Feistb2eb3f52018-12-04 16:17:50 -0800537 setupSensorMatch(matches, *dbusConnection, type,
James Feist9a25ed42019-10-15 15:43:44 -0700538 [self, type](const double& value,
James Feistb2eb3f52018-12-04 16:17:50 -0800539 sdbusplus::message::message& message) {
540 if (type == "power")
541 {
James Feista5e58722019-04-22 14:43:11 -0700542 std::string path = message.get_path();
543 if (path.find("PS") != std::string::npos &&
544 boost::ends_with(path, "Input_Power"))
545 {
James Feist9a25ed42019-10-15 15:43:44 -0700546 self->powerReadings[message.get_path()] =
547 value;
James Feista5e58722019-04-22 14:43:11 -0700548 }
James Feistb2eb3f52018-12-04 16:17:50 -0800549 }
550 else if (type == inletTemperatureSensor)
551 {
James Feist9a25ed42019-10-15 15:43:44 -0700552 self->inletTemp = value;
James Feistb2eb3f52018-12-04 16:17:50 -0800553 }
James Feist9a25ed42019-10-15 15:43:44 -0700554 self->updateReading();
James Feistb2eb3f52018-12-04 16:17:50 -0800555 });
James Feistbc896df2018-11-26 16:28:17 -0800556 }
James Feist9566bfa2019-01-29 15:31:23 -0800557 dbusConnection->async_method_call(
James Feist9a25ed42019-10-15 15:43:44 -0700558 [self](boost::system::error_code ec,
James Feist9566bfa2019-01-29 15:31:23 -0800559 const std::variant<double>& value) {
560 if (ec)
561 {
562 // sensor not ready yet
563 return;
564 }
565
James Feist9a25ed42019-10-15 15:43:44 -0700566 self->inletTemp = std::visit(VariantToDoubleVisitor(), value);
James Feist9566bfa2019-01-29 15:31:23 -0800567 },
568 "xyz.openbmc_project.HwmonTempSensor",
569 std::string("/xyz/openbmc_project/sensors/") + inletTemperatureSensor,
James Feista5e58722019-04-22 14:43:11 -0700570 properties::interface, properties::get, sensorValueInterface, "Value");
571 dbusConnection->async_method_call(
James Feist9a25ed42019-10-15 15:43:44 -0700572 [self](boost::system::error_code ec, const GetSubTreeType& subtree) {
James Feista5e58722019-04-22 14:43:11 -0700573 if (ec)
574 {
575 std::cerr << "Error contacting mapper\n";
576 return;
577 }
578 for (const auto& item : subtree)
579 {
580 size_t lastSlash = item.first.rfind("/");
581 if (lastSlash == std::string::npos ||
582 lastSlash == item.first.size() || !item.second.size())
583 {
584 continue;
585 }
586 std::string sensorName = item.first.substr(lastSlash + 1);
587 if (boost::starts_with(sensorName, "PS") &&
588 boost::ends_with(sensorName, "Input_Power"))
589 {
590 const std::string& path = item.first;
James Feist9a25ed42019-10-15 15:43:44 -0700591 self->dbusConnection->async_method_call(
592 [self, path](boost::system::error_code ec,
James Feista5e58722019-04-22 14:43:11 -0700593 const std::variant<double>& value) {
594 if (ec)
595 {
596 std::cerr << "Error getting value from " << path
597 << "\n";
598 }
599
600 double reading =
601 std::visit(VariantToDoubleVisitor(), value);
Ed Tanous8a57ec02020-10-09 12:46:52 -0700602 if constexpr (debug)
James Feista5e58722019-04-22 14:43:11 -0700603 {
604 std::cerr << path << "Reading " << reading
605 << "\n";
606 }
James Feist9a25ed42019-10-15 15:43:44 -0700607 self->powerReadings[path] = reading;
James Feista5e58722019-04-22 14:43:11 -0700608 },
609 item.second[0].first, item.first, properties::interface,
610 properties::get, sensorValueInterface, "Value");
611 }
612 }
613 },
614 mapper::busName, mapper::path, mapper::interface, mapper::subtree,
615 "/xyz/openbmc_project/sensors/power", 0,
616 std::array<const char*, 1>{sensorValueInterface});
James Feistbc896df2018-11-26 16:28:17 -0800617}
618
619void ExitAirTempSensor::updateReading(void)
620{
621
622 double val = 0.0;
623 if (calculate(val))
624 {
James Feist18af4232019-03-13 11:14:00 -0700625 val = std::floor(val + 0.5);
James Feistbc896df2018-11-26 16:28:17 -0800626 updateValue(val);
627 }
628 else
629 {
630 updateValue(std::numeric_limits<double>::quiet_NaN());
631 }
632}
633
James Feistb2eb3f52018-12-04 16:17:50 -0800634double ExitAirTempSensor::getTotalCFM(void)
James Feistbc896df2018-11-26 16:28:17 -0800635{
James Feistb2eb3f52018-12-04 16:17:50 -0800636 double sum = 0;
637 for (auto& sensor : cfmSensors)
James Feistbc896df2018-11-26 16:28:17 -0800638 {
James Feistb2eb3f52018-12-04 16:17:50 -0800639 double reading = 0;
640 if (!sensor->calculate(reading))
James Feistbc896df2018-11-26 16:28:17 -0800641 {
James Feistbc896df2018-11-26 16:28:17 -0800642 return -1;
643 }
James Feistb2eb3f52018-12-04 16:17:50 -0800644 sum += reading;
James Feistbc896df2018-11-26 16:28:17 -0800645 }
James Feistb2eb3f52018-12-04 16:17:50 -0800646
647 return sum;
James Feistbc896df2018-11-26 16:28:17 -0800648}
649
650bool ExitAirTempSensor::calculate(double& val)
651{
James Feistae11cfc2019-05-07 15:01:20 -0700652 constexpr size_t maxErrorPrint = 1;
James Feistbc896df2018-11-26 16:28:17 -0800653 static bool firstRead = false;
James Feistae11cfc2019-05-07 15:01:20 -0700654 static size_t errorPrint = maxErrorPrint;
655
James Feistbc896df2018-11-26 16:28:17 -0800656 double cfm = getTotalCFM();
657 if (cfm <= 0)
658 {
659 std::cerr << "Error getting cfm\n";
660 return false;
661 }
662
663 // if there is an error getting inlet temp, return error
664 if (std::isnan(inletTemp))
665 {
James Feistae11cfc2019-05-07 15:01:20 -0700666 if (errorPrint > 0)
667 {
668 errorPrint--;
669 std::cerr << "Cannot get inlet temp\n";
670 }
James Feistbc896df2018-11-26 16:28:17 -0800671 val = 0;
672 return false;
673 }
674
675 // if fans are off, just make the exit temp equal to inlet
James Feist71d31b22019-01-02 16:57:54 -0800676 if (!isPowerOn())
James Feistbc896df2018-11-26 16:28:17 -0800677 {
678 val = inletTemp;
679 return true;
680 }
681
682 double totalPower = 0;
683 for (const auto& reading : powerReadings)
684 {
685 if (std::isnan(reading.second))
686 {
687 continue;
688 }
689 totalPower += reading.second;
690 }
691
692 // Calculate power correction factor
693 // Ci = CL + (CH - CL)/(QMax - QMin) * (CFM - QMin)
Ed Tanous8a57ec02020-10-09 12:46:52 -0700694 double powerFactor = 0.0;
James Feistbc896df2018-11-26 16:28:17 -0800695 if (cfm <= qMin)
696 {
697 powerFactor = powerFactorMin;
698 }
699 else if (cfm >= qMax)
700 {
701 powerFactor = powerFactorMax;
702 }
703 else
704 {
705 powerFactor = powerFactorMin + ((powerFactorMax - powerFactorMin) /
706 (qMax - qMin) * (cfm - qMin));
707 }
708
Ed Tanous8a57ec02020-10-09 12:46:52 -0700709 totalPower *= powerFactor;
James Feistbc896df2018-11-26 16:28:17 -0800710 totalPower += pOffset;
711
712 if (totalPower == 0)
713 {
James Feistae11cfc2019-05-07 15:01:20 -0700714 if (errorPrint > 0)
715 {
716 errorPrint--;
717 std::cerr << "total power 0\n";
718 }
James Feistbc896df2018-11-26 16:28:17 -0800719 val = 0;
720 return false;
721 }
722
Ed Tanous8a57ec02020-10-09 12:46:52 -0700723 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800724 {
725 std::cout << "Power Factor " << powerFactor << "\n";
726 std::cout << "Inlet Temp " << inletTemp << "\n";
727 std::cout << "Total Power" << totalPower << "\n";
728 }
729
730 // Calculate the exit air temp
731 // Texit = Tfp + (1.76 * TotalPower / CFM * Faltitude)
Ed Tanous8a57ec02020-10-09 12:46:52 -0700732 double reading = 1.76 * totalPower * altitudeFactor;
James Feistbc896df2018-11-26 16:28:17 -0800733 reading /= cfm;
734 reading += inletTemp;
735
Ed Tanous8a57ec02020-10-09 12:46:52 -0700736 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800737 {
738 std::cout << "Reading 1: " << reading << "\n";
739 }
740
741 // Now perform the exponential average
742 // Calculate alpha based on SDR values and CFM
743 // Ai = As + (Af - As)/(QMax - QMin) * (CFM - QMin)
744
745 double alpha = 0.0;
746 if (cfm < qMin)
747 {
748 alpha = alphaS;
749 }
750 else if (cfm >= qMax)
751 {
752 alpha = alphaF;
753 }
754 else
755 {
756 alpha = alphaS + ((alphaF - alphaS) * (cfm - qMin) / (qMax - qMin));
757 }
758
759 auto time = std::chrono::system_clock::now();
760 if (!firstRead)
761 {
762 firstRead = true;
763 lastTime = time;
764 lastReading = reading;
765 }
766 double alphaDT =
767 std::chrono::duration_cast<std::chrono::seconds>(time - lastTime)
768 .count() *
769 alpha;
770
771 // cap at 1.0 or the below fails
772 if (alphaDT > 1.0)
773 {
774 alphaDT = 1.0;
775 }
776
Ed Tanous8a57ec02020-10-09 12:46:52 -0700777 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800778 {
779 std::cout << "AlphaDT: " << alphaDT << "\n";
780 }
781
782 reading = ((reading * alphaDT) + (lastReading * (1.0 - alphaDT)));
783
Ed Tanous8a57ec02020-10-09 12:46:52 -0700784 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800785 {
786 std::cout << "Reading 2: " << reading << "\n";
787 }
788
789 val = reading;
790 lastReading = reading;
791 lastTime = time;
James Feistae11cfc2019-05-07 15:01:20 -0700792 errorPrint = maxErrorPrint;
James Feistbc896df2018-11-26 16:28:17 -0800793 return true;
794}
795
796void ExitAirTempSensor::checkThresholds(void)
797{
798 thresholds::checkThresholds(this);
799}
800
James Feistbc896df2018-11-26 16:28:17 -0800801static void loadVariantPathArray(
802 const boost::container::flat_map<std::string, BasicVariantType>& data,
803 const std::string& key, std::vector<std::string>& resp)
804{
805 auto it = data.find(key);
806 if (it == data.end())
807 {
808 std::cerr << "Configuration missing " << key << "\n";
809 throw std::invalid_argument("Key Missing");
810 }
811 BasicVariantType copy = it->second;
James Feist3eb82622019-02-08 13:10:22 -0800812 std::vector<std::string> config = std::get<std::vector<std::string>>(copy);
James Feistbc896df2018-11-26 16:28:17 -0800813 for (auto& str : config)
814 {
815 boost::replace_all(str, " ", "_");
816 }
817 resp = std::move(config);
818}
819
820void createSensor(sdbusplus::asio::object_server& objectServer,
James Feistb2eb3f52018-12-04 16:17:50 -0800821 std::shared_ptr<ExitAirTempSensor>& exitAirSensor,
James Feistbc896df2018-11-26 16:28:17 -0800822 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
823{
824 if (!dbusConnection)
825 {
826 std::cerr << "Connection not created\n";
827 return;
828 }
James Feist655f3762020-10-05 15:28:15 -0700829 auto getter = std::make_shared<GetSensorConfiguration>(
830 dbusConnection,
831 std::move([&objectServer, &dbusConnection,
832 &exitAirSensor](const ManagedObjectType& resp) {
James Feist9a25ed42019-10-15 15:43:44 -0700833 cfmSensors.clear();
James Feistbc896df2018-11-26 16:28:17 -0800834 for (const auto& pathPair : resp)
835 {
836 for (const auto& entry : pathPair.second)
837 {
838 if (entry.first == exitAirIface)
839 {
James Feistbc896df2018-11-26 16:28:17 -0800840 // thresholds should be under the same path
841 std::vector<thresholds::Threshold> sensorThresholds;
842 parseThresholdsFromConfig(pathPair.second,
843 sensorThresholds);
James Feistbc896df2018-11-26 16:28:17 -0800844
James Feist523828e2019-03-04 14:38:37 -0800845 std::string name =
846 loadVariant<std::string>(entry.second, "Name");
847 exitAirSensor = std::make_shared<ExitAirTempSensor>(
848 dbusConnection, name, pathPair.first.str,
849 objectServer, std::move(sensorThresholds));
James Feistb2eb3f52018-12-04 16:17:50 -0800850 exitAirSensor->powerFactorMin =
851 loadVariant<double>(entry.second, "PowerFactorMin");
852 exitAirSensor->powerFactorMax =
853 loadVariant<double>(entry.second, "PowerFactorMax");
854 exitAirSensor->qMin =
855 loadVariant<double>(entry.second, "QMin");
856 exitAirSensor->qMax =
857 loadVariant<double>(entry.second, "QMax");
858 exitAirSensor->alphaS =
859 loadVariant<double>(entry.second, "AlphaS");
860 exitAirSensor->alphaF =
861 loadVariant<double>(entry.second, "AlphaF");
James Feistbc896df2018-11-26 16:28:17 -0800862 }
863 else if (entry.first == cfmIface)
864
865 {
James Feistb2eb3f52018-12-04 16:17:50 -0800866 // thresholds should be under the same path
867 std::vector<thresholds::Threshold> sensorThresholds;
868 parseThresholdsFromConfig(pathPair.second,
869 sensorThresholds);
870 std::string name =
871 loadVariant<std::string>(entry.second, "Name");
James Feist9a25ed42019-10-15 15:43:44 -0700872 auto sensor = std::make_shared<CFMSensor>(
James Feistb2eb3f52018-12-04 16:17:50 -0800873 dbusConnection, name, pathPair.first.str,
874 objectServer, std::move(sensorThresholds),
875 exitAirSensor);
876 loadVariantPathArray(entry.second, "Tachs",
877 sensor->tachs);
878 sensor->maxCFM =
879 loadVariant<double>(entry.second, "MaxCFM");
James Feistbc896df2018-11-26 16:28:17 -0800880
881 // change these into percent upon getting the data
James Feistb2eb3f52018-12-04 16:17:50 -0800882 sensor->c1 =
883 loadVariant<double>(entry.second, "C1") / 100;
884 sensor->c2 =
885 loadVariant<double>(entry.second, "C2") / 100;
886 sensor->tachMinPercent =
887 loadVariant<double>(entry.second,
888 "TachMinPercent") /
James Feistbc896df2018-11-26 16:28:17 -0800889 100;
James Feistb2eb3f52018-12-04 16:17:50 -0800890 sensor->tachMaxPercent =
891 loadVariant<double>(entry.second,
892 "TachMaxPercent") /
James Feistbc896df2018-11-26 16:28:17 -0800893 100;
James Feist13452092019-03-07 16:38:12 -0800894 sensor->createMaxCFMIface();
James Feist9a25ed42019-10-15 15:43:44 -0700895 sensor->setupMatches();
James Feistbc896df2018-11-26 16:28:17 -0800896
James Feistb2eb3f52018-12-04 16:17:50 -0800897 cfmSensors.emplace_back(std::move(sensor));
James Feistbc896df2018-11-26 16:28:17 -0800898 }
899 }
900 }
James Feistb2eb3f52018-12-04 16:17:50 -0800901 if (exitAirSensor)
James Feistbc896df2018-11-26 16:28:17 -0800902 {
James Feist9a25ed42019-10-15 15:43:44 -0700903 exitAirSensor->setupMatches();
James Feistb2eb3f52018-12-04 16:17:50 -0800904 exitAirSensor->updateReading();
James Feistbc896df2018-11-26 16:28:17 -0800905 }
James Feist655f3762020-10-05 15:28:15 -0700906 }));
907 getter->getConfiguration(
908 std::vector<std::string>(monitorIfaces.begin(), monitorIfaces.end()));
James Feistbc896df2018-11-26 16:28:17 -0800909}
910
James Feistb6c0b912019-07-09 12:21:44 -0700911int main()
James Feistbc896df2018-11-26 16:28:17 -0800912{
913
914 boost::asio::io_service io;
915 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
916 systemBus->request_name("xyz.openbmc_project.ExitAirTempSensor");
917 sdbusplus::asio::object_server objectServer(systemBus);
918 std::shared_ptr<ExitAirTempSensor> sensor =
919 nullptr; // wait until we find the config
920 std::vector<std::unique_ptr<sdbusplus::bus::match::match>> matches;
921
922 io.post([&]() { createSensor(objectServer, sensor, systemBus); });
923
924 boost::asio::deadline_timer configTimer(io);
925
926 std::function<void(sdbusplus::message::message&)> eventHandler =
James Feistb6c0b912019-07-09 12:21:44 -0700927 [&](sdbusplus::message::message&) {
James Feistbc896df2018-11-26 16:28:17 -0800928 configTimer.expires_from_now(boost::posix_time::seconds(1));
929 // create a timer because normally multiple properties change
930 configTimer.async_wait([&](const boost::system::error_code& ec) {
931 if (ec == boost::asio::error::operation_aborted)
932 {
933 return; // we're being canceled
934 }
935 createSensor(objectServer, sensor, systemBus);
936 if (!sensor)
937 {
938 std::cout << "Configuration not detected\n";
939 }
940 });
941 };
James Feistbc896df2018-11-26 16:28:17 -0800942 for (const char* type : monitorIfaces)
943 {
944 auto match = std::make_unique<sdbusplus::bus::match::match>(
945 static_cast<sdbusplus::bus::bus&>(*systemBus),
946 "type='signal',member='PropertiesChanged',path_namespace='" +
947 std::string(inventoryPath) + "',arg0namespace='" + type + "'",
948 eventHandler);
949 matches.emplace_back(std::move(match));
950 }
951
952 io.run();
953}