blob: 4d77ef44f1a5b2c92ba0eb609e6fecea8e70edbe [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) {
Ed Tanousbb679322022-05-16 16:10:00 -070073 std::string objectName;
74 boost::container::flat_map<std::string, std::variant<double, int64_t>>
75 values;
76 message.read(objectName, values);
77 auto findValue = values.find("Value");
78 if (findValue == values.end())
79 {
80 return;
81 }
82 double value = std::visit(VariantToDoubleVisitor(), findValue->second);
83 if (std::isnan(value))
84 {
85 return;
86 }
James Feist9566bfa2019-01-29 15:31:23 -080087
Ed Tanousbb679322022-05-16 16:10:00 -070088 callback(value, message);
89 };
James Feistb2eb3f52018-12-04 16:17:50 -080090 matches.emplace_back(connection,
91 "type='signal',"
92 "member='PropertiesChanged',interface='org."
93 "freedesktop.DBus.Properties',path_"
94 "namespace='/xyz/openbmc_project/sensors/" +
95 std::string(type) +
96 "',arg0='xyz.openbmc_project.Sensor.Value'",
97 std::move(eventHandler));
98}
99
James Feist13452092019-03-07 16:38:12 -0800100static void setMaxPWM(const std::shared_ptr<sdbusplus::asio::connection>& conn,
101 double value)
102{
103 using GetSubTreeType = std::vector<std::pair<
104 std::string,
105 std::vector<std::pair<std::string, std::vector<std::string>>>>>;
106
107 conn->async_method_call(
108 [conn, value](const boost::system::error_code ec,
109 const GetSubTreeType& ret) {
Ed Tanousbb679322022-05-16 16:10:00 -0700110 if (ec)
111 {
112 std::cerr << "Error calling mapper\n";
113 return;
114 }
115 for (const auto& [path, objDict] : ret)
116 {
117 if (objDict.empty())
James Feist13452092019-03-07 16:38:12 -0800118 {
James Feist13452092019-03-07 16:38:12 -0800119 return;
120 }
Ed Tanousbb679322022-05-16 16:10:00 -0700121 const std::string& owner = objDict.begin()->first;
122
123 conn->async_method_call(
124 [conn, value, owner,
125 path{path}](const boost::system::error_code ec,
126 const std::variant<std::string>& classType) {
127 if (ec)
128 {
129 std::cerr << "Error getting pid class\n";
130 return;
131 }
132 auto classStr = std::get_if<std::string>(&classType);
133 if (classStr == nullptr || *classStr != "fan")
James Feist13452092019-03-07 16:38:12 -0800134 {
135 return;
136 }
James Feist13452092019-03-07 16:38:12 -0800137 conn->async_method_call(
Ed Tanousbb679322022-05-16 16:10:00 -0700138 [](boost::system::error_code& ec) {
139 if (ec)
140 {
141 std::cerr << "Error setting pid class\n";
142 return;
143 }
James Feist13452092019-03-07 16:38:12 -0800144 },
Ed Tanousbb679322022-05-16 16:10:00 -0700145 owner, path, "org.freedesktop.DBus.Properties", "Set",
146 pidConfigurationType, "OutLimitMax",
147 std::variant<double>(value));
148 },
149 owner, path, "org.freedesktop.DBus.Properties", "Get",
150 pidConfigurationType, "Class");
151 }
James Feist13452092019-03-07 16:38:12 -0800152 },
James Feista5e58722019-04-22 14:43:11 -0700153 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
154 0, std::array<std::string, 1>{pidConfigurationType});
James Feist13452092019-03-07 16:38:12 -0800155}
156
James Feistb2eb3f52018-12-04 16:17:50 -0800157CFMSensor::CFMSensor(std::shared_ptr<sdbusplus::asio::connection>& conn,
158 const std::string& sensorName,
159 const std::string& sensorConfiguration,
160 sdbusplus::asio::object_server& objectServer,
James Feistb839c052019-05-15 10:25:24 -0700161 std::vector<thresholds::Threshold>&& thresholdData,
James Feistb2eb3f52018-12-04 16:17:50 -0800162 std::shared_ptr<ExitAirTempSensor>& parent) :
Zhikui Renda98f092021-11-01 09:41:08 -0700163 Sensor(escapeName(sensorName), std::move(thresholdData),
164 sensorConfiguration, "xyz.openbmc_project.Configuration.ExitAirTemp",
165 false, false, cfmMaxReading, cfmMinReading, conn, PowerState::on),
Brad Bishopfbb44ad2019-11-08 09:42:37 -0500166 std::enable_shared_from_this<CFMSensor>(), parent(parent),
James Feiste3338522020-09-15 15:40:30 -0700167 objServer(objectServer)
James Feistb2eb3f52018-12-04 16:17:50 -0800168{
Basheer Ahmed Muddebihale5b867b2021-07-26 08:32:19 -0700169 sensorInterface = objectServer.add_interface(
170 "/xyz/openbmc_project/sensors/airflow/" + name,
171 "xyz.openbmc_project.Sensor.Value");
James Feistb2eb3f52018-12-04 16:17:50 -0800172
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530173 for (const auto& threshold : thresholds)
James Feistb2eb3f52018-12-04 16:17:50 -0800174 {
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530175 std::string interface = thresholds::getInterface(threshold.level);
176 thresholdInterfaces[static_cast<size_t>(threshold.level)] =
177 objectServer.add_interface(
178 "/xyz/openbmc_project/sensors/airflow/" + name, interface);
James Feistb2eb3f52018-12-04 16:17:50 -0800179 }
James Feist078f2322019-03-08 11:09:05 -0800180
181 association = objectServer.add_interface(
Basheer Ahmed Muddebihale5b867b2021-07-26 08:32:19 -0700182 "/xyz/openbmc_project/sensors/airflow/" + name, association::interface);
James Feist078f2322019-03-08 11:09:05 -0800183
Andrei Kartashev39287412022-02-04 16:04:47 +0300184 setInitialProperties(sensor_paths::unitCFM);
James Feist9a25ed42019-10-15 15:43:44 -0700185
James Feist13452092019-03-07 16:38:12 -0800186 pwmLimitIface =
187 objectServer.add_interface("/xyz/openbmc_project/control/pwm_limit",
188 "xyz.openbmc_project.Control.PWMLimit");
189 cfmLimitIface =
190 objectServer.add_interface("/xyz/openbmc_project/control/MaxCFM",
191 "xyz.openbmc_project.Control.CFMLimit");
James Feist9a25ed42019-10-15 15:43:44 -0700192}
James Feist13452092019-03-07 16:38:12 -0800193
James Feist9a25ed42019-10-15 15:43:44 -0700194void CFMSensor::setupMatches()
195{
196
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700197 std::weak_ptr<CFMSensor> weakRef = weak_from_this();
Ed Tanous8a17c302021-09-02 15:07:11 -0700198 setupSensorMatch(
199 matches, *dbusConnection, "fan_tach",
200 [weakRef](const double& value, sdbusplus::message::message& message) {
Ed Tanousbb679322022-05-16 16:10:00 -0700201 auto self = weakRef.lock();
202 if (!self)
203 {
204 return;
205 }
206 self->tachReadings[message.get_path()] = value;
207 if (self->tachRanges.find(message.get_path()) == self->tachRanges.end())
208 {
209 // calls update reading after updating ranges
210 self->addTachRanges(message.get_sender(), message.get_path());
211 }
212 else
213 {
214 self->updateReading();
215 }
Ed Tanous8a17c302021-09-02 15:07:11 -0700216 });
James Feist9a25ed42019-10-15 15:43:44 -0700217
218 dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700219 [weakRef](const boost::system::error_code ec,
220 const std::variant<double> cfmVariant) {
Ed Tanousbb679322022-05-16 16:10:00 -0700221 auto self = weakRef.lock();
222 if (!self)
223 {
224 return;
225 }
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700226
Ed Tanousbb679322022-05-16 16:10:00 -0700227 uint64_t maxRpm = 100;
228 if (!ec)
229 {
James Feist13452092019-03-07 16:38:12 -0800230
Ed Tanousbb679322022-05-16 16:10:00 -0700231 auto cfm = std::get_if<double>(&cfmVariant);
232 if (cfm != nullptr && *cfm >= minSystemCfm)
233 {
234 maxRpm = self->getMaxRpm(*cfm);
James Feist13452092019-03-07 16:38:12 -0800235 }
Ed Tanousbb679322022-05-16 16:10:00 -0700236 }
237 self->pwmLimitIface->register_property("Limit", maxRpm);
238 self->pwmLimitIface->initialize();
239 setMaxPWM(self->dbusConnection, maxRpm);
James Feist13452092019-03-07 16:38:12 -0800240 },
241 settingsDaemon, cfmSettingPath, "org.freedesktop.DBus.Properties",
242 "Get", cfmSettingIface, "Limit");
243
Ed Tanousbb679322022-05-16 16:10:00 -0700244 matches.emplace_back(*dbusConnection,
245 "type='signal',"
246 "member='PropertiesChanged',interface='org."
247 "freedesktop.DBus.Properties',path='" +
248 std::string(cfmSettingPath) + "',arg0='" +
249 std::string(cfmSettingIface) + "'",
250 [weakRef](sdbusplus::message::message& message) {
251 auto self = weakRef.lock();
252 if (!self)
253 {
254 return;
255 }
256 boost::container::flat_map<std::string, std::variant<double>> values;
257 std::string objectName;
258 message.read(objectName, values);
259 const auto findValue = values.find("Limit");
260 if (findValue == values.end())
261 {
262 return;
263 }
264 const auto reading = std::get_if<double>(&(findValue->second));
265 if (reading == nullptr)
266 {
267 std::cerr << "Got CFM Limit of wrong type\n";
268 return;
269 }
270 if (*reading < minSystemCfm && *reading != 0)
271 {
272 std::cerr << "Illegal CFM setting detected\n";
273 return;
274 }
275 uint64_t maxRpm = self->getMaxRpm(*reading);
276 self->pwmLimitIface->set_property("Limit", maxRpm);
277 setMaxPWM(self->dbusConnection, maxRpm);
278 });
James Feistb2eb3f52018-12-04 16:17:50 -0800279}
280
James Feist9566bfa2019-01-29 15:31:23 -0800281CFMSensor::~CFMSensor()
282{
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530283 for (const auto& iface : thresholdInterfaces)
284 {
285 objServer.remove_interface(iface);
286 }
James Feist9566bfa2019-01-29 15:31:23 -0800287 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800288 objServer.remove_interface(association);
James Feist13452092019-03-07 16:38:12 -0800289 objServer.remove_interface(cfmLimitIface);
290 objServer.remove_interface(pwmLimitIface);
291}
292
293void CFMSensor::createMaxCFMIface(void)
294{
James Feistb6c0b912019-07-09 12:21:44 -0700295 cfmLimitIface->register_property("Limit", c2 * maxCFM * tachs.size());
James Feist13452092019-03-07 16:38:12 -0800296 cfmLimitIface->initialize();
James Feist9566bfa2019-01-29 15:31:23 -0800297}
298
James Feistb2eb3f52018-12-04 16:17:50 -0800299void CFMSensor::addTachRanges(const std::string& serviceName,
300 const std::string& path)
301{
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700302 std::weak_ptr<CFMSensor> weakRef = weak_from_this();
James Feistb2eb3f52018-12-04 16:17:50 -0800303 dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700304 [weakRef,
305 path](const boost::system::error_code ec,
306 const boost::container::flat_map<std::string, BasicVariantType>&
307 data) {
Ed Tanousbb679322022-05-16 16:10:00 -0700308 if (ec)
309 {
310 std::cerr << "Error getting properties from " << path << "\n";
311 return;
312 }
313 auto self = weakRef.lock();
314 if (!self)
315 {
316 return;
317 }
318 double max = loadVariant<double>(data, "MaxValue");
319 double min = loadVariant<double>(data, "MinValue");
320 self->tachRanges[path] = std::make_pair(min, max);
321 self->updateReading();
James Feistb2eb3f52018-12-04 16:17:50 -0800322 },
323 serviceName, path, "org.freedesktop.DBus.Properties", "GetAll",
324 "xyz.openbmc_project.Sensor.Value");
325}
326
327void CFMSensor::checkThresholds(void)
328{
329 thresholds::checkThresholds(this);
330}
331
332void CFMSensor::updateReading(void)
333{
334 double val = 0.0;
335 if (calculate(val))
336 {
337 if (value != val && parent)
338 {
339 parent->updateReading();
340 }
341 updateValue(val);
342 }
343 else
344 {
345 updateValue(std::numeric_limits<double>::quiet_NaN());
346 }
347}
348
James Feist13452092019-03-07 16:38:12 -0800349uint64_t CFMSensor::getMaxRpm(uint64_t cfmMaxSetting)
350{
351 uint64_t pwmPercent = 100;
352 double totalCFM = std::numeric_limits<double>::max();
353 if (cfmMaxSetting == 0)
354 {
355 return pwmPercent;
356 }
357
James Feist52427952019-04-05 14:23:35 -0700358 bool firstLoop = true;
James Feist13452092019-03-07 16:38:12 -0800359 while (totalCFM > cfmMaxSetting)
360 {
James Feist52427952019-04-05 14:23:35 -0700361 if (firstLoop)
362 {
363 firstLoop = false;
364 }
365 else
366 {
367 pwmPercent--;
368 }
369
James Feist13452092019-03-07 16:38:12 -0800370 double ci = 0;
371 if (pwmPercent == 0)
372 {
373 ci = 0;
374 }
375 else if (pwmPercent < tachMinPercent)
376 {
377 ci = c1;
378 }
379 else if (pwmPercent > tachMaxPercent)
380 {
381 ci = c2;
382 }
383 else
384 {
385 ci = c1 + (((c2 - c1) * (pwmPercent - tachMinPercent)) /
386 (tachMaxPercent - tachMinPercent));
387 }
388
389 // Now calculate the CFM for this tach
390 // CFMi = Ci * Qmaxi * TACHi
391 totalCFM = ci * maxCFM * pwmPercent;
392 totalCFM *= tachs.size();
393 // divide by 100 since pwm is in percent
394 totalCFM /= 100;
395
James Feist13452092019-03-07 16:38:12 -0800396 if (pwmPercent <= 0)
397 {
398 break;
399 }
400 }
James Feist52427952019-04-05 14:23:35 -0700401
James Feist13452092019-03-07 16:38:12 -0800402 return pwmPercent;
403}
404
James Feistb2eb3f52018-12-04 16:17:50 -0800405bool CFMSensor::calculate(double& value)
406{
407 double totalCFM = 0;
408 for (const std::string& tachName : tachs)
409 {
James Feist9566bfa2019-01-29 15:31:23 -0800410
James Feistb2eb3f52018-12-04 16:17:50 -0800411 auto findReading = std::find_if(
412 tachReadings.begin(), tachReadings.end(), [&](const auto& item) {
413 return boost::ends_with(item.first, tachName);
414 });
Ed Tanousbb679322022-05-16 16:10:00 -0700415 auto findRange = std::find_if(tachRanges.begin(), tachRanges.end(),
416 [&](const auto& item) {
417 return boost::ends_with(item.first, tachName);
418 });
James Feistb2eb3f52018-12-04 16:17:50 -0800419 if (findReading == tachReadings.end())
420 {
Ed Tanous8a57ec02020-10-09 12:46:52 -0700421 if constexpr (debug)
James Feista96329f2019-01-24 10:08:27 -0800422 {
423 std::cerr << "Can't find " << tachName << "in readings\n";
424 }
James Feist9566bfa2019-01-29 15:31:23 -0800425 continue; // haven't gotten a reading
James Feistb2eb3f52018-12-04 16:17:50 -0800426 }
427
428 if (findRange == tachRanges.end())
429 {
James Feist523828e2019-03-04 14:38:37 -0800430 std::cerr << "Can't find " << tachName << " in ranges\n";
James Feistb2eb3f52018-12-04 16:17:50 -0800431 return false; // haven't gotten a max / min
432 }
433
434 // avoid divide by 0
435 if (findRange->second.second == 0)
436 {
437 std::cerr << "Tach Max Set to 0 " << tachName << "\n";
438 return false;
439 }
440
441 double rpm = findReading->second;
442
443 // for now assume the min for a fan is always 0, divide by max to get
444 // percent and mult by 100
445 rpm /= findRange->second.second;
446 rpm *= 100;
447
Ed Tanous8a57ec02020-10-09 12:46:52 -0700448 if constexpr (debug)
James Feistb2eb3f52018-12-04 16:17:50 -0800449 {
450 std::cout << "Tach " << tachName << "at " << rpm << "\n";
451 }
452
453 // Do a linear interpolation to get Ci
454 // Ci = C1 + (C2 - C1)/(RPM2 - RPM1) * (TACHi - TACH1)
455
456 double ci = 0;
457 if (rpm == 0)
458 {
459 ci = 0;
460 }
461 else if (rpm < tachMinPercent)
462 {
463 ci = c1;
464 }
465 else if (rpm > tachMaxPercent)
466 {
467 ci = c2;
468 }
469 else
470 {
471 ci = c1 + (((c2 - c1) * (rpm - tachMinPercent)) /
472 (tachMaxPercent - tachMinPercent));
473 }
474
475 // Now calculate the CFM for this tach
476 // CFMi = Ci * Qmaxi * TACHi
477 totalCFM += ci * maxCFM * rpm;
Ed Tanous8a57ec02020-10-09 12:46:52 -0700478 if constexpr (debug)
James Feista5e58722019-04-22 14:43:11 -0700479 {
480 std::cerr << "totalCFM = " << totalCFM << "\n";
481 std::cerr << "Ci " << ci << " MaxCFM " << maxCFM << " rpm " << rpm
482 << "\n";
483 std::cerr << "c1 " << c1 << " c2 " << c2 << " max "
484 << tachMaxPercent << " min " << tachMinPercent << "\n";
485 }
James Feistb2eb3f52018-12-04 16:17:50 -0800486 }
487
488 // divide by 100 since rpm is in percent
489 value = totalCFM / 100;
Ed Tanous8a57ec02020-10-09 12:46:52 -0700490 if constexpr (debug)
James Feista5e58722019-04-22 14:43:11 -0700491 {
492 std::cerr << "cfm value = " << value << "\n";
493 }
James Feist9566bfa2019-01-29 15:31:23 -0800494 return true;
James Feistb2eb3f52018-12-04 16:17:50 -0800495}
496
497static constexpr double exitAirMaxReading = 127;
498static constexpr double exitAirMinReading = -128;
James Feistbc896df2018-11-26 16:28:17 -0800499ExitAirTempSensor::ExitAirTempSensor(
500 std::shared_ptr<sdbusplus::asio::connection>& conn,
James Feistb2eb3f52018-12-04 16:17:50 -0800501 const std::string& sensorName, const std::string& sensorConfiguration,
James Feistbc896df2018-11-26 16:28:17 -0800502 sdbusplus::asio::object_server& objectServer,
James Feistb839c052019-05-15 10:25:24 -0700503 std::vector<thresholds::Threshold>&& thresholdData) :
Zhikui Renda98f092021-11-01 09:41:08 -0700504 Sensor(escapeName(sensorName), std::move(thresholdData),
505 sensorConfiguration, "xyz.openbmc_project.Configuration.ExitAirTemp",
506 false, false, exitAirMaxReading, exitAirMinReading, conn,
507 PowerState::on),
James Feiste3338522020-09-15 15:40:30 -0700508 std::enable_shared_from_this<ExitAirTempSensor>(), objServer(objectServer)
James Feistbc896df2018-11-26 16:28:17 -0800509{
510 sensorInterface = objectServer.add_interface(
511 "/xyz/openbmc_project/sensors/temperature/" + name,
512 "xyz.openbmc_project.Sensor.Value");
513
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530514 for (const auto& threshold : thresholds)
James Feistbc896df2018-11-26 16:28:17 -0800515 {
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530516 std::string interface = thresholds::getInterface(threshold.level);
517 thresholdInterfaces[static_cast<size_t>(threshold.level)] =
518 objectServer.add_interface(
519 "/xyz/openbmc_project/sensors/temperature/" + name, interface);
James Feistbc896df2018-11-26 16:28:17 -0800520 }
James Feist078f2322019-03-08 11:09:05 -0800521 association = objectServer.add_interface(
522 "/xyz/openbmc_project/sensors/temperature/" + name,
James Feist2adc95c2019-09-30 14:55:28 -0700523 association::interface);
Andrei Kartashev39287412022-02-04 16:04:47 +0300524 setInitialProperties(sensor_paths::unitDegreesC);
James Feistbc896df2018-11-26 16:28:17 -0800525}
526
527ExitAirTempSensor::~ExitAirTempSensor()
528{
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530529 for (const auto& iface : thresholdInterfaces)
530 {
531 objServer.remove_interface(iface);
532 }
James Feist523828e2019-03-04 14:38:37 -0800533 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800534 objServer.remove_interface(association);
James Feistbc896df2018-11-26 16:28:17 -0800535}
536
537void ExitAirTempSensor::setupMatches(void)
538{
Brandon Kim66558232021-11-09 16:53:08 -0800539 constexpr const auto matchTypes{
540 std::to_array<const char*>({"power", inletTemperatureSensor})};
James Feistbc896df2018-11-26 16:28:17 -0800541
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700542 std::weak_ptr<ExitAirTempSensor> weakRef = weak_from_this();
Ed Tanous13b63f82021-05-11 16:12:52 -0700543 for (const std::string type : matchTypes)
James Feistbc896df2018-11-26 16:28:17 -0800544 {
James Feistb2eb3f52018-12-04 16:17:50 -0800545 setupSensorMatch(matches, *dbusConnection, type,
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700546 [weakRef, type](const double& value,
547 sdbusplus::message::message& message) {
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700548 auto self = weakRef.lock();
549 if (!self)
550 {
551 return;
552 }
Ed Tanousbb679322022-05-16 16:10:00 -0700553 if (type == "power")
554 {
555 std::string path = message.get_path();
556 if (path.find("PS") != std::string::npos &&
557 boost::ends_with(path, "Input_Power"))
558 {
559 self->powerReadings[message.get_path()] = value;
560 }
561 }
562 else if (type == inletTemperatureSensor)
563 {
564 self->inletTemp = value;
565 }
566 self->updateReading();
567 });
568 }
569 dbusConnection->async_method_call(
570 [weakRef](boost::system::error_code ec,
571 const std::variant<double>& value) {
572 if (ec)
573 {
574 // sensor not ready yet
575 return;
576 }
577 auto self = weakRef.lock();
578 if (!self)
579 {
580 return;
581 }
582 self->inletTemp = std::visit(VariantToDoubleVisitor(), value);
James Feist9566bfa2019-01-29 15:31:23 -0800583 },
584 "xyz.openbmc_project.HwmonTempSensor",
585 std::string("/xyz/openbmc_project/sensors/") + inletTemperatureSensor,
James Feista5e58722019-04-22 14:43:11 -0700586 properties::interface, properties::get, sensorValueInterface, "Value");
587 dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700588 [weakRef](boost::system::error_code ec, const GetSubTreeType& subtree) {
Ed Tanousbb679322022-05-16 16:10:00 -0700589 if (ec)
590 {
591 std::cerr << "Error contacting mapper\n";
592 return;
593 }
594 auto self = weakRef.lock();
595 if (!self)
596 {
597 return;
598 }
599 for (const auto& item : subtree)
600 {
601 size_t lastSlash = item.first.rfind("/");
602 if (lastSlash == std::string::npos ||
603 lastSlash == item.first.size() || !item.second.size())
James Feista5e58722019-04-22 14:43:11 -0700604 {
Ed Tanousbb679322022-05-16 16:10:00 -0700605 continue;
James Feista5e58722019-04-22 14:43:11 -0700606 }
Ed Tanousbb679322022-05-16 16:10:00 -0700607 std::string sensorName = item.first.substr(lastSlash + 1);
608 if (boost::starts_with(sensorName, "PS") &&
609 boost::ends_with(sensorName, "Input_Power"))
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700610 {
Ed Tanousbb679322022-05-16 16:10:00 -0700611 const std::string& path = item.first;
612 self->dbusConnection->async_method_call(
613 [weakRef, path](boost::system::error_code ec,
614 const std::variant<double>& value) {
615 if (ec)
616 {
617 std::cerr << "Error getting value from " << path
618 << "\n";
619 }
620 auto self = weakRef.lock();
621 if (!self)
622 {
623 return;
624 }
625 double reading =
626 std::visit(VariantToDoubleVisitor(), value);
627 if constexpr (debug)
628 {
629 std::cerr << path << "Reading " << reading << "\n";
630 }
631 self->powerReadings[path] = reading;
632 },
633 item.second[0].first, item.first, properties::interface,
634 properties::get, sensorValueInterface, "Value");
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700635 }
Ed Tanousbb679322022-05-16 16:10:00 -0700636 }
James Feista5e58722019-04-22 14:43:11 -0700637 },
638 mapper::busName, mapper::path, mapper::interface, mapper::subtree,
639 "/xyz/openbmc_project/sensors/power", 0,
640 std::array<const char*, 1>{sensorValueInterface});
James Feistbc896df2018-11-26 16:28:17 -0800641}
642
643void ExitAirTempSensor::updateReading(void)
644{
645
646 double val = 0.0;
647 if (calculate(val))
648 {
James Feist18af4232019-03-13 11:14:00 -0700649 val = std::floor(val + 0.5);
James Feistbc896df2018-11-26 16:28:17 -0800650 updateValue(val);
651 }
652 else
653 {
654 updateValue(std::numeric_limits<double>::quiet_NaN());
655 }
656}
657
James Feistb2eb3f52018-12-04 16:17:50 -0800658double ExitAirTempSensor::getTotalCFM(void)
James Feistbc896df2018-11-26 16:28:17 -0800659{
James Feistb2eb3f52018-12-04 16:17:50 -0800660 double sum = 0;
661 for (auto& sensor : cfmSensors)
James Feistbc896df2018-11-26 16:28:17 -0800662 {
James Feistb2eb3f52018-12-04 16:17:50 -0800663 double reading = 0;
664 if (!sensor->calculate(reading))
James Feistbc896df2018-11-26 16:28:17 -0800665 {
James Feistbc896df2018-11-26 16:28:17 -0800666 return -1;
667 }
James Feistb2eb3f52018-12-04 16:17:50 -0800668 sum += reading;
James Feistbc896df2018-11-26 16:28:17 -0800669 }
James Feistb2eb3f52018-12-04 16:17:50 -0800670
671 return sum;
James Feistbc896df2018-11-26 16:28:17 -0800672}
673
674bool ExitAirTempSensor::calculate(double& val)
675{
Zhikui Ren12e3d672020-12-03 15:14:49 -0800676 constexpr size_t maxErrorPrint = 5;
James Feistbc896df2018-11-26 16:28:17 -0800677 static bool firstRead = false;
James Feistae11cfc2019-05-07 15:01:20 -0700678 static size_t errorPrint = maxErrorPrint;
679
James Feistbc896df2018-11-26 16:28:17 -0800680 double cfm = getTotalCFM();
681 if (cfm <= 0)
682 {
683 std::cerr << "Error getting cfm\n";
684 return false;
685 }
686
Zhikui Ren12e3d672020-12-03 15:14:49 -0800687 // Though cfm is not expected to be less than qMin normally,
688 // it is not a hard limit for exit air temp calculation.
689 // 50% qMin is chosen as a generic limit between providing
690 // a valid derived exit air temp and reporting exit air temp not available.
691 constexpr const double cfmLimitFactor = 0.5;
692 if (cfm < (qMin * cfmLimitFactor))
693 {
694 if (errorPrint > 0)
695 {
696 errorPrint--;
697 std::cerr << "cfm " << cfm << " is too low, expected qMin " << qMin
698 << "\n";
699 }
700 val = 0;
701 return false;
702 }
703
James Feistbc896df2018-11-26 16:28:17 -0800704 // if there is an error getting inlet temp, return error
705 if (std::isnan(inletTemp))
706 {
James Feistae11cfc2019-05-07 15:01:20 -0700707 if (errorPrint > 0)
708 {
709 errorPrint--;
710 std::cerr << "Cannot get inlet temp\n";
711 }
James Feistbc896df2018-11-26 16:28:17 -0800712 val = 0;
713 return false;
714 }
715
716 // if fans are off, just make the exit temp equal to inlet
James Feist71d31b22019-01-02 16:57:54 -0800717 if (!isPowerOn())
James Feistbc896df2018-11-26 16:28:17 -0800718 {
719 val = inletTemp;
720 return true;
721 }
722
723 double totalPower = 0;
724 for (const auto& reading : powerReadings)
725 {
726 if (std::isnan(reading.second))
727 {
728 continue;
729 }
730 totalPower += reading.second;
731 }
732
733 // Calculate power correction factor
734 // Ci = CL + (CH - CL)/(QMax - QMin) * (CFM - QMin)
Ed Tanous8a57ec02020-10-09 12:46:52 -0700735 double powerFactor = 0.0;
James Feistbc896df2018-11-26 16:28:17 -0800736 if (cfm <= qMin)
737 {
738 powerFactor = powerFactorMin;
739 }
740 else if (cfm >= qMax)
741 {
742 powerFactor = powerFactorMax;
743 }
744 else
745 {
746 powerFactor = powerFactorMin + ((powerFactorMax - powerFactorMin) /
747 (qMax - qMin) * (cfm - qMin));
748 }
749
Ed Tanous8a57ec02020-10-09 12:46:52 -0700750 totalPower *= powerFactor;
James Feistbc896df2018-11-26 16:28:17 -0800751 totalPower += pOffset;
752
753 if (totalPower == 0)
754 {
James Feistae11cfc2019-05-07 15:01:20 -0700755 if (errorPrint > 0)
756 {
757 errorPrint--;
758 std::cerr << "total power 0\n";
759 }
James Feistbc896df2018-11-26 16:28:17 -0800760 val = 0;
761 return false;
762 }
763
Ed Tanous8a57ec02020-10-09 12:46:52 -0700764 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800765 {
766 std::cout << "Power Factor " << powerFactor << "\n";
767 std::cout << "Inlet Temp " << inletTemp << "\n";
768 std::cout << "Total Power" << totalPower << "\n";
769 }
770
771 // Calculate the exit air temp
772 // Texit = Tfp + (1.76 * TotalPower / CFM * Faltitude)
Ed Tanous8a57ec02020-10-09 12:46:52 -0700773 double reading = 1.76 * totalPower * altitudeFactor;
James Feistbc896df2018-11-26 16:28:17 -0800774 reading /= cfm;
775 reading += inletTemp;
776
Ed Tanous8a57ec02020-10-09 12:46:52 -0700777 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800778 {
779 std::cout << "Reading 1: " << reading << "\n";
780 }
781
782 // Now perform the exponential average
783 // Calculate alpha based on SDR values and CFM
784 // Ai = As + (Af - As)/(QMax - QMin) * (CFM - QMin)
785
786 double alpha = 0.0;
787 if (cfm < qMin)
788 {
789 alpha = alphaS;
790 }
791 else if (cfm >= qMax)
792 {
793 alpha = alphaF;
794 }
795 else
796 {
797 alpha = alphaS + ((alphaF - alphaS) * (cfm - qMin) / (qMax - qMin));
798 }
799
Zhikui Ren12e3d672020-12-03 15:14:49 -0800800 auto time = std::chrono::steady_clock::now();
James Feistbc896df2018-11-26 16:28:17 -0800801 if (!firstRead)
802 {
803 firstRead = true;
804 lastTime = time;
805 lastReading = reading;
806 }
807 double alphaDT =
808 std::chrono::duration_cast<std::chrono::seconds>(time - lastTime)
809 .count() *
810 alpha;
811
812 // cap at 1.0 or the below fails
813 if (alphaDT > 1.0)
814 {
815 alphaDT = 1.0;
816 }
817
Ed Tanous8a57ec02020-10-09 12:46:52 -0700818 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800819 {
820 std::cout << "AlphaDT: " << alphaDT << "\n";
821 }
822
823 reading = ((reading * alphaDT) + (lastReading * (1.0 - alphaDT)));
824
Ed Tanous8a57ec02020-10-09 12:46:52 -0700825 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800826 {
827 std::cout << "Reading 2: " << reading << "\n";
828 }
829
830 val = reading;
831 lastReading = reading;
832 lastTime = time;
James Feistae11cfc2019-05-07 15:01:20 -0700833 errorPrint = maxErrorPrint;
James Feistbc896df2018-11-26 16:28:17 -0800834 return true;
835}
836
837void ExitAirTempSensor::checkThresholds(void)
838{
839 thresholds::checkThresholds(this);
840}
841
James Feistbc896df2018-11-26 16:28:17 -0800842static void loadVariantPathArray(
843 const boost::container::flat_map<std::string, BasicVariantType>& data,
844 const std::string& key, std::vector<std::string>& resp)
845{
846 auto it = data.find(key);
847 if (it == data.end())
848 {
849 std::cerr << "Configuration missing " << key << "\n";
850 throw std::invalid_argument("Key Missing");
851 }
852 BasicVariantType copy = it->second;
James Feist3eb82622019-02-08 13:10:22 -0800853 std::vector<std::string> config = std::get<std::vector<std::string>>(copy);
James Feistbc896df2018-11-26 16:28:17 -0800854 for (auto& str : config)
855 {
856 boost::replace_all(str, " ", "_");
857 }
858 resp = std::move(config);
859}
860
861void createSensor(sdbusplus::asio::object_server& objectServer,
James Feistb2eb3f52018-12-04 16:17:50 -0800862 std::shared_ptr<ExitAirTempSensor>& exitAirSensor,
James Feistbc896df2018-11-26 16:28:17 -0800863 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
864{
865 if (!dbusConnection)
866 {
867 std::cerr << "Connection not created\n";
868 return;
869 }
James Feist655f3762020-10-05 15:28:15 -0700870 auto getter = std::make_shared<GetSensorConfiguration>(
Ed Tanousbb679322022-05-16 16:10:00 -0700871 dbusConnection,
872 [&objectServer, &dbusConnection,
873 &exitAirSensor](const ManagedObjectType& resp) {
874 cfmSensors.clear();
875 for (const auto& pathPair : resp)
876 {
877 for (const auto& entry : pathPair.second)
James Feistbc896df2018-11-26 16:28:17 -0800878 {
Ed Tanousbb679322022-05-16 16:10:00 -0700879 if (entry.first == exitAirIface)
James Feistbc896df2018-11-26 16:28:17 -0800880 {
Ed Tanousbb679322022-05-16 16:10:00 -0700881 // thresholds should be under the same path
882 std::vector<thresholds::Threshold> sensorThresholds;
883 parseThresholdsFromConfig(pathPair.second,
884 sensorThresholds);
James Feistbc896df2018-11-26 16:28:17 -0800885
Ed Tanousbb679322022-05-16 16:10:00 -0700886 std::string name =
887 loadVariant<std::string>(entry.second, "Name");
888 exitAirSensor = std::make_shared<ExitAirTempSensor>(
889 dbusConnection, name, pathPair.first.str, objectServer,
890 std::move(sensorThresholds));
891 exitAirSensor->powerFactorMin =
892 loadVariant<double>(entry.second, "PowerFactorMin");
893 exitAirSensor->powerFactorMax =
894 loadVariant<double>(entry.second, "PowerFactorMax");
895 exitAirSensor->qMin =
896 loadVariant<double>(entry.second, "QMin");
897 exitAirSensor->qMax =
898 loadVariant<double>(entry.second, "QMax");
899 exitAirSensor->alphaS =
900 loadVariant<double>(entry.second, "AlphaS");
901 exitAirSensor->alphaF =
902 loadVariant<double>(entry.second, "AlphaF");
903 }
904 else if (entry.first == cfmIface)
James Feistbc896df2018-11-26 16:28:17 -0800905
Ed Tanousbb679322022-05-16 16:10:00 -0700906 {
907 // thresholds should be under the same path
908 std::vector<thresholds::Threshold> sensorThresholds;
909 parseThresholdsFromConfig(pathPair.second,
910 sensorThresholds);
911 std::string name =
912 loadVariant<std::string>(entry.second, "Name");
913 auto sensor = std::make_shared<CFMSensor>(
914 dbusConnection, name, pathPair.first.str, objectServer,
915 std::move(sensorThresholds), exitAirSensor);
916 loadVariantPathArray(entry.second, "Tachs", sensor->tachs);
917 sensor->maxCFM =
918 loadVariant<double>(entry.second, "MaxCFM");
James Feistbc896df2018-11-26 16:28:17 -0800919
Ed Tanousbb679322022-05-16 16:10:00 -0700920 // change these into percent upon getting the data
921 sensor->c1 = loadVariant<double>(entry.second, "C1") / 100;
922 sensor->c2 = loadVariant<double>(entry.second, "C2") / 100;
923 sensor->tachMinPercent =
924 loadVariant<double>(entry.second, "TachMinPercent");
925 sensor->tachMaxPercent =
926 loadVariant<double>(entry.second, "TachMaxPercent");
927 sensor->createMaxCFMIface();
928 sensor->setupMatches();
James Feistbc896df2018-11-26 16:28:17 -0800929
Ed Tanousbb679322022-05-16 16:10:00 -0700930 cfmSensors.emplace_back(std::move(sensor));
James Feistbc896df2018-11-26 16:28:17 -0800931 }
932 }
Ed Tanousbb679322022-05-16 16:10:00 -0700933 }
934 if (exitAirSensor)
935 {
936 exitAirSensor->setupMatches();
937 exitAirSensor->updateReading();
938 }
Ed Tanous8a17c302021-09-02 15:07:11 -0700939 });
James Feist655f3762020-10-05 15:28:15 -0700940 getter->getConfiguration(
941 std::vector<std::string>(monitorIfaces.begin(), monitorIfaces.end()));
James Feistbc896df2018-11-26 16:28:17 -0800942}
943
James Feistb6c0b912019-07-09 12:21:44 -0700944int main()
James Feistbc896df2018-11-26 16:28:17 -0800945{
946
947 boost::asio::io_service io;
948 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
949 systemBus->request_name("xyz.openbmc_project.ExitAirTempSensor");
950 sdbusplus::asio::object_server objectServer(systemBus);
951 std::shared_ptr<ExitAirTempSensor> sensor =
952 nullptr; // wait until we find the config
953 std::vector<std::unique_ptr<sdbusplus::bus::match::match>> matches;
954
955 io.post([&]() { createSensor(objectServer, sensor, systemBus); });
956
957 boost::asio::deadline_timer configTimer(io);
958
959 std::function<void(sdbusplus::message::message&)> eventHandler =
James Feistb6c0b912019-07-09 12:21:44 -0700960 [&](sdbusplus::message::message&) {
Ed Tanousbb679322022-05-16 16:10:00 -0700961 configTimer.expires_from_now(boost::posix_time::seconds(1));
962 // create a timer because normally multiple properties change
963 configTimer.async_wait([&](const boost::system::error_code& ec) {
964 if (ec == boost::asio::error::operation_aborted)
965 {
966 return; // we're being canceled
967 }
968 createSensor(objectServer, sensor, systemBus);
969 if (!sensor)
970 {
971 std::cout << "Configuration not detected\n";
972 }
973 });
974 };
James Feistbc896df2018-11-26 16:28:17 -0800975 for (const char* type : monitorIfaces)
976 {
977 auto match = std::make_unique<sdbusplus::bus::match::match>(
978 static_cast<sdbusplus::bus::bus&>(*systemBus),
979 "type='signal',member='PropertiesChanged',path_namespace='" +
980 std::string(inventoryPath) + "',arg0namespace='" + type + "'",
981 eventHandler);
982 matches.emplace_back(std::move(match));
983 }
984
Bruce Lee913d4d02021-07-22 10:18:42 +0800985 setupManufacturingModeMatch(*systemBus);
James Feistbc896df2018-11-26 16:28:17 -0800986 io.run();
Zhikui Ren8685b172021-06-29 15:16:52 -0700987 return 0;
James Feistbc896df2018-11-26 16:28:17 -0800988}