blob: 58f746822af80da72afc40521c218c339752cb4c [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
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530175 for (const auto& threshold : thresholds)
James Feistb2eb3f52018-12-04 16:17:50 -0800176 {
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530177 std::string interface = thresholds::getInterface(threshold.level);
178 thresholdInterfaces[static_cast<size_t>(threshold.level)] =
179 objectServer.add_interface(
180 "/xyz/openbmc_project/sensors/airflow/" + name, interface);
James Feistb2eb3f52018-12-04 16:17:50 -0800181 }
James Feist078f2322019-03-08 11:09:05 -0800182
183 association = objectServer.add_interface(
Basheer Ahmed Muddebihale5b867b2021-07-26 08:32:19 -0700184 "/xyz/openbmc_project/sensors/airflow/" + name, association::interface);
James Feist078f2322019-03-08 11:09:05 -0800185
Andrei Kartashev39287412022-02-04 16:04:47 +0300186 setInitialProperties(sensor_paths::unitCFM);
James Feist9a25ed42019-10-15 15:43:44 -0700187
James Feist13452092019-03-07 16:38:12 -0800188 pwmLimitIface =
189 objectServer.add_interface("/xyz/openbmc_project/control/pwm_limit",
190 "xyz.openbmc_project.Control.PWMLimit");
191 cfmLimitIface =
192 objectServer.add_interface("/xyz/openbmc_project/control/MaxCFM",
193 "xyz.openbmc_project.Control.CFMLimit");
James Feist9a25ed42019-10-15 15:43:44 -0700194}
James Feist13452092019-03-07 16:38:12 -0800195
James Feist9a25ed42019-10-15 15:43:44 -0700196void CFMSensor::setupMatches()
197{
198
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700199 std::weak_ptr<CFMSensor> weakRef = weak_from_this();
Ed Tanous8a17c302021-09-02 15:07:11 -0700200 setupSensorMatch(
201 matches, *dbusConnection, "fan_tach",
202 [weakRef](const double& value, sdbusplus::message::message& message) {
203 auto self = weakRef.lock();
204 if (!self)
205 {
206 return;
207 }
208 self->tachReadings[message.get_path()] = value;
209 if (self->tachRanges.find(message.get_path()) ==
210 self->tachRanges.end())
211 {
212 // calls update reading after updating ranges
213 self->addTachRanges(message.get_sender(), message.get_path());
214 }
215 else
216 {
217 self->updateReading();
218 }
219 });
James Feist9a25ed42019-10-15 15:43:44 -0700220
221 dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700222 [weakRef](const boost::system::error_code ec,
223 const std::variant<double> cfmVariant) {
224 auto self = weakRef.lock();
225 if (!self)
226 {
227 return;
228 }
229
James Feist13452092019-03-07 16:38:12 -0800230 uint64_t maxRpm = 100;
231 if (!ec)
232 {
233
234 auto cfm = std::get_if<double>(&cfmVariant);
Josh Lehanff336992020-02-26 11:13:19 -0800235 if (cfm != nullptr && *cfm >= minSystemCfm)
James Feist13452092019-03-07 16:38:12 -0800236 {
James Feist9a25ed42019-10-15 15:43:44 -0700237 maxRpm = self->getMaxRpm(*cfm);
James Feist13452092019-03-07 16:38:12 -0800238 }
239 }
James Feist9a25ed42019-10-15 15:43:44 -0700240 self->pwmLimitIface->register_property("Limit", maxRpm);
241 self->pwmLimitIface->initialize();
242 setMaxPWM(self->dbusConnection, maxRpm);
James Feist13452092019-03-07 16:38:12 -0800243 },
244 settingsDaemon, cfmSettingPath, "org.freedesktop.DBus.Properties",
245 "Get", cfmSettingIface, "Limit");
246
247 matches.emplace_back(
James Feist9a25ed42019-10-15 15:43:44 -0700248 *dbusConnection,
James Feist13452092019-03-07 16:38:12 -0800249 "type='signal',"
250 "member='PropertiesChanged',interface='org."
251 "freedesktop.DBus.Properties',path='" +
252 std::string(cfmSettingPath) + "',arg0='" +
253 std::string(cfmSettingIface) + "'",
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700254 [weakRef](sdbusplus::message::message& message) {
255 auto self = weakRef.lock();
256 if (!self)
257 {
258 return;
259 }
James Feist13452092019-03-07 16:38:12 -0800260 boost::container::flat_map<std::string, std::variant<double>>
261 values;
262 std::string objectName;
263 message.read(objectName, values);
264 const auto findValue = values.find("Limit");
265 if (findValue == values.end())
266 {
267 return;
268 }
269 const auto reading = std::get_if<double>(&(findValue->second));
270 if (reading == nullptr)
271 {
272 std::cerr << "Got CFM Limit of wrong type\n";
273 return;
274 }
275 if (*reading < minSystemCfm && *reading != 0)
276 {
277 std::cerr << "Illegal CFM setting detected\n";
278 return;
279 }
James Feist9a25ed42019-10-15 15:43:44 -0700280 uint64_t maxRpm = self->getMaxRpm(*reading);
281 self->pwmLimitIface->set_property("Limit", maxRpm);
282 setMaxPWM(self->dbusConnection, maxRpm);
James Feist13452092019-03-07 16:38:12 -0800283 });
James Feistb2eb3f52018-12-04 16:17:50 -0800284}
285
James Feist9566bfa2019-01-29 15:31:23 -0800286CFMSensor::~CFMSensor()
287{
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530288 for (const auto& iface : thresholdInterfaces)
289 {
290 objServer.remove_interface(iface);
291 }
James Feist9566bfa2019-01-29 15:31:23 -0800292 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800293 objServer.remove_interface(association);
James Feist13452092019-03-07 16:38:12 -0800294 objServer.remove_interface(cfmLimitIface);
295 objServer.remove_interface(pwmLimitIface);
296}
297
298void CFMSensor::createMaxCFMIface(void)
299{
James Feistb6c0b912019-07-09 12:21:44 -0700300 cfmLimitIface->register_property("Limit", c2 * maxCFM * tachs.size());
James Feist13452092019-03-07 16:38:12 -0800301 cfmLimitIface->initialize();
James Feist9566bfa2019-01-29 15:31:23 -0800302}
303
James Feistb2eb3f52018-12-04 16:17:50 -0800304void CFMSensor::addTachRanges(const std::string& serviceName,
305 const std::string& path)
306{
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700307 std::weak_ptr<CFMSensor> weakRef = weak_from_this();
James Feistb2eb3f52018-12-04 16:17:50 -0800308 dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700309 [weakRef,
310 path](const boost::system::error_code ec,
311 const boost::container::flat_map<std::string, BasicVariantType>&
312 data) {
James Feistb2eb3f52018-12-04 16:17:50 -0800313 if (ec)
314 {
315 std::cerr << "Error getting properties from " << path << "\n";
James Feist1ccdb5e2019-01-24 09:44:01 -0800316 return;
James Feistb2eb3f52018-12-04 16:17:50 -0800317 }
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700318 auto self = weakRef.lock();
319 if (!self)
320 {
321 return;
322 }
James Feistb2eb3f52018-12-04 16:17:50 -0800323 double max = loadVariant<double>(data, "MaxValue");
324 double min = loadVariant<double>(data, "MinValue");
James Feist9a25ed42019-10-15 15:43:44 -0700325 self->tachRanges[path] = std::make_pair(min, max);
326 self->updateReading();
James Feistb2eb3f52018-12-04 16:17:50 -0800327 },
328 serviceName, path, "org.freedesktop.DBus.Properties", "GetAll",
329 "xyz.openbmc_project.Sensor.Value");
330}
331
332void CFMSensor::checkThresholds(void)
333{
334 thresholds::checkThresholds(this);
335}
336
337void CFMSensor::updateReading(void)
338{
339 double val = 0.0;
340 if (calculate(val))
341 {
342 if (value != val && parent)
343 {
344 parent->updateReading();
345 }
346 updateValue(val);
347 }
348 else
349 {
350 updateValue(std::numeric_limits<double>::quiet_NaN());
351 }
352}
353
James Feist13452092019-03-07 16:38:12 -0800354uint64_t CFMSensor::getMaxRpm(uint64_t cfmMaxSetting)
355{
356 uint64_t pwmPercent = 100;
357 double totalCFM = std::numeric_limits<double>::max();
358 if (cfmMaxSetting == 0)
359 {
360 return pwmPercent;
361 }
362
James Feist52427952019-04-05 14:23:35 -0700363 bool firstLoop = true;
James Feist13452092019-03-07 16:38:12 -0800364 while (totalCFM > cfmMaxSetting)
365 {
James Feist52427952019-04-05 14:23:35 -0700366 if (firstLoop)
367 {
368 firstLoop = false;
369 }
370 else
371 {
372 pwmPercent--;
373 }
374
James Feist13452092019-03-07 16:38:12 -0800375 double ci = 0;
376 if (pwmPercent == 0)
377 {
378 ci = 0;
379 }
380 else if (pwmPercent < tachMinPercent)
381 {
382 ci = c1;
383 }
384 else if (pwmPercent > tachMaxPercent)
385 {
386 ci = c2;
387 }
388 else
389 {
390 ci = c1 + (((c2 - c1) * (pwmPercent - tachMinPercent)) /
391 (tachMaxPercent - tachMinPercent));
392 }
393
394 // Now calculate the CFM for this tach
395 // CFMi = Ci * Qmaxi * TACHi
396 totalCFM = ci * maxCFM * pwmPercent;
397 totalCFM *= tachs.size();
398 // divide by 100 since pwm is in percent
399 totalCFM /= 100;
400
James Feist13452092019-03-07 16:38:12 -0800401 if (pwmPercent <= 0)
402 {
403 break;
404 }
405 }
James Feist52427952019-04-05 14:23:35 -0700406
James Feist13452092019-03-07 16:38:12 -0800407 return pwmPercent;
408}
409
James Feistb2eb3f52018-12-04 16:17:50 -0800410bool CFMSensor::calculate(double& value)
411{
412 double totalCFM = 0;
413 for (const std::string& tachName : tachs)
414 {
James Feist9566bfa2019-01-29 15:31:23 -0800415
James Feistb2eb3f52018-12-04 16:17:50 -0800416 auto findReading = std::find_if(
417 tachReadings.begin(), tachReadings.end(), [&](const auto& item) {
418 return boost::ends_with(item.first, tachName);
419 });
420 auto findRange = std::find_if(
421 tachRanges.begin(), tachRanges.end(), [&](const auto& item) {
422 return boost::ends_with(item.first, tachName);
423 });
424 if (findReading == tachReadings.end())
425 {
Ed Tanous8a57ec02020-10-09 12:46:52 -0700426 if constexpr (debug)
James Feista96329f2019-01-24 10:08:27 -0800427 {
428 std::cerr << "Can't find " << tachName << "in readings\n";
429 }
James Feist9566bfa2019-01-29 15:31:23 -0800430 continue; // haven't gotten a reading
James Feistb2eb3f52018-12-04 16:17:50 -0800431 }
432
433 if (findRange == tachRanges.end())
434 {
James Feist523828e2019-03-04 14:38:37 -0800435 std::cerr << "Can't find " << tachName << " in ranges\n";
James Feistb2eb3f52018-12-04 16:17:50 -0800436 return false; // haven't gotten a max / min
437 }
438
439 // avoid divide by 0
440 if (findRange->second.second == 0)
441 {
442 std::cerr << "Tach Max Set to 0 " << tachName << "\n";
443 return false;
444 }
445
446 double rpm = findReading->second;
447
448 // for now assume the min for a fan is always 0, divide by max to get
449 // percent and mult by 100
450 rpm /= findRange->second.second;
451 rpm *= 100;
452
Ed Tanous8a57ec02020-10-09 12:46:52 -0700453 if constexpr (debug)
James Feistb2eb3f52018-12-04 16:17:50 -0800454 {
455 std::cout << "Tach " << tachName << "at " << rpm << "\n";
456 }
457
458 // Do a linear interpolation to get Ci
459 // Ci = C1 + (C2 - C1)/(RPM2 - RPM1) * (TACHi - TACH1)
460
461 double ci = 0;
462 if (rpm == 0)
463 {
464 ci = 0;
465 }
466 else if (rpm < tachMinPercent)
467 {
468 ci = c1;
469 }
470 else if (rpm > tachMaxPercent)
471 {
472 ci = c2;
473 }
474 else
475 {
476 ci = c1 + (((c2 - c1) * (rpm - tachMinPercent)) /
477 (tachMaxPercent - tachMinPercent));
478 }
479
480 // Now calculate the CFM for this tach
481 // CFMi = Ci * Qmaxi * TACHi
482 totalCFM += ci * maxCFM * rpm;
Ed Tanous8a57ec02020-10-09 12:46:52 -0700483 if constexpr (debug)
James Feista5e58722019-04-22 14:43:11 -0700484 {
485 std::cerr << "totalCFM = " << totalCFM << "\n";
486 std::cerr << "Ci " << ci << " MaxCFM " << maxCFM << " rpm " << rpm
487 << "\n";
488 std::cerr << "c1 " << c1 << " c2 " << c2 << " max "
489 << tachMaxPercent << " min " << tachMinPercent << "\n";
490 }
James Feistb2eb3f52018-12-04 16:17:50 -0800491 }
492
493 // divide by 100 since rpm is in percent
494 value = totalCFM / 100;
Ed Tanous8a57ec02020-10-09 12:46:52 -0700495 if constexpr (debug)
James Feista5e58722019-04-22 14:43:11 -0700496 {
497 std::cerr << "cfm value = " << value << "\n";
498 }
James Feist9566bfa2019-01-29 15:31:23 -0800499 return true;
James Feistb2eb3f52018-12-04 16:17:50 -0800500}
501
502static constexpr double exitAirMaxReading = 127;
503static constexpr double exitAirMinReading = -128;
James Feistbc896df2018-11-26 16:28:17 -0800504ExitAirTempSensor::ExitAirTempSensor(
505 std::shared_ptr<sdbusplus::asio::connection>& conn,
James Feistb2eb3f52018-12-04 16:17:50 -0800506 const std::string& sensorName, const std::string& sensorConfiguration,
James Feistbc896df2018-11-26 16:28:17 -0800507 sdbusplus::asio::object_server& objectServer,
James Feistb839c052019-05-15 10:25:24 -0700508 std::vector<thresholds::Threshold>&& thresholdData) :
Zhikui Renda98f092021-11-01 09:41:08 -0700509 Sensor(escapeName(sensorName), std::move(thresholdData),
510 sensorConfiguration, "xyz.openbmc_project.Configuration.ExitAirTemp",
511 false, false, exitAirMaxReading, exitAirMinReading, conn,
512 PowerState::on),
James Feiste3338522020-09-15 15:40:30 -0700513 std::enable_shared_from_this<ExitAirTempSensor>(), objServer(objectServer)
James Feistbc896df2018-11-26 16:28:17 -0800514{
515 sensorInterface = objectServer.add_interface(
516 "/xyz/openbmc_project/sensors/temperature/" + name,
517 "xyz.openbmc_project.Sensor.Value");
518
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530519 for (const auto& threshold : thresholds)
James Feistbc896df2018-11-26 16:28:17 -0800520 {
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530521 std::string interface = thresholds::getInterface(threshold.level);
522 thresholdInterfaces[static_cast<size_t>(threshold.level)] =
523 objectServer.add_interface(
524 "/xyz/openbmc_project/sensors/temperature/" + name, interface);
James Feistbc896df2018-11-26 16:28:17 -0800525 }
James Feist078f2322019-03-08 11:09:05 -0800526 association = objectServer.add_interface(
527 "/xyz/openbmc_project/sensors/temperature/" + name,
James Feist2adc95c2019-09-30 14:55:28 -0700528 association::interface);
Andrei Kartashev39287412022-02-04 16:04:47 +0300529 setInitialProperties(sensor_paths::unitDegreesC);
James Feistbc896df2018-11-26 16:28:17 -0800530}
531
532ExitAirTempSensor::~ExitAirTempSensor()
533{
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530534 for (const auto& iface : thresholdInterfaces)
535 {
536 objServer.remove_interface(iface);
537 }
James Feist523828e2019-03-04 14:38:37 -0800538 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800539 objServer.remove_interface(association);
James Feistbc896df2018-11-26 16:28:17 -0800540}
541
542void ExitAirTempSensor::setupMatches(void)
543{
Brandon Kim66558232021-11-09 16:53:08 -0800544 constexpr const auto matchTypes{
545 std::to_array<const char*>({"power", inletTemperatureSensor})};
James Feistbc896df2018-11-26 16:28:17 -0800546
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700547 std::weak_ptr<ExitAirTempSensor> weakRef = weak_from_this();
Ed Tanous13b63f82021-05-11 16:12:52 -0700548 for (const std::string type : matchTypes)
James Feistbc896df2018-11-26 16:28:17 -0800549 {
James Feistb2eb3f52018-12-04 16:17:50 -0800550 setupSensorMatch(matches, *dbusConnection, type,
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700551 [weakRef, type](const double& value,
552 sdbusplus::message::message& message) {
553 auto self = weakRef.lock();
554 if (!self)
555 {
556 return;
557 }
James Feistb2eb3f52018-12-04 16:17:50 -0800558 if (type == "power")
559 {
James Feista5e58722019-04-22 14:43:11 -0700560 std::string path = message.get_path();
561 if (path.find("PS") != std::string::npos &&
562 boost::ends_with(path, "Input_Power"))
563 {
James Feist9a25ed42019-10-15 15:43:44 -0700564 self->powerReadings[message.get_path()] =
565 value;
James Feista5e58722019-04-22 14:43:11 -0700566 }
James Feistb2eb3f52018-12-04 16:17:50 -0800567 }
568 else if (type == inletTemperatureSensor)
569 {
James Feist9a25ed42019-10-15 15:43:44 -0700570 self->inletTemp = value;
James Feistb2eb3f52018-12-04 16:17:50 -0800571 }
James Feist9a25ed42019-10-15 15:43:44 -0700572 self->updateReading();
James Feistb2eb3f52018-12-04 16:17:50 -0800573 });
James Feistbc896df2018-11-26 16:28:17 -0800574 }
James Feist9566bfa2019-01-29 15:31:23 -0800575 dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700576 [weakRef](boost::system::error_code ec,
577 const std::variant<double>& value) {
James Feist9566bfa2019-01-29 15:31:23 -0800578 if (ec)
579 {
580 // sensor not ready yet
581 return;
582 }
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700583 auto self = weakRef.lock();
584 if (!self)
585 {
586 return;
587 }
James Feist9a25ed42019-10-15 15:43:44 -0700588 self->inletTemp = std::visit(VariantToDoubleVisitor(), value);
James Feist9566bfa2019-01-29 15:31:23 -0800589 },
590 "xyz.openbmc_project.HwmonTempSensor",
591 std::string("/xyz/openbmc_project/sensors/") + inletTemperatureSensor,
James Feista5e58722019-04-22 14:43:11 -0700592 properties::interface, properties::get, sensorValueInterface, "Value");
593 dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700594 [weakRef](boost::system::error_code ec, const GetSubTreeType& subtree) {
James Feista5e58722019-04-22 14:43:11 -0700595 if (ec)
596 {
597 std::cerr << "Error contacting mapper\n";
598 return;
599 }
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700600 auto self = weakRef.lock();
601 if (!self)
602 {
603 return;
604 }
James Feista5e58722019-04-22 14:43:11 -0700605 for (const auto& item : subtree)
606 {
607 size_t lastSlash = item.first.rfind("/");
608 if (lastSlash == std::string::npos ||
609 lastSlash == item.first.size() || !item.second.size())
610 {
611 continue;
612 }
613 std::string sensorName = item.first.substr(lastSlash + 1);
614 if (boost::starts_with(sensorName, "PS") &&
615 boost::ends_with(sensorName, "Input_Power"))
616 {
617 const std::string& path = item.first;
James Feist9a25ed42019-10-15 15:43:44 -0700618 self->dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700619 [weakRef, path](boost::system::error_code ec,
620 const std::variant<double>& value) {
James Feista5e58722019-04-22 14:43:11 -0700621 if (ec)
622 {
623 std::cerr << "Error getting value from " << path
624 << "\n";
625 }
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700626 auto self = weakRef.lock();
627 if (!self)
628 {
629 return;
630 }
James Feista5e58722019-04-22 14:43:11 -0700631 double reading =
632 std::visit(VariantToDoubleVisitor(), value);
Ed Tanous8a57ec02020-10-09 12:46:52 -0700633 if constexpr (debug)
James Feista5e58722019-04-22 14:43:11 -0700634 {
635 std::cerr << path << "Reading " << reading
636 << "\n";
637 }
James Feist9a25ed42019-10-15 15:43:44 -0700638 self->powerReadings[path] = reading;
James Feista5e58722019-04-22 14:43:11 -0700639 },
640 item.second[0].first, item.first, properties::interface,
641 properties::get, sensorValueInterface, "Value");
642 }
643 }
644 },
645 mapper::busName, mapper::path, mapper::interface, mapper::subtree,
646 "/xyz/openbmc_project/sensors/power", 0,
647 std::array<const char*, 1>{sensorValueInterface});
James Feistbc896df2018-11-26 16:28:17 -0800648}
649
650void ExitAirTempSensor::updateReading(void)
651{
652
653 double val = 0.0;
654 if (calculate(val))
655 {
James Feist18af4232019-03-13 11:14:00 -0700656 val = std::floor(val + 0.5);
James Feistbc896df2018-11-26 16:28:17 -0800657 updateValue(val);
658 }
659 else
660 {
661 updateValue(std::numeric_limits<double>::quiet_NaN());
662 }
663}
664
James Feistb2eb3f52018-12-04 16:17:50 -0800665double ExitAirTempSensor::getTotalCFM(void)
James Feistbc896df2018-11-26 16:28:17 -0800666{
James Feistb2eb3f52018-12-04 16:17:50 -0800667 double sum = 0;
668 for (auto& sensor : cfmSensors)
James Feistbc896df2018-11-26 16:28:17 -0800669 {
James Feistb2eb3f52018-12-04 16:17:50 -0800670 double reading = 0;
671 if (!sensor->calculate(reading))
James Feistbc896df2018-11-26 16:28:17 -0800672 {
James Feistbc896df2018-11-26 16:28:17 -0800673 return -1;
674 }
James Feistb2eb3f52018-12-04 16:17:50 -0800675 sum += reading;
James Feistbc896df2018-11-26 16:28:17 -0800676 }
James Feistb2eb3f52018-12-04 16:17:50 -0800677
678 return sum;
James Feistbc896df2018-11-26 16:28:17 -0800679}
680
681bool ExitAirTempSensor::calculate(double& val)
682{
Zhikui Ren12e3d672020-12-03 15:14:49 -0800683 constexpr size_t maxErrorPrint = 5;
James Feistbc896df2018-11-26 16:28:17 -0800684 static bool firstRead = false;
James Feistae11cfc2019-05-07 15:01:20 -0700685 static size_t errorPrint = maxErrorPrint;
686
James Feistbc896df2018-11-26 16:28:17 -0800687 double cfm = getTotalCFM();
688 if (cfm <= 0)
689 {
690 std::cerr << "Error getting cfm\n";
691 return false;
692 }
693
Zhikui Ren12e3d672020-12-03 15:14:49 -0800694 // Though cfm is not expected to be less than qMin normally,
695 // it is not a hard limit for exit air temp calculation.
696 // 50% qMin is chosen as a generic limit between providing
697 // a valid derived exit air temp and reporting exit air temp not available.
698 constexpr const double cfmLimitFactor = 0.5;
699 if (cfm < (qMin * cfmLimitFactor))
700 {
701 if (errorPrint > 0)
702 {
703 errorPrint--;
704 std::cerr << "cfm " << cfm << " is too low, expected qMin " << qMin
705 << "\n";
706 }
707 val = 0;
708 return false;
709 }
710
James Feistbc896df2018-11-26 16:28:17 -0800711 // if there is an error getting inlet temp, return error
712 if (std::isnan(inletTemp))
713 {
James Feistae11cfc2019-05-07 15:01:20 -0700714 if (errorPrint > 0)
715 {
716 errorPrint--;
717 std::cerr << "Cannot get inlet temp\n";
718 }
James Feistbc896df2018-11-26 16:28:17 -0800719 val = 0;
720 return false;
721 }
722
723 // if fans are off, just make the exit temp equal to inlet
James Feist71d31b22019-01-02 16:57:54 -0800724 if (!isPowerOn())
James Feistbc896df2018-11-26 16:28:17 -0800725 {
726 val = inletTemp;
727 return true;
728 }
729
730 double totalPower = 0;
731 for (const auto& reading : powerReadings)
732 {
733 if (std::isnan(reading.second))
734 {
735 continue;
736 }
737 totalPower += reading.second;
738 }
739
740 // Calculate power correction factor
741 // Ci = CL + (CH - CL)/(QMax - QMin) * (CFM - QMin)
Ed Tanous8a57ec02020-10-09 12:46:52 -0700742 double powerFactor = 0.0;
James Feistbc896df2018-11-26 16:28:17 -0800743 if (cfm <= qMin)
744 {
745 powerFactor = powerFactorMin;
746 }
747 else if (cfm >= qMax)
748 {
749 powerFactor = powerFactorMax;
750 }
751 else
752 {
753 powerFactor = powerFactorMin + ((powerFactorMax - powerFactorMin) /
754 (qMax - qMin) * (cfm - qMin));
755 }
756
Ed Tanous8a57ec02020-10-09 12:46:52 -0700757 totalPower *= powerFactor;
James Feistbc896df2018-11-26 16:28:17 -0800758 totalPower += pOffset;
759
760 if (totalPower == 0)
761 {
James Feistae11cfc2019-05-07 15:01:20 -0700762 if (errorPrint > 0)
763 {
764 errorPrint--;
765 std::cerr << "total power 0\n";
766 }
James Feistbc896df2018-11-26 16:28:17 -0800767 val = 0;
768 return false;
769 }
770
Ed Tanous8a57ec02020-10-09 12:46:52 -0700771 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800772 {
773 std::cout << "Power Factor " << powerFactor << "\n";
774 std::cout << "Inlet Temp " << inletTemp << "\n";
775 std::cout << "Total Power" << totalPower << "\n";
776 }
777
778 // Calculate the exit air temp
779 // Texit = Tfp + (1.76 * TotalPower / CFM * Faltitude)
Ed Tanous8a57ec02020-10-09 12:46:52 -0700780 double reading = 1.76 * totalPower * altitudeFactor;
James Feistbc896df2018-11-26 16:28:17 -0800781 reading /= cfm;
782 reading += inletTemp;
783
Ed Tanous8a57ec02020-10-09 12:46:52 -0700784 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800785 {
786 std::cout << "Reading 1: " << reading << "\n";
787 }
788
789 // Now perform the exponential average
790 // Calculate alpha based on SDR values and CFM
791 // Ai = As + (Af - As)/(QMax - QMin) * (CFM - QMin)
792
793 double alpha = 0.0;
794 if (cfm < qMin)
795 {
796 alpha = alphaS;
797 }
798 else if (cfm >= qMax)
799 {
800 alpha = alphaF;
801 }
802 else
803 {
804 alpha = alphaS + ((alphaF - alphaS) * (cfm - qMin) / (qMax - qMin));
805 }
806
Zhikui Ren12e3d672020-12-03 15:14:49 -0800807 auto time = std::chrono::steady_clock::now();
James Feistbc896df2018-11-26 16:28:17 -0800808 if (!firstRead)
809 {
810 firstRead = true;
811 lastTime = time;
812 lastReading = reading;
813 }
814 double alphaDT =
815 std::chrono::duration_cast<std::chrono::seconds>(time - lastTime)
816 .count() *
817 alpha;
818
819 // cap at 1.0 or the below fails
820 if (alphaDT > 1.0)
821 {
822 alphaDT = 1.0;
823 }
824
Ed Tanous8a57ec02020-10-09 12:46:52 -0700825 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800826 {
827 std::cout << "AlphaDT: " << alphaDT << "\n";
828 }
829
830 reading = ((reading * alphaDT) + (lastReading * (1.0 - alphaDT)));
831
Ed Tanous8a57ec02020-10-09 12:46:52 -0700832 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800833 {
834 std::cout << "Reading 2: " << reading << "\n";
835 }
836
837 val = reading;
838 lastReading = reading;
839 lastTime = time;
James Feistae11cfc2019-05-07 15:01:20 -0700840 errorPrint = maxErrorPrint;
James Feistbc896df2018-11-26 16:28:17 -0800841 return true;
842}
843
844void ExitAirTempSensor::checkThresholds(void)
845{
846 thresholds::checkThresholds(this);
847}
848
James Feistbc896df2018-11-26 16:28:17 -0800849static void loadVariantPathArray(
850 const boost::container::flat_map<std::string, BasicVariantType>& data,
851 const std::string& key, std::vector<std::string>& resp)
852{
853 auto it = data.find(key);
854 if (it == data.end())
855 {
856 std::cerr << "Configuration missing " << key << "\n";
857 throw std::invalid_argument("Key Missing");
858 }
859 BasicVariantType copy = it->second;
James Feist3eb82622019-02-08 13:10:22 -0800860 std::vector<std::string> config = std::get<std::vector<std::string>>(copy);
James Feistbc896df2018-11-26 16:28:17 -0800861 for (auto& str : config)
862 {
863 boost::replace_all(str, " ", "_");
864 }
865 resp = std::move(config);
866}
867
868void createSensor(sdbusplus::asio::object_server& objectServer,
James Feistb2eb3f52018-12-04 16:17:50 -0800869 std::shared_ptr<ExitAirTempSensor>& exitAirSensor,
James Feistbc896df2018-11-26 16:28:17 -0800870 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
871{
872 if (!dbusConnection)
873 {
874 std::cerr << "Connection not created\n";
875 return;
876 }
James Feist655f3762020-10-05 15:28:15 -0700877 auto getter = std::make_shared<GetSensorConfiguration>(
Ed Tanous8a17c302021-09-02 15:07:11 -0700878 dbusConnection, [&objectServer, &dbusConnection,
879 &exitAirSensor](const ManagedObjectType& resp) {
James Feist9a25ed42019-10-15 15:43:44 -0700880 cfmSensors.clear();
James Feistbc896df2018-11-26 16:28:17 -0800881 for (const auto& pathPair : resp)
882 {
883 for (const auto& entry : pathPair.second)
884 {
885 if (entry.first == exitAirIface)
886 {
James Feistbc896df2018-11-26 16:28:17 -0800887 // thresholds should be under the same path
888 std::vector<thresholds::Threshold> sensorThresholds;
889 parseThresholdsFromConfig(pathPair.second,
890 sensorThresholds);
James Feistbc896df2018-11-26 16:28:17 -0800891
James Feist523828e2019-03-04 14:38:37 -0800892 std::string name =
893 loadVariant<std::string>(entry.second, "Name");
894 exitAirSensor = std::make_shared<ExitAirTempSensor>(
895 dbusConnection, name, pathPair.first.str,
896 objectServer, std::move(sensorThresholds));
James Feistb2eb3f52018-12-04 16:17:50 -0800897 exitAirSensor->powerFactorMin =
898 loadVariant<double>(entry.second, "PowerFactorMin");
899 exitAirSensor->powerFactorMax =
900 loadVariant<double>(entry.second, "PowerFactorMax");
901 exitAirSensor->qMin =
902 loadVariant<double>(entry.second, "QMin");
903 exitAirSensor->qMax =
904 loadVariant<double>(entry.second, "QMax");
905 exitAirSensor->alphaS =
906 loadVariant<double>(entry.second, "AlphaS");
907 exitAirSensor->alphaF =
908 loadVariant<double>(entry.second, "AlphaF");
James Feistbc896df2018-11-26 16:28:17 -0800909 }
910 else if (entry.first == cfmIface)
911
912 {
James Feistb2eb3f52018-12-04 16:17:50 -0800913 // thresholds should be under the same path
914 std::vector<thresholds::Threshold> sensorThresholds;
915 parseThresholdsFromConfig(pathPair.second,
916 sensorThresholds);
917 std::string name =
918 loadVariant<std::string>(entry.second, "Name");
James Feist9a25ed42019-10-15 15:43:44 -0700919 auto sensor = std::make_shared<CFMSensor>(
James Feistb2eb3f52018-12-04 16:17:50 -0800920 dbusConnection, name, pathPair.first.str,
921 objectServer, std::move(sensorThresholds),
922 exitAirSensor);
923 loadVariantPathArray(entry.second, "Tachs",
924 sensor->tachs);
925 sensor->maxCFM =
926 loadVariant<double>(entry.second, "MaxCFM");
James Feistbc896df2018-11-26 16:28:17 -0800927
928 // change these into percent upon getting the data
James Feistb2eb3f52018-12-04 16:17:50 -0800929 sensor->c1 =
930 loadVariant<double>(entry.second, "C1") / 100;
931 sensor->c2 =
932 loadVariant<double>(entry.second, "C2") / 100;
933 sensor->tachMinPercent =
Zhikui Ren12e3d672020-12-03 15:14:49 -0800934 loadVariant<double>(entry.second, "TachMinPercent");
James Feistb2eb3f52018-12-04 16:17:50 -0800935 sensor->tachMaxPercent =
Zhikui Ren12e3d672020-12-03 15:14:49 -0800936 loadVariant<double>(entry.second, "TachMaxPercent");
James Feist13452092019-03-07 16:38:12 -0800937 sensor->createMaxCFMIface();
James Feist9a25ed42019-10-15 15:43:44 -0700938 sensor->setupMatches();
James Feistbc896df2018-11-26 16:28:17 -0800939
James Feistb2eb3f52018-12-04 16:17:50 -0800940 cfmSensors.emplace_back(std::move(sensor));
James Feistbc896df2018-11-26 16:28:17 -0800941 }
942 }
943 }
James Feistb2eb3f52018-12-04 16:17:50 -0800944 if (exitAirSensor)
James Feistbc896df2018-11-26 16:28:17 -0800945 {
James Feist9a25ed42019-10-15 15:43:44 -0700946 exitAirSensor->setupMatches();
James Feistb2eb3f52018-12-04 16:17:50 -0800947 exitAirSensor->updateReading();
James Feistbc896df2018-11-26 16:28:17 -0800948 }
Ed Tanous8a17c302021-09-02 15:07:11 -0700949 });
James Feist655f3762020-10-05 15:28:15 -0700950 getter->getConfiguration(
951 std::vector<std::string>(monitorIfaces.begin(), monitorIfaces.end()));
James Feistbc896df2018-11-26 16:28:17 -0800952}
953
James Feistb6c0b912019-07-09 12:21:44 -0700954int main()
James Feistbc896df2018-11-26 16:28:17 -0800955{
956
957 boost::asio::io_service io;
958 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
959 systemBus->request_name("xyz.openbmc_project.ExitAirTempSensor");
960 sdbusplus::asio::object_server objectServer(systemBus);
961 std::shared_ptr<ExitAirTempSensor> sensor =
962 nullptr; // wait until we find the config
963 std::vector<std::unique_ptr<sdbusplus::bus::match::match>> matches;
964
965 io.post([&]() { createSensor(objectServer, sensor, systemBus); });
966
967 boost::asio::deadline_timer configTimer(io);
968
969 std::function<void(sdbusplus::message::message&)> eventHandler =
James Feistb6c0b912019-07-09 12:21:44 -0700970 [&](sdbusplus::message::message&) {
James Feistbc896df2018-11-26 16:28:17 -0800971 configTimer.expires_from_now(boost::posix_time::seconds(1));
972 // create a timer because normally multiple properties change
973 configTimer.async_wait([&](const boost::system::error_code& ec) {
974 if (ec == boost::asio::error::operation_aborted)
975 {
976 return; // we're being canceled
977 }
978 createSensor(objectServer, sensor, systemBus);
979 if (!sensor)
980 {
981 std::cout << "Configuration not detected\n";
982 }
983 });
984 };
James Feistbc896df2018-11-26 16:28:17 -0800985 for (const char* type : monitorIfaces)
986 {
987 auto match = std::make_unique<sdbusplus::bus::match::match>(
988 static_cast<sdbusplus::bus::bus&>(*systemBus),
989 "type='signal',member='PropertiesChanged',path_namespace='" +
990 std::string(inventoryPath) + "',arg0namespace='" + type + "'",
991 eventHandler);
992 matches.emplace_back(std::move(match));
993 }
994
Bruce Lee913d4d02021-07-22 10:18:42 +0800995 setupManufacturingModeMatch(*systemBus);
James Feistbc896df2018-11-26 16:28:17 -0800996 io.run();
Zhikui Ren8685b172021-06-29 15:16:52 -0700997 return 0;
James Feistbc896df2018-11-26 16:28:17 -0800998}