blob: 67c690f2dc1fe7602dee3b92d683f08ce20221a9 [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 }
Ed Tanous2049bd22022-07-09 07:20:26 -0700132 const auto* classStr = std::get_if<std::string>(&classType);
Ed Tanousbb679322022-05-16 16:10:00 -0700133 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),
Ed Tanous2049bd22022-07-09 07:20:26 -0700166 parent(parent), objServer(objectServer)
James Feistb2eb3f52018-12-04 16:17:50 -0800167{
Basheer Ahmed Muddebihale5b867b2021-07-26 08:32:19 -0700168 sensorInterface = objectServer.add_interface(
169 "/xyz/openbmc_project/sensors/airflow/" + name,
170 "xyz.openbmc_project.Sensor.Value");
James Feistb2eb3f52018-12-04 16:17:50 -0800171
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530172 for (const auto& threshold : thresholds)
James Feistb2eb3f52018-12-04 16:17:50 -0800173 {
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530174 std::string interface = thresholds::getInterface(threshold.level);
175 thresholdInterfaces[static_cast<size_t>(threshold.level)] =
176 objectServer.add_interface(
177 "/xyz/openbmc_project/sensors/airflow/" + name, interface);
James Feistb2eb3f52018-12-04 16:17:50 -0800178 }
James Feist078f2322019-03-08 11:09:05 -0800179
180 association = objectServer.add_interface(
Basheer Ahmed Muddebihale5b867b2021-07-26 08:32:19 -0700181 "/xyz/openbmc_project/sensors/airflow/" + name, association::interface);
James Feist078f2322019-03-08 11:09:05 -0800182
Andrei Kartashev39287412022-02-04 16:04:47 +0300183 setInitialProperties(sensor_paths::unitCFM);
James Feist9a25ed42019-10-15 15:43:44 -0700184
James Feist13452092019-03-07 16:38:12 -0800185 pwmLimitIface =
186 objectServer.add_interface("/xyz/openbmc_project/control/pwm_limit",
187 "xyz.openbmc_project.Control.PWMLimit");
188 cfmLimitIface =
189 objectServer.add_interface("/xyz/openbmc_project/control/MaxCFM",
190 "xyz.openbmc_project.Control.CFMLimit");
James Feist9a25ed42019-10-15 15:43:44 -0700191}
James Feist13452092019-03-07 16:38:12 -0800192
James Feist9a25ed42019-10-15 15:43:44 -0700193void CFMSensor::setupMatches()
194{
195
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700196 std::weak_ptr<CFMSensor> weakRef = weak_from_this();
Ed Tanous8a17c302021-09-02 15:07:11 -0700197 setupSensorMatch(
198 matches, *dbusConnection, "fan_tach",
199 [weakRef](const double& value, sdbusplus::message::message& message) {
Ed Tanousbb679322022-05-16 16:10:00 -0700200 auto self = weakRef.lock();
201 if (!self)
202 {
203 return;
204 }
205 self->tachReadings[message.get_path()] = value;
206 if (self->tachRanges.find(message.get_path()) == self->tachRanges.end())
207 {
208 // calls update reading after updating ranges
209 self->addTachRanges(message.get_sender(), message.get_path());
210 }
211 else
212 {
213 self->updateReading();
214 }
Ed Tanous8a17c302021-09-02 15:07:11 -0700215 });
James Feist9a25ed42019-10-15 15:43:44 -0700216
217 dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700218 [weakRef](const boost::system::error_code ec,
219 const std::variant<double> cfmVariant) {
Ed Tanousbb679322022-05-16 16:10:00 -0700220 auto self = weakRef.lock();
221 if (!self)
222 {
223 return;
224 }
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700225
Ed Tanousbb679322022-05-16 16:10:00 -0700226 uint64_t maxRpm = 100;
227 if (!ec)
228 {
James Feist13452092019-03-07 16:38:12 -0800229
Ed Tanous2049bd22022-07-09 07:20:26 -0700230 const auto* cfm = std::get_if<double>(&cfmVariant);
Ed Tanousbb679322022-05-16 16:10:00 -0700231 if (cfm != nullptr && *cfm >= minSystemCfm)
232 {
233 maxRpm = self->getMaxRpm(*cfm);
James Feist13452092019-03-07 16:38:12 -0800234 }
Ed Tanousbb679322022-05-16 16:10:00 -0700235 }
236 self->pwmLimitIface->register_property("Limit", maxRpm);
237 self->pwmLimitIface->initialize();
238 setMaxPWM(self->dbusConnection, maxRpm);
James Feist13452092019-03-07 16:38:12 -0800239 },
240 settingsDaemon, cfmSettingPath, "org.freedesktop.DBus.Properties",
241 "Get", cfmSettingIface, "Limit");
242
Ed Tanousbb679322022-05-16 16:10:00 -0700243 matches.emplace_back(*dbusConnection,
244 "type='signal',"
245 "member='PropertiesChanged',interface='org."
246 "freedesktop.DBus.Properties',path='" +
247 std::string(cfmSettingPath) + "',arg0='" +
248 std::string(cfmSettingIface) + "'",
249 [weakRef](sdbusplus::message::message& message) {
250 auto self = weakRef.lock();
251 if (!self)
252 {
253 return;
254 }
255 boost::container::flat_map<std::string, std::variant<double>> values;
256 std::string objectName;
257 message.read(objectName, values);
258 const auto findValue = values.find("Limit");
259 if (findValue == values.end())
260 {
261 return;
262 }
Ed Tanous2049bd22022-07-09 07:20:26 -0700263 auto* const reading = std::get_if<double>(&(findValue->second));
Ed Tanousbb679322022-05-16 16:10:00 -0700264 if (reading == nullptr)
265 {
266 std::cerr << "Got CFM Limit of wrong type\n";
267 return;
268 }
269 if (*reading < minSystemCfm && *reading != 0)
270 {
271 std::cerr << "Illegal CFM setting detected\n";
272 return;
273 }
274 uint64_t maxRpm = self->getMaxRpm(*reading);
275 self->pwmLimitIface->set_property("Limit", maxRpm);
276 setMaxPWM(self->dbusConnection, maxRpm);
277 });
James Feistb2eb3f52018-12-04 16:17:50 -0800278}
279
James Feist9566bfa2019-01-29 15:31:23 -0800280CFMSensor::~CFMSensor()
281{
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530282 for (const auto& iface : thresholdInterfaces)
283 {
284 objServer.remove_interface(iface);
285 }
James Feist9566bfa2019-01-29 15:31:23 -0800286 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800287 objServer.remove_interface(association);
James Feist13452092019-03-07 16:38:12 -0800288 objServer.remove_interface(cfmLimitIface);
289 objServer.remove_interface(pwmLimitIface);
290}
291
292void CFMSensor::createMaxCFMIface(void)
293{
James Feistb6c0b912019-07-09 12:21:44 -0700294 cfmLimitIface->register_property("Limit", c2 * maxCFM * tachs.size());
James Feist13452092019-03-07 16:38:12 -0800295 cfmLimitIface->initialize();
James Feist9566bfa2019-01-29 15:31:23 -0800296}
297
James Feistb2eb3f52018-12-04 16:17:50 -0800298void CFMSensor::addTachRanges(const std::string& serviceName,
299 const std::string& path)
300{
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700301 std::weak_ptr<CFMSensor> weakRef = weak_from_this();
James Feistb2eb3f52018-12-04 16:17:50 -0800302 dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700303 [weakRef,
304 path](const boost::system::error_code ec,
305 const boost::container::flat_map<std::string, BasicVariantType>&
306 data) {
Ed Tanousbb679322022-05-16 16:10:00 -0700307 if (ec)
308 {
309 std::cerr << "Error getting properties from " << path << "\n";
310 return;
311 }
312 auto self = weakRef.lock();
313 if (!self)
314 {
315 return;
316 }
317 double max = loadVariant<double>(data, "MaxValue");
318 double min = loadVariant<double>(data, "MinValue");
319 self->tachRanges[path] = std::make_pair(min, max);
320 self->updateReading();
James Feistb2eb3f52018-12-04 16:17:50 -0800321 },
322 serviceName, path, "org.freedesktop.DBus.Properties", "GetAll",
323 "xyz.openbmc_project.Sensor.Value");
324}
325
326void CFMSensor::checkThresholds(void)
327{
328 thresholds::checkThresholds(this);
329}
330
331void CFMSensor::updateReading(void)
332{
333 double val = 0.0;
334 if (calculate(val))
335 {
336 if (value != val && parent)
337 {
338 parent->updateReading();
339 }
340 updateValue(val);
341 }
342 else
343 {
344 updateValue(std::numeric_limits<double>::quiet_NaN());
345 }
346}
347
Ed Tanous2049bd22022-07-09 07:20:26 -0700348uint64_t CFMSensor::getMaxRpm(uint64_t cfmMaxSetting) const
James Feist13452092019-03-07 16:38:12 -0800349{
350 uint64_t pwmPercent = 100;
351 double totalCFM = std::numeric_limits<double>::max();
352 if (cfmMaxSetting == 0)
353 {
354 return pwmPercent;
355 }
356
James Feist52427952019-04-05 14:23:35 -0700357 bool firstLoop = true;
James Feist13452092019-03-07 16:38:12 -0800358 while (totalCFM > cfmMaxSetting)
359 {
James Feist52427952019-04-05 14:23:35 -0700360 if (firstLoop)
361 {
362 firstLoop = false;
363 }
364 else
365 {
366 pwmPercent--;
367 }
368
James Feist13452092019-03-07 16:38:12 -0800369 double ci = 0;
370 if (pwmPercent == 0)
371 {
372 ci = 0;
373 }
374 else if (pwmPercent < tachMinPercent)
375 {
376 ci = c1;
377 }
378 else if (pwmPercent > tachMaxPercent)
379 {
380 ci = c2;
381 }
382 else
383 {
384 ci = c1 + (((c2 - c1) * (pwmPercent - tachMinPercent)) /
385 (tachMaxPercent - tachMinPercent));
386 }
387
388 // Now calculate the CFM for this tach
389 // CFMi = Ci * Qmaxi * TACHi
390 totalCFM = ci * maxCFM * pwmPercent;
391 totalCFM *= tachs.size();
392 // divide by 100 since pwm is in percent
393 totalCFM /= 100;
394
James Feist13452092019-03-07 16:38:12 -0800395 if (pwmPercent <= 0)
396 {
397 break;
398 }
399 }
James Feist52427952019-04-05 14:23:35 -0700400
James Feist13452092019-03-07 16:38:12 -0800401 return pwmPercent;
402}
403
James Feistb2eb3f52018-12-04 16:17:50 -0800404bool CFMSensor::calculate(double& value)
405{
406 double totalCFM = 0;
407 for (const std::string& tachName : tachs)
408 {
James Feist9566bfa2019-01-29 15:31:23 -0800409
James Feistb2eb3f52018-12-04 16:17:50 -0800410 auto findReading = std::find_if(
411 tachReadings.begin(), tachReadings.end(), [&](const auto& item) {
412 return boost::ends_with(item.first, tachName);
413 });
Ed Tanousbb679322022-05-16 16:10:00 -0700414 auto findRange = std::find_if(tachRanges.begin(), tachRanges.end(),
415 [&](const auto& item) {
416 return boost::ends_with(item.first, tachName);
417 });
James Feistb2eb3f52018-12-04 16:17:50 -0800418 if (findReading == tachReadings.end())
419 {
Ed Tanous8a57ec02020-10-09 12:46:52 -0700420 if constexpr (debug)
James Feista96329f2019-01-24 10:08:27 -0800421 {
422 std::cerr << "Can't find " << tachName << "in readings\n";
423 }
James Feist9566bfa2019-01-29 15:31:23 -0800424 continue; // haven't gotten a reading
James Feistb2eb3f52018-12-04 16:17:50 -0800425 }
426
427 if (findRange == tachRanges.end())
428 {
James Feist523828e2019-03-04 14:38:37 -0800429 std::cerr << "Can't find " << tachName << " in ranges\n";
James Feistb2eb3f52018-12-04 16:17:50 -0800430 return false; // haven't gotten a max / min
431 }
432
433 // avoid divide by 0
434 if (findRange->second.second == 0)
435 {
436 std::cerr << "Tach Max Set to 0 " << tachName << "\n";
437 return false;
438 }
439
440 double rpm = findReading->second;
441
442 // for now assume the min for a fan is always 0, divide by max to get
443 // percent and mult by 100
444 rpm /= findRange->second.second;
445 rpm *= 100;
446
Ed Tanous8a57ec02020-10-09 12:46:52 -0700447 if constexpr (debug)
James Feistb2eb3f52018-12-04 16:17:50 -0800448 {
449 std::cout << "Tach " << tachName << "at " << rpm << "\n";
450 }
451
452 // Do a linear interpolation to get Ci
453 // Ci = C1 + (C2 - C1)/(RPM2 - RPM1) * (TACHi - TACH1)
454
455 double ci = 0;
456 if (rpm == 0)
457 {
458 ci = 0;
459 }
460 else if (rpm < tachMinPercent)
461 {
462 ci = c1;
463 }
464 else if (rpm > tachMaxPercent)
465 {
466 ci = c2;
467 }
468 else
469 {
470 ci = c1 + (((c2 - c1) * (rpm - tachMinPercent)) /
471 (tachMaxPercent - tachMinPercent));
472 }
473
474 // Now calculate the CFM for this tach
475 // CFMi = Ci * Qmaxi * TACHi
476 totalCFM += ci * maxCFM * rpm;
Ed Tanous8a57ec02020-10-09 12:46:52 -0700477 if constexpr (debug)
James Feista5e58722019-04-22 14:43:11 -0700478 {
479 std::cerr << "totalCFM = " << totalCFM << "\n";
480 std::cerr << "Ci " << ci << " MaxCFM " << maxCFM << " rpm " << rpm
481 << "\n";
482 std::cerr << "c1 " << c1 << " c2 " << c2 << " max "
483 << tachMaxPercent << " min " << tachMinPercent << "\n";
484 }
James Feistb2eb3f52018-12-04 16:17:50 -0800485 }
486
487 // divide by 100 since rpm is in percent
488 value = totalCFM / 100;
Ed Tanous8a57ec02020-10-09 12:46:52 -0700489 if constexpr (debug)
James Feista5e58722019-04-22 14:43:11 -0700490 {
491 std::cerr << "cfm value = " << value << "\n";
492 }
James Feist9566bfa2019-01-29 15:31:23 -0800493 return true;
James Feistb2eb3f52018-12-04 16:17:50 -0800494}
495
496static constexpr double exitAirMaxReading = 127;
497static constexpr double exitAirMinReading = -128;
James Feistbc896df2018-11-26 16:28:17 -0800498ExitAirTempSensor::ExitAirTempSensor(
499 std::shared_ptr<sdbusplus::asio::connection>& conn,
James Feistb2eb3f52018-12-04 16:17:50 -0800500 const std::string& sensorName, const std::string& sensorConfiguration,
James Feistbc896df2018-11-26 16:28:17 -0800501 sdbusplus::asio::object_server& objectServer,
James Feistb839c052019-05-15 10:25:24 -0700502 std::vector<thresholds::Threshold>&& thresholdData) :
Zhikui Renda98f092021-11-01 09:41:08 -0700503 Sensor(escapeName(sensorName), std::move(thresholdData),
504 sensorConfiguration, "xyz.openbmc_project.Configuration.ExitAirTemp",
505 false, false, exitAirMaxReading, exitAirMinReading, conn,
506 PowerState::on),
Ed Tanous2049bd22022-07-09 07:20:26 -0700507 objServer(objectServer)
James Feistbc896df2018-11-26 16:28:17 -0800508{
509 sensorInterface = objectServer.add_interface(
510 "/xyz/openbmc_project/sensors/temperature/" + name,
511 "xyz.openbmc_project.Sensor.Value");
512
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530513 for (const auto& threshold : thresholds)
James Feistbc896df2018-11-26 16:28:17 -0800514 {
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530515 std::string interface = thresholds::getInterface(threshold.level);
516 thresholdInterfaces[static_cast<size_t>(threshold.level)] =
517 objectServer.add_interface(
518 "/xyz/openbmc_project/sensors/temperature/" + name, interface);
James Feistbc896df2018-11-26 16:28:17 -0800519 }
James Feist078f2322019-03-08 11:09:05 -0800520 association = objectServer.add_interface(
521 "/xyz/openbmc_project/sensors/temperature/" + name,
James Feist2adc95c2019-09-30 14:55:28 -0700522 association::interface);
Andrei Kartashev39287412022-02-04 16:04:47 +0300523 setInitialProperties(sensor_paths::unitDegreesC);
James Feistbc896df2018-11-26 16:28:17 -0800524}
525
526ExitAirTempSensor::~ExitAirTempSensor()
527{
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530528 for (const auto& iface : thresholdInterfaces)
529 {
530 objServer.remove_interface(iface);
531 }
James Feist523828e2019-03-04 14:38:37 -0800532 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800533 objServer.remove_interface(association);
James Feistbc896df2018-11-26 16:28:17 -0800534}
535
536void ExitAirTempSensor::setupMatches(void)
537{
Brandon Kim66558232021-11-09 16:53:08 -0800538 constexpr const auto matchTypes{
539 std::to_array<const char*>({"power", inletTemperatureSensor})};
James Feistbc896df2018-11-26 16:28:17 -0800540
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700541 std::weak_ptr<ExitAirTempSensor> weakRef = weak_from_this();
Ed Tanous13b63f82021-05-11 16:12:52 -0700542 for (const std::string type : matchTypes)
James Feistbc896df2018-11-26 16:28:17 -0800543 {
James Feistb2eb3f52018-12-04 16:17:50 -0800544 setupSensorMatch(matches, *dbusConnection, type,
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700545 [weakRef, type](const double& value,
546 sdbusplus::message::message& message) {
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700547 auto self = weakRef.lock();
548 if (!self)
549 {
550 return;
551 }
Ed Tanousbb679322022-05-16 16:10:00 -0700552 if (type == "power")
553 {
554 std::string path = message.get_path();
555 if (path.find("PS") != std::string::npos &&
556 boost::ends_with(path, "Input_Power"))
557 {
558 self->powerReadings[message.get_path()] = value;
559 }
560 }
561 else if (type == inletTemperatureSensor)
562 {
563 self->inletTemp = value;
564 }
565 self->updateReading();
566 });
567 }
568 dbusConnection->async_method_call(
569 [weakRef](boost::system::error_code ec,
570 const std::variant<double>& value) {
571 if (ec)
572 {
573 // sensor not ready yet
574 return;
575 }
576 auto self = weakRef.lock();
577 if (!self)
578 {
579 return;
580 }
581 self->inletTemp = std::visit(VariantToDoubleVisitor(), value);
James Feist9566bfa2019-01-29 15:31:23 -0800582 },
583 "xyz.openbmc_project.HwmonTempSensor",
584 std::string("/xyz/openbmc_project/sensors/") + inletTemperatureSensor,
James Feista5e58722019-04-22 14:43:11 -0700585 properties::interface, properties::get, sensorValueInterface, "Value");
586 dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700587 [weakRef](boost::system::error_code ec, const GetSubTreeType& subtree) {
Ed Tanousbb679322022-05-16 16:10:00 -0700588 if (ec)
589 {
590 std::cerr << "Error contacting mapper\n";
591 return;
592 }
593 auto self = weakRef.lock();
594 if (!self)
595 {
596 return;
597 }
598 for (const auto& item : subtree)
599 {
600 size_t lastSlash = item.first.rfind("/");
601 if (lastSlash == std::string::npos ||
Ed Tanous2049bd22022-07-09 07:20:26 -0700602 lastSlash == item.first.size() || item.second.empty())
James Feista5e58722019-04-22 14:43:11 -0700603 {
Ed Tanousbb679322022-05-16 16:10:00 -0700604 continue;
James Feista5e58722019-04-22 14:43:11 -0700605 }
Ed Tanousbb679322022-05-16 16:10:00 -0700606 std::string sensorName = item.first.substr(lastSlash + 1);
607 if (boost::starts_with(sensorName, "PS") &&
608 boost::ends_with(sensorName, "Input_Power"))
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700609 {
Ed Tanousbb679322022-05-16 16:10:00 -0700610 const std::string& path = item.first;
611 self->dbusConnection->async_method_call(
612 [weakRef, path](boost::system::error_code ec,
613 const std::variant<double>& value) {
614 if (ec)
615 {
616 std::cerr << "Error getting value from " << path
617 << "\n";
618 }
619 auto self = weakRef.lock();
620 if (!self)
621 {
622 return;
623 }
624 double reading =
625 std::visit(VariantToDoubleVisitor(), value);
626 if constexpr (debug)
627 {
628 std::cerr << path << "Reading " << reading << "\n";
629 }
630 self->powerReadings[path] = reading;
631 },
632 item.second[0].first, item.first, properties::interface,
633 properties::get, sensorValueInterface, "Value");
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700634 }
Ed Tanousbb679322022-05-16 16:10:00 -0700635 }
James Feista5e58722019-04-22 14:43:11 -0700636 },
637 mapper::busName, mapper::path, mapper::interface, mapper::subtree,
638 "/xyz/openbmc_project/sensors/power", 0,
639 std::array<const char*, 1>{sensorValueInterface});
James Feistbc896df2018-11-26 16:28:17 -0800640}
641
642void ExitAirTempSensor::updateReading(void)
643{
644
645 double val = 0.0;
646 if (calculate(val))
647 {
James Feist18af4232019-03-13 11:14:00 -0700648 val = std::floor(val + 0.5);
James Feistbc896df2018-11-26 16:28:17 -0800649 updateValue(val);
650 }
651 else
652 {
653 updateValue(std::numeric_limits<double>::quiet_NaN());
654 }
655}
656
James Feistb2eb3f52018-12-04 16:17:50 -0800657double ExitAirTempSensor::getTotalCFM(void)
James Feistbc896df2018-11-26 16:28:17 -0800658{
James Feistb2eb3f52018-12-04 16:17:50 -0800659 double sum = 0;
660 for (auto& sensor : cfmSensors)
James Feistbc896df2018-11-26 16:28:17 -0800661 {
James Feistb2eb3f52018-12-04 16:17:50 -0800662 double reading = 0;
663 if (!sensor->calculate(reading))
James Feistbc896df2018-11-26 16:28:17 -0800664 {
James Feistbc896df2018-11-26 16:28:17 -0800665 return -1;
666 }
James Feistb2eb3f52018-12-04 16:17:50 -0800667 sum += reading;
James Feistbc896df2018-11-26 16:28:17 -0800668 }
James Feistb2eb3f52018-12-04 16:17:50 -0800669
670 return sum;
James Feistbc896df2018-11-26 16:28:17 -0800671}
672
673bool ExitAirTempSensor::calculate(double& val)
674{
Zhikui Ren12e3d672020-12-03 15:14:49 -0800675 constexpr size_t maxErrorPrint = 5;
James Feistbc896df2018-11-26 16:28:17 -0800676 static bool firstRead = false;
James Feistae11cfc2019-05-07 15:01:20 -0700677 static size_t errorPrint = maxErrorPrint;
678
James Feistbc896df2018-11-26 16:28:17 -0800679 double cfm = getTotalCFM();
680 if (cfm <= 0)
681 {
682 std::cerr << "Error getting cfm\n";
683 return false;
684 }
685
Zhikui Ren12e3d672020-12-03 15:14:49 -0800686 // Though cfm is not expected to be less than qMin normally,
687 // it is not a hard limit for exit air temp calculation.
688 // 50% qMin is chosen as a generic limit between providing
689 // a valid derived exit air temp and reporting exit air temp not available.
690 constexpr const double cfmLimitFactor = 0.5;
691 if (cfm < (qMin * cfmLimitFactor))
692 {
693 if (errorPrint > 0)
694 {
695 errorPrint--;
696 std::cerr << "cfm " << cfm << " is too low, expected qMin " << qMin
697 << "\n";
698 }
699 val = 0;
700 return false;
701 }
702
James Feistbc896df2018-11-26 16:28:17 -0800703 // if there is an error getting inlet temp, return error
704 if (std::isnan(inletTemp))
705 {
James Feistae11cfc2019-05-07 15:01:20 -0700706 if (errorPrint > 0)
707 {
708 errorPrint--;
709 std::cerr << "Cannot get inlet temp\n";
710 }
James Feistbc896df2018-11-26 16:28:17 -0800711 val = 0;
712 return false;
713 }
714
715 // if fans are off, just make the exit temp equal to inlet
James Feist71d31b22019-01-02 16:57:54 -0800716 if (!isPowerOn())
James Feistbc896df2018-11-26 16:28:17 -0800717 {
718 val = inletTemp;
719 return true;
720 }
721
722 double totalPower = 0;
723 for (const auto& reading : powerReadings)
724 {
725 if (std::isnan(reading.second))
726 {
727 continue;
728 }
729 totalPower += reading.second;
730 }
731
732 // Calculate power correction factor
733 // Ci = CL + (CH - CL)/(QMax - QMin) * (CFM - QMin)
Ed Tanous8a57ec02020-10-09 12:46:52 -0700734 double powerFactor = 0.0;
James Feistbc896df2018-11-26 16:28:17 -0800735 if (cfm <= qMin)
736 {
737 powerFactor = powerFactorMin;
738 }
739 else if (cfm >= qMax)
740 {
741 powerFactor = powerFactorMax;
742 }
743 else
744 {
745 powerFactor = powerFactorMin + ((powerFactorMax - powerFactorMin) /
746 (qMax - qMin) * (cfm - qMin));
747 }
748
Ed Tanous8a57ec02020-10-09 12:46:52 -0700749 totalPower *= powerFactor;
James Feistbc896df2018-11-26 16:28:17 -0800750 totalPower += pOffset;
751
752 if (totalPower == 0)
753 {
James Feistae11cfc2019-05-07 15:01:20 -0700754 if (errorPrint > 0)
755 {
756 errorPrint--;
757 std::cerr << "total power 0\n";
758 }
James Feistbc896df2018-11-26 16:28:17 -0800759 val = 0;
760 return false;
761 }
762
Ed Tanous8a57ec02020-10-09 12:46:52 -0700763 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800764 {
765 std::cout << "Power Factor " << powerFactor << "\n";
766 std::cout << "Inlet Temp " << inletTemp << "\n";
767 std::cout << "Total Power" << totalPower << "\n";
768 }
769
770 // Calculate the exit air temp
771 // Texit = Tfp + (1.76 * TotalPower / CFM * Faltitude)
Ed Tanous8a57ec02020-10-09 12:46:52 -0700772 double reading = 1.76 * totalPower * altitudeFactor;
James Feistbc896df2018-11-26 16:28:17 -0800773 reading /= cfm;
774 reading += inletTemp;
775
Ed Tanous8a57ec02020-10-09 12:46:52 -0700776 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800777 {
778 std::cout << "Reading 1: " << reading << "\n";
779 }
780
781 // Now perform the exponential average
782 // Calculate alpha based on SDR values and CFM
783 // Ai = As + (Af - As)/(QMax - QMin) * (CFM - QMin)
784
785 double alpha = 0.0;
786 if (cfm < qMin)
787 {
788 alpha = alphaS;
789 }
790 else if (cfm >= qMax)
791 {
792 alpha = alphaF;
793 }
794 else
795 {
796 alpha = alphaS + ((alphaF - alphaS) * (cfm - qMin) / (qMax - qMin));
797 }
798
Zhikui Ren12e3d672020-12-03 15:14:49 -0800799 auto time = std::chrono::steady_clock::now();
James Feistbc896df2018-11-26 16:28:17 -0800800 if (!firstRead)
801 {
802 firstRead = true;
803 lastTime = time;
804 lastReading = reading;
805 }
806 double alphaDT =
807 std::chrono::duration_cast<std::chrono::seconds>(time - lastTime)
808 .count() *
809 alpha;
810
811 // cap at 1.0 or the below fails
812 if (alphaDT > 1.0)
813 {
814 alphaDT = 1.0;
815 }
816
Ed Tanous8a57ec02020-10-09 12:46:52 -0700817 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800818 {
819 std::cout << "AlphaDT: " << alphaDT << "\n";
820 }
821
822 reading = ((reading * alphaDT) + (lastReading * (1.0 - alphaDT)));
823
Ed Tanous8a57ec02020-10-09 12:46:52 -0700824 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800825 {
826 std::cout << "Reading 2: " << reading << "\n";
827 }
828
829 val = reading;
830 lastReading = reading;
831 lastTime = time;
James Feistae11cfc2019-05-07 15:01:20 -0700832 errorPrint = maxErrorPrint;
James Feistbc896df2018-11-26 16:28:17 -0800833 return true;
834}
835
836void ExitAirTempSensor::checkThresholds(void)
837{
838 thresholds::checkThresholds(this);
839}
840
James Feistbc896df2018-11-26 16:28:17 -0800841static void loadVariantPathArray(
842 const boost::container::flat_map<std::string, BasicVariantType>& data,
843 const std::string& key, std::vector<std::string>& resp)
844{
845 auto it = data.find(key);
846 if (it == data.end())
847 {
848 std::cerr << "Configuration missing " << key << "\n";
849 throw std::invalid_argument("Key Missing");
850 }
851 BasicVariantType copy = it->second;
James Feist3eb82622019-02-08 13:10:22 -0800852 std::vector<std::string> config = std::get<std::vector<std::string>>(copy);
James Feistbc896df2018-11-26 16:28:17 -0800853 for (auto& str : config)
854 {
855 boost::replace_all(str, " ", "_");
856 }
857 resp = std::move(config);
858}
859
860void createSensor(sdbusplus::asio::object_server& objectServer,
James Feistb2eb3f52018-12-04 16:17:50 -0800861 std::shared_ptr<ExitAirTempSensor>& exitAirSensor,
James Feistbc896df2018-11-26 16:28:17 -0800862 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
863{
864 if (!dbusConnection)
865 {
866 std::cerr << "Connection not created\n";
867 return;
868 }
James Feist655f3762020-10-05 15:28:15 -0700869 auto getter = std::make_shared<GetSensorConfiguration>(
Ed Tanousbb679322022-05-16 16:10:00 -0700870 dbusConnection,
871 [&objectServer, &dbusConnection,
872 &exitAirSensor](const ManagedObjectType& resp) {
873 cfmSensors.clear();
874 for (const auto& pathPair : resp)
875 {
876 for (const auto& entry : pathPair.second)
James Feistbc896df2018-11-26 16:28:17 -0800877 {
Ed Tanousbb679322022-05-16 16:10:00 -0700878 if (entry.first == exitAirIface)
James Feistbc896df2018-11-26 16:28:17 -0800879 {
Ed Tanousbb679322022-05-16 16:10:00 -0700880 // thresholds should be under the same path
881 std::vector<thresholds::Threshold> sensorThresholds;
882 parseThresholdsFromConfig(pathPair.second,
883 sensorThresholds);
James Feistbc896df2018-11-26 16:28:17 -0800884
Ed Tanousbb679322022-05-16 16:10:00 -0700885 std::string name =
886 loadVariant<std::string>(entry.second, "Name");
887 exitAirSensor = std::make_shared<ExitAirTempSensor>(
888 dbusConnection, name, pathPair.first.str, objectServer,
889 std::move(sensorThresholds));
890 exitAirSensor->powerFactorMin =
891 loadVariant<double>(entry.second, "PowerFactorMin");
892 exitAirSensor->powerFactorMax =
893 loadVariant<double>(entry.second, "PowerFactorMax");
894 exitAirSensor->qMin =
895 loadVariant<double>(entry.second, "QMin");
896 exitAirSensor->qMax =
897 loadVariant<double>(entry.second, "QMax");
898 exitAirSensor->alphaS =
899 loadVariant<double>(entry.second, "AlphaS");
900 exitAirSensor->alphaF =
901 loadVariant<double>(entry.second, "AlphaF");
902 }
903 else if (entry.first == cfmIface)
James Feistbc896df2018-11-26 16:28:17 -0800904
Ed Tanousbb679322022-05-16 16:10:00 -0700905 {
906 // thresholds should be under the same path
907 std::vector<thresholds::Threshold> sensorThresholds;
908 parseThresholdsFromConfig(pathPair.second,
909 sensorThresholds);
910 std::string name =
911 loadVariant<std::string>(entry.second, "Name");
912 auto sensor = std::make_shared<CFMSensor>(
913 dbusConnection, name, pathPair.first.str, objectServer,
914 std::move(sensorThresholds), exitAirSensor);
915 loadVariantPathArray(entry.second, "Tachs", sensor->tachs);
916 sensor->maxCFM =
917 loadVariant<double>(entry.second, "MaxCFM");
James Feistbc896df2018-11-26 16:28:17 -0800918
Ed Tanousbb679322022-05-16 16:10:00 -0700919 // change these into percent upon getting the data
920 sensor->c1 = loadVariant<double>(entry.second, "C1") / 100;
921 sensor->c2 = loadVariant<double>(entry.second, "C2") / 100;
922 sensor->tachMinPercent =
923 loadVariant<double>(entry.second, "TachMinPercent");
924 sensor->tachMaxPercent =
925 loadVariant<double>(entry.second, "TachMaxPercent");
926 sensor->createMaxCFMIface();
927 sensor->setupMatches();
James Feistbc896df2018-11-26 16:28:17 -0800928
Ed Tanousbb679322022-05-16 16:10:00 -0700929 cfmSensors.emplace_back(std::move(sensor));
James Feistbc896df2018-11-26 16:28:17 -0800930 }
931 }
Ed Tanousbb679322022-05-16 16:10:00 -0700932 }
933 if (exitAirSensor)
934 {
935 exitAirSensor->setupMatches();
936 exitAirSensor->updateReading();
937 }
Ed Tanous8a17c302021-09-02 15:07:11 -0700938 });
James Feist655f3762020-10-05 15:28:15 -0700939 getter->getConfiguration(
940 std::vector<std::string>(monitorIfaces.begin(), monitorIfaces.end()));
James Feistbc896df2018-11-26 16:28:17 -0800941}
942
James Feistb6c0b912019-07-09 12:21:44 -0700943int main()
James Feistbc896df2018-11-26 16:28:17 -0800944{
945
946 boost::asio::io_service io;
947 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
948 systemBus->request_name("xyz.openbmc_project.ExitAirTempSensor");
949 sdbusplus::asio::object_server objectServer(systemBus);
950 std::shared_ptr<ExitAirTempSensor> sensor =
951 nullptr; // wait until we find the config
952 std::vector<std::unique_ptr<sdbusplus::bus::match::match>> matches;
953
954 io.post([&]() { createSensor(objectServer, sensor, systemBus); });
955
956 boost::asio::deadline_timer configTimer(io);
957
958 std::function<void(sdbusplus::message::message&)> eventHandler =
James Feistb6c0b912019-07-09 12:21:44 -0700959 [&](sdbusplus::message::message&) {
Ed Tanousbb679322022-05-16 16:10:00 -0700960 configTimer.expires_from_now(boost::posix_time::seconds(1));
961 // create a timer because normally multiple properties change
962 configTimer.async_wait([&](const boost::system::error_code& ec) {
963 if (ec == boost::asio::error::operation_aborted)
964 {
965 return; // we're being canceled
966 }
967 createSensor(objectServer, sensor, systemBus);
968 if (!sensor)
969 {
970 std::cout << "Configuration not detected\n";
971 }
972 });
973 };
James Feistbc896df2018-11-26 16:28:17 -0800974 for (const char* type : monitorIfaces)
975 {
976 auto match = std::make_unique<sdbusplus::bus::match::match>(
977 static_cast<sdbusplus::bus::bus&>(*systemBus),
978 "type='signal',member='PropertiesChanged',path_namespace='" +
979 std::string(inventoryPath) + "',arg0namespace='" + type + "'",
980 eventHandler);
981 matches.emplace_back(std::move(match));
982 }
983
Bruce Lee913d4d02021-07-22 10:18:42 +0800984 setupManufacturingModeMatch(*systemBus);
James Feistbc896df2018-11-26 16:28:17 -0800985 io.run();
Zhikui Ren8685b172021-06-29 15:16:52 -0700986 return 0;
James Feistbc896df2018-11-26 16:28:17 -0800987}