blob: a36c183c302e33213d9f4d71dd8977fe677cf406 [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
Brandon Kim66558232021-11-09 16:53:08 -080060constexpr const auto monitorIfaces{
61 std::to_array<const char*>({exitAirIface, cfmIface})};
James Feist655f3762020-10-05 15:28:15 -070062
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) :
Zhikui Renda98f092021-11-01 09:41:08 -0700165 Sensor(escapeName(sensorName), std::move(thresholdData),
166 sensorConfiguration, "xyz.openbmc_project.Configuration.ExitAirTemp",
167 false, false, cfmMaxReading, cfmMinReading, conn, PowerState::on),
Brad Bishopfbb44ad2019-11-08 09:42:37 -0500168 std::enable_shared_from_this<CFMSensor>(), parent(parent),
James Feiste3338522020-09-15 15:40:30 -0700169 objServer(objectServer)
James Feistb2eb3f52018-12-04 16:17:50 -0800170{
Basheer Ahmed Muddebihale5b867b2021-07-26 08:32:19 -0700171 sensorInterface = objectServer.add_interface(
172 "/xyz/openbmc_project/sensors/airflow/" + name,
173 "xyz.openbmc_project.Sensor.Value");
James Feistb2eb3f52018-12-04 16:17:50 -0800174
175 if (thresholds::hasWarningInterface(thresholds))
176 {
177 thresholdInterfaceWarning = objectServer.add_interface(
Basheer Ahmed Muddebihale5b867b2021-07-26 08:32:19 -0700178 "/xyz/openbmc_project/sensors/airflow/" + name,
James Feistb2eb3f52018-12-04 16:17:50 -0800179 "xyz.openbmc_project.Sensor.Threshold.Warning");
180 }
181 if (thresholds::hasCriticalInterface(thresholds))
182 {
183 thresholdInterfaceCritical = objectServer.add_interface(
Basheer Ahmed Muddebihale5b867b2021-07-26 08:32:19 -0700184 "/xyz/openbmc_project/sensors/airflow/" + name,
James Feistb2eb3f52018-12-04 16:17:50 -0800185 "xyz.openbmc_project.Sensor.Threshold.Critical");
186 }
James Feist078f2322019-03-08 11:09:05 -0800187
188 association = objectServer.add_interface(
Basheer Ahmed Muddebihale5b867b2021-07-26 08:32:19 -0700189 "/xyz/openbmc_project/sensors/airflow/" + name, association::interface);
James Feist078f2322019-03-08 11:09:05 -0800190
Zev Weiss6b6891c2021-04-22 02:46:21 -0500191 setInitialProperties(conn, sensor_paths::unitCFM);
James Feist9a25ed42019-10-15 15:43:44 -0700192
James Feist13452092019-03-07 16:38:12 -0800193 pwmLimitIface =
194 objectServer.add_interface("/xyz/openbmc_project/control/pwm_limit",
195 "xyz.openbmc_project.Control.PWMLimit");
196 cfmLimitIface =
197 objectServer.add_interface("/xyz/openbmc_project/control/MaxCFM",
198 "xyz.openbmc_project.Control.CFMLimit");
James Feist9a25ed42019-10-15 15:43:44 -0700199}
James Feist13452092019-03-07 16:38:12 -0800200
James Feist9a25ed42019-10-15 15:43:44 -0700201void CFMSensor::setupMatches()
202{
203
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700204 std::weak_ptr<CFMSensor> weakRef = weak_from_this();
Ed Tanous8a17c302021-09-02 15:07:11 -0700205 setupSensorMatch(
206 matches, *dbusConnection, "fan_tach",
207 [weakRef](const double& value, sdbusplus::message::message& message) {
208 auto self = weakRef.lock();
209 if (!self)
210 {
211 return;
212 }
213 self->tachReadings[message.get_path()] = value;
214 if (self->tachRanges.find(message.get_path()) ==
215 self->tachRanges.end())
216 {
217 // calls update reading after updating ranges
218 self->addTachRanges(message.get_sender(), message.get_path());
219 }
220 else
221 {
222 self->updateReading();
223 }
224 });
James Feist9a25ed42019-10-15 15:43:44 -0700225
226 dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700227 [weakRef](const boost::system::error_code ec,
228 const std::variant<double> cfmVariant) {
229 auto self = weakRef.lock();
230 if (!self)
231 {
232 return;
233 }
234
James Feist13452092019-03-07 16:38:12 -0800235 uint64_t maxRpm = 100;
236 if (!ec)
237 {
238
239 auto cfm = std::get_if<double>(&cfmVariant);
Josh Lehanff336992020-02-26 11:13:19 -0800240 if (cfm != nullptr && *cfm >= minSystemCfm)
James Feist13452092019-03-07 16:38:12 -0800241 {
James Feist9a25ed42019-10-15 15:43:44 -0700242 maxRpm = self->getMaxRpm(*cfm);
James Feist13452092019-03-07 16:38:12 -0800243 }
244 }
James Feist9a25ed42019-10-15 15:43:44 -0700245 self->pwmLimitIface->register_property("Limit", maxRpm);
246 self->pwmLimitIface->initialize();
247 setMaxPWM(self->dbusConnection, maxRpm);
James Feist13452092019-03-07 16:38:12 -0800248 },
249 settingsDaemon, cfmSettingPath, "org.freedesktop.DBus.Properties",
250 "Get", cfmSettingIface, "Limit");
251
252 matches.emplace_back(
James Feist9a25ed42019-10-15 15:43:44 -0700253 *dbusConnection,
James Feist13452092019-03-07 16:38:12 -0800254 "type='signal',"
255 "member='PropertiesChanged',interface='org."
256 "freedesktop.DBus.Properties',path='" +
257 std::string(cfmSettingPath) + "',arg0='" +
258 std::string(cfmSettingIface) + "'",
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700259 [weakRef](sdbusplus::message::message& message) {
260 auto self = weakRef.lock();
261 if (!self)
262 {
263 return;
264 }
James Feist13452092019-03-07 16:38:12 -0800265 boost::container::flat_map<std::string, std::variant<double>>
266 values;
267 std::string objectName;
268 message.read(objectName, values);
269 const auto findValue = values.find("Limit");
270 if (findValue == values.end())
271 {
272 return;
273 }
274 const auto reading = std::get_if<double>(&(findValue->second));
275 if (reading == nullptr)
276 {
277 std::cerr << "Got CFM Limit of wrong type\n";
278 return;
279 }
280 if (*reading < minSystemCfm && *reading != 0)
281 {
282 std::cerr << "Illegal CFM setting detected\n";
283 return;
284 }
James Feist9a25ed42019-10-15 15:43:44 -0700285 uint64_t maxRpm = self->getMaxRpm(*reading);
286 self->pwmLimitIface->set_property("Limit", maxRpm);
287 setMaxPWM(self->dbusConnection, maxRpm);
James Feist13452092019-03-07 16:38:12 -0800288 });
James Feistb2eb3f52018-12-04 16:17:50 -0800289}
290
James Feist9566bfa2019-01-29 15:31:23 -0800291CFMSensor::~CFMSensor()
292{
293 objServer.remove_interface(thresholdInterfaceWarning);
294 objServer.remove_interface(thresholdInterfaceCritical);
295 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800296 objServer.remove_interface(association);
James Feist13452092019-03-07 16:38:12 -0800297 objServer.remove_interface(cfmLimitIface);
298 objServer.remove_interface(pwmLimitIface);
299}
300
301void CFMSensor::createMaxCFMIface(void)
302{
James Feistb6c0b912019-07-09 12:21:44 -0700303 cfmLimitIface->register_property("Limit", c2 * maxCFM * tachs.size());
James Feist13452092019-03-07 16:38:12 -0800304 cfmLimitIface->initialize();
James Feist9566bfa2019-01-29 15:31:23 -0800305}
306
James Feistb2eb3f52018-12-04 16:17:50 -0800307void CFMSensor::addTachRanges(const std::string& serviceName,
308 const std::string& path)
309{
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700310 std::weak_ptr<CFMSensor> weakRef = weak_from_this();
James Feistb2eb3f52018-12-04 16:17:50 -0800311 dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700312 [weakRef,
313 path](const boost::system::error_code ec,
314 const boost::container::flat_map<std::string, BasicVariantType>&
315 data) {
James Feistb2eb3f52018-12-04 16:17:50 -0800316 if (ec)
317 {
318 std::cerr << "Error getting properties from " << path << "\n";
James Feist1ccdb5e2019-01-24 09:44:01 -0800319 return;
James Feistb2eb3f52018-12-04 16:17:50 -0800320 }
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700321 auto self = weakRef.lock();
322 if (!self)
323 {
324 return;
325 }
James Feistb2eb3f52018-12-04 16:17:50 -0800326 double max = loadVariant<double>(data, "MaxValue");
327 double min = loadVariant<double>(data, "MinValue");
James Feist9a25ed42019-10-15 15:43:44 -0700328 self->tachRanges[path] = std::make_pair(min, max);
329 self->updateReading();
James Feistb2eb3f52018-12-04 16:17:50 -0800330 },
331 serviceName, path, "org.freedesktop.DBus.Properties", "GetAll",
332 "xyz.openbmc_project.Sensor.Value");
333}
334
335void CFMSensor::checkThresholds(void)
336{
337 thresholds::checkThresholds(this);
338}
339
340void CFMSensor::updateReading(void)
341{
342 double val = 0.0;
343 if (calculate(val))
344 {
345 if (value != val && parent)
346 {
347 parent->updateReading();
348 }
349 updateValue(val);
350 }
351 else
352 {
353 updateValue(std::numeric_limits<double>::quiet_NaN());
354 }
355}
356
James Feist13452092019-03-07 16:38:12 -0800357uint64_t CFMSensor::getMaxRpm(uint64_t cfmMaxSetting)
358{
359 uint64_t pwmPercent = 100;
360 double totalCFM = std::numeric_limits<double>::max();
361 if (cfmMaxSetting == 0)
362 {
363 return pwmPercent;
364 }
365
James Feist52427952019-04-05 14:23:35 -0700366 bool firstLoop = true;
James Feist13452092019-03-07 16:38:12 -0800367 while (totalCFM > cfmMaxSetting)
368 {
James Feist52427952019-04-05 14:23:35 -0700369 if (firstLoop)
370 {
371 firstLoop = false;
372 }
373 else
374 {
375 pwmPercent--;
376 }
377
James Feist13452092019-03-07 16:38:12 -0800378 double ci = 0;
379 if (pwmPercent == 0)
380 {
381 ci = 0;
382 }
383 else if (pwmPercent < tachMinPercent)
384 {
385 ci = c1;
386 }
387 else if (pwmPercent > tachMaxPercent)
388 {
389 ci = c2;
390 }
391 else
392 {
393 ci = c1 + (((c2 - c1) * (pwmPercent - tachMinPercent)) /
394 (tachMaxPercent - tachMinPercent));
395 }
396
397 // Now calculate the CFM for this tach
398 // CFMi = Ci * Qmaxi * TACHi
399 totalCFM = ci * maxCFM * pwmPercent;
400 totalCFM *= tachs.size();
401 // divide by 100 since pwm is in percent
402 totalCFM /= 100;
403
James Feist13452092019-03-07 16:38:12 -0800404 if (pwmPercent <= 0)
405 {
406 break;
407 }
408 }
James Feist52427952019-04-05 14:23:35 -0700409
James Feist13452092019-03-07 16:38:12 -0800410 return pwmPercent;
411}
412
James Feistb2eb3f52018-12-04 16:17:50 -0800413bool CFMSensor::calculate(double& value)
414{
415 double totalCFM = 0;
416 for (const std::string& tachName : tachs)
417 {
James Feist9566bfa2019-01-29 15:31:23 -0800418
James Feistb2eb3f52018-12-04 16:17:50 -0800419 auto findReading = std::find_if(
420 tachReadings.begin(), tachReadings.end(), [&](const auto& item) {
421 return boost::ends_with(item.first, tachName);
422 });
423 auto findRange = std::find_if(
424 tachRanges.begin(), tachRanges.end(), [&](const auto& item) {
425 return boost::ends_with(item.first, tachName);
426 });
427 if (findReading == tachReadings.end())
428 {
Ed Tanous8a57ec02020-10-09 12:46:52 -0700429 if constexpr (debug)
James Feista96329f2019-01-24 10:08:27 -0800430 {
431 std::cerr << "Can't find " << tachName << "in readings\n";
432 }
James Feist9566bfa2019-01-29 15:31:23 -0800433 continue; // haven't gotten a reading
James Feistb2eb3f52018-12-04 16:17:50 -0800434 }
435
436 if (findRange == tachRanges.end())
437 {
James Feist523828e2019-03-04 14:38:37 -0800438 std::cerr << "Can't find " << tachName << " in ranges\n";
James Feistb2eb3f52018-12-04 16:17:50 -0800439 return false; // haven't gotten a max / min
440 }
441
442 // avoid divide by 0
443 if (findRange->second.second == 0)
444 {
445 std::cerr << "Tach Max Set to 0 " << tachName << "\n";
446 return false;
447 }
448
449 double rpm = findReading->second;
450
451 // for now assume the min for a fan is always 0, divide by max to get
452 // percent and mult by 100
453 rpm /= findRange->second.second;
454 rpm *= 100;
455
Ed Tanous8a57ec02020-10-09 12:46:52 -0700456 if constexpr (debug)
James Feistb2eb3f52018-12-04 16:17:50 -0800457 {
458 std::cout << "Tach " << tachName << "at " << rpm << "\n";
459 }
460
461 // Do a linear interpolation to get Ci
462 // Ci = C1 + (C2 - C1)/(RPM2 - RPM1) * (TACHi - TACH1)
463
464 double ci = 0;
465 if (rpm == 0)
466 {
467 ci = 0;
468 }
469 else if (rpm < tachMinPercent)
470 {
471 ci = c1;
472 }
473 else if (rpm > tachMaxPercent)
474 {
475 ci = c2;
476 }
477 else
478 {
479 ci = c1 + (((c2 - c1) * (rpm - tachMinPercent)) /
480 (tachMaxPercent - tachMinPercent));
481 }
482
483 // Now calculate the CFM for this tach
484 // CFMi = Ci * Qmaxi * TACHi
485 totalCFM += ci * maxCFM * rpm;
Ed Tanous8a57ec02020-10-09 12:46:52 -0700486 if constexpr (debug)
James Feista5e58722019-04-22 14:43:11 -0700487 {
488 std::cerr << "totalCFM = " << totalCFM << "\n";
489 std::cerr << "Ci " << ci << " MaxCFM " << maxCFM << " rpm " << rpm
490 << "\n";
491 std::cerr << "c1 " << c1 << " c2 " << c2 << " max "
492 << tachMaxPercent << " min " << tachMinPercent << "\n";
493 }
James Feistb2eb3f52018-12-04 16:17:50 -0800494 }
495
496 // divide by 100 since rpm is in percent
497 value = totalCFM / 100;
Ed Tanous8a57ec02020-10-09 12:46:52 -0700498 if constexpr (debug)
James Feista5e58722019-04-22 14:43:11 -0700499 {
500 std::cerr << "cfm value = " << value << "\n";
501 }
James Feist9566bfa2019-01-29 15:31:23 -0800502 return true;
James Feistb2eb3f52018-12-04 16:17:50 -0800503}
504
505static constexpr double exitAirMaxReading = 127;
506static constexpr double exitAirMinReading = -128;
James Feistbc896df2018-11-26 16:28:17 -0800507ExitAirTempSensor::ExitAirTempSensor(
508 std::shared_ptr<sdbusplus::asio::connection>& conn,
James Feistb2eb3f52018-12-04 16:17:50 -0800509 const std::string& sensorName, const std::string& sensorConfiguration,
James Feistbc896df2018-11-26 16:28:17 -0800510 sdbusplus::asio::object_server& objectServer,
James Feistb839c052019-05-15 10:25:24 -0700511 std::vector<thresholds::Threshold>&& thresholdData) :
Zhikui Renda98f092021-11-01 09:41:08 -0700512 Sensor(escapeName(sensorName), std::move(thresholdData),
513 sensorConfiguration, "xyz.openbmc_project.Configuration.ExitAirTemp",
514 false, false, exitAirMaxReading, exitAirMinReading, conn,
515 PowerState::on),
James Feiste3338522020-09-15 15:40:30 -0700516 std::enable_shared_from_this<ExitAirTempSensor>(), objServer(objectServer)
James Feistbc896df2018-11-26 16:28:17 -0800517{
518 sensorInterface = objectServer.add_interface(
519 "/xyz/openbmc_project/sensors/temperature/" + name,
520 "xyz.openbmc_project.Sensor.Value");
521
522 if (thresholds::hasWarningInterface(thresholds))
523 {
524 thresholdInterfaceWarning = objectServer.add_interface(
525 "/xyz/openbmc_project/sensors/temperature/" + name,
526 "xyz.openbmc_project.Sensor.Threshold.Warning");
527 }
528 if (thresholds::hasCriticalInterface(thresholds))
529 {
530 thresholdInterfaceCritical = objectServer.add_interface(
531 "/xyz/openbmc_project/sensors/temperature/" + name,
532 "xyz.openbmc_project.Sensor.Threshold.Critical");
533 }
James Feist078f2322019-03-08 11:09:05 -0800534 association = objectServer.add_interface(
535 "/xyz/openbmc_project/sensors/temperature/" + name,
James Feist2adc95c2019-09-30 14:55:28 -0700536 association::interface);
Zev Weiss6b6891c2021-04-22 02:46:21 -0500537 setInitialProperties(conn, sensor_paths::unitDegreesC);
James Feistbc896df2018-11-26 16:28:17 -0800538}
539
540ExitAirTempSensor::~ExitAirTempSensor()
541{
James Feist523828e2019-03-04 14:38:37 -0800542 objServer.remove_interface(thresholdInterfaceWarning);
543 objServer.remove_interface(thresholdInterfaceCritical);
544 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800545 objServer.remove_interface(association);
James Feistbc896df2018-11-26 16:28:17 -0800546}
547
548void ExitAirTempSensor::setupMatches(void)
549{
Brandon Kim66558232021-11-09 16:53:08 -0800550 constexpr const auto matchTypes{
551 std::to_array<const char*>({"power", inletTemperatureSensor})};
James Feistbc896df2018-11-26 16:28:17 -0800552
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700553 std::weak_ptr<ExitAirTempSensor> weakRef = weak_from_this();
Ed Tanous13b63f82021-05-11 16:12:52 -0700554 for (const std::string type : matchTypes)
James Feistbc896df2018-11-26 16:28:17 -0800555 {
James Feistb2eb3f52018-12-04 16:17:50 -0800556 setupSensorMatch(matches, *dbusConnection, type,
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700557 [weakRef, type](const double& value,
558 sdbusplus::message::message& message) {
559 auto self = weakRef.lock();
560 if (!self)
561 {
562 return;
563 }
James Feistb2eb3f52018-12-04 16:17:50 -0800564 if (type == "power")
565 {
James Feista5e58722019-04-22 14:43:11 -0700566 std::string path = message.get_path();
567 if (path.find("PS") != std::string::npos &&
568 boost::ends_with(path, "Input_Power"))
569 {
James Feist9a25ed42019-10-15 15:43:44 -0700570 self->powerReadings[message.get_path()] =
571 value;
James Feista5e58722019-04-22 14:43:11 -0700572 }
James Feistb2eb3f52018-12-04 16:17:50 -0800573 }
574 else if (type == inletTemperatureSensor)
575 {
James Feist9a25ed42019-10-15 15:43:44 -0700576 self->inletTemp = value;
James Feistb2eb3f52018-12-04 16:17:50 -0800577 }
James Feist9a25ed42019-10-15 15:43:44 -0700578 self->updateReading();
James Feistb2eb3f52018-12-04 16:17:50 -0800579 });
James Feistbc896df2018-11-26 16:28:17 -0800580 }
James Feist9566bfa2019-01-29 15:31:23 -0800581 dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700582 [weakRef](boost::system::error_code ec,
583 const std::variant<double>& value) {
James Feist9566bfa2019-01-29 15:31:23 -0800584 if (ec)
585 {
586 // sensor not ready yet
587 return;
588 }
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700589 auto self = weakRef.lock();
590 if (!self)
591 {
592 return;
593 }
James Feist9a25ed42019-10-15 15:43:44 -0700594 self->inletTemp = std::visit(VariantToDoubleVisitor(), value);
James Feist9566bfa2019-01-29 15:31:23 -0800595 },
596 "xyz.openbmc_project.HwmonTempSensor",
597 std::string("/xyz/openbmc_project/sensors/") + inletTemperatureSensor,
James Feista5e58722019-04-22 14:43:11 -0700598 properties::interface, properties::get, sensorValueInterface, "Value");
599 dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700600 [weakRef](boost::system::error_code ec, const GetSubTreeType& subtree) {
James Feista5e58722019-04-22 14:43:11 -0700601 if (ec)
602 {
603 std::cerr << "Error contacting mapper\n";
604 return;
605 }
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700606 auto self = weakRef.lock();
607 if (!self)
608 {
609 return;
610 }
James Feista5e58722019-04-22 14:43:11 -0700611 for (const auto& item : subtree)
612 {
613 size_t lastSlash = item.first.rfind("/");
614 if (lastSlash == std::string::npos ||
615 lastSlash == item.first.size() || !item.second.size())
616 {
617 continue;
618 }
619 std::string sensorName = item.first.substr(lastSlash + 1);
620 if (boost::starts_with(sensorName, "PS") &&
621 boost::ends_with(sensorName, "Input_Power"))
622 {
623 const std::string& path = item.first;
James Feist9a25ed42019-10-15 15:43:44 -0700624 self->dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700625 [weakRef, path](boost::system::error_code ec,
626 const std::variant<double>& value) {
James Feista5e58722019-04-22 14:43:11 -0700627 if (ec)
628 {
629 std::cerr << "Error getting value from " << path
630 << "\n";
631 }
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700632 auto self = weakRef.lock();
633 if (!self)
634 {
635 return;
636 }
James Feista5e58722019-04-22 14:43:11 -0700637 double reading =
638 std::visit(VariantToDoubleVisitor(), value);
Ed Tanous8a57ec02020-10-09 12:46:52 -0700639 if constexpr (debug)
James Feista5e58722019-04-22 14:43:11 -0700640 {
641 std::cerr << path << "Reading " << reading
642 << "\n";
643 }
James Feist9a25ed42019-10-15 15:43:44 -0700644 self->powerReadings[path] = reading;
James Feista5e58722019-04-22 14:43:11 -0700645 },
646 item.second[0].first, item.first, properties::interface,
647 properties::get, sensorValueInterface, "Value");
648 }
649 }
650 },
651 mapper::busName, mapper::path, mapper::interface, mapper::subtree,
652 "/xyz/openbmc_project/sensors/power", 0,
653 std::array<const char*, 1>{sensorValueInterface});
James Feistbc896df2018-11-26 16:28:17 -0800654}
655
656void ExitAirTempSensor::updateReading(void)
657{
658
659 double val = 0.0;
660 if (calculate(val))
661 {
James Feist18af4232019-03-13 11:14:00 -0700662 val = std::floor(val + 0.5);
James Feistbc896df2018-11-26 16:28:17 -0800663 updateValue(val);
664 }
665 else
666 {
667 updateValue(std::numeric_limits<double>::quiet_NaN());
668 }
669}
670
James Feistb2eb3f52018-12-04 16:17:50 -0800671double ExitAirTempSensor::getTotalCFM(void)
James Feistbc896df2018-11-26 16:28:17 -0800672{
James Feistb2eb3f52018-12-04 16:17:50 -0800673 double sum = 0;
674 for (auto& sensor : cfmSensors)
James Feistbc896df2018-11-26 16:28:17 -0800675 {
James Feistb2eb3f52018-12-04 16:17:50 -0800676 double reading = 0;
677 if (!sensor->calculate(reading))
James Feistbc896df2018-11-26 16:28:17 -0800678 {
James Feistbc896df2018-11-26 16:28:17 -0800679 return -1;
680 }
James Feistb2eb3f52018-12-04 16:17:50 -0800681 sum += reading;
James Feistbc896df2018-11-26 16:28:17 -0800682 }
James Feistb2eb3f52018-12-04 16:17:50 -0800683
684 return sum;
James Feistbc896df2018-11-26 16:28:17 -0800685}
686
687bool ExitAirTempSensor::calculate(double& val)
688{
Zhikui Ren12e3d672020-12-03 15:14:49 -0800689 constexpr size_t maxErrorPrint = 5;
James Feistbc896df2018-11-26 16:28:17 -0800690 static bool firstRead = false;
James Feistae11cfc2019-05-07 15:01:20 -0700691 static size_t errorPrint = maxErrorPrint;
692
James Feistbc896df2018-11-26 16:28:17 -0800693 double cfm = getTotalCFM();
694 if (cfm <= 0)
695 {
696 std::cerr << "Error getting cfm\n";
697 return false;
698 }
699
Zhikui Ren12e3d672020-12-03 15:14:49 -0800700 // Though cfm is not expected to be less than qMin normally,
701 // it is not a hard limit for exit air temp calculation.
702 // 50% qMin is chosen as a generic limit between providing
703 // a valid derived exit air temp and reporting exit air temp not available.
704 constexpr const double cfmLimitFactor = 0.5;
705 if (cfm < (qMin * cfmLimitFactor))
706 {
707 if (errorPrint > 0)
708 {
709 errorPrint--;
710 std::cerr << "cfm " << cfm << " is too low, expected qMin " << qMin
711 << "\n";
712 }
713 val = 0;
714 return false;
715 }
716
James Feistbc896df2018-11-26 16:28:17 -0800717 // if there is an error getting inlet temp, return error
718 if (std::isnan(inletTemp))
719 {
James Feistae11cfc2019-05-07 15:01:20 -0700720 if (errorPrint > 0)
721 {
722 errorPrint--;
723 std::cerr << "Cannot get inlet temp\n";
724 }
James Feistbc896df2018-11-26 16:28:17 -0800725 val = 0;
726 return false;
727 }
728
729 // if fans are off, just make the exit temp equal to inlet
James Feist71d31b22019-01-02 16:57:54 -0800730 if (!isPowerOn())
James Feistbc896df2018-11-26 16:28:17 -0800731 {
732 val = inletTemp;
733 return true;
734 }
735
736 double totalPower = 0;
737 for (const auto& reading : powerReadings)
738 {
739 if (std::isnan(reading.second))
740 {
741 continue;
742 }
743 totalPower += reading.second;
744 }
745
746 // Calculate power correction factor
747 // Ci = CL + (CH - CL)/(QMax - QMin) * (CFM - QMin)
Ed Tanous8a57ec02020-10-09 12:46:52 -0700748 double powerFactor = 0.0;
James Feistbc896df2018-11-26 16:28:17 -0800749 if (cfm <= qMin)
750 {
751 powerFactor = powerFactorMin;
752 }
753 else if (cfm >= qMax)
754 {
755 powerFactor = powerFactorMax;
756 }
757 else
758 {
759 powerFactor = powerFactorMin + ((powerFactorMax - powerFactorMin) /
760 (qMax - qMin) * (cfm - qMin));
761 }
762
Ed Tanous8a57ec02020-10-09 12:46:52 -0700763 totalPower *= powerFactor;
James Feistbc896df2018-11-26 16:28:17 -0800764 totalPower += pOffset;
765
766 if (totalPower == 0)
767 {
James Feistae11cfc2019-05-07 15:01:20 -0700768 if (errorPrint > 0)
769 {
770 errorPrint--;
771 std::cerr << "total power 0\n";
772 }
James Feistbc896df2018-11-26 16:28:17 -0800773 val = 0;
774 return false;
775 }
776
Ed Tanous8a57ec02020-10-09 12:46:52 -0700777 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800778 {
779 std::cout << "Power Factor " << powerFactor << "\n";
780 std::cout << "Inlet Temp " << inletTemp << "\n";
781 std::cout << "Total Power" << totalPower << "\n";
782 }
783
784 // Calculate the exit air temp
785 // Texit = Tfp + (1.76 * TotalPower / CFM * Faltitude)
Ed Tanous8a57ec02020-10-09 12:46:52 -0700786 double reading = 1.76 * totalPower * altitudeFactor;
James Feistbc896df2018-11-26 16:28:17 -0800787 reading /= cfm;
788 reading += inletTemp;
789
Ed Tanous8a57ec02020-10-09 12:46:52 -0700790 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800791 {
792 std::cout << "Reading 1: " << reading << "\n";
793 }
794
795 // Now perform the exponential average
796 // Calculate alpha based on SDR values and CFM
797 // Ai = As + (Af - As)/(QMax - QMin) * (CFM - QMin)
798
799 double alpha = 0.0;
800 if (cfm < qMin)
801 {
802 alpha = alphaS;
803 }
804 else if (cfm >= qMax)
805 {
806 alpha = alphaF;
807 }
808 else
809 {
810 alpha = alphaS + ((alphaF - alphaS) * (cfm - qMin) / (qMax - qMin));
811 }
812
Zhikui Ren12e3d672020-12-03 15:14:49 -0800813 auto time = std::chrono::steady_clock::now();
James Feistbc896df2018-11-26 16:28:17 -0800814 if (!firstRead)
815 {
816 firstRead = true;
817 lastTime = time;
818 lastReading = reading;
819 }
820 double alphaDT =
821 std::chrono::duration_cast<std::chrono::seconds>(time - lastTime)
822 .count() *
823 alpha;
824
825 // cap at 1.0 or the below fails
826 if (alphaDT > 1.0)
827 {
828 alphaDT = 1.0;
829 }
830
Ed Tanous8a57ec02020-10-09 12:46:52 -0700831 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800832 {
833 std::cout << "AlphaDT: " << alphaDT << "\n";
834 }
835
836 reading = ((reading * alphaDT) + (lastReading * (1.0 - alphaDT)));
837
Ed Tanous8a57ec02020-10-09 12:46:52 -0700838 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800839 {
840 std::cout << "Reading 2: " << reading << "\n";
841 }
842
843 val = reading;
844 lastReading = reading;
845 lastTime = time;
James Feistae11cfc2019-05-07 15:01:20 -0700846 errorPrint = maxErrorPrint;
James Feistbc896df2018-11-26 16:28:17 -0800847 return true;
848}
849
850void ExitAirTempSensor::checkThresholds(void)
851{
852 thresholds::checkThresholds(this);
853}
854
James Feistbc896df2018-11-26 16:28:17 -0800855static void loadVariantPathArray(
856 const boost::container::flat_map<std::string, BasicVariantType>& data,
857 const std::string& key, std::vector<std::string>& resp)
858{
859 auto it = data.find(key);
860 if (it == data.end())
861 {
862 std::cerr << "Configuration missing " << key << "\n";
863 throw std::invalid_argument("Key Missing");
864 }
865 BasicVariantType copy = it->second;
James Feist3eb82622019-02-08 13:10:22 -0800866 std::vector<std::string> config = std::get<std::vector<std::string>>(copy);
James Feistbc896df2018-11-26 16:28:17 -0800867 for (auto& str : config)
868 {
869 boost::replace_all(str, " ", "_");
870 }
871 resp = std::move(config);
872}
873
874void createSensor(sdbusplus::asio::object_server& objectServer,
James Feistb2eb3f52018-12-04 16:17:50 -0800875 std::shared_ptr<ExitAirTempSensor>& exitAirSensor,
James Feistbc896df2018-11-26 16:28:17 -0800876 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
877{
878 if (!dbusConnection)
879 {
880 std::cerr << "Connection not created\n";
881 return;
882 }
James Feist655f3762020-10-05 15:28:15 -0700883 auto getter = std::make_shared<GetSensorConfiguration>(
Ed Tanous8a17c302021-09-02 15:07:11 -0700884 dbusConnection, [&objectServer, &dbusConnection,
885 &exitAirSensor](const ManagedObjectType& resp) {
James Feist9a25ed42019-10-15 15:43:44 -0700886 cfmSensors.clear();
James Feistbc896df2018-11-26 16:28:17 -0800887 for (const auto& pathPair : resp)
888 {
889 for (const auto& entry : pathPair.second)
890 {
891 if (entry.first == exitAirIface)
892 {
James Feistbc896df2018-11-26 16:28:17 -0800893 // thresholds should be under the same path
894 std::vector<thresholds::Threshold> sensorThresholds;
895 parseThresholdsFromConfig(pathPair.second,
896 sensorThresholds);
James Feistbc896df2018-11-26 16:28:17 -0800897
James Feist523828e2019-03-04 14:38:37 -0800898 std::string name =
899 loadVariant<std::string>(entry.second, "Name");
900 exitAirSensor = std::make_shared<ExitAirTempSensor>(
901 dbusConnection, name, pathPair.first.str,
902 objectServer, std::move(sensorThresholds));
James Feistb2eb3f52018-12-04 16:17:50 -0800903 exitAirSensor->powerFactorMin =
904 loadVariant<double>(entry.second, "PowerFactorMin");
905 exitAirSensor->powerFactorMax =
906 loadVariant<double>(entry.second, "PowerFactorMax");
907 exitAirSensor->qMin =
908 loadVariant<double>(entry.second, "QMin");
909 exitAirSensor->qMax =
910 loadVariant<double>(entry.second, "QMax");
911 exitAirSensor->alphaS =
912 loadVariant<double>(entry.second, "AlphaS");
913 exitAirSensor->alphaF =
914 loadVariant<double>(entry.second, "AlphaF");
James Feistbc896df2018-11-26 16:28:17 -0800915 }
916 else if (entry.first == cfmIface)
917
918 {
James Feistb2eb3f52018-12-04 16:17:50 -0800919 // thresholds should be under the same path
920 std::vector<thresholds::Threshold> sensorThresholds;
921 parseThresholdsFromConfig(pathPair.second,
922 sensorThresholds);
923 std::string name =
924 loadVariant<std::string>(entry.second, "Name");
James Feist9a25ed42019-10-15 15:43:44 -0700925 auto sensor = std::make_shared<CFMSensor>(
James Feistb2eb3f52018-12-04 16:17:50 -0800926 dbusConnection, name, pathPair.first.str,
927 objectServer, std::move(sensorThresholds),
928 exitAirSensor);
929 loadVariantPathArray(entry.second, "Tachs",
930 sensor->tachs);
931 sensor->maxCFM =
932 loadVariant<double>(entry.second, "MaxCFM");
James Feistbc896df2018-11-26 16:28:17 -0800933
934 // change these into percent upon getting the data
James Feistb2eb3f52018-12-04 16:17:50 -0800935 sensor->c1 =
936 loadVariant<double>(entry.second, "C1") / 100;
937 sensor->c2 =
938 loadVariant<double>(entry.second, "C2") / 100;
939 sensor->tachMinPercent =
Zhikui Ren12e3d672020-12-03 15:14:49 -0800940 loadVariant<double>(entry.second, "TachMinPercent");
James Feistb2eb3f52018-12-04 16:17:50 -0800941 sensor->tachMaxPercent =
Zhikui Ren12e3d672020-12-03 15:14:49 -0800942 loadVariant<double>(entry.second, "TachMaxPercent");
James Feist13452092019-03-07 16:38:12 -0800943 sensor->createMaxCFMIface();
James Feist9a25ed42019-10-15 15:43:44 -0700944 sensor->setupMatches();
James Feistbc896df2018-11-26 16:28:17 -0800945
James Feistb2eb3f52018-12-04 16:17:50 -0800946 cfmSensors.emplace_back(std::move(sensor));
James Feistbc896df2018-11-26 16:28:17 -0800947 }
948 }
949 }
James Feistb2eb3f52018-12-04 16:17:50 -0800950 if (exitAirSensor)
James Feistbc896df2018-11-26 16:28:17 -0800951 {
James Feist9a25ed42019-10-15 15:43:44 -0700952 exitAirSensor->setupMatches();
James Feistb2eb3f52018-12-04 16:17:50 -0800953 exitAirSensor->updateReading();
James Feistbc896df2018-11-26 16:28:17 -0800954 }
Ed Tanous8a17c302021-09-02 15:07:11 -0700955 });
James Feist655f3762020-10-05 15:28:15 -0700956 getter->getConfiguration(
957 std::vector<std::string>(monitorIfaces.begin(), monitorIfaces.end()));
James Feistbc896df2018-11-26 16:28:17 -0800958}
959
James Feistb6c0b912019-07-09 12:21:44 -0700960int main()
James Feistbc896df2018-11-26 16:28:17 -0800961{
962
963 boost::asio::io_service io;
964 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
965 systemBus->request_name("xyz.openbmc_project.ExitAirTempSensor");
966 sdbusplus::asio::object_server objectServer(systemBus);
967 std::shared_ptr<ExitAirTempSensor> sensor =
968 nullptr; // wait until we find the config
969 std::vector<std::unique_ptr<sdbusplus::bus::match::match>> matches;
970
971 io.post([&]() { createSensor(objectServer, sensor, systemBus); });
972
973 boost::asio::deadline_timer configTimer(io);
974
975 std::function<void(sdbusplus::message::message&)> eventHandler =
James Feistb6c0b912019-07-09 12:21:44 -0700976 [&](sdbusplus::message::message&) {
James Feistbc896df2018-11-26 16:28:17 -0800977 configTimer.expires_from_now(boost::posix_time::seconds(1));
978 // create a timer because normally multiple properties change
979 configTimer.async_wait([&](const boost::system::error_code& ec) {
980 if (ec == boost::asio::error::operation_aborted)
981 {
982 return; // we're being canceled
983 }
984 createSensor(objectServer, sensor, systemBus);
985 if (!sensor)
986 {
987 std::cout << "Configuration not detected\n";
988 }
989 });
990 };
James Feistbc896df2018-11-26 16:28:17 -0800991 for (const char* type : monitorIfaces)
992 {
993 auto match = std::make_unique<sdbusplus::bus::match::match>(
994 static_cast<sdbusplus::bus::bus&>(*systemBus),
995 "type='signal',member='PropertiesChanged',path_namespace='" +
996 std::string(inventoryPath) + "',arg0namespace='" + type + "'",
997 eventHandler);
998 matches.emplace_back(std::move(match));
999 }
1000
Bruce Lee913d4d02021-07-22 10:18:42 +08001001 setupManufacturingModeMatch(*systemBus);
James Feistbc896df2018-11-26 16:28:17 -08001002 io.run();
Zhikui Ren8685b172021-06-29 15:16:52 -07001003 return 0;
James Feistbc896df2018-11-26 16:28:17 -08001004}