blob: ea5a4324b11191f374dfad9fcadbbdec027d3eb0 [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/replace.hpp>
Patrick Venture96e97db2019-10-31 13:44:38 -070021#include <boost/container/flat_map.hpp>
James Feist38fb5982020-05-28 10:09:54 -070022#include <sdbusplus/asio/connection.hpp>
23#include <sdbusplus/asio/object_server.hpp>
24#include <sdbusplus/bus/match.hpp>
25
26#include <array>
James Feistbc896df2018-11-26 16:28:17 -080027#include <chrono>
Patrick Venture96e97db2019-10-31 13:44:38 -070028#include <cmath>
29#include <functional>
James Feistbc896df2018-11-26 16:28:17 -080030#include <iostream>
31#include <limits>
Patrick Venture96e97db2019-10-31 13:44:38 -070032#include <memory>
James Feistbc896df2018-11-26 16:28:17 -080033#include <numeric>
Patrick Venture96e97db2019-10-31 13:44:38 -070034#include <stdexcept>
35#include <utility>
36#include <variant>
James Feistbc896df2018-11-26 16:28:17 -080037#include <vector>
38
Ed Tanous8a57ec02020-10-09 12:46:52 -070039constexpr const double altitudeFactor = 1.14;
James Feistbc896df2018-11-26 16:28:17 -080040constexpr const char* exitAirIface =
41 "xyz.openbmc_project.Configuration.ExitAirTempSensor";
42constexpr const char* cfmIface = "xyz.openbmc_project.Configuration.CFMSensor";
43
44// todo: this *might* need to be configurable
45constexpr const char* inletTemperatureSensor = "temperature/Front_Panel_Temp";
James Feist13452092019-03-07 16:38:12 -080046constexpr const char* pidConfigurationType =
47 "xyz.openbmc_project.Configuration.Pid";
48constexpr const char* settingsDaemon = "xyz.openbmc_project.Settings";
49constexpr const char* cfmSettingPath = "/xyz/openbmc_project/control/cfm_limit";
50constexpr const char* cfmSettingIface = "xyz.openbmc_project.Control.CFMLimit";
James Feistbc896df2018-11-26 16:28:17 -080051
Ed Tanous8a57ec02020-10-09 12:46:52 -070052static constexpr bool debug = false;
James Feistbc896df2018-11-26 16:28:17 -080053
James Feistb2eb3f52018-12-04 16:17:50 -080054static constexpr double cfmMaxReading = 255;
55static constexpr double cfmMinReading = 0;
56
James Feist13452092019-03-07 16:38:12 -080057static constexpr size_t minSystemCfm = 50;
58
Brandon Kim66558232021-11-09 16:53:08 -080059constexpr const auto monitorIfaces{
60 std::to_array<const char*>({exitAirIface, cfmIface})};
James Feist655f3762020-10-05 15:28:15 -070061
James Feist9a25ed42019-10-15 15:43:44 -070062static std::vector<std::shared_ptr<CFMSensor>> cfmSensors;
63
James Feistb2eb3f52018-12-04 16:17:50 -080064static void setupSensorMatch(
Patrick Williams92f8f512022-07-22 19:26:55 -050065 std::vector<sdbusplus::bus::match_t>& matches, sdbusplus::bus_t& connection,
66 const std::string& type,
67 std::function<void(const double&, sdbusplus::message_t&)>&& callback)
James Feistb2eb3f52018-12-04 16:17:50 -080068{
69
Patrick Williams92f8f512022-07-22 19:26:55 -050070 std::function<void(sdbusplus::message_t & message)> eventHandler =
71 [callback{std::move(callback)}](sdbusplus::message_t& message) {
Ed Tanousbb679322022-05-16 16:10:00 -070072 std::string objectName;
73 boost::container::flat_map<std::string, std::variant<double, int64_t>>
74 values;
75 message.read(objectName, values);
76 auto findValue = values.find("Value");
77 if (findValue == values.end())
78 {
79 return;
80 }
81 double value = std::visit(VariantToDoubleVisitor(), findValue->second);
82 if (std::isnan(value))
83 {
84 return;
85 }
James Feist9566bfa2019-01-29 15:31:23 -080086
Ed Tanousbb679322022-05-16 16:10:00 -070087 callback(value, message);
88 };
James Feistb2eb3f52018-12-04 16:17:50 -080089 matches.emplace_back(connection,
90 "type='signal',"
91 "member='PropertiesChanged',interface='org."
92 "freedesktop.DBus.Properties',path_"
93 "namespace='/xyz/openbmc_project/sensors/" +
94 std::string(type) +
95 "',arg0='xyz.openbmc_project.Sensor.Value'",
96 std::move(eventHandler));
97}
98
James Feist13452092019-03-07 16:38:12 -080099static void setMaxPWM(const std::shared_ptr<sdbusplus::asio::connection>& conn,
100 double value)
101{
102 using GetSubTreeType = std::vector<std::pair<
103 std::string,
104 std::vector<std::pair<std::string, std::vector<std::string>>>>>;
105
106 conn->async_method_call(
107 [conn, value](const boost::system::error_code ec,
108 const GetSubTreeType& ret) {
Ed Tanousbb679322022-05-16 16:10:00 -0700109 if (ec)
110 {
111 std::cerr << "Error calling mapper\n";
112 return;
113 }
114 for (const auto& [path, objDict] : ret)
115 {
116 if (objDict.empty())
James Feist13452092019-03-07 16:38:12 -0800117 {
James Feist13452092019-03-07 16:38:12 -0800118 return;
119 }
Ed Tanousbb679322022-05-16 16:10:00 -0700120 const std::string& owner = objDict.begin()->first;
121
122 conn->async_method_call(
123 [conn, value, owner,
124 path{path}](const boost::system::error_code ec,
125 const std::variant<std::string>& classType) {
126 if (ec)
127 {
128 std::cerr << "Error getting pid class\n";
129 return;
130 }
Ed Tanous2049bd22022-07-09 07:20:26 -0700131 const auto* classStr = std::get_if<std::string>(&classType);
Ed Tanousbb679322022-05-16 16:10:00 -0700132 if (classStr == nullptr || *classStr != "fan")
James Feist13452092019-03-07 16:38:12 -0800133 {
134 return;
135 }
James Feist13452092019-03-07 16:38:12 -0800136 conn->async_method_call(
Ed Tanousbb679322022-05-16 16:10:00 -0700137 [](boost::system::error_code& ec) {
138 if (ec)
139 {
140 std::cerr << "Error setting pid class\n";
141 return;
142 }
James Feist13452092019-03-07 16:38:12 -0800143 },
Ed Tanousbb679322022-05-16 16:10:00 -0700144 owner, path, "org.freedesktop.DBus.Properties", "Set",
145 pidConfigurationType, "OutLimitMax",
146 std::variant<double>(value));
147 },
148 owner, path, "org.freedesktop.DBus.Properties", "Get",
149 pidConfigurationType, "Class");
150 }
James Feist13452092019-03-07 16:38:12 -0800151 },
James Feista5e58722019-04-22 14:43:11 -0700152 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
153 0, std::array<std::string, 1>{pidConfigurationType});
James Feist13452092019-03-07 16:38:12 -0800154}
155
James Feistb2eb3f52018-12-04 16:17:50 -0800156CFMSensor::CFMSensor(std::shared_ptr<sdbusplus::asio::connection>& conn,
157 const std::string& sensorName,
158 const std::string& sensorConfiguration,
159 sdbusplus::asio::object_server& objectServer,
James Feistb839c052019-05-15 10:25:24 -0700160 std::vector<thresholds::Threshold>&& thresholdData,
James Feistb2eb3f52018-12-04 16:17:50 -0800161 std::shared_ptr<ExitAirTempSensor>& parent) :
Zhikui Renda98f092021-11-01 09:41:08 -0700162 Sensor(escapeName(sensorName), std::move(thresholdData),
Zev Weiss34d7b972022-08-17 19:38:59 -0700163 sensorConfiguration, "xyz.openbmc_project.Configuration.CFMSensor",
Zhikui Renda98f092021-11-01 09:41:08 -0700164 false, false, cfmMaxReading, cfmMinReading, conn, PowerState::on),
Ed Tanous2049bd22022-07-09 07:20:26 -0700165 parent(parent), objServer(objectServer)
James Feistb2eb3f52018-12-04 16:17:50 -0800166{
Basheer Ahmed Muddebihale5b867b2021-07-26 08:32:19 -0700167 sensorInterface = objectServer.add_interface(
168 "/xyz/openbmc_project/sensors/airflow/" + name,
169 "xyz.openbmc_project.Sensor.Value");
James Feistb2eb3f52018-12-04 16:17:50 -0800170
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530171 for (const auto& threshold : thresholds)
James Feistb2eb3f52018-12-04 16:17:50 -0800172 {
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530173 std::string interface = thresholds::getInterface(threshold.level);
174 thresholdInterfaces[static_cast<size_t>(threshold.level)] =
175 objectServer.add_interface(
176 "/xyz/openbmc_project/sensors/airflow/" + name, interface);
James Feistb2eb3f52018-12-04 16:17:50 -0800177 }
James Feist078f2322019-03-08 11:09:05 -0800178
179 association = objectServer.add_interface(
Basheer Ahmed Muddebihale5b867b2021-07-26 08:32:19 -0700180 "/xyz/openbmc_project/sensors/airflow/" + name, association::interface);
James Feist078f2322019-03-08 11:09:05 -0800181
Andrei Kartashev39287412022-02-04 16:04:47 +0300182 setInitialProperties(sensor_paths::unitCFM);
James Feist9a25ed42019-10-15 15:43:44 -0700183
James Feist13452092019-03-07 16:38:12 -0800184 pwmLimitIface =
185 objectServer.add_interface("/xyz/openbmc_project/control/pwm_limit",
186 "xyz.openbmc_project.Control.PWMLimit");
187 cfmLimitIface =
188 objectServer.add_interface("/xyz/openbmc_project/control/MaxCFM",
189 "xyz.openbmc_project.Control.CFMLimit");
James Feist9a25ed42019-10-15 15:43:44 -0700190}
James Feist13452092019-03-07 16:38:12 -0800191
James Feist9a25ed42019-10-15 15:43:44 -0700192void CFMSensor::setupMatches()
193{
194
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700195 std::weak_ptr<CFMSensor> weakRef = weak_from_this();
Ed Tanous8a17c302021-09-02 15:07:11 -0700196 setupSensorMatch(
197 matches, *dbusConnection, "fan_tach",
Patrick Williams92f8f512022-07-22 19:26:55 -0500198 [weakRef](const double& value, sdbusplus::message_t& message) {
Ed Tanousbb679322022-05-16 16:10:00 -0700199 auto self = weakRef.lock();
200 if (!self)
201 {
202 return;
203 }
204 self->tachReadings[message.get_path()] = value;
205 if (self->tachRanges.find(message.get_path()) == self->tachRanges.end())
206 {
207 // calls update reading after updating ranges
208 self->addTachRanges(message.get_sender(), message.get_path());
209 }
210 else
211 {
212 self->updateReading();
213 }
Ed Tanous8a17c302021-09-02 15:07:11 -0700214 });
James Feist9a25ed42019-10-15 15:43:44 -0700215
216 dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700217 [weakRef](const boost::system::error_code ec,
218 const std::variant<double> cfmVariant) {
Ed Tanousbb679322022-05-16 16:10:00 -0700219 auto self = weakRef.lock();
220 if (!self)
221 {
222 return;
223 }
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700224
Ed Tanousbb679322022-05-16 16:10:00 -0700225 uint64_t maxRpm = 100;
226 if (!ec)
227 {
James Feist13452092019-03-07 16:38:12 -0800228
Ed Tanous2049bd22022-07-09 07:20:26 -0700229 const auto* cfm = std::get_if<double>(&cfmVariant);
Ed Tanousbb679322022-05-16 16:10:00 -0700230 if (cfm != nullptr && *cfm >= minSystemCfm)
231 {
232 maxRpm = self->getMaxRpm(*cfm);
James Feist13452092019-03-07 16:38:12 -0800233 }
Ed Tanousbb679322022-05-16 16:10:00 -0700234 }
235 self->pwmLimitIface->register_property("Limit", maxRpm);
236 self->pwmLimitIface->initialize();
237 setMaxPWM(self->dbusConnection, maxRpm);
James Feist13452092019-03-07 16:38:12 -0800238 },
239 settingsDaemon, cfmSettingPath, "org.freedesktop.DBus.Properties",
240 "Get", cfmSettingIface, "Limit");
241
Ed Tanousbb679322022-05-16 16:10:00 -0700242 matches.emplace_back(*dbusConnection,
243 "type='signal',"
244 "member='PropertiesChanged',interface='org."
245 "freedesktop.DBus.Properties',path='" +
246 std::string(cfmSettingPath) + "',arg0='" +
247 std::string(cfmSettingIface) + "'",
Patrick Williams92f8f512022-07-22 19:26:55 -0500248 [weakRef](sdbusplus::message_t& message) {
Ed Tanousbb679322022-05-16 16:10:00 -0700249 auto self = weakRef.lock();
250 if (!self)
251 {
252 return;
253 }
254 boost::container::flat_map<std::string, std::variant<double>> values;
255 std::string objectName;
256 message.read(objectName, values);
257 const auto findValue = values.find("Limit");
258 if (findValue == values.end())
259 {
260 return;
261 }
Ed Tanous2049bd22022-07-09 07:20:26 -0700262 auto* const reading = std::get_if<double>(&(findValue->second));
Ed Tanousbb679322022-05-16 16:10:00 -0700263 if (reading == nullptr)
264 {
265 std::cerr << "Got CFM Limit of wrong type\n";
266 return;
267 }
268 if (*reading < minSystemCfm && *reading != 0)
269 {
270 std::cerr << "Illegal CFM setting detected\n";
271 return;
272 }
273 uint64_t maxRpm = self->getMaxRpm(*reading);
274 self->pwmLimitIface->set_property("Limit", maxRpm);
275 setMaxPWM(self->dbusConnection, maxRpm);
276 });
James Feistb2eb3f52018-12-04 16:17:50 -0800277}
278
James Feist9566bfa2019-01-29 15:31:23 -0800279CFMSensor::~CFMSensor()
280{
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530281 for (const auto& iface : thresholdInterfaces)
282 {
283 objServer.remove_interface(iface);
284 }
James Feist9566bfa2019-01-29 15:31:23 -0800285 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800286 objServer.remove_interface(association);
James Feist13452092019-03-07 16:38:12 -0800287 objServer.remove_interface(cfmLimitIface);
288 objServer.remove_interface(pwmLimitIface);
289}
290
291void CFMSensor::createMaxCFMIface(void)
292{
James Feistb6c0b912019-07-09 12:21:44 -0700293 cfmLimitIface->register_property("Limit", c2 * maxCFM * tachs.size());
James Feist13452092019-03-07 16:38:12 -0800294 cfmLimitIface->initialize();
James Feist9566bfa2019-01-29 15:31:23 -0800295}
296
James Feistb2eb3f52018-12-04 16:17:50 -0800297void CFMSensor::addTachRanges(const std::string& serviceName,
298 const std::string& path)
299{
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700300 std::weak_ptr<CFMSensor> weakRef = weak_from_this();
James Feistb2eb3f52018-12-04 16:17:50 -0800301 dbusConnection->async_method_call(
Zev Weissafd15042022-07-18 12:28:40 -0700302 [weakRef, path](const boost::system::error_code ec,
303 const SensorBaseConfigMap& data) {
Ed Tanousbb679322022-05-16 16:10:00 -0700304 if (ec)
305 {
306 std::cerr << "Error getting properties from " << path << "\n";
307 return;
308 }
309 auto self = weakRef.lock();
310 if (!self)
311 {
312 return;
313 }
314 double max = loadVariant<double>(data, "MaxValue");
315 double min = loadVariant<double>(data, "MinValue");
316 self->tachRanges[path] = std::make_pair(min, max);
317 self->updateReading();
James Feistb2eb3f52018-12-04 16:17:50 -0800318 },
319 serviceName, path, "org.freedesktop.DBus.Properties", "GetAll",
320 "xyz.openbmc_project.Sensor.Value");
321}
322
323void CFMSensor::checkThresholds(void)
324{
325 thresholds::checkThresholds(this);
326}
327
328void CFMSensor::updateReading(void)
329{
330 double val = 0.0;
331 if (calculate(val))
332 {
333 if (value != val && parent)
334 {
335 parent->updateReading();
336 }
337 updateValue(val);
338 }
339 else
340 {
341 updateValue(std::numeric_limits<double>::quiet_NaN());
342 }
343}
344
Ed Tanous2049bd22022-07-09 07:20:26 -0700345uint64_t CFMSensor::getMaxRpm(uint64_t cfmMaxSetting) const
James Feist13452092019-03-07 16:38:12 -0800346{
347 uint64_t pwmPercent = 100;
348 double totalCFM = std::numeric_limits<double>::max();
349 if (cfmMaxSetting == 0)
350 {
351 return pwmPercent;
352 }
353
James Feist52427952019-04-05 14:23:35 -0700354 bool firstLoop = true;
James Feist13452092019-03-07 16:38:12 -0800355 while (totalCFM > cfmMaxSetting)
356 {
James Feist52427952019-04-05 14:23:35 -0700357 if (firstLoop)
358 {
359 firstLoop = false;
360 }
361 else
362 {
363 pwmPercent--;
364 }
365
James Feist13452092019-03-07 16:38:12 -0800366 double ci = 0;
367 if (pwmPercent == 0)
368 {
369 ci = 0;
370 }
371 else if (pwmPercent < tachMinPercent)
372 {
373 ci = c1;
374 }
375 else if (pwmPercent > tachMaxPercent)
376 {
377 ci = c2;
378 }
379 else
380 {
381 ci = c1 + (((c2 - c1) * (pwmPercent - tachMinPercent)) /
382 (tachMaxPercent - tachMinPercent));
383 }
384
385 // Now calculate the CFM for this tach
386 // CFMi = Ci * Qmaxi * TACHi
387 totalCFM = ci * maxCFM * pwmPercent;
388 totalCFM *= tachs.size();
389 // divide by 100 since pwm is in percent
390 totalCFM /= 100;
391
James Feist13452092019-03-07 16:38:12 -0800392 if (pwmPercent <= 0)
393 {
394 break;
395 }
396 }
James Feist52427952019-04-05 14:23:35 -0700397
James Feist13452092019-03-07 16:38:12 -0800398 return pwmPercent;
399}
400
James Feistb2eb3f52018-12-04 16:17:50 -0800401bool CFMSensor::calculate(double& value)
402{
403 double totalCFM = 0;
404 for (const std::string& tachName : tachs)
405 {
James Feist9566bfa2019-01-29 15:31:23 -0800406
James Feistb2eb3f52018-12-04 16:17:50 -0800407 auto findReading = std::find_if(
Zev Weiss6c106d62022-08-17 20:50:00 -0700408 tachReadings.begin(), tachReadings.end(),
409 [&](const auto& item) { return item.first.ends_with(tachName); });
Ed Tanousbb679322022-05-16 16:10:00 -0700410 auto findRange = std::find_if(tachRanges.begin(), tachRanges.end(),
411 [&](const auto& item) {
Zev Weiss6c106d62022-08-17 20:50:00 -0700412 return item.first.ends_with(tachName);
Ed Tanousbb679322022-05-16 16:10:00 -0700413 });
James Feistb2eb3f52018-12-04 16:17:50 -0800414 if (findReading == tachReadings.end())
415 {
Ed Tanous8a57ec02020-10-09 12:46:52 -0700416 if constexpr (debug)
James Feista96329f2019-01-24 10:08:27 -0800417 {
418 std::cerr << "Can't find " << tachName << "in readings\n";
419 }
James Feist9566bfa2019-01-29 15:31:23 -0800420 continue; // haven't gotten a reading
James Feistb2eb3f52018-12-04 16:17:50 -0800421 }
422
423 if (findRange == tachRanges.end())
424 {
James Feist523828e2019-03-04 14:38:37 -0800425 std::cerr << "Can't find " << tachName << " in ranges\n";
James Feistb2eb3f52018-12-04 16:17:50 -0800426 return false; // haven't gotten a max / min
427 }
428
429 // avoid divide by 0
430 if (findRange->second.second == 0)
431 {
432 std::cerr << "Tach Max Set to 0 " << tachName << "\n";
433 return false;
434 }
435
436 double rpm = findReading->second;
437
438 // for now assume the min for a fan is always 0, divide by max to get
439 // percent and mult by 100
440 rpm /= findRange->second.second;
441 rpm *= 100;
442
Ed Tanous8a57ec02020-10-09 12:46:52 -0700443 if constexpr (debug)
James Feistb2eb3f52018-12-04 16:17:50 -0800444 {
445 std::cout << "Tach " << tachName << "at " << rpm << "\n";
446 }
447
448 // Do a linear interpolation to get Ci
449 // Ci = C1 + (C2 - C1)/(RPM2 - RPM1) * (TACHi - TACH1)
450
451 double ci = 0;
452 if (rpm == 0)
453 {
454 ci = 0;
455 }
456 else if (rpm < tachMinPercent)
457 {
458 ci = c1;
459 }
460 else if (rpm > tachMaxPercent)
461 {
462 ci = c2;
463 }
464 else
465 {
466 ci = c1 + (((c2 - c1) * (rpm - tachMinPercent)) /
467 (tachMaxPercent - tachMinPercent));
468 }
469
470 // Now calculate the CFM for this tach
471 // CFMi = Ci * Qmaxi * TACHi
472 totalCFM += ci * maxCFM * rpm;
Ed Tanous8a57ec02020-10-09 12:46:52 -0700473 if constexpr (debug)
James Feista5e58722019-04-22 14:43:11 -0700474 {
475 std::cerr << "totalCFM = " << totalCFM << "\n";
476 std::cerr << "Ci " << ci << " MaxCFM " << maxCFM << " rpm " << rpm
477 << "\n";
478 std::cerr << "c1 " << c1 << " c2 " << c2 << " max "
479 << tachMaxPercent << " min " << tachMinPercent << "\n";
480 }
James Feistb2eb3f52018-12-04 16:17:50 -0800481 }
482
483 // divide by 100 since rpm is in percent
484 value = totalCFM / 100;
Ed Tanous8a57ec02020-10-09 12:46:52 -0700485 if constexpr (debug)
James Feista5e58722019-04-22 14:43:11 -0700486 {
487 std::cerr << "cfm value = " << value << "\n";
488 }
James Feist9566bfa2019-01-29 15:31:23 -0800489 return true;
James Feistb2eb3f52018-12-04 16:17:50 -0800490}
491
492static constexpr double exitAirMaxReading = 127;
493static constexpr double exitAirMinReading = -128;
James Feistbc896df2018-11-26 16:28:17 -0800494ExitAirTempSensor::ExitAirTempSensor(
495 std::shared_ptr<sdbusplus::asio::connection>& conn,
James Feistb2eb3f52018-12-04 16:17:50 -0800496 const std::string& sensorName, const std::string& sensorConfiguration,
James Feistbc896df2018-11-26 16:28:17 -0800497 sdbusplus::asio::object_server& objectServer,
James Feistb839c052019-05-15 10:25:24 -0700498 std::vector<thresholds::Threshold>&& thresholdData) :
Zhikui Renda98f092021-11-01 09:41:08 -0700499 Sensor(escapeName(sensorName), std::move(thresholdData),
500 sensorConfiguration, "xyz.openbmc_project.Configuration.ExitAirTemp",
501 false, false, exitAirMaxReading, exitAirMinReading, conn,
502 PowerState::on),
Ed Tanous2049bd22022-07-09 07:20:26 -0700503 objServer(objectServer)
James Feistbc896df2018-11-26 16:28:17 -0800504{
505 sensorInterface = objectServer.add_interface(
506 "/xyz/openbmc_project/sensors/temperature/" + name,
507 "xyz.openbmc_project.Sensor.Value");
508
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530509 for (const auto& threshold : thresholds)
James Feistbc896df2018-11-26 16:28:17 -0800510 {
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530511 std::string interface = thresholds::getInterface(threshold.level);
512 thresholdInterfaces[static_cast<size_t>(threshold.level)] =
513 objectServer.add_interface(
514 "/xyz/openbmc_project/sensors/temperature/" + name, interface);
James Feistbc896df2018-11-26 16:28:17 -0800515 }
James Feist078f2322019-03-08 11:09:05 -0800516 association = objectServer.add_interface(
517 "/xyz/openbmc_project/sensors/temperature/" + name,
James Feist2adc95c2019-09-30 14:55:28 -0700518 association::interface);
Andrei Kartashev39287412022-02-04 16:04:47 +0300519 setInitialProperties(sensor_paths::unitDegreesC);
James Feistbc896df2018-11-26 16:28:17 -0800520}
521
522ExitAirTempSensor::~ExitAirTempSensor()
523{
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530524 for (const auto& iface : thresholdInterfaces)
525 {
526 objServer.remove_interface(iface);
527 }
James Feist523828e2019-03-04 14:38:37 -0800528 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800529 objServer.remove_interface(association);
James Feistbc896df2018-11-26 16:28:17 -0800530}
531
532void ExitAirTempSensor::setupMatches(void)
533{
Brandon Kim66558232021-11-09 16:53:08 -0800534 constexpr const auto matchTypes{
535 std::to_array<const char*>({"power", inletTemperatureSensor})};
James Feistbc896df2018-11-26 16:28:17 -0800536
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700537 std::weak_ptr<ExitAirTempSensor> weakRef = weak_from_this();
Ed Tanous13b63f82021-05-11 16:12:52 -0700538 for (const std::string type : matchTypes)
James Feistbc896df2018-11-26 16:28:17 -0800539 {
James Feistb2eb3f52018-12-04 16:17:50 -0800540 setupSensorMatch(matches, *dbusConnection, type,
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700541 [weakRef, type](const double& value,
Patrick Williams92f8f512022-07-22 19:26:55 -0500542 sdbusplus::message_t& message) {
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700543 auto self = weakRef.lock();
544 if (!self)
545 {
546 return;
547 }
Ed Tanousbb679322022-05-16 16:10:00 -0700548 if (type == "power")
549 {
550 std::string path = message.get_path();
551 if (path.find("PS") != std::string::npos &&
Zev Weiss6c106d62022-08-17 20:50:00 -0700552 path.ends_with("Input_Power"))
Ed Tanousbb679322022-05-16 16:10:00 -0700553 {
554 self->powerReadings[message.get_path()] = value;
555 }
556 }
557 else if (type == inletTemperatureSensor)
558 {
559 self->inletTemp = value;
560 }
561 self->updateReading();
562 });
563 }
564 dbusConnection->async_method_call(
565 [weakRef](boost::system::error_code ec,
566 const std::variant<double>& value) {
567 if (ec)
568 {
569 // sensor not ready yet
570 return;
571 }
572 auto self = weakRef.lock();
573 if (!self)
574 {
575 return;
576 }
577 self->inletTemp = std::visit(VariantToDoubleVisitor(), value);
James Feist9566bfa2019-01-29 15:31:23 -0800578 },
579 "xyz.openbmc_project.HwmonTempSensor",
580 std::string("/xyz/openbmc_project/sensors/") + inletTemperatureSensor,
James Feista5e58722019-04-22 14:43:11 -0700581 properties::interface, properties::get, sensorValueInterface, "Value");
582 dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700583 [weakRef](boost::system::error_code ec, const GetSubTreeType& subtree) {
Ed Tanousbb679322022-05-16 16:10:00 -0700584 if (ec)
585 {
586 std::cerr << "Error contacting mapper\n";
587 return;
588 }
589 auto self = weakRef.lock();
590 if (!self)
591 {
592 return;
593 }
Zev Weiss72f322f2022-08-12 18:21:01 -0700594 for (const auto& [path, matches] : subtree)
Ed Tanousbb679322022-05-16 16:10:00 -0700595 {
Zev Weiss72f322f2022-08-12 18:21:01 -0700596 size_t lastSlash = path.rfind('/');
597 if (lastSlash == std::string::npos || lastSlash == path.size() ||
598 matches.empty())
James Feista5e58722019-04-22 14:43:11 -0700599 {
Ed Tanousbb679322022-05-16 16:10:00 -0700600 continue;
James Feista5e58722019-04-22 14:43:11 -0700601 }
Zev Weiss72f322f2022-08-12 18:21:01 -0700602 std::string sensorName = path.substr(lastSlash + 1);
Zev Weiss6c106d62022-08-17 20:50:00 -0700603 if (sensorName.starts_with("PS") &&
604 sensorName.ends_with("Input_Power"))
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700605 {
Zev Weiss72f322f2022-08-12 18:21:01 -0700606 // lambda capture requires a proper variable (not a structured
607 // binding)
608 const std::string& cbPath = path;
Ed Tanousbb679322022-05-16 16:10:00 -0700609 self->dbusConnection->async_method_call(
Zev Weiss72f322f2022-08-12 18:21:01 -0700610 [weakRef, cbPath](boost::system::error_code ec,
611 const std::variant<double>& value) {
Ed Tanousbb679322022-05-16 16:10:00 -0700612 if (ec)
613 {
Zev Weiss72f322f2022-08-12 18:21:01 -0700614 std::cerr << "Error getting value from " << cbPath
Ed Tanousbb679322022-05-16 16:10:00 -0700615 << "\n";
616 }
617 auto self = weakRef.lock();
618 if (!self)
619 {
620 return;
621 }
622 double reading =
623 std::visit(VariantToDoubleVisitor(), value);
624 if constexpr (debug)
625 {
Zev Weiss72f322f2022-08-12 18:21:01 -0700626 std::cerr << cbPath << "Reading " << reading << "\n";
Ed Tanousbb679322022-05-16 16:10:00 -0700627 }
Zev Weiss72f322f2022-08-12 18:21:01 -0700628 self->powerReadings[cbPath] = reading;
Ed Tanousbb679322022-05-16 16:10:00 -0700629 },
Zev Weiss72f322f2022-08-12 18:21:01 -0700630 matches[0].first, cbPath, properties::interface,
Ed Tanousbb679322022-05-16 16:10:00 -0700631 properties::get, sensorValueInterface, "Value");
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700632 }
Ed Tanousbb679322022-05-16 16:10:00 -0700633 }
James Feista5e58722019-04-22 14:43:11 -0700634 },
635 mapper::busName, mapper::path, mapper::interface, mapper::subtree,
636 "/xyz/openbmc_project/sensors/power", 0,
637 std::array<const char*, 1>{sensorValueInterface});
James Feistbc896df2018-11-26 16:28:17 -0800638}
639
640void ExitAirTempSensor::updateReading(void)
641{
642
643 double val = 0.0;
644 if (calculate(val))
645 {
James Feist18af4232019-03-13 11:14:00 -0700646 val = std::floor(val + 0.5);
James Feistbc896df2018-11-26 16:28:17 -0800647 updateValue(val);
648 }
649 else
650 {
651 updateValue(std::numeric_limits<double>::quiet_NaN());
652 }
653}
654
James Feistb2eb3f52018-12-04 16:17:50 -0800655double ExitAirTempSensor::getTotalCFM(void)
James Feistbc896df2018-11-26 16:28:17 -0800656{
James Feistb2eb3f52018-12-04 16:17:50 -0800657 double sum = 0;
658 for (auto& sensor : cfmSensors)
James Feistbc896df2018-11-26 16:28:17 -0800659 {
James Feistb2eb3f52018-12-04 16:17:50 -0800660 double reading = 0;
661 if (!sensor->calculate(reading))
James Feistbc896df2018-11-26 16:28:17 -0800662 {
James Feistbc896df2018-11-26 16:28:17 -0800663 return -1;
664 }
James Feistb2eb3f52018-12-04 16:17:50 -0800665 sum += reading;
James Feistbc896df2018-11-26 16:28:17 -0800666 }
James Feistb2eb3f52018-12-04 16:17:50 -0800667
668 return sum;
James Feistbc896df2018-11-26 16:28:17 -0800669}
670
671bool ExitAirTempSensor::calculate(double& val)
672{
Zhikui Ren12e3d672020-12-03 15:14:49 -0800673 constexpr size_t maxErrorPrint = 5;
James Feistbc896df2018-11-26 16:28:17 -0800674 static bool firstRead = false;
James Feistae11cfc2019-05-07 15:01:20 -0700675 static size_t errorPrint = maxErrorPrint;
676
James Feistbc896df2018-11-26 16:28:17 -0800677 double cfm = getTotalCFM();
678 if (cfm <= 0)
679 {
680 std::cerr << "Error getting cfm\n";
681 return false;
682 }
683
Zhikui Ren12e3d672020-12-03 15:14:49 -0800684 // Though cfm is not expected to be less than qMin normally,
685 // it is not a hard limit for exit air temp calculation.
686 // 50% qMin is chosen as a generic limit between providing
687 // a valid derived exit air temp and reporting exit air temp not available.
688 constexpr const double cfmLimitFactor = 0.5;
689 if (cfm < (qMin * cfmLimitFactor))
690 {
691 if (errorPrint > 0)
692 {
693 errorPrint--;
694 std::cerr << "cfm " << cfm << " is too low, expected qMin " << qMin
695 << "\n";
696 }
697 val = 0;
698 return false;
699 }
700
James Feistbc896df2018-11-26 16:28:17 -0800701 // if there is an error getting inlet temp, return error
702 if (std::isnan(inletTemp))
703 {
James Feistae11cfc2019-05-07 15:01:20 -0700704 if (errorPrint > 0)
705 {
706 errorPrint--;
707 std::cerr << "Cannot get inlet temp\n";
708 }
James Feistbc896df2018-11-26 16:28:17 -0800709 val = 0;
710 return false;
711 }
712
713 // if fans are off, just make the exit temp equal to inlet
James Feist71d31b22019-01-02 16:57:54 -0800714 if (!isPowerOn())
James Feistbc896df2018-11-26 16:28:17 -0800715 {
716 val = inletTemp;
717 return true;
718 }
719
720 double totalPower = 0;
Zev Weiss72f322f2022-08-12 18:21:01 -0700721 for (const auto& [path, reading] : powerReadings)
James Feistbc896df2018-11-26 16:28:17 -0800722 {
Zev Weiss72f322f2022-08-12 18:21:01 -0700723 if (std::isnan(reading))
James Feistbc896df2018-11-26 16:28:17 -0800724 {
725 continue;
726 }
Zev Weiss72f322f2022-08-12 18:21:01 -0700727 totalPower += reading;
James Feistbc896df2018-11-26 16:28:17 -0800728 }
729
730 // Calculate power correction factor
731 // Ci = CL + (CH - CL)/(QMax - QMin) * (CFM - QMin)
Ed Tanous8a57ec02020-10-09 12:46:52 -0700732 double powerFactor = 0.0;
James Feistbc896df2018-11-26 16:28:17 -0800733 if (cfm <= qMin)
734 {
735 powerFactor = powerFactorMin;
736 }
737 else if (cfm >= qMax)
738 {
739 powerFactor = powerFactorMax;
740 }
741 else
742 {
743 powerFactor = powerFactorMin + ((powerFactorMax - powerFactorMin) /
744 (qMax - qMin) * (cfm - qMin));
745 }
746
Ed Tanous8a57ec02020-10-09 12:46:52 -0700747 totalPower *= powerFactor;
James Feistbc896df2018-11-26 16:28:17 -0800748 totalPower += pOffset;
749
750 if (totalPower == 0)
751 {
James Feistae11cfc2019-05-07 15:01:20 -0700752 if (errorPrint > 0)
753 {
754 errorPrint--;
755 std::cerr << "total power 0\n";
756 }
James Feistbc896df2018-11-26 16:28:17 -0800757 val = 0;
758 return false;
759 }
760
Ed Tanous8a57ec02020-10-09 12:46:52 -0700761 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800762 {
763 std::cout << "Power Factor " << powerFactor << "\n";
764 std::cout << "Inlet Temp " << inletTemp << "\n";
765 std::cout << "Total Power" << totalPower << "\n";
766 }
767
768 // Calculate the exit air temp
769 // Texit = Tfp + (1.76 * TotalPower / CFM * Faltitude)
Ed Tanous8a57ec02020-10-09 12:46:52 -0700770 double reading = 1.76 * totalPower * altitudeFactor;
James Feistbc896df2018-11-26 16:28:17 -0800771 reading /= cfm;
772 reading += inletTemp;
773
Ed Tanous8a57ec02020-10-09 12:46:52 -0700774 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800775 {
776 std::cout << "Reading 1: " << reading << "\n";
777 }
778
779 // Now perform the exponential average
780 // Calculate alpha based on SDR values and CFM
781 // Ai = As + (Af - As)/(QMax - QMin) * (CFM - QMin)
782
783 double alpha = 0.0;
784 if (cfm < qMin)
785 {
786 alpha = alphaS;
787 }
788 else if (cfm >= qMax)
789 {
790 alpha = alphaF;
791 }
792 else
793 {
794 alpha = alphaS + ((alphaF - alphaS) * (cfm - qMin) / (qMax - qMin));
795 }
796
Zhikui Ren12e3d672020-12-03 15:14:49 -0800797 auto time = std::chrono::steady_clock::now();
James Feistbc896df2018-11-26 16:28:17 -0800798 if (!firstRead)
799 {
800 firstRead = true;
801 lastTime = time;
802 lastReading = reading;
803 }
804 double alphaDT =
805 std::chrono::duration_cast<std::chrono::seconds>(time - lastTime)
806 .count() *
807 alpha;
808
809 // cap at 1.0 or the below fails
810 if (alphaDT > 1.0)
811 {
812 alphaDT = 1.0;
813 }
814
Ed Tanous8a57ec02020-10-09 12:46:52 -0700815 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800816 {
817 std::cout << "AlphaDT: " << alphaDT << "\n";
818 }
819
820 reading = ((reading * alphaDT) + (lastReading * (1.0 - alphaDT)));
821
Ed Tanous8a57ec02020-10-09 12:46:52 -0700822 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800823 {
824 std::cout << "Reading 2: " << reading << "\n";
825 }
826
827 val = reading;
828 lastReading = reading;
829 lastTime = time;
James Feistae11cfc2019-05-07 15:01:20 -0700830 errorPrint = maxErrorPrint;
James Feistbc896df2018-11-26 16:28:17 -0800831 return true;
832}
833
834void ExitAirTempSensor::checkThresholds(void)
835{
836 thresholds::checkThresholds(this);
837}
838
Zev Weissafd15042022-07-18 12:28:40 -0700839static void loadVariantPathArray(const SensorBaseConfigMap& data,
840 const std::string& key,
841 std::vector<std::string>& resp)
James Feistbc896df2018-11-26 16:28:17 -0800842{
843 auto it = data.find(key);
844 if (it == data.end())
845 {
846 std::cerr << "Configuration missing " << key << "\n";
847 throw std::invalid_argument("Key Missing");
848 }
849 BasicVariantType copy = it->second;
James Feist3eb82622019-02-08 13:10:22 -0800850 std::vector<std::string> config = std::get<std::vector<std::string>>(copy);
James Feistbc896df2018-11-26 16:28:17 -0800851 for (auto& str : config)
852 {
853 boost::replace_all(str, " ", "_");
854 }
855 resp = std::move(config);
856}
857
858void createSensor(sdbusplus::asio::object_server& objectServer,
James Feistb2eb3f52018-12-04 16:17:50 -0800859 std::shared_ptr<ExitAirTempSensor>& exitAirSensor,
James Feistbc896df2018-11-26 16:28:17 -0800860 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
861{
862 if (!dbusConnection)
863 {
864 std::cerr << "Connection not created\n";
865 return;
866 }
James Feist655f3762020-10-05 15:28:15 -0700867 auto getter = std::make_shared<GetSensorConfiguration>(
Ed Tanousbb679322022-05-16 16:10:00 -0700868 dbusConnection,
869 [&objectServer, &dbusConnection,
870 &exitAirSensor](const ManagedObjectType& resp) {
871 cfmSensors.clear();
Zev Weiss72f322f2022-08-12 18:21:01 -0700872 for (const auto& [path, interfaces] : resp)
Ed Tanousbb679322022-05-16 16:10:00 -0700873 {
Zev Weiss72f322f2022-08-12 18:21:01 -0700874 for (const auto& [intf, cfg] : interfaces)
James Feistbc896df2018-11-26 16:28:17 -0800875 {
Zev Weiss72f322f2022-08-12 18:21:01 -0700876 if (intf == exitAirIface)
James Feistbc896df2018-11-26 16:28:17 -0800877 {
Ed Tanousbb679322022-05-16 16:10:00 -0700878 // thresholds should be under the same path
879 std::vector<thresholds::Threshold> sensorThresholds;
Zev Weiss72f322f2022-08-12 18:21:01 -0700880 parseThresholdsFromConfig(interfaces, sensorThresholds);
James Feistbc896df2018-11-26 16:28:17 -0800881
Zev Weiss72f322f2022-08-12 18:21:01 -0700882 std::string name = loadVariant<std::string>(cfg, "Name");
Ed Tanousbb679322022-05-16 16:10:00 -0700883 exitAirSensor = std::make_shared<ExitAirTempSensor>(
Zev Weiss72f322f2022-08-12 18:21:01 -0700884 dbusConnection, name, path.str, objectServer,
Ed Tanousbb679322022-05-16 16:10:00 -0700885 std::move(sensorThresholds));
886 exitAirSensor->powerFactorMin =
Zev Weiss72f322f2022-08-12 18:21:01 -0700887 loadVariant<double>(cfg, "PowerFactorMin");
Ed Tanousbb679322022-05-16 16:10:00 -0700888 exitAirSensor->powerFactorMax =
Zev Weiss72f322f2022-08-12 18:21:01 -0700889 loadVariant<double>(cfg, "PowerFactorMax");
890 exitAirSensor->qMin = loadVariant<double>(cfg, "QMin");
891 exitAirSensor->qMax = loadVariant<double>(cfg, "QMax");
892 exitAirSensor->alphaS = loadVariant<double>(cfg, "AlphaS");
893 exitAirSensor->alphaF = loadVariant<double>(cfg, "AlphaF");
Ed Tanousbb679322022-05-16 16:10:00 -0700894 }
Zev Weiss72f322f2022-08-12 18:21:01 -0700895 else if (intf == cfmIface)
Ed Tanousbb679322022-05-16 16:10:00 -0700896 {
897 // thresholds should be under the same path
898 std::vector<thresholds::Threshold> sensorThresholds;
Zev Weiss72f322f2022-08-12 18:21:01 -0700899 parseThresholdsFromConfig(interfaces, sensorThresholds);
900 std::string name = loadVariant<std::string>(cfg, "Name");
Ed Tanousbb679322022-05-16 16:10:00 -0700901 auto sensor = std::make_shared<CFMSensor>(
Zev Weiss72f322f2022-08-12 18:21:01 -0700902 dbusConnection, name, path.str, objectServer,
Ed Tanousbb679322022-05-16 16:10:00 -0700903 std::move(sensorThresholds), exitAirSensor);
Zev Weiss72f322f2022-08-12 18:21:01 -0700904 loadVariantPathArray(cfg, "Tachs", sensor->tachs);
905 sensor->maxCFM = loadVariant<double>(cfg, "MaxCFM");
James Feistbc896df2018-11-26 16:28:17 -0800906
Ed Tanousbb679322022-05-16 16:10:00 -0700907 // change these into percent upon getting the data
Zev Weiss72f322f2022-08-12 18:21:01 -0700908 sensor->c1 = loadVariant<double>(cfg, "C1") / 100;
909 sensor->c2 = loadVariant<double>(cfg, "C2") / 100;
Ed Tanousbb679322022-05-16 16:10:00 -0700910 sensor->tachMinPercent =
Zev Weiss72f322f2022-08-12 18:21:01 -0700911 loadVariant<double>(cfg, "TachMinPercent");
Ed Tanousbb679322022-05-16 16:10:00 -0700912 sensor->tachMaxPercent =
Zev Weiss72f322f2022-08-12 18:21:01 -0700913 loadVariant<double>(cfg, "TachMaxPercent");
Ed Tanousbb679322022-05-16 16:10:00 -0700914 sensor->createMaxCFMIface();
915 sensor->setupMatches();
James Feistbc896df2018-11-26 16:28:17 -0800916
Ed Tanousbb679322022-05-16 16:10:00 -0700917 cfmSensors.emplace_back(std::move(sensor));
James Feistbc896df2018-11-26 16:28:17 -0800918 }
919 }
Ed Tanousbb679322022-05-16 16:10:00 -0700920 }
921 if (exitAirSensor)
922 {
923 exitAirSensor->setupMatches();
924 exitAirSensor->updateReading();
925 }
Ed Tanous8a17c302021-09-02 15:07:11 -0700926 });
James Feist655f3762020-10-05 15:28:15 -0700927 getter->getConfiguration(
928 std::vector<std::string>(monitorIfaces.begin(), monitorIfaces.end()));
James Feistbc896df2018-11-26 16:28:17 -0800929}
930
James Feistb6c0b912019-07-09 12:21:44 -0700931int main()
James Feistbc896df2018-11-26 16:28:17 -0800932{
933
934 boost::asio::io_service io;
935 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
936 systemBus->request_name("xyz.openbmc_project.ExitAirTempSensor");
937 sdbusplus::asio::object_server objectServer(systemBus);
938 std::shared_ptr<ExitAirTempSensor> sensor =
939 nullptr; // wait until we find the config
James Feistbc896df2018-11-26 16:28:17 -0800940
941 io.post([&]() { createSensor(objectServer, sensor, systemBus); });
942
943 boost::asio::deadline_timer configTimer(io);
944
Patrick Williams92f8f512022-07-22 19:26:55 -0500945 std::function<void(sdbusplus::message_t&)> eventHandler =
946 [&](sdbusplus::message_t&) {
Ed Tanousbb679322022-05-16 16:10:00 -0700947 configTimer.expires_from_now(boost::posix_time::seconds(1));
948 // create a timer because normally multiple properties change
949 configTimer.async_wait([&](const boost::system::error_code& ec) {
950 if (ec == boost::asio::error::operation_aborted)
951 {
952 return; // we're being canceled
953 }
954 createSensor(objectServer, sensor, systemBus);
955 if (!sensor)
956 {
957 std::cout << "Configuration not detected\n";
958 }
959 });
960 };
Zev Weiss214d9712022-08-12 12:54:31 -0700961 std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
962 setupPropertiesChangedMatches(*systemBus, monitorIfaces, eventHandler);
James Feistbc896df2018-11-26 16:28:17 -0800963
Bruce Lee913d4d02021-07-22 10:18:42 +0800964 setupManufacturingModeMatch(*systemBus);
James Feistbc896df2018-11-26 16:28:17 -0800965 io.run();
Zhikui Ren8685b172021-06-29 15:16:52 -0700966 return 0;
James Feistbc896df2018-11-26 16:28:17 -0800967}