blob: 0165ffcca24d94b1ce20e7491c969800f582514d [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,
Bruce Lee1263c3d2021-06-04 15:16:33 +0800167 "xyz.openbmc_project.Configuration.ExitAirTemp", false,
168 cfmMaxReading, 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{
Basheer Ahmed Muddebihale5b867b2021-07-26 08:32:19 -0700172 sensorInterface = objectServer.add_interface(
173 "/xyz/openbmc_project/sensors/airflow/" + name,
174 "xyz.openbmc_project.Sensor.Value");
James Feistb2eb3f52018-12-04 16:17:50 -0800175
176 if (thresholds::hasWarningInterface(thresholds))
177 {
178 thresholdInterfaceWarning = objectServer.add_interface(
Basheer Ahmed Muddebihale5b867b2021-07-26 08:32:19 -0700179 "/xyz/openbmc_project/sensors/airflow/" + name,
James Feistb2eb3f52018-12-04 16:17:50 -0800180 "xyz.openbmc_project.Sensor.Threshold.Warning");
181 }
182 if (thresholds::hasCriticalInterface(thresholds))
183 {
184 thresholdInterfaceCritical = objectServer.add_interface(
Basheer Ahmed Muddebihale5b867b2021-07-26 08:32:19 -0700185 "/xyz/openbmc_project/sensors/airflow/" + name,
James Feistb2eb3f52018-12-04 16:17:50 -0800186 "xyz.openbmc_project.Sensor.Threshold.Critical");
187 }
James Feist078f2322019-03-08 11:09:05 -0800188
189 association = objectServer.add_interface(
Basheer Ahmed Muddebihale5b867b2021-07-26 08:32:19 -0700190 "/xyz/openbmc_project/sensors/airflow/" + name, association::interface);
James Feist078f2322019-03-08 11:09:05 -0800191
Zev Weiss6b6891c2021-04-22 02:46:21 -0500192 setInitialProperties(conn, sensor_paths::unitCFM);
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
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700205 std::weak_ptr<CFMSensor> weakRef = weak_from_this();
Ed Tanous8a17c302021-09-02 15:07:11 -0700206 setupSensorMatch(
207 matches, *dbusConnection, "fan_tach",
208 [weakRef](const double& value, sdbusplus::message::message& message) {
209 auto self = weakRef.lock();
210 if (!self)
211 {
212 return;
213 }
214 self->tachReadings[message.get_path()] = value;
215 if (self->tachRanges.find(message.get_path()) ==
216 self->tachRanges.end())
217 {
218 // calls update reading after updating ranges
219 self->addTachRanges(message.get_sender(), message.get_path());
220 }
221 else
222 {
223 self->updateReading();
224 }
225 });
James Feist9a25ed42019-10-15 15:43:44 -0700226
227 dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700228 [weakRef](const boost::system::error_code ec,
229 const std::variant<double> cfmVariant) {
230 auto self = weakRef.lock();
231 if (!self)
232 {
233 return;
234 }
235
James Feist13452092019-03-07 16:38:12 -0800236 uint64_t maxRpm = 100;
237 if (!ec)
238 {
239
240 auto cfm = std::get_if<double>(&cfmVariant);
Josh Lehanff336992020-02-26 11:13:19 -0800241 if (cfm != nullptr && *cfm >= minSystemCfm)
James Feist13452092019-03-07 16:38:12 -0800242 {
James Feist9a25ed42019-10-15 15:43:44 -0700243 maxRpm = self->getMaxRpm(*cfm);
James Feist13452092019-03-07 16:38:12 -0800244 }
245 }
James Feist9a25ed42019-10-15 15:43:44 -0700246 self->pwmLimitIface->register_property("Limit", maxRpm);
247 self->pwmLimitIface->initialize();
248 setMaxPWM(self->dbusConnection, maxRpm);
James Feist13452092019-03-07 16:38:12 -0800249 },
250 settingsDaemon, cfmSettingPath, "org.freedesktop.DBus.Properties",
251 "Get", cfmSettingIface, "Limit");
252
253 matches.emplace_back(
James Feist9a25ed42019-10-15 15:43:44 -0700254 *dbusConnection,
James Feist13452092019-03-07 16:38:12 -0800255 "type='signal',"
256 "member='PropertiesChanged',interface='org."
257 "freedesktop.DBus.Properties',path='" +
258 std::string(cfmSettingPath) + "',arg0='" +
259 std::string(cfmSettingIface) + "'",
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700260 [weakRef](sdbusplus::message::message& message) {
261 auto self = weakRef.lock();
262 if (!self)
263 {
264 return;
265 }
James Feist13452092019-03-07 16:38:12 -0800266 boost::container::flat_map<std::string, std::variant<double>>
267 values;
268 std::string objectName;
269 message.read(objectName, values);
270 const auto findValue = values.find("Limit");
271 if (findValue == values.end())
272 {
273 return;
274 }
275 const auto reading = std::get_if<double>(&(findValue->second));
276 if (reading == nullptr)
277 {
278 std::cerr << "Got CFM Limit of wrong type\n";
279 return;
280 }
281 if (*reading < minSystemCfm && *reading != 0)
282 {
283 std::cerr << "Illegal CFM setting detected\n";
284 return;
285 }
James Feist9a25ed42019-10-15 15:43:44 -0700286 uint64_t maxRpm = self->getMaxRpm(*reading);
287 self->pwmLimitIface->set_property("Limit", maxRpm);
288 setMaxPWM(self->dbusConnection, maxRpm);
James Feist13452092019-03-07 16:38:12 -0800289 });
James Feistb2eb3f52018-12-04 16:17:50 -0800290}
291
James Feist9566bfa2019-01-29 15:31:23 -0800292CFMSensor::~CFMSensor()
293{
294 objServer.remove_interface(thresholdInterfaceWarning);
295 objServer.remove_interface(thresholdInterfaceCritical);
296 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800297 objServer.remove_interface(association);
James Feist13452092019-03-07 16:38:12 -0800298 objServer.remove_interface(cfmLimitIface);
299 objServer.remove_interface(pwmLimitIface);
300}
301
302void CFMSensor::createMaxCFMIface(void)
303{
James Feistb6c0b912019-07-09 12:21:44 -0700304 cfmLimitIface->register_property("Limit", c2 * maxCFM * tachs.size());
James Feist13452092019-03-07 16:38:12 -0800305 cfmLimitIface->initialize();
James Feist9566bfa2019-01-29 15:31:23 -0800306}
307
James Feistb2eb3f52018-12-04 16:17:50 -0800308void CFMSensor::addTachRanges(const std::string& serviceName,
309 const std::string& path)
310{
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700311 std::weak_ptr<CFMSensor> weakRef = weak_from_this();
James Feistb2eb3f52018-12-04 16:17:50 -0800312 dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700313 [weakRef,
314 path](const boost::system::error_code ec,
315 const boost::container::flat_map<std::string, BasicVariantType>&
316 data) {
James Feistb2eb3f52018-12-04 16:17:50 -0800317 if (ec)
318 {
319 std::cerr << "Error getting properties from " << path << "\n";
James Feist1ccdb5e2019-01-24 09:44:01 -0800320 return;
James Feistb2eb3f52018-12-04 16:17:50 -0800321 }
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700322 auto self = weakRef.lock();
323 if (!self)
324 {
325 return;
326 }
James Feistb2eb3f52018-12-04 16:17:50 -0800327 double max = loadVariant<double>(data, "MaxValue");
328 double min = loadVariant<double>(data, "MinValue");
James Feist9a25ed42019-10-15 15:43:44 -0700329 self->tachRanges[path] = std::make_pair(min, max);
330 self->updateReading();
James Feistb2eb3f52018-12-04 16:17:50 -0800331 },
332 serviceName, path, "org.freedesktop.DBus.Properties", "GetAll",
333 "xyz.openbmc_project.Sensor.Value");
334}
335
336void CFMSensor::checkThresholds(void)
337{
338 thresholds::checkThresholds(this);
339}
340
341void CFMSensor::updateReading(void)
342{
343 double val = 0.0;
344 if (calculate(val))
345 {
346 if (value != val && parent)
347 {
348 parent->updateReading();
349 }
350 updateValue(val);
351 }
352 else
353 {
354 updateValue(std::numeric_limits<double>::quiet_NaN());
355 }
356}
357
James Feist13452092019-03-07 16:38:12 -0800358uint64_t CFMSensor::getMaxRpm(uint64_t cfmMaxSetting)
359{
360 uint64_t pwmPercent = 100;
361 double totalCFM = std::numeric_limits<double>::max();
362 if (cfmMaxSetting == 0)
363 {
364 return pwmPercent;
365 }
366
James Feist52427952019-04-05 14:23:35 -0700367 bool firstLoop = true;
James Feist13452092019-03-07 16:38:12 -0800368 while (totalCFM > cfmMaxSetting)
369 {
James Feist52427952019-04-05 14:23:35 -0700370 if (firstLoop)
371 {
372 firstLoop = false;
373 }
374 else
375 {
376 pwmPercent--;
377 }
378
James Feist13452092019-03-07 16:38:12 -0800379 double ci = 0;
380 if (pwmPercent == 0)
381 {
382 ci = 0;
383 }
384 else if (pwmPercent < tachMinPercent)
385 {
386 ci = c1;
387 }
388 else if (pwmPercent > tachMaxPercent)
389 {
390 ci = c2;
391 }
392 else
393 {
394 ci = c1 + (((c2 - c1) * (pwmPercent - tachMinPercent)) /
395 (tachMaxPercent - tachMinPercent));
396 }
397
398 // Now calculate the CFM for this tach
399 // CFMi = Ci * Qmaxi * TACHi
400 totalCFM = ci * maxCFM * pwmPercent;
401 totalCFM *= tachs.size();
402 // divide by 100 since pwm is in percent
403 totalCFM /= 100;
404
James Feist13452092019-03-07 16:38:12 -0800405 if (pwmPercent <= 0)
406 {
407 break;
408 }
409 }
James Feist52427952019-04-05 14:23:35 -0700410
James Feist13452092019-03-07 16:38:12 -0800411 return pwmPercent;
412}
413
James Feistb2eb3f52018-12-04 16:17:50 -0800414bool CFMSensor::calculate(double& value)
415{
416 double totalCFM = 0;
417 for (const std::string& tachName : tachs)
418 {
James Feist9566bfa2019-01-29 15:31:23 -0800419
James Feistb2eb3f52018-12-04 16:17:50 -0800420 auto findReading = std::find_if(
421 tachReadings.begin(), tachReadings.end(), [&](const auto& item) {
422 return boost::ends_with(item.first, tachName);
423 });
424 auto findRange = std::find_if(
425 tachRanges.begin(), tachRanges.end(), [&](const auto& item) {
426 return boost::ends_with(item.first, tachName);
427 });
428 if (findReading == tachReadings.end())
429 {
Ed Tanous8a57ec02020-10-09 12:46:52 -0700430 if constexpr (debug)
James Feista96329f2019-01-24 10:08:27 -0800431 {
432 std::cerr << "Can't find " << tachName << "in readings\n";
433 }
James Feist9566bfa2019-01-29 15:31:23 -0800434 continue; // haven't gotten a reading
James Feistb2eb3f52018-12-04 16:17:50 -0800435 }
436
437 if (findRange == tachRanges.end())
438 {
James Feist523828e2019-03-04 14:38:37 -0800439 std::cerr << "Can't find " << tachName << " in ranges\n";
James Feistb2eb3f52018-12-04 16:17:50 -0800440 return false; // haven't gotten a max / min
441 }
442
443 // avoid divide by 0
444 if (findRange->second.second == 0)
445 {
446 std::cerr << "Tach Max Set to 0 " << tachName << "\n";
447 return false;
448 }
449
450 double rpm = findReading->second;
451
452 // for now assume the min for a fan is always 0, divide by max to get
453 // percent and mult by 100
454 rpm /= findRange->second.second;
455 rpm *= 100;
456
Ed Tanous8a57ec02020-10-09 12:46:52 -0700457 if constexpr (debug)
James Feistb2eb3f52018-12-04 16:17:50 -0800458 {
459 std::cout << "Tach " << tachName << "at " << rpm << "\n";
460 }
461
462 // Do a linear interpolation to get Ci
463 // Ci = C1 + (C2 - C1)/(RPM2 - RPM1) * (TACHi - TACH1)
464
465 double ci = 0;
466 if (rpm == 0)
467 {
468 ci = 0;
469 }
470 else if (rpm < tachMinPercent)
471 {
472 ci = c1;
473 }
474 else if (rpm > tachMaxPercent)
475 {
476 ci = c2;
477 }
478 else
479 {
480 ci = c1 + (((c2 - c1) * (rpm - tachMinPercent)) /
481 (tachMaxPercent - tachMinPercent));
482 }
483
484 // Now calculate the CFM for this tach
485 // CFMi = Ci * Qmaxi * TACHi
486 totalCFM += ci * maxCFM * rpm;
Ed Tanous8a57ec02020-10-09 12:46:52 -0700487 if constexpr (debug)
James Feista5e58722019-04-22 14:43:11 -0700488 {
489 std::cerr << "totalCFM = " << totalCFM << "\n";
490 std::cerr << "Ci " << ci << " MaxCFM " << maxCFM << " rpm " << rpm
491 << "\n";
492 std::cerr << "c1 " << c1 << " c2 " << c2 << " max "
493 << tachMaxPercent << " min " << tachMinPercent << "\n";
494 }
James Feistb2eb3f52018-12-04 16:17:50 -0800495 }
496
497 // divide by 100 since rpm is in percent
498 value = totalCFM / 100;
Ed Tanous8a57ec02020-10-09 12:46:52 -0700499 if constexpr (debug)
James Feista5e58722019-04-22 14:43:11 -0700500 {
501 std::cerr << "cfm value = " << value << "\n";
502 }
James Feist9566bfa2019-01-29 15:31:23 -0800503 return true;
James Feistb2eb3f52018-12-04 16:17:50 -0800504}
505
506static constexpr double exitAirMaxReading = 127;
507static constexpr double exitAirMinReading = -128;
James Feistbc896df2018-11-26 16:28:17 -0800508ExitAirTempSensor::ExitAirTempSensor(
509 std::shared_ptr<sdbusplus::asio::connection>& conn,
James Feistb2eb3f52018-12-04 16:17:50 -0800510 const std::string& sensorName, const std::string& sensorConfiguration,
James Feistbc896df2018-11-26 16:28:17 -0800511 sdbusplus::asio::object_server& objectServer,
James Feistb839c052019-05-15 10:25:24 -0700512 std::vector<thresholds::Threshold>&& thresholdData) :
James Feistb2eb3f52018-12-04 16:17:50 -0800513 Sensor(boost::replace_all_copy(sensorName, " ", "_"),
James Feist930fcde2019-05-28 12:58:43 -0700514 std::move(thresholdData), sensorConfiguration,
Bruce Lee1263c3d2021-06-04 15:16:33 +0800515 "xyz.openbmc_project.Configuration.ExitAirTemp", false,
516 exitAirMaxReading, exitAirMinReading, conn, PowerState::on),
James Feiste3338522020-09-15 15:40:30 -0700517 std::enable_shared_from_this<ExitAirTempSensor>(), objServer(objectServer)
James Feistbc896df2018-11-26 16:28:17 -0800518{
519 sensorInterface = objectServer.add_interface(
520 "/xyz/openbmc_project/sensors/temperature/" + name,
521 "xyz.openbmc_project.Sensor.Value");
522
523 if (thresholds::hasWarningInterface(thresholds))
524 {
525 thresholdInterfaceWarning = objectServer.add_interface(
526 "/xyz/openbmc_project/sensors/temperature/" + name,
527 "xyz.openbmc_project.Sensor.Threshold.Warning");
528 }
529 if (thresholds::hasCriticalInterface(thresholds))
530 {
531 thresholdInterfaceCritical = objectServer.add_interface(
532 "/xyz/openbmc_project/sensors/temperature/" + name,
533 "xyz.openbmc_project.Sensor.Threshold.Critical");
534 }
James Feist078f2322019-03-08 11:09:05 -0800535 association = objectServer.add_interface(
536 "/xyz/openbmc_project/sensors/temperature/" + name,
James Feist2adc95c2019-09-30 14:55:28 -0700537 association::interface);
Zev Weiss6b6891c2021-04-22 02:46:21 -0500538 setInitialProperties(conn, sensor_paths::unitDegreesC);
James Feistbc896df2018-11-26 16:28:17 -0800539}
540
541ExitAirTempSensor::~ExitAirTempSensor()
542{
James Feist523828e2019-03-04 14:38:37 -0800543 objServer.remove_interface(thresholdInterfaceWarning);
544 objServer.remove_interface(thresholdInterfaceCritical);
545 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800546 objServer.remove_interface(association);
James Feistbc896df2018-11-26 16:28:17 -0800547}
548
549void ExitAirTempSensor::setupMatches(void)
550{
James Feistb2eb3f52018-12-04 16:17:50 -0800551 constexpr const std::array<const char*, 2> matchTypes = {
552 "power", inletTemperatureSensor};
James Feistbc896df2018-11-26 16:28:17 -0800553
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700554 std::weak_ptr<ExitAirTempSensor> weakRef = weak_from_this();
Ed Tanous13b63f82021-05-11 16:12:52 -0700555 for (const std::string type : matchTypes)
James Feistbc896df2018-11-26 16:28:17 -0800556 {
James Feistb2eb3f52018-12-04 16:17:50 -0800557 setupSensorMatch(matches, *dbusConnection, type,
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700558 [weakRef, type](const double& value,
559 sdbusplus::message::message& message) {
560 auto self = weakRef.lock();
561 if (!self)
562 {
563 return;
564 }
James Feistb2eb3f52018-12-04 16:17:50 -0800565 if (type == "power")
566 {
James Feista5e58722019-04-22 14:43:11 -0700567 std::string path = message.get_path();
568 if (path.find("PS") != std::string::npos &&
569 boost::ends_with(path, "Input_Power"))
570 {
James Feist9a25ed42019-10-15 15:43:44 -0700571 self->powerReadings[message.get_path()] =
572 value;
James Feista5e58722019-04-22 14:43:11 -0700573 }
James Feistb2eb3f52018-12-04 16:17:50 -0800574 }
575 else if (type == inletTemperatureSensor)
576 {
James Feist9a25ed42019-10-15 15:43:44 -0700577 self->inletTemp = value;
James Feistb2eb3f52018-12-04 16:17:50 -0800578 }
James Feist9a25ed42019-10-15 15:43:44 -0700579 self->updateReading();
James Feistb2eb3f52018-12-04 16:17:50 -0800580 });
James Feistbc896df2018-11-26 16:28:17 -0800581 }
James Feist9566bfa2019-01-29 15:31:23 -0800582 dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700583 [weakRef](boost::system::error_code ec,
584 const std::variant<double>& value) {
James Feist9566bfa2019-01-29 15:31:23 -0800585 if (ec)
586 {
587 // sensor not ready yet
588 return;
589 }
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700590 auto self = weakRef.lock();
591 if (!self)
592 {
593 return;
594 }
James Feist9a25ed42019-10-15 15:43:44 -0700595 self->inletTemp = std::visit(VariantToDoubleVisitor(), value);
James Feist9566bfa2019-01-29 15:31:23 -0800596 },
597 "xyz.openbmc_project.HwmonTempSensor",
598 std::string("/xyz/openbmc_project/sensors/") + inletTemperatureSensor,
James Feista5e58722019-04-22 14:43:11 -0700599 properties::interface, properties::get, sensorValueInterface, "Value");
600 dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700601 [weakRef](boost::system::error_code ec, const GetSubTreeType& subtree) {
James Feista5e58722019-04-22 14:43:11 -0700602 if (ec)
603 {
604 std::cerr << "Error contacting mapper\n";
605 return;
606 }
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700607 auto self = weakRef.lock();
608 if (!self)
609 {
610 return;
611 }
James Feista5e58722019-04-22 14:43:11 -0700612 for (const auto& item : subtree)
613 {
614 size_t lastSlash = item.first.rfind("/");
615 if (lastSlash == std::string::npos ||
616 lastSlash == item.first.size() || !item.second.size())
617 {
618 continue;
619 }
620 std::string sensorName = item.first.substr(lastSlash + 1);
621 if (boost::starts_with(sensorName, "PS") &&
622 boost::ends_with(sensorName, "Input_Power"))
623 {
624 const std::string& path = item.first;
James Feist9a25ed42019-10-15 15:43:44 -0700625 self->dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700626 [weakRef, path](boost::system::error_code ec,
627 const std::variant<double>& value) {
James Feista5e58722019-04-22 14:43:11 -0700628 if (ec)
629 {
630 std::cerr << "Error getting value from " << path
631 << "\n";
632 }
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700633 auto self = weakRef.lock();
634 if (!self)
635 {
636 return;
637 }
James Feista5e58722019-04-22 14:43:11 -0700638 double reading =
639 std::visit(VariantToDoubleVisitor(), value);
Ed Tanous8a57ec02020-10-09 12:46:52 -0700640 if constexpr (debug)
James Feista5e58722019-04-22 14:43:11 -0700641 {
642 std::cerr << path << "Reading " << reading
643 << "\n";
644 }
James Feist9a25ed42019-10-15 15:43:44 -0700645 self->powerReadings[path] = reading;
James Feista5e58722019-04-22 14:43:11 -0700646 },
647 item.second[0].first, item.first, properties::interface,
648 properties::get, sensorValueInterface, "Value");
649 }
650 }
651 },
652 mapper::busName, mapper::path, mapper::interface, mapper::subtree,
653 "/xyz/openbmc_project/sensors/power", 0,
654 std::array<const char*, 1>{sensorValueInterface});
James Feistbc896df2018-11-26 16:28:17 -0800655}
656
657void ExitAirTempSensor::updateReading(void)
658{
659
660 double val = 0.0;
661 if (calculate(val))
662 {
James Feist18af4232019-03-13 11:14:00 -0700663 val = std::floor(val + 0.5);
James Feistbc896df2018-11-26 16:28:17 -0800664 updateValue(val);
665 }
666 else
667 {
668 updateValue(std::numeric_limits<double>::quiet_NaN());
669 }
670}
671
James Feistb2eb3f52018-12-04 16:17:50 -0800672double ExitAirTempSensor::getTotalCFM(void)
James Feistbc896df2018-11-26 16:28:17 -0800673{
James Feistb2eb3f52018-12-04 16:17:50 -0800674 double sum = 0;
675 for (auto& sensor : cfmSensors)
James Feistbc896df2018-11-26 16:28:17 -0800676 {
James Feistb2eb3f52018-12-04 16:17:50 -0800677 double reading = 0;
678 if (!sensor->calculate(reading))
James Feistbc896df2018-11-26 16:28:17 -0800679 {
James Feistbc896df2018-11-26 16:28:17 -0800680 return -1;
681 }
James Feistb2eb3f52018-12-04 16:17:50 -0800682 sum += reading;
James Feistbc896df2018-11-26 16:28:17 -0800683 }
James Feistb2eb3f52018-12-04 16:17:50 -0800684
685 return sum;
James Feistbc896df2018-11-26 16:28:17 -0800686}
687
688bool ExitAirTempSensor::calculate(double& val)
689{
Zhikui Ren12e3d672020-12-03 15:14:49 -0800690 constexpr size_t maxErrorPrint = 5;
James Feistbc896df2018-11-26 16:28:17 -0800691 static bool firstRead = false;
James Feistae11cfc2019-05-07 15:01:20 -0700692 static size_t errorPrint = maxErrorPrint;
693
James Feistbc896df2018-11-26 16:28:17 -0800694 double cfm = getTotalCFM();
695 if (cfm <= 0)
696 {
697 std::cerr << "Error getting cfm\n";
698 return false;
699 }
700
Zhikui Ren12e3d672020-12-03 15:14:49 -0800701 // Though cfm is not expected to be less than qMin normally,
702 // it is not a hard limit for exit air temp calculation.
703 // 50% qMin is chosen as a generic limit between providing
704 // a valid derived exit air temp and reporting exit air temp not available.
705 constexpr const double cfmLimitFactor = 0.5;
706 if (cfm < (qMin * cfmLimitFactor))
707 {
708 if (errorPrint > 0)
709 {
710 errorPrint--;
711 std::cerr << "cfm " << cfm << " is too low, expected qMin " << qMin
712 << "\n";
713 }
714 val = 0;
715 return false;
716 }
717
James Feistbc896df2018-11-26 16:28:17 -0800718 // if there is an error getting inlet temp, return error
719 if (std::isnan(inletTemp))
720 {
James Feistae11cfc2019-05-07 15:01:20 -0700721 if (errorPrint > 0)
722 {
723 errorPrint--;
724 std::cerr << "Cannot get inlet temp\n";
725 }
James Feistbc896df2018-11-26 16:28:17 -0800726 val = 0;
727 return false;
728 }
729
730 // if fans are off, just make the exit temp equal to inlet
James Feist71d31b22019-01-02 16:57:54 -0800731 if (!isPowerOn())
James Feistbc896df2018-11-26 16:28:17 -0800732 {
733 val = inletTemp;
734 return true;
735 }
736
737 double totalPower = 0;
738 for (const auto& reading : powerReadings)
739 {
740 if (std::isnan(reading.second))
741 {
742 continue;
743 }
744 totalPower += reading.second;
745 }
746
747 // Calculate power correction factor
748 // Ci = CL + (CH - CL)/(QMax - QMin) * (CFM - QMin)
Ed Tanous8a57ec02020-10-09 12:46:52 -0700749 double powerFactor = 0.0;
James Feistbc896df2018-11-26 16:28:17 -0800750 if (cfm <= qMin)
751 {
752 powerFactor = powerFactorMin;
753 }
754 else if (cfm >= qMax)
755 {
756 powerFactor = powerFactorMax;
757 }
758 else
759 {
760 powerFactor = powerFactorMin + ((powerFactorMax - powerFactorMin) /
761 (qMax - qMin) * (cfm - qMin));
762 }
763
Ed Tanous8a57ec02020-10-09 12:46:52 -0700764 totalPower *= powerFactor;
James Feistbc896df2018-11-26 16:28:17 -0800765 totalPower += pOffset;
766
767 if (totalPower == 0)
768 {
James Feistae11cfc2019-05-07 15:01:20 -0700769 if (errorPrint > 0)
770 {
771 errorPrint--;
772 std::cerr << "total power 0\n";
773 }
James Feistbc896df2018-11-26 16:28:17 -0800774 val = 0;
775 return false;
776 }
777
Ed Tanous8a57ec02020-10-09 12:46:52 -0700778 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800779 {
780 std::cout << "Power Factor " << powerFactor << "\n";
781 std::cout << "Inlet Temp " << inletTemp << "\n";
782 std::cout << "Total Power" << totalPower << "\n";
783 }
784
785 // Calculate the exit air temp
786 // Texit = Tfp + (1.76 * TotalPower / CFM * Faltitude)
Ed Tanous8a57ec02020-10-09 12:46:52 -0700787 double reading = 1.76 * totalPower * altitudeFactor;
James Feistbc896df2018-11-26 16:28:17 -0800788 reading /= cfm;
789 reading += inletTemp;
790
Ed Tanous8a57ec02020-10-09 12:46:52 -0700791 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800792 {
793 std::cout << "Reading 1: " << reading << "\n";
794 }
795
796 // Now perform the exponential average
797 // Calculate alpha based on SDR values and CFM
798 // Ai = As + (Af - As)/(QMax - QMin) * (CFM - QMin)
799
800 double alpha = 0.0;
801 if (cfm < qMin)
802 {
803 alpha = alphaS;
804 }
805 else if (cfm >= qMax)
806 {
807 alpha = alphaF;
808 }
809 else
810 {
811 alpha = alphaS + ((alphaF - alphaS) * (cfm - qMin) / (qMax - qMin));
812 }
813
Zhikui Ren12e3d672020-12-03 15:14:49 -0800814 auto time = std::chrono::steady_clock::now();
James Feistbc896df2018-11-26 16:28:17 -0800815 if (!firstRead)
816 {
817 firstRead = true;
818 lastTime = time;
819 lastReading = reading;
820 }
821 double alphaDT =
822 std::chrono::duration_cast<std::chrono::seconds>(time - lastTime)
823 .count() *
824 alpha;
825
826 // cap at 1.0 or the below fails
827 if (alphaDT > 1.0)
828 {
829 alphaDT = 1.0;
830 }
831
Ed Tanous8a57ec02020-10-09 12:46:52 -0700832 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800833 {
834 std::cout << "AlphaDT: " << alphaDT << "\n";
835 }
836
837 reading = ((reading * alphaDT) + (lastReading * (1.0 - alphaDT)));
838
Ed Tanous8a57ec02020-10-09 12:46:52 -0700839 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800840 {
841 std::cout << "Reading 2: " << reading << "\n";
842 }
843
844 val = reading;
845 lastReading = reading;
846 lastTime = time;
James Feistae11cfc2019-05-07 15:01:20 -0700847 errorPrint = maxErrorPrint;
James Feistbc896df2018-11-26 16:28:17 -0800848 return true;
849}
850
851void ExitAirTempSensor::checkThresholds(void)
852{
853 thresholds::checkThresholds(this);
854}
855
James Feistbc896df2018-11-26 16:28:17 -0800856static void loadVariantPathArray(
857 const boost::container::flat_map<std::string, BasicVariantType>& data,
858 const std::string& key, std::vector<std::string>& resp)
859{
860 auto it = data.find(key);
861 if (it == data.end())
862 {
863 std::cerr << "Configuration missing " << key << "\n";
864 throw std::invalid_argument("Key Missing");
865 }
866 BasicVariantType copy = it->second;
James Feist3eb82622019-02-08 13:10:22 -0800867 std::vector<std::string> config = std::get<std::vector<std::string>>(copy);
James Feistbc896df2018-11-26 16:28:17 -0800868 for (auto& str : config)
869 {
870 boost::replace_all(str, " ", "_");
871 }
872 resp = std::move(config);
873}
874
875void createSensor(sdbusplus::asio::object_server& objectServer,
James Feistb2eb3f52018-12-04 16:17:50 -0800876 std::shared_ptr<ExitAirTempSensor>& exitAirSensor,
James Feistbc896df2018-11-26 16:28:17 -0800877 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
878{
879 if (!dbusConnection)
880 {
881 std::cerr << "Connection not created\n";
882 return;
883 }
James Feist655f3762020-10-05 15:28:15 -0700884 auto getter = std::make_shared<GetSensorConfiguration>(
Ed Tanous8a17c302021-09-02 15:07:11 -0700885 dbusConnection, [&objectServer, &dbusConnection,
886 &exitAirSensor](const ManagedObjectType& resp) {
James Feist9a25ed42019-10-15 15:43:44 -0700887 cfmSensors.clear();
James Feistbc896df2018-11-26 16:28:17 -0800888 for (const auto& pathPair : resp)
889 {
890 for (const auto& entry : pathPair.second)
891 {
892 if (entry.first == exitAirIface)
893 {
James Feistbc896df2018-11-26 16:28:17 -0800894 // thresholds should be under the same path
895 std::vector<thresholds::Threshold> sensorThresholds;
896 parseThresholdsFromConfig(pathPair.second,
897 sensorThresholds);
James Feistbc896df2018-11-26 16:28:17 -0800898
James Feist523828e2019-03-04 14:38:37 -0800899 std::string name =
900 loadVariant<std::string>(entry.second, "Name");
901 exitAirSensor = std::make_shared<ExitAirTempSensor>(
902 dbusConnection, name, pathPair.first.str,
903 objectServer, std::move(sensorThresholds));
James Feistb2eb3f52018-12-04 16:17:50 -0800904 exitAirSensor->powerFactorMin =
905 loadVariant<double>(entry.second, "PowerFactorMin");
906 exitAirSensor->powerFactorMax =
907 loadVariant<double>(entry.second, "PowerFactorMax");
908 exitAirSensor->qMin =
909 loadVariant<double>(entry.second, "QMin");
910 exitAirSensor->qMax =
911 loadVariant<double>(entry.second, "QMax");
912 exitAirSensor->alphaS =
913 loadVariant<double>(entry.second, "AlphaS");
914 exitAirSensor->alphaF =
915 loadVariant<double>(entry.second, "AlphaF");
James Feistbc896df2018-11-26 16:28:17 -0800916 }
917 else if (entry.first == cfmIface)
918
919 {
James Feistb2eb3f52018-12-04 16:17:50 -0800920 // thresholds should be under the same path
921 std::vector<thresholds::Threshold> sensorThresholds;
922 parseThresholdsFromConfig(pathPair.second,
923 sensorThresholds);
924 std::string name =
925 loadVariant<std::string>(entry.second, "Name");
James Feist9a25ed42019-10-15 15:43:44 -0700926 auto sensor = std::make_shared<CFMSensor>(
James Feistb2eb3f52018-12-04 16:17:50 -0800927 dbusConnection, name, pathPair.first.str,
928 objectServer, std::move(sensorThresholds),
929 exitAirSensor);
930 loadVariantPathArray(entry.second, "Tachs",
931 sensor->tachs);
932 sensor->maxCFM =
933 loadVariant<double>(entry.second, "MaxCFM");
James Feistbc896df2018-11-26 16:28:17 -0800934
935 // change these into percent upon getting the data
James Feistb2eb3f52018-12-04 16:17:50 -0800936 sensor->c1 =
937 loadVariant<double>(entry.second, "C1") / 100;
938 sensor->c2 =
939 loadVariant<double>(entry.second, "C2") / 100;
940 sensor->tachMinPercent =
Zhikui Ren12e3d672020-12-03 15:14:49 -0800941 loadVariant<double>(entry.second, "TachMinPercent");
James Feistb2eb3f52018-12-04 16:17:50 -0800942 sensor->tachMaxPercent =
Zhikui Ren12e3d672020-12-03 15:14:49 -0800943 loadVariant<double>(entry.second, "TachMaxPercent");
James Feist13452092019-03-07 16:38:12 -0800944 sensor->createMaxCFMIface();
James Feist9a25ed42019-10-15 15:43:44 -0700945 sensor->setupMatches();
James Feistbc896df2018-11-26 16:28:17 -0800946
James Feistb2eb3f52018-12-04 16:17:50 -0800947 cfmSensors.emplace_back(std::move(sensor));
James Feistbc896df2018-11-26 16:28:17 -0800948 }
949 }
950 }
James Feistb2eb3f52018-12-04 16:17:50 -0800951 if (exitAirSensor)
James Feistbc896df2018-11-26 16:28:17 -0800952 {
James Feist9a25ed42019-10-15 15:43:44 -0700953 exitAirSensor->setupMatches();
James Feistb2eb3f52018-12-04 16:17:50 -0800954 exitAirSensor->updateReading();
James Feistbc896df2018-11-26 16:28:17 -0800955 }
Ed Tanous8a17c302021-09-02 15:07:11 -0700956 });
James Feist655f3762020-10-05 15:28:15 -0700957 getter->getConfiguration(
958 std::vector<std::string>(monitorIfaces.begin(), monitorIfaces.end()));
James Feistbc896df2018-11-26 16:28:17 -0800959}
960
James Feistb6c0b912019-07-09 12:21:44 -0700961int main()
James Feistbc896df2018-11-26 16:28:17 -0800962{
963
964 boost::asio::io_service io;
965 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
966 systemBus->request_name("xyz.openbmc_project.ExitAirTempSensor");
967 sdbusplus::asio::object_server objectServer(systemBus);
968 std::shared_ptr<ExitAirTempSensor> sensor =
969 nullptr; // wait until we find the config
970 std::vector<std::unique_ptr<sdbusplus::bus::match::match>> matches;
971
972 io.post([&]() { createSensor(objectServer, sensor, systemBus); });
973
974 boost::asio::deadline_timer configTimer(io);
975
976 std::function<void(sdbusplus::message::message&)> eventHandler =
James Feistb6c0b912019-07-09 12:21:44 -0700977 [&](sdbusplus::message::message&) {
James Feistbc896df2018-11-26 16:28:17 -0800978 configTimer.expires_from_now(boost::posix_time::seconds(1));
979 // create a timer because normally multiple properties change
980 configTimer.async_wait([&](const boost::system::error_code& ec) {
981 if (ec == boost::asio::error::operation_aborted)
982 {
983 return; // we're being canceled
984 }
985 createSensor(objectServer, sensor, systemBus);
986 if (!sensor)
987 {
988 std::cout << "Configuration not detected\n";
989 }
990 });
991 };
James Feistbc896df2018-11-26 16:28:17 -0800992 for (const char* type : monitorIfaces)
993 {
994 auto match = std::make_unique<sdbusplus::bus::match::match>(
995 static_cast<sdbusplus::bus::bus&>(*systemBus),
996 "type='signal',member='PropertiesChanged',path_namespace='" +
997 std::string(inventoryPath) + "',arg0namespace='" + type + "'",
998 eventHandler);
999 matches.emplace_back(std::move(match));
1000 }
1001
Bruce Lee913d4d02021-07-22 10:18:42 +08001002 setupManufacturingModeMatch(*systemBus);
James Feistbc896df2018-11-26 16:28:17 -08001003 io.run();
Zhikui Ren8685b172021-06-29 15:16:52 -07001004 return 0;
James Feistbc896df2018-11-26 16:28:17 -08001005}