blob: bd8960ba5c21b9e1a6b441cda516c74c2e7d1820 [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
Andrew Jefferye73bd0a2023-01-25 10:39:57 +103017#include "ExitAirTempSensor.hpp"
18
Ed Tanouseacbfdd2024-04-04 12:00:24 -070019#include "SensorPaths.hpp"
20#include "Thresholds.hpp"
Andrew Jefferye73bd0a2023-01-25 10:39:57 +103021#include "Utils.hpp"
22#include "VariantVisitors.hpp"
Ed Tanouseacbfdd2024-04-04 12:00:24 -070023#include "sensor.hpp"
Andrew Jefferye73bd0a2023-01-25 10:39:57 +103024
James Feistbc896df2018-11-26 16:28:17 -080025#include <boost/algorithm/string/replace.hpp>
Ed Tanouseacbfdd2024-04-04 12:00:24 -070026#include <boost/asio/error.hpp>
27#include <boost/asio/io_context.hpp>
28#include <boost/asio/post.hpp>
29#include <boost/asio/steady_timer.hpp>
Patrick Venture96e97db2019-10-31 13:44:38 -070030#include <boost/container/flat_map.hpp>
James Feist38fb5982020-05-28 10:09:54 -070031#include <sdbusplus/asio/connection.hpp>
32#include <sdbusplus/asio/object_server.hpp>
Ed Tanouseacbfdd2024-04-04 12:00:24 -070033#include <sdbusplus/bus.hpp>
James Feist38fb5982020-05-28 10:09:54 -070034#include <sdbusplus/bus/match.hpp>
Ed Tanouseacbfdd2024-04-04 12:00:24 -070035#include <sdbusplus/message.hpp>
James Feist38fb5982020-05-28 10:09:54 -070036
Ed Tanouseacbfdd2024-04-04 12:00:24 -070037#include <algorithm>
James Feist38fb5982020-05-28 10:09:54 -070038#include <array>
James Feistbc896df2018-11-26 16:28:17 -080039#include <chrono>
Patrick Venture96e97db2019-10-31 13:44:38 -070040#include <cmath>
Ed Tanouseacbfdd2024-04-04 12:00:24 -070041#include <cstddef>
42#include <cstdint>
Patrick Venture96e97db2019-10-31 13:44:38 -070043#include <functional>
James Feistbc896df2018-11-26 16:28:17 -080044#include <iostream>
45#include <limits>
Patrick Venture96e97db2019-10-31 13:44:38 -070046#include <memory>
Patrick Venture96e97db2019-10-31 13:44:38 -070047#include <stdexcept>
Ed Tanouseacbfdd2024-04-04 12:00:24 -070048#include <string>
Patrick Venture96e97db2019-10-31 13:44:38 -070049#include <utility>
50#include <variant>
James Feistbc896df2018-11-26 16:28:17 -080051#include <vector>
52
Ed Tanous8a57ec02020-10-09 12:46:52 -070053constexpr const double altitudeFactor = 1.14;
Zev Weiss054aad82022-08-18 01:37:34 -070054constexpr const char* exitAirType = "ExitAirTempSensor";
55constexpr const char* cfmType = "CFMSensor";
James Feistbc896df2018-11-26 16:28:17 -080056
57// todo: this *might* need to be configurable
58constexpr const char* inletTemperatureSensor = "temperature/Front_Panel_Temp";
James Feist13452092019-03-07 16:38:12 -080059constexpr const char* pidConfigurationType =
60 "xyz.openbmc_project.Configuration.Pid";
61constexpr const char* settingsDaemon = "xyz.openbmc_project.Settings";
62constexpr const char* cfmSettingPath = "/xyz/openbmc_project/control/cfm_limit";
63constexpr const char* cfmSettingIface = "xyz.openbmc_project.Control.CFMLimit";
James Feistbc896df2018-11-26 16:28:17 -080064
Ed Tanous8a57ec02020-10-09 12:46:52 -070065static constexpr bool debug = false;
James Feistbc896df2018-11-26 16:28:17 -080066
James Feistb2eb3f52018-12-04 16:17:50 -080067static constexpr double cfmMaxReading = 255;
68static constexpr double cfmMinReading = 0;
69
James Feist13452092019-03-07 16:38:12 -080070static constexpr size_t minSystemCfm = 50;
71
Zev Weiss054aad82022-08-18 01:37:34 -070072constexpr const auto monitorTypes{
73 std::to_array<const char*>({exitAirType, cfmType})};
James Feist655f3762020-10-05 15:28:15 -070074
James Feist9a25ed42019-10-15 15:43:44 -070075static std::vector<std::shared_ptr<CFMSensor>> cfmSensors;
76
James Feistb2eb3f52018-12-04 16:17:50 -080077static void setupSensorMatch(
Patrick Williams92f8f512022-07-22 19:26:55 -050078 std::vector<sdbusplus::bus::match_t>& matches, sdbusplus::bus_t& connection,
79 const std::string& type,
80 std::function<void(const double&, sdbusplus::message_t&)>&& callback)
James Feistb2eb3f52018-12-04 16:17:50 -080081{
Patrick Williams92f8f512022-07-22 19:26:55 -050082 std::function<void(sdbusplus::message_t & message)> eventHandler =
83 [callback{std::move(callback)}](sdbusplus::message_t& message) {
Patrick Williams2aaf7172024-08-16 15:20:40 -040084 std::string objectName;
85 boost::container::flat_map<std::string,
86 std::variant<double, int64_t>>
87 values;
88 message.read(objectName, values);
89 auto findValue = values.find("Value");
90 if (findValue == values.end())
91 {
92 return;
93 }
94 double value =
95 std::visit(VariantToDoubleVisitor(), findValue->second);
96 if (std::isnan(value))
97 {
98 return;
99 }
James Feist9566bfa2019-01-29 15:31:23 -0800100
Patrick Williams2aaf7172024-08-16 15:20:40 -0400101 callback(value, message);
102 };
103 matches.emplace_back(
104 connection,
105 "type='signal',"
106 "member='PropertiesChanged',interface='org."
107 "freedesktop.DBus.Properties',path_"
108 "namespace='/xyz/openbmc_project/sensors/" +
109 std::string(type) + "',arg0='xyz.openbmc_project.Sensor.Value'",
110 std::move(eventHandler));
James Feistb2eb3f52018-12-04 16:17:50 -0800111}
112
James Feist13452092019-03-07 16:38:12 -0800113static void setMaxPWM(const std::shared_ptr<sdbusplus::asio::connection>& conn,
114 double value)
115{
116 using GetSubTreeType = std::vector<std::pair<
117 std::string,
118 std::vector<std::pair<std::string, std::vector<std::string>>>>>;
119
120 conn->async_method_call(
Patrick Williams2aaf7172024-08-16 15:20:40 -0400121 [conn,
122 value](const boost::system::error_code ec, const GetSubTreeType& ret) {
123 if (ec)
James Feist13452092019-03-07 16:38:12 -0800124 {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400125 std::cerr << "Error calling mapper\n";
James Feist13452092019-03-07 16:38:12 -0800126 return;
127 }
Patrick Williams2aaf7172024-08-16 15:20:40 -0400128 for (const auto& [path, objDict] : ret)
129 {
130 if (objDict.empty())
131 {
132 return;
133 }
134 const std::string& owner = objDict.begin()->first;
Ed Tanousbb679322022-05-16 16:10:00 -0700135
James Feist13452092019-03-07 16:38:12 -0800136 conn->async_method_call(
Patrick Williams2aaf7172024-08-16 15:20:40 -0400137 [conn, value, owner,
138 path{path}](const boost::system::error_code ec,
139 const std::variant<std::string>& classType) {
140 if (ec)
141 {
142 std::cerr << "Error getting pid class\n";
143 return;
144 }
145 const auto* classStr =
146 std::get_if<std::string>(&classType);
147 if (classStr == nullptr || *classStr != "fan")
148 {
149 return;
150 }
151 conn->async_method_call(
152 [](boost::system::error_code& ec) {
153 if (ec)
154 {
155 std::cerr << "Error setting pid class\n";
156 return;
157 }
158 },
159 owner, path, "org.freedesktop.DBus.Properties",
160 "Set", pidConfigurationType, "OutLimitMax",
161 std::variant<double>(value));
162 },
163 owner, path, "org.freedesktop.DBus.Properties", "Get",
164 pidConfigurationType, "Class");
165 }
166 },
James Feista5e58722019-04-22 14:43:11 -0700167 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
168 0, std::array<std::string, 1>{pidConfigurationType});
James Feist13452092019-03-07 16:38:12 -0800169}
170
James Feistb2eb3f52018-12-04 16:17:50 -0800171CFMSensor::CFMSensor(std::shared_ptr<sdbusplus::asio::connection>& conn,
172 const std::string& sensorName,
173 const std::string& sensorConfiguration,
174 sdbusplus::asio::object_server& objectServer,
James Feistb839c052019-05-15 10:25:24 -0700175 std::vector<thresholds::Threshold>&& thresholdData,
James Feistb2eb3f52018-12-04 16:17:50 -0800176 std::shared_ptr<ExitAirTempSensor>& parent) :
Zhikui Renda98f092021-11-01 09:41:08 -0700177 Sensor(escapeName(sensorName), std::move(thresholdData),
Zev Weiss054aad82022-08-18 01:37:34 -0700178 sensorConfiguration, "CFMSensor", false, false, cfmMaxReading,
179 cfmMinReading, conn, PowerState::on),
Ed Tanous2049bd22022-07-09 07:20:26 -0700180 parent(parent), objServer(objectServer)
James Feistb2eb3f52018-12-04 16:17:50 -0800181{
Basheer Ahmed Muddebihale5b867b2021-07-26 08:32:19 -0700182 sensorInterface = objectServer.add_interface(
183 "/xyz/openbmc_project/sensors/airflow/" + name,
184 "xyz.openbmc_project.Sensor.Value");
James Feistb2eb3f52018-12-04 16:17:50 -0800185
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530186 for (const auto& threshold : thresholds)
James Feistb2eb3f52018-12-04 16:17:50 -0800187 {
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530188 std::string interface = thresholds::getInterface(threshold.level);
189 thresholdInterfaces[static_cast<size_t>(threshold.level)] =
190 objectServer.add_interface(
191 "/xyz/openbmc_project/sensors/airflow/" + name, interface);
James Feistb2eb3f52018-12-04 16:17:50 -0800192 }
James Feist078f2322019-03-08 11:09:05 -0800193
194 association = objectServer.add_interface(
Basheer Ahmed Muddebihale5b867b2021-07-26 08:32:19 -0700195 "/xyz/openbmc_project/sensors/airflow/" + name, association::interface);
James Feist078f2322019-03-08 11:09:05 -0800196
Andrei Kartashev39287412022-02-04 16:04:47 +0300197 setInitialProperties(sensor_paths::unitCFM);
James Feist9a25ed42019-10-15 15:43:44 -0700198
James Feist13452092019-03-07 16:38:12 -0800199 pwmLimitIface =
200 objectServer.add_interface("/xyz/openbmc_project/control/pwm_limit",
201 "xyz.openbmc_project.Control.PWMLimit");
202 cfmLimitIface =
203 objectServer.add_interface("/xyz/openbmc_project/control/MaxCFM",
204 "xyz.openbmc_project.Control.CFMLimit");
James Feist9a25ed42019-10-15 15:43:44 -0700205}
James Feist13452092019-03-07 16:38:12 -0800206
James Feist9a25ed42019-10-15 15:43:44 -0700207void CFMSensor::setupMatches()
208{
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700209 std::weak_ptr<CFMSensor> weakRef = weak_from_this();
Ed Tanous8a17c302021-09-02 15:07:11 -0700210 setupSensorMatch(
211 matches, *dbusConnection, "fan_tach",
Patrick Williams92f8f512022-07-22 19:26:55 -0500212 [weakRef](const double& value, sdbusplus::message_t& message) {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400213 auto self = weakRef.lock();
214 if (!self)
215 {
216 return;
217 }
218 self->tachReadings[message.get_path()] = value;
219 if (self->tachRanges.find(message.get_path()) ==
220 self->tachRanges.end())
221 {
222 // calls update reading after updating ranges
223 self->addTachRanges(message.get_sender(), message.get_path());
224 }
225 else
226 {
227 self->updateReading();
228 }
229 });
James Feist9a25ed42019-10-15 15:43:44 -0700230
231 dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700232 [weakRef](const boost::system::error_code ec,
233 const std::variant<double> cfmVariant) {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400234 auto self = weakRef.lock();
235 if (!self)
Ed Tanousbb679322022-05-16 16:10:00 -0700236 {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400237 return;
James Feist13452092019-03-07 16:38:12 -0800238 }
Patrick Williams2aaf7172024-08-16 15:20:40 -0400239
240 uint64_t maxRpm = 100;
241 if (!ec)
242 {
243 const auto* cfm = std::get_if<double>(&cfmVariant);
244 if (cfm != nullptr && *cfm >= minSystemCfm)
245 {
246 maxRpm = self->getMaxRpm(*cfm);
247 }
248 }
249 self->pwmLimitIface->register_property("Limit", maxRpm);
250 self->pwmLimitIface->initialize();
251 setMaxPWM(self->dbusConnection, maxRpm);
252 },
James Feist13452092019-03-07 16:38:12 -0800253 settingsDaemon, cfmSettingPath, "org.freedesktop.DBus.Properties",
254 "Get", cfmSettingIface, "Limit");
255
Patrick Williams2aaf7172024-08-16 15:20:40 -0400256 matches.emplace_back(
257 *dbusConnection,
258 "type='signal',"
259 "member='PropertiesChanged',interface='org."
260 "freedesktop.DBus.Properties',path='" +
261 std::string(cfmSettingPath) + "',arg0='" +
262 std::string(cfmSettingIface) + "'",
263 [weakRef](sdbusplus::message_t& message) {
264 auto self = weakRef.lock();
265 if (!self)
266 {
267 return;
268 }
269 boost::container::flat_map<std::string, std::variant<double>>
270 values;
271 std::string objectName;
272 message.read(objectName, values);
273 const auto findValue = values.find("Limit");
274 if (findValue == values.end())
275 {
276 return;
277 }
278 auto* const reading = std::get_if<double>(&(findValue->second));
279 if (reading == nullptr)
280 {
281 std::cerr << "Got CFM Limit of wrong type\n";
282 return;
283 }
284 if (*reading < minSystemCfm && *reading != 0)
285 {
286 std::cerr << "Illegal CFM setting detected\n";
287 return;
288 }
289 uint64_t maxRpm = self->getMaxRpm(*reading);
290 self->pwmLimitIface->set_property("Limit", maxRpm);
291 setMaxPWM(self->dbusConnection, maxRpm);
292 });
James Feistb2eb3f52018-12-04 16:17:50 -0800293}
294
James Feist9566bfa2019-01-29 15:31:23 -0800295CFMSensor::~CFMSensor()
296{
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530297 for (const auto& iface : thresholdInterfaces)
298 {
299 objServer.remove_interface(iface);
300 }
James Feist9566bfa2019-01-29 15:31:23 -0800301 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800302 objServer.remove_interface(association);
James Feist13452092019-03-07 16:38:12 -0800303 objServer.remove_interface(cfmLimitIface);
304 objServer.remove_interface(pwmLimitIface);
305}
306
Ed Tanous201a1012024-04-03 18:07:28 -0700307void CFMSensor::createMaxCFMIface()
James Feist13452092019-03-07 16:38:12 -0800308{
James Feistb6c0b912019-07-09 12:21:44 -0700309 cfmLimitIface->register_property("Limit", c2 * maxCFM * tachs.size());
James Feist13452092019-03-07 16:38:12 -0800310 cfmLimitIface->initialize();
James Feist9566bfa2019-01-29 15:31:23 -0800311}
312
James Feistb2eb3f52018-12-04 16:17:50 -0800313void CFMSensor::addTachRanges(const std::string& serviceName,
314 const std::string& path)
315{
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700316 std::weak_ptr<CFMSensor> weakRef = weak_from_this();
James Feistb2eb3f52018-12-04 16:17:50 -0800317 dbusConnection->async_method_call(
Zev Weissafd15042022-07-18 12:28:40 -0700318 [weakRef, path](const boost::system::error_code ec,
319 const SensorBaseConfigMap& data) {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400320 if (ec)
321 {
322 std::cerr << "Error getting properties from " << path << "\n";
323 return;
324 }
325 auto self = weakRef.lock();
326 if (!self)
327 {
328 return;
329 }
330 double max = loadVariant<double>(data, "MaxValue");
331 double min = loadVariant<double>(data, "MinValue");
332 self->tachRanges[path] = std::make_pair(min, max);
333 self->updateReading();
334 },
James Feistb2eb3f52018-12-04 16:17:50 -0800335 serviceName, path, "org.freedesktop.DBus.Properties", "GetAll",
336 "xyz.openbmc_project.Sensor.Value");
337}
338
Ed Tanous201a1012024-04-03 18:07:28 -0700339void CFMSensor::checkThresholds()
James Feistb2eb3f52018-12-04 16:17:50 -0800340{
341 thresholds::checkThresholds(this);
342}
343
Ed Tanous201a1012024-04-03 18:07:28 -0700344void CFMSensor::updateReading()
James Feistb2eb3f52018-12-04 16:17:50 -0800345{
346 double val = 0.0;
347 if (calculate(val))
348 {
349 if (value != val && parent)
350 {
351 parent->updateReading();
352 }
353 updateValue(val);
354 }
355 else
356 {
357 updateValue(std::numeric_limits<double>::quiet_NaN());
358 }
359}
360
Ed Tanous2049bd22022-07-09 07:20:26 -0700361uint64_t CFMSensor::getMaxRpm(uint64_t cfmMaxSetting) const
James Feist13452092019-03-07 16:38:12 -0800362{
363 uint64_t pwmPercent = 100;
364 double totalCFM = std::numeric_limits<double>::max();
365 if (cfmMaxSetting == 0)
366 {
367 return pwmPercent;
368 }
369
James Feist52427952019-04-05 14:23:35 -0700370 bool firstLoop = true;
James Feist13452092019-03-07 16:38:12 -0800371 while (totalCFM > cfmMaxSetting)
372 {
James Feist52427952019-04-05 14:23:35 -0700373 if (firstLoop)
374 {
375 firstLoop = false;
376 }
377 else
378 {
379 pwmPercent--;
380 }
381
James Feist13452092019-03-07 16:38:12 -0800382 double ci = 0;
383 if (pwmPercent == 0)
384 {
385 ci = 0;
386 }
387 else if (pwmPercent < tachMinPercent)
388 {
389 ci = c1;
390 }
391 else if (pwmPercent > tachMaxPercent)
392 {
393 ci = c2;
394 }
395 else
396 {
397 ci = c1 + (((c2 - c1) * (pwmPercent - tachMinPercent)) /
398 (tachMaxPercent - tachMinPercent));
399 }
400
401 // Now calculate the CFM for this tach
402 // CFMi = Ci * Qmaxi * TACHi
403 totalCFM = ci * maxCFM * pwmPercent;
404 totalCFM *= tachs.size();
405 // divide by 100 since pwm is in percent
406 totalCFM /= 100;
407
James Feist13452092019-03-07 16:38:12 -0800408 if (pwmPercent <= 0)
409 {
410 break;
411 }
412 }
James Feist52427952019-04-05 14:23:35 -0700413
James Feist13452092019-03-07 16:38:12 -0800414 return pwmPercent;
415}
416
James Feistb2eb3f52018-12-04 16:17:50 -0800417bool CFMSensor::calculate(double& value)
418{
419 double totalCFM = 0;
420 for (const std::string& tachName : tachs)
421 {
422 auto findReading = std::find_if(
Zev Weiss6c106d62022-08-17 20:50:00 -0700423 tachReadings.begin(), tachReadings.end(),
424 [&](const auto& item) { return item.first.ends_with(tachName); });
Patrick Williams597e8422023-10-20 11:19:01 -0500425 auto findRange = std::find_if(
426 tachRanges.begin(), tachRanges.end(),
427 [&](const auto& item) { return item.first.ends_with(tachName); });
James Feistb2eb3f52018-12-04 16:17:50 -0800428 if (findReading == tachReadings.end())
429 {
Ed Tanous8a57ec02020-10-09 12:46:52 -0700430 if constexpr (debug)
James Feista96329f2019-01-24 10:08:27 -0800431 {
432 std::cerr << "Can't find " << tachName << "in readings\n";
433 }
James Feist9566bfa2019-01-29 15:31:23 -0800434 continue; // haven't gotten a reading
James Feistb2eb3f52018-12-04 16:17:50 -0800435 }
436
437 if (findRange == tachRanges.end())
438 {
James Feist523828e2019-03-04 14:38:37 -0800439 std::cerr << "Can't find " << tachName << " in ranges\n";
James Feistb2eb3f52018-12-04 16:17:50 -0800440 return false; // haven't gotten a max / min
441 }
442
443 // avoid divide by 0
444 if (findRange->second.second == 0)
445 {
446 std::cerr << "Tach Max Set to 0 " << tachName << "\n";
447 return false;
448 }
449
450 double rpm = findReading->second;
451
452 // for now assume the min for a fan is always 0, divide by max to get
453 // percent and mult by 100
454 rpm /= findRange->second.second;
455 rpm *= 100;
456
Ed Tanous8a57ec02020-10-09 12:46:52 -0700457 if constexpr (debug)
James Feistb2eb3f52018-12-04 16:17:50 -0800458 {
459 std::cout << "Tach " << tachName << "at " << rpm << "\n";
460 }
461
462 // Do a linear interpolation to get Ci
463 // Ci = C1 + (C2 - C1)/(RPM2 - RPM1) * (TACHi - TACH1)
464
465 double ci = 0;
466 if (rpm == 0)
467 {
468 ci = 0;
469 }
470 else if (rpm < tachMinPercent)
471 {
472 ci = c1;
473 }
474 else if (rpm > tachMaxPercent)
475 {
476 ci = c2;
477 }
478 else
479 {
480 ci = c1 + (((c2 - c1) * (rpm - tachMinPercent)) /
481 (tachMaxPercent - tachMinPercent));
482 }
483
484 // Now calculate the CFM for this tach
485 // CFMi = Ci * Qmaxi * TACHi
486 totalCFM += ci * maxCFM * rpm;
Ed Tanous8a57ec02020-10-09 12:46:52 -0700487 if constexpr (debug)
James Feista5e58722019-04-22 14:43:11 -0700488 {
489 std::cerr << "totalCFM = " << totalCFM << "\n";
490 std::cerr << "Ci " << ci << " MaxCFM " << maxCFM << " rpm " << rpm
491 << "\n";
492 std::cerr << "c1 " << c1 << " c2 " << c2 << " max "
493 << tachMaxPercent << " min " << tachMinPercent << "\n";
494 }
James Feistb2eb3f52018-12-04 16:17:50 -0800495 }
496
497 // divide by 100 since rpm is in percent
498 value = totalCFM / 100;
Ed Tanous8a57ec02020-10-09 12:46:52 -0700499 if constexpr (debug)
James Feista5e58722019-04-22 14:43:11 -0700500 {
501 std::cerr << "cfm value = " << value << "\n";
502 }
James Feist9566bfa2019-01-29 15:31:23 -0800503 return true;
James Feistb2eb3f52018-12-04 16:17:50 -0800504}
505
506static constexpr double exitAirMaxReading = 127;
507static constexpr double exitAirMinReading = -128;
James Feistbc896df2018-11-26 16:28:17 -0800508ExitAirTempSensor::ExitAirTempSensor(
509 std::shared_ptr<sdbusplus::asio::connection>& conn,
James Feistb2eb3f52018-12-04 16:17:50 -0800510 const std::string& sensorName, const std::string& sensorConfiguration,
James Feistbc896df2018-11-26 16:28:17 -0800511 sdbusplus::asio::object_server& objectServer,
James Feistb839c052019-05-15 10:25:24 -0700512 std::vector<thresholds::Threshold>&& thresholdData) :
Zhikui Renda98f092021-11-01 09:41:08 -0700513 Sensor(escapeName(sensorName), std::move(thresholdData),
Zev Weiss054aad82022-08-18 01:37:34 -0700514 sensorConfiguration, "ExitAirTemp", false, false, exitAirMaxReading,
515 exitAirMinReading, conn, PowerState::on),
Ed Tanous2049bd22022-07-09 07:20:26 -0700516 objServer(objectServer)
James Feistbc896df2018-11-26 16:28:17 -0800517{
518 sensorInterface = objectServer.add_interface(
519 "/xyz/openbmc_project/sensors/temperature/" + name,
520 "xyz.openbmc_project.Sensor.Value");
521
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530522 for (const auto& threshold : thresholds)
James Feistbc896df2018-11-26 16:28:17 -0800523 {
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530524 std::string interface = thresholds::getInterface(threshold.level);
525 thresholdInterfaces[static_cast<size_t>(threshold.level)] =
526 objectServer.add_interface(
527 "/xyz/openbmc_project/sensors/temperature/" + name, interface);
James Feistbc896df2018-11-26 16:28:17 -0800528 }
James Feist078f2322019-03-08 11:09:05 -0800529 association = objectServer.add_interface(
530 "/xyz/openbmc_project/sensors/temperature/" + name,
James Feist2adc95c2019-09-30 14:55:28 -0700531 association::interface);
Andrei Kartashev39287412022-02-04 16:04:47 +0300532 setInitialProperties(sensor_paths::unitDegreesC);
James Feistbc896df2018-11-26 16:28:17 -0800533}
534
535ExitAirTempSensor::~ExitAirTempSensor()
536{
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530537 for (const auto& iface : thresholdInterfaces)
538 {
539 objServer.remove_interface(iface);
540 }
James Feist523828e2019-03-04 14:38:37 -0800541 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800542 objServer.remove_interface(association);
James Feistbc896df2018-11-26 16:28:17 -0800543}
544
Ed Tanous201a1012024-04-03 18:07:28 -0700545void ExitAirTempSensor::setupMatches()
James Feistbc896df2018-11-26 16:28:17 -0800546{
Brandon Kim66558232021-11-09 16:53:08 -0800547 constexpr const auto matchTypes{
548 std::to_array<const char*>({"power", inletTemperatureSensor})};
James Feistbc896df2018-11-26 16:28:17 -0800549
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700550 std::weak_ptr<ExitAirTempSensor> weakRef = weak_from_this();
Ed Tanous13b63f82021-05-11 16:12:52 -0700551 for (const std::string type : matchTypes)
James Feistbc896df2018-11-26 16:28:17 -0800552 {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400553 setupSensorMatch(
554 matches, *dbusConnection, type,
555 [weakRef,
556 type](const double& value, sdbusplus::message_t& message) {
557 auto self = weakRef.lock();
558 if (!self)
559 {
560 return;
561 }
562 if (type == "power")
563 {
564 std::string path = message.get_path();
565 if (path.find("PS") != std::string::npos &&
566 path.ends_with("Input_Power"))
567 {
568 self->powerReadings[message.get_path()] = value;
569 }
570 }
571 else if (type == inletTemperatureSensor)
572 {
573 self->inletTemp = value;
574 }
575 self->updateReading();
576 });
577 }
578 dbusConnection->async_method_call(
579 [weakRef](boost::system::error_code ec,
580 const std::variant<double>& value) {
581 if (ec)
582 {
583 // sensor not ready yet
584 return;
585 }
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700586 auto self = weakRef.lock();
587 if (!self)
588 {
589 return;
590 }
Patrick Williams2aaf7172024-08-16 15:20:40 -0400591 self->inletTemp = std::visit(VariantToDoubleVisitor(), value);
592 },
James Feist9566bfa2019-01-29 15:31:23 -0800593 "xyz.openbmc_project.HwmonTempSensor",
594 std::string("/xyz/openbmc_project/sensors/") + inletTemperatureSensor,
James Feista5e58722019-04-22 14:43:11 -0700595 properties::interface, properties::get, sensorValueInterface, "Value");
596 dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700597 [weakRef](boost::system::error_code ec, const GetSubTreeType& subtree) {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400598 if (ec)
James Feista5e58722019-04-22 14:43:11 -0700599 {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400600 std::cerr << "Error contacting mapper\n";
601 return;
James Feista5e58722019-04-22 14:43:11 -0700602 }
Patrick Williams2aaf7172024-08-16 15:20:40 -0400603 auto self = weakRef.lock();
604 if (!self)
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700605 {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400606 return;
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700607 }
Patrick Williams2aaf7172024-08-16 15:20:40 -0400608 for (const auto& [path, matches] : subtree)
609 {
610 size_t lastSlash = path.rfind('/');
611 if (lastSlash == std::string::npos ||
612 lastSlash == path.size() || matches.empty())
613 {
614 continue;
615 }
616 std::string sensorName = path.substr(lastSlash + 1);
617 if (sensorName.starts_with("PS") &&
618 sensorName.ends_with("Input_Power"))
619 {
620 // lambda capture requires a proper variable (not a
621 // structured binding)
622 const std::string& cbPath = path;
623 self->dbusConnection->async_method_call(
624 [weakRef, cbPath](boost::system::error_code ec,
625 const std::variant<double>& value) {
626 if (ec)
627 {
628 std::cerr << "Error getting value from "
629 << cbPath << "\n";
630 }
631 auto self = weakRef.lock();
632 if (!self)
633 {
634 return;
635 }
636 double reading =
637 std::visit(VariantToDoubleVisitor(), value);
638 if constexpr (debug)
639 {
640 std::cerr
641 << cbPath << "Reading " << reading << "\n";
642 }
643 self->powerReadings[cbPath] = reading;
644 },
645 matches[0].first, cbPath, properties::interface,
646 properties::get, sensorValueInterface, "Value");
647 }
648 }
649 },
James Feista5e58722019-04-22 14:43:11 -0700650 mapper::busName, mapper::path, mapper::interface, mapper::subtree,
651 "/xyz/openbmc_project/sensors/power", 0,
652 std::array<const char*, 1>{sensorValueInterface});
James Feistbc896df2018-11-26 16:28:17 -0800653}
654
Ed Tanous201a1012024-04-03 18:07:28 -0700655void ExitAirTempSensor::updateReading()
James Feistbc896df2018-11-26 16:28:17 -0800656{
James Feistbc896df2018-11-26 16:28:17 -0800657 double val = 0.0;
658 if (calculate(val))
659 {
James Feist18af4232019-03-13 11:14:00 -0700660 val = std::floor(val + 0.5);
James Feistbc896df2018-11-26 16:28:17 -0800661 updateValue(val);
662 }
663 else
664 {
665 updateValue(std::numeric_limits<double>::quiet_NaN());
666 }
667}
668
Ed Tanous201a1012024-04-03 18:07:28 -0700669double ExitAirTempSensor::getTotalCFM()
James Feistbc896df2018-11-26 16:28:17 -0800670{
James Feistb2eb3f52018-12-04 16:17:50 -0800671 double sum = 0;
672 for (auto& sensor : cfmSensors)
James Feistbc896df2018-11-26 16:28:17 -0800673 {
James Feistb2eb3f52018-12-04 16:17:50 -0800674 double reading = 0;
675 if (!sensor->calculate(reading))
James Feistbc896df2018-11-26 16:28:17 -0800676 {
James Feistbc896df2018-11-26 16:28:17 -0800677 return -1;
678 }
James Feistb2eb3f52018-12-04 16:17:50 -0800679 sum += reading;
James Feistbc896df2018-11-26 16:28:17 -0800680 }
James Feistb2eb3f52018-12-04 16:17:50 -0800681
682 return sum;
James Feistbc896df2018-11-26 16:28:17 -0800683}
684
685bool ExitAirTempSensor::calculate(double& val)
686{
Zhikui Ren12e3d672020-12-03 15:14:49 -0800687 constexpr size_t maxErrorPrint = 5;
James Feistbc896df2018-11-26 16:28:17 -0800688 static bool firstRead = false;
James Feistae11cfc2019-05-07 15:01:20 -0700689 static size_t errorPrint = maxErrorPrint;
690
James Feistbc896df2018-11-26 16:28:17 -0800691 double cfm = getTotalCFM();
692 if (cfm <= 0)
693 {
694 std::cerr << "Error getting cfm\n";
695 return false;
696 }
697
Zhikui Ren12e3d672020-12-03 15:14:49 -0800698 // Though cfm is not expected to be less than qMin normally,
699 // it is not a hard limit for exit air temp calculation.
700 // 50% qMin is chosen as a generic limit between providing
701 // a valid derived exit air temp and reporting exit air temp not available.
702 constexpr const double cfmLimitFactor = 0.5;
703 if (cfm < (qMin * cfmLimitFactor))
704 {
705 if (errorPrint > 0)
706 {
707 errorPrint--;
708 std::cerr << "cfm " << cfm << " is too low, expected qMin " << qMin
709 << "\n";
710 }
711 val = 0;
712 return false;
713 }
714
James Feistbc896df2018-11-26 16:28:17 -0800715 // if there is an error getting inlet temp, return error
716 if (std::isnan(inletTemp))
717 {
James Feistae11cfc2019-05-07 15:01:20 -0700718 if (errorPrint > 0)
719 {
720 errorPrint--;
721 std::cerr << "Cannot get inlet temp\n";
722 }
James Feistbc896df2018-11-26 16:28:17 -0800723 val = 0;
724 return false;
725 }
726
727 // if fans are off, just make the exit temp equal to inlet
James Feist71d31b22019-01-02 16:57:54 -0800728 if (!isPowerOn())
James Feistbc896df2018-11-26 16:28:17 -0800729 {
730 val = inletTemp;
731 return true;
732 }
733
734 double totalPower = 0;
Zev Weiss72f322f2022-08-12 18:21:01 -0700735 for (const auto& [path, reading] : powerReadings)
James Feistbc896df2018-11-26 16:28:17 -0800736 {
Zev Weiss72f322f2022-08-12 18:21:01 -0700737 if (std::isnan(reading))
James Feistbc896df2018-11-26 16:28:17 -0800738 {
739 continue;
740 }
Zev Weiss72f322f2022-08-12 18:21:01 -0700741 totalPower += reading;
James Feistbc896df2018-11-26 16:28:17 -0800742 }
743
744 // Calculate power correction factor
745 // Ci = CL + (CH - CL)/(QMax - QMin) * (CFM - QMin)
Ed Tanous8a57ec02020-10-09 12:46:52 -0700746 double powerFactor = 0.0;
James Feistbc896df2018-11-26 16:28:17 -0800747 if (cfm <= qMin)
748 {
749 powerFactor = powerFactorMin;
750 }
751 else if (cfm >= qMax)
752 {
753 powerFactor = powerFactorMax;
754 }
755 else
756 {
757 powerFactor = powerFactorMin + ((powerFactorMax - powerFactorMin) /
758 (qMax - qMin) * (cfm - qMin));
759 }
760
Ed Tanous8a57ec02020-10-09 12:46:52 -0700761 totalPower *= powerFactor;
James Feistbc896df2018-11-26 16:28:17 -0800762 totalPower += pOffset;
763
764 if (totalPower == 0)
765 {
James Feistae11cfc2019-05-07 15:01:20 -0700766 if (errorPrint > 0)
767 {
768 errorPrint--;
769 std::cerr << "total power 0\n";
770 }
James Feistbc896df2018-11-26 16:28:17 -0800771 val = 0;
772 return false;
773 }
774
Ed Tanous8a57ec02020-10-09 12:46:52 -0700775 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800776 {
777 std::cout << "Power Factor " << powerFactor << "\n";
778 std::cout << "Inlet Temp " << inletTemp << "\n";
779 std::cout << "Total Power" << totalPower << "\n";
780 }
781
782 // Calculate the exit air temp
783 // Texit = Tfp + (1.76 * TotalPower / CFM * Faltitude)
Ed Tanous8a57ec02020-10-09 12:46:52 -0700784 double reading = 1.76 * totalPower * altitudeFactor;
James Feistbc896df2018-11-26 16:28:17 -0800785 reading /= cfm;
786 reading += inletTemp;
787
Ed Tanous8a57ec02020-10-09 12:46:52 -0700788 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800789 {
790 std::cout << "Reading 1: " << reading << "\n";
791 }
792
793 // Now perform the exponential average
794 // Calculate alpha based on SDR values and CFM
795 // Ai = As + (Af - As)/(QMax - QMin) * (CFM - QMin)
796
797 double alpha = 0.0;
798 if (cfm < qMin)
799 {
800 alpha = alphaS;
801 }
802 else if (cfm >= qMax)
803 {
804 alpha = alphaF;
805 }
806 else
807 {
808 alpha = alphaS + ((alphaF - alphaS) * (cfm - qMin) / (qMax - qMin));
809 }
810
Zhikui Ren12e3d672020-12-03 15:14:49 -0800811 auto time = std::chrono::steady_clock::now();
James Feistbc896df2018-11-26 16:28:17 -0800812 if (!firstRead)
813 {
814 firstRead = true;
815 lastTime = time;
816 lastReading = reading;
817 }
818 double alphaDT =
819 std::chrono::duration_cast<std::chrono::seconds>(time - lastTime)
820 .count() *
821 alpha;
822
823 // cap at 1.0 or the below fails
824 if (alphaDT > 1.0)
825 {
826 alphaDT = 1.0;
827 }
828
Ed Tanous8a57ec02020-10-09 12:46:52 -0700829 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800830 {
831 std::cout << "AlphaDT: " << alphaDT << "\n";
832 }
833
834 reading = ((reading * alphaDT) + (lastReading * (1.0 - alphaDT)));
835
Ed Tanous8a57ec02020-10-09 12:46:52 -0700836 if constexpr (debug)
James Feistbc896df2018-11-26 16:28:17 -0800837 {
838 std::cout << "Reading 2: " << reading << "\n";
839 }
840
841 val = reading;
842 lastReading = reading;
843 lastTime = time;
James Feistae11cfc2019-05-07 15:01:20 -0700844 errorPrint = maxErrorPrint;
James Feistbc896df2018-11-26 16:28:17 -0800845 return true;
846}
847
Ed Tanous201a1012024-04-03 18:07:28 -0700848void ExitAirTempSensor::checkThresholds()
James Feistbc896df2018-11-26 16:28:17 -0800849{
850 thresholds::checkThresholds(this);
851}
852
Zev Weissafd15042022-07-18 12:28:40 -0700853static void loadVariantPathArray(const SensorBaseConfigMap& data,
854 const std::string& key,
855 std::vector<std::string>& resp)
James Feistbc896df2018-11-26 16:28:17 -0800856{
857 auto it = data.find(key);
858 if (it == data.end())
859 {
860 std::cerr << "Configuration missing " << key << "\n";
861 throw std::invalid_argument("Key Missing");
862 }
863 BasicVariantType copy = it->second;
James Feist3eb82622019-02-08 13:10:22 -0800864 std::vector<std::string> config = std::get<std::vector<std::string>>(copy);
James Feistbc896df2018-11-26 16:28:17 -0800865 for (auto& str : config)
866 {
867 boost::replace_all(str, " ", "_");
868 }
869 resp = std::move(config);
870}
871
872void createSensor(sdbusplus::asio::object_server& objectServer,
James Feistb2eb3f52018-12-04 16:17:50 -0800873 std::shared_ptr<ExitAirTempSensor>& exitAirSensor,
James Feistbc896df2018-11-26 16:28:17 -0800874 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
875{
876 if (!dbusConnection)
877 {
878 std::cerr << "Connection not created\n";
879 return;
880 }
James Feist655f3762020-10-05 15:28:15 -0700881 auto getter = std::make_shared<GetSensorConfiguration>(
Patrick Williams597e8422023-10-20 11:19:01 -0500882 dbusConnection, [&objectServer, &dbusConnection,
883 &exitAirSensor](const ManagedObjectType& resp) {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400884 cfmSensors.clear();
885 for (const auto& [path, interfaces] : resp)
James Feistbc896df2018-11-26 16:28:17 -0800886 {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400887 for (const auto& [intf, cfg] : interfaces)
James Feistbc896df2018-11-26 16:28:17 -0800888 {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400889 if (intf == configInterfaceName(exitAirType))
890 {
891 // thresholds should be under the same path
892 std::vector<thresholds::Threshold> sensorThresholds;
893 parseThresholdsFromConfig(interfaces, sensorThresholds);
James Feistbc896df2018-11-26 16:28:17 -0800894
Patrick Williams2aaf7172024-08-16 15:20:40 -0400895 std::string name =
896 loadVariant<std::string>(cfg, "Name");
897 exitAirSensor = nullptr;
898 exitAirSensor = std::make_shared<ExitAirTempSensor>(
899 dbusConnection, name, path.str, objectServer,
900 std::move(sensorThresholds));
901 exitAirSensor->powerFactorMin =
902 loadVariant<double>(cfg, "PowerFactorMin");
903 exitAirSensor->powerFactorMax =
904 loadVariant<double>(cfg, "PowerFactorMax");
905 exitAirSensor->qMin = loadVariant<double>(cfg, "QMin");
906 exitAirSensor->qMax = loadVariant<double>(cfg, "QMax");
907 exitAirSensor->alphaS =
908 loadVariant<double>(cfg, "AlphaS");
909 exitAirSensor->alphaF =
910 loadVariant<double>(cfg, "AlphaF");
911 }
912 else if (intf == configInterfaceName(cfmType))
913 {
914 // thresholds should be under the same path
915 std::vector<thresholds::Threshold> sensorThresholds;
916 parseThresholdsFromConfig(interfaces, sensorThresholds);
917 std::string name =
918 loadVariant<std::string>(cfg, "Name");
919 auto sensor = std::make_shared<CFMSensor>(
920 dbusConnection, name, path.str, objectServer,
921 std::move(sensorThresholds), exitAirSensor);
922 loadVariantPathArray(cfg, "Tachs", sensor->tachs);
923 sensor->maxCFM = loadVariant<double>(cfg, "MaxCFM");
James Feistbc896df2018-11-26 16:28:17 -0800924
Patrick Williams2aaf7172024-08-16 15:20:40 -0400925 // change these into percent upon getting the data
926 sensor->c1 = loadVariant<double>(cfg, "C1") / 100;
927 sensor->c2 = loadVariant<double>(cfg, "C2") / 100;
928 sensor->tachMinPercent =
929 loadVariant<double>(cfg, "TachMinPercent");
930 sensor->tachMaxPercent =
931 loadVariant<double>(cfg, "TachMaxPercent");
932 sensor->createMaxCFMIface();
933 sensor->setupMatches();
James Feistbc896df2018-11-26 16:28:17 -0800934
Patrick Williams2aaf7172024-08-16 15:20:40 -0400935 cfmSensors.emplace_back(std::move(sensor));
936 }
James Feistbc896df2018-11-26 16:28:17 -0800937 }
938 }
Patrick Williams2aaf7172024-08-16 15:20:40 -0400939 if (exitAirSensor)
940 {
941 exitAirSensor->setupMatches();
942 exitAirSensor->updateReading();
943 }
944 });
James Feist655f3762020-10-05 15:28:15 -0700945 getter->getConfiguration(
Zev Weiss054aad82022-08-18 01:37:34 -0700946 std::vector<std::string>(monitorTypes.begin(), monitorTypes.end()));
James Feistbc896df2018-11-26 16:28:17 -0800947}
948
James Feistb6c0b912019-07-09 12:21:44 -0700949int main()
James Feistbc896df2018-11-26 16:28:17 -0800950{
Ed Tanous1f978632023-02-28 18:16:39 -0800951 boost::asio::io_context io;
James Feistbc896df2018-11-26 16:28:17 -0800952 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
Johnathan Mantey661d4372022-10-27 09:00:59 -0700953 sdbusplus::asio::object_server objectServer(systemBus, true);
954 objectServer.add_manager("/xyz/openbmc_project/sensors");
James Feistbc896df2018-11-26 16:28:17 -0800955 systemBus->request_name("xyz.openbmc_project.ExitAirTempSensor");
James Feistbc896df2018-11-26 16:28:17 -0800956 std::shared_ptr<ExitAirTempSensor> sensor =
957 nullptr; // wait until we find the config
James Feistbc896df2018-11-26 16:28:17 -0800958
Ed Tanous83db50c2023-03-01 10:20:24 -0800959 boost::asio::post(io,
960 [&]() { createSensor(objectServer, sensor, systemBus); });
James Feistbc896df2018-11-26 16:28:17 -0800961
Ed Tanous9b4a20e2022-09-06 08:47:11 -0700962 boost::asio::steady_timer configTimer(io);
James Feistbc896df2018-11-26 16:28:17 -0800963
Patrick Williams92f8f512022-07-22 19:26:55 -0500964 std::function<void(sdbusplus::message_t&)> eventHandler =
965 [&](sdbusplus::message_t&) {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400966 configTimer.expires_after(std::chrono::seconds(1));
967 // create a timer because normally multiple properties change
968 configTimer.async_wait([&](const boost::system::error_code& ec) {
969 if (ec == boost::asio::error::operation_aborted)
970 {
971 return; // we're being canceled
972 }
973 createSensor(objectServer, sensor, systemBus);
974 if (!sensor)
975 {
976 std::cout << "Configuration not detected\n";
977 }
978 });
979 };
Zev Weiss214d9712022-08-12 12:54:31 -0700980 std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
Zev Weiss054aad82022-08-18 01:37:34 -0700981 setupPropertiesChangedMatches(*systemBus, monitorTypes, eventHandler);
James Feistbc896df2018-11-26 16:28:17 -0800982
Bruce Lee913d4d02021-07-22 10:18:42 +0800983 setupManufacturingModeMatch(*systemBus);
James Feistbc896df2018-11-26 16:28:17 -0800984 io.run();
Zhikui Ren8685b172021-06-29 15:16:52 -0700985 return 0;
James Feistbc896df2018-11-26 16:28:17 -0800986}