blob: 40650f6075dc99f99d6c6eef40079c868ffbd0a0 [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>
George Liuce1abb62025-02-20 10:44:47 +080031#include <phosphor-logging/lg2.hpp>
James Feist38fb5982020-05-28 10:09:54 -070032#include <sdbusplus/asio/connection.hpp>
33#include <sdbusplus/asio/object_server.hpp>
Ed Tanouseacbfdd2024-04-04 12:00:24 -070034#include <sdbusplus/bus.hpp>
James Feist38fb5982020-05-28 10:09:54 -070035#include <sdbusplus/bus/match.hpp>
Ed Tanouseacbfdd2024-04-04 12:00:24 -070036#include <sdbusplus/message.hpp>
James Feist38fb5982020-05-28 10:09:54 -070037
Ed Tanouseacbfdd2024-04-04 12:00:24 -070038#include <algorithm>
James Feist38fb5982020-05-28 10:09:54 -070039#include <array>
James Feistbc896df2018-11-26 16:28:17 -080040#include <chrono>
Patrick Venture96e97db2019-10-31 13:44:38 -070041#include <cmath>
Ed Tanouseacbfdd2024-04-04 12:00:24 -070042#include <cstddef>
43#include <cstdint>
Patrick Venture96e97db2019-10-31 13:44:38 -070044#include <functional>
James Feistbc896df2018-11-26 16:28:17 -080045#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
James Feistb2eb3f52018-12-04 16:17:50 -080065static constexpr double cfmMaxReading = 255;
66static constexpr double cfmMinReading = 0;
67
James Feist13452092019-03-07 16:38:12 -080068static constexpr size_t minSystemCfm = 50;
69
Zev Weiss054aad82022-08-18 01:37:34 -070070constexpr const auto monitorTypes{
71 std::to_array<const char*>({exitAirType, cfmType})};
James Feist655f3762020-10-05 15:28:15 -070072
James Feist9a25ed42019-10-15 15:43:44 -070073static std::vector<std::shared_ptr<CFMSensor>> cfmSensors;
74
James Feistb2eb3f52018-12-04 16:17:50 -080075static void setupSensorMatch(
Patrick Williams92f8f512022-07-22 19:26:55 -050076 std::vector<sdbusplus::bus::match_t>& matches, sdbusplus::bus_t& connection,
77 const std::string& type,
78 std::function<void(const double&, sdbusplus::message_t&)>&& callback)
James Feistb2eb3f52018-12-04 16:17:50 -080079{
Patrick Williams92f8f512022-07-22 19:26:55 -050080 std::function<void(sdbusplus::message_t & message)> eventHandler =
81 [callback{std::move(callback)}](sdbusplus::message_t& message) {
Patrick Williams2aaf7172024-08-16 15:20:40 -040082 std::string objectName;
83 boost::container::flat_map<std::string,
84 std::variant<double, int64_t>>
85 values;
86 message.read(objectName, values);
87 auto findValue = values.find("Value");
88 if (findValue == values.end())
89 {
90 return;
91 }
92 double value =
93 std::visit(VariantToDoubleVisitor(), findValue->second);
94 if (std::isnan(value))
95 {
96 return;
97 }
James Feist9566bfa2019-01-29 15:31:23 -080098
Patrick Williams2aaf7172024-08-16 15:20:40 -040099 callback(value, message);
100 };
101 matches.emplace_back(
102 connection,
103 "type='signal',"
104 "member='PropertiesChanged',interface='org."
105 "freedesktop.DBus.Properties',path_"
106 "namespace='/xyz/openbmc_project/sensors/" +
107 std::string(type) + "',arg0='xyz.openbmc_project.Sensor.Value'",
108 std::move(eventHandler));
James Feistb2eb3f52018-12-04 16:17:50 -0800109}
110
James Feist13452092019-03-07 16:38:12 -0800111static void setMaxPWM(const std::shared_ptr<sdbusplus::asio::connection>& conn,
112 double value)
113{
James Feist13452092019-03-07 16:38:12 -0800114 conn->async_method_call(
Patrick Williams2aaf7172024-08-16 15:20:40 -0400115 [conn,
116 value](const boost::system::error_code ec, const GetSubTreeType& ret) {
117 if (ec)
James Feist13452092019-03-07 16:38:12 -0800118 {
George Liuce1abb62025-02-20 10:44:47 +0800119 lg2::error("Error calling mapper");
James Feist13452092019-03-07 16:38:12 -0800120 return;
121 }
Patrick Williams2aaf7172024-08-16 15:20:40 -0400122 for (const auto& [path, objDict] : ret)
123 {
124 if (objDict.empty())
125 {
126 return;
127 }
128 const std::string& owner = objDict.begin()->first;
Ed Tanousbb679322022-05-16 16:10:00 -0700129
James Feist13452092019-03-07 16:38:12 -0800130 conn->async_method_call(
Patrick Williams2aaf7172024-08-16 15:20:40 -0400131 [conn, value, owner,
132 path{path}](const boost::system::error_code ec,
133 const std::variant<std::string>& classType) {
134 if (ec)
135 {
George Liuce1abb62025-02-20 10:44:47 +0800136 lg2::error("Error getting pid class");
Patrick Williams2aaf7172024-08-16 15:20:40 -0400137 return;
138 }
139 const auto* classStr =
140 std::get_if<std::string>(&classType);
141 if (classStr == nullptr || *classStr != "fan")
142 {
143 return;
144 }
145 conn->async_method_call(
146 [](boost::system::error_code& ec) {
147 if (ec)
148 {
George Liuce1abb62025-02-20 10:44:47 +0800149 lg2::error("Error setting pid class");
Patrick Williams2aaf7172024-08-16 15:20:40 -0400150 return;
151 }
152 },
153 owner, path, "org.freedesktop.DBus.Properties",
154 "Set", pidConfigurationType, "OutLimitMax",
155 std::variant<double>(value));
156 },
157 owner, path, "org.freedesktop.DBus.Properties", "Get",
158 pidConfigurationType, "Class");
159 }
160 },
James Feista5e58722019-04-22 14:43:11 -0700161 mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
162 0, std::array<std::string, 1>{pidConfigurationType});
James Feist13452092019-03-07 16:38:12 -0800163}
164
James Feistb2eb3f52018-12-04 16:17:50 -0800165CFMSensor::CFMSensor(std::shared_ptr<sdbusplus::asio::connection>& conn,
166 const std::string& sensorName,
167 const std::string& sensorConfiguration,
168 sdbusplus::asio::object_server& objectServer,
James Feistb839c052019-05-15 10:25:24 -0700169 std::vector<thresholds::Threshold>&& thresholdData,
James Feistb2eb3f52018-12-04 16:17:50 -0800170 std::shared_ptr<ExitAirTempSensor>& parent) :
Zhikui Renda98f092021-11-01 09:41:08 -0700171 Sensor(escapeName(sensorName), std::move(thresholdData),
Zev Weiss054aad82022-08-18 01:37:34 -0700172 sensorConfiguration, "CFMSensor", false, false, cfmMaxReading,
173 cfmMinReading, conn, PowerState::on),
Ed Tanous2049bd22022-07-09 07:20:26 -0700174 parent(parent), objServer(objectServer)
James Feistb2eb3f52018-12-04 16:17:50 -0800175{
Basheer Ahmed Muddebihale5b867b2021-07-26 08:32:19 -0700176 sensorInterface = objectServer.add_interface(
177 "/xyz/openbmc_project/sensors/airflow/" + name,
178 "xyz.openbmc_project.Sensor.Value");
James Feistb2eb3f52018-12-04 16:17:50 -0800179
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530180 for (const auto& threshold : thresholds)
James Feistb2eb3f52018-12-04 16:17:50 -0800181 {
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530182 std::string interface = thresholds::getInterface(threshold.level);
183 thresholdInterfaces[static_cast<size_t>(threshold.level)] =
184 objectServer.add_interface(
185 "/xyz/openbmc_project/sensors/airflow/" + name, interface);
James Feistb2eb3f52018-12-04 16:17:50 -0800186 }
James Feist078f2322019-03-08 11:09:05 -0800187
188 association = objectServer.add_interface(
Basheer Ahmed Muddebihale5b867b2021-07-26 08:32:19 -0700189 "/xyz/openbmc_project/sensors/airflow/" + name, association::interface);
James Feist078f2322019-03-08 11:09:05 -0800190
Andrei Kartashev39287412022-02-04 16:04:47 +0300191 setInitialProperties(sensor_paths::unitCFM);
James Feist9a25ed42019-10-15 15:43:44 -0700192
James Feist13452092019-03-07 16:38:12 -0800193 pwmLimitIface =
194 objectServer.add_interface("/xyz/openbmc_project/control/pwm_limit",
195 "xyz.openbmc_project.Control.PWMLimit");
196 cfmLimitIface =
197 objectServer.add_interface("/xyz/openbmc_project/control/MaxCFM",
198 "xyz.openbmc_project.Control.CFMLimit");
James Feist9a25ed42019-10-15 15:43:44 -0700199}
James Feist13452092019-03-07 16:38:12 -0800200
James Feist9a25ed42019-10-15 15:43:44 -0700201void CFMSensor::setupMatches()
202{
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700203 std::weak_ptr<CFMSensor> weakRef = weak_from_this();
Ed Tanous8a17c302021-09-02 15:07:11 -0700204 setupSensorMatch(
205 matches, *dbusConnection, "fan_tach",
Patrick Williams92f8f512022-07-22 19:26:55 -0500206 [weakRef](const double& value, sdbusplus::message_t& message) {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400207 auto self = weakRef.lock();
208 if (!self)
209 {
210 return;
211 }
212 self->tachReadings[message.get_path()] = value;
213 if (self->tachRanges.find(message.get_path()) ==
214 self->tachRanges.end())
215 {
216 // calls update reading after updating ranges
217 self->addTachRanges(message.get_sender(), message.get_path());
218 }
219 else
220 {
221 self->updateReading();
222 }
223 });
James Feist9a25ed42019-10-15 15:43:44 -0700224
225 dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700226 [weakRef](const boost::system::error_code ec,
227 const std::variant<double> cfmVariant) {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400228 auto self = weakRef.lock();
229 if (!self)
Ed Tanousbb679322022-05-16 16:10:00 -0700230 {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400231 return;
James Feist13452092019-03-07 16:38:12 -0800232 }
Patrick Williams2aaf7172024-08-16 15:20:40 -0400233
234 uint64_t maxRpm = 100;
235 if (!ec)
236 {
237 const auto* cfm = std::get_if<double>(&cfmVariant);
238 if (cfm != nullptr && *cfm >= minSystemCfm)
239 {
240 maxRpm = self->getMaxRpm(*cfm);
241 }
242 }
243 self->pwmLimitIface->register_property("Limit", maxRpm);
244 self->pwmLimitIface->initialize();
245 setMaxPWM(self->dbusConnection, maxRpm);
246 },
James Feist13452092019-03-07 16:38:12 -0800247 settingsDaemon, cfmSettingPath, "org.freedesktop.DBus.Properties",
248 "Get", cfmSettingIface, "Limit");
249
Patrick Williams2aaf7172024-08-16 15:20:40 -0400250 matches.emplace_back(
251 *dbusConnection,
252 "type='signal',"
253 "member='PropertiesChanged',interface='org."
254 "freedesktop.DBus.Properties',path='" +
255 std::string(cfmSettingPath) + "',arg0='" +
256 std::string(cfmSettingIface) + "'",
257 [weakRef](sdbusplus::message_t& message) {
258 auto self = weakRef.lock();
259 if (!self)
260 {
261 return;
262 }
263 boost::container::flat_map<std::string, std::variant<double>>
264 values;
265 std::string objectName;
266 message.read(objectName, values);
267 const auto findValue = values.find("Limit");
268 if (findValue == values.end())
269 {
270 return;
271 }
272 auto* const reading = std::get_if<double>(&(findValue->second));
273 if (reading == nullptr)
274 {
George Liuce1abb62025-02-20 10:44:47 +0800275 lg2::error("Got CFM Limit of wrong type");
Patrick Williams2aaf7172024-08-16 15:20:40 -0400276 return;
277 }
278 if (*reading < minSystemCfm && *reading != 0)
279 {
George Liuce1abb62025-02-20 10:44:47 +0800280 lg2::error("Illegal CFM setting detected");
Patrick Williams2aaf7172024-08-16 15:20:40 -0400281 return;
282 }
283 uint64_t maxRpm = self->getMaxRpm(*reading);
284 self->pwmLimitIface->set_property("Limit", maxRpm);
285 setMaxPWM(self->dbusConnection, maxRpm);
286 });
James Feistb2eb3f52018-12-04 16:17:50 -0800287}
288
James Feist9566bfa2019-01-29 15:31:23 -0800289CFMSensor::~CFMSensor()
290{
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530291 for (const auto& iface : thresholdInterfaces)
292 {
293 objServer.remove_interface(iface);
294 }
James Feist9566bfa2019-01-29 15:31:23 -0800295 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800296 objServer.remove_interface(association);
James Feist13452092019-03-07 16:38:12 -0800297 objServer.remove_interface(cfmLimitIface);
298 objServer.remove_interface(pwmLimitIface);
299}
300
Ed Tanous201a1012024-04-03 18:07:28 -0700301void CFMSensor::createMaxCFMIface()
James Feist13452092019-03-07 16:38:12 -0800302{
James Feistb6c0b912019-07-09 12:21:44 -0700303 cfmLimitIface->register_property("Limit", c2 * maxCFM * tachs.size());
James Feist13452092019-03-07 16:38:12 -0800304 cfmLimitIface->initialize();
James Feist9566bfa2019-01-29 15:31:23 -0800305}
306
James Feistb2eb3f52018-12-04 16:17:50 -0800307void CFMSensor::addTachRanges(const std::string& serviceName,
308 const std::string& path)
309{
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700310 std::weak_ptr<CFMSensor> weakRef = weak_from_this();
James Feistb2eb3f52018-12-04 16:17:50 -0800311 dbusConnection->async_method_call(
Zev Weissafd15042022-07-18 12:28:40 -0700312 [weakRef, path](const boost::system::error_code ec,
313 const SensorBaseConfigMap& data) {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400314 if (ec)
315 {
George Liuce1abb62025-02-20 10:44:47 +0800316 lg2::error("Error getting properties from '{PATH}'", "PATH",
317 path);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400318 return;
319 }
320 auto self = weakRef.lock();
321 if (!self)
322 {
323 return;
324 }
325 double max = loadVariant<double>(data, "MaxValue");
326 double min = loadVariant<double>(data, "MinValue");
327 self->tachRanges[path] = std::make_pair(min, max);
328 self->updateReading();
329 },
James Feistb2eb3f52018-12-04 16:17:50 -0800330 serviceName, path, "org.freedesktop.DBus.Properties", "GetAll",
331 "xyz.openbmc_project.Sensor.Value");
332}
333
Ed Tanous201a1012024-04-03 18:07:28 -0700334void CFMSensor::checkThresholds()
James Feistb2eb3f52018-12-04 16:17:50 -0800335{
336 thresholds::checkThresholds(this);
337}
338
Ed Tanous201a1012024-04-03 18:07:28 -0700339void CFMSensor::updateReading()
James Feistb2eb3f52018-12-04 16:17:50 -0800340{
341 double val = 0.0;
342 if (calculate(val))
343 {
344 if (value != val && parent)
345 {
346 parent->updateReading();
347 }
348 updateValue(val);
349 }
350 else
351 {
352 updateValue(std::numeric_limits<double>::quiet_NaN());
353 }
354}
355
Ed Tanous2049bd22022-07-09 07:20:26 -0700356uint64_t CFMSensor::getMaxRpm(uint64_t cfmMaxSetting) const
James Feist13452092019-03-07 16:38:12 -0800357{
358 uint64_t pwmPercent = 100;
359 double totalCFM = std::numeric_limits<double>::max();
360 if (cfmMaxSetting == 0)
361 {
362 return pwmPercent;
363 }
364
James Feist52427952019-04-05 14:23:35 -0700365 bool firstLoop = true;
James Feist13452092019-03-07 16:38:12 -0800366 while (totalCFM > cfmMaxSetting)
367 {
James Feist52427952019-04-05 14:23:35 -0700368 if (firstLoop)
369 {
370 firstLoop = false;
371 }
372 else
373 {
374 pwmPercent--;
375 }
376
James Feist13452092019-03-07 16:38:12 -0800377 double ci = 0;
378 if (pwmPercent == 0)
379 {
380 ci = 0;
381 }
382 else if (pwmPercent < tachMinPercent)
383 {
384 ci = c1;
385 }
386 else if (pwmPercent > tachMaxPercent)
387 {
388 ci = c2;
389 }
390 else
391 {
392 ci = c1 + (((c2 - c1) * (pwmPercent - tachMinPercent)) /
393 (tachMaxPercent - tachMinPercent));
394 }
395
396 // Now calculate the CFM for this tach
397 // CFMi = Ci * Qmaxi * TACHi
398 totalCFM = ci * maxCFM * pwmPercent;
399 totalCFM *= tachs.size();
400 // divide by 100 since pwm is in percent
401 totalCFM /= 100;
402
James Feist13452092019-03-07 16:38:12 -0800403 if (pwmPercent <= 0)
404 {
405 break;
406 }
407 }
James Feist52427952019-04-05 14:23:35 -0700408
James Feist13452092019-03-07 16:38:12 -0800409 return pwmPercent;
410}
411
James Feistb2eb3f52018-12-04 16:17:50 -0800412bool CFMSensor::calculate(double& value)
413{
414 double totalCFM = 0;
415 for (const std::string& tachName : tachs)
416 {
417 auto findReading = std::find_if(
Zev Weiss6c106d62022-08-17 20:50:00 -0700418 tachReadings.begin(), tachReadings.end(),
419 [&](const auto& item) { return item.first.ends_with(tachName); });
Patrick Williams597e8422023-10-20 11:19:01 -0500420 auto findRange = std::find_if(
421 tachRanges.begin(), tachRanges.end(),
422 [&](const auto& item) { return item.first.ends_with(tachName); });
James Feistb2eb3f52018-12-04 16:17:50 -0800423 if (findReading == tachReadings.end())
424 {
Alexander Hansen89be6142025-09-18 15:36:16 +0200425 lg2::debug("Can't find '{NAME}' in readings", "NAME", tachName);
James Feist9566bfa2019-01-29 15:31:23 -0800426 continue; // haven't gotten a reading
James Feistb2eb3f52018-12-04 16:17:50 -0800427 }
428
429 if (findRange == tachRanges.end())
430 {
George Liuce1abb62025-02-20 10:44:47 +0800431 lg2::error("Can't find '{NAME}' in ranges", "NAME", tachName);
James Feistb2eb3f52018-12-04 16:17:50 -0800432 return false; // haven't gotten a max / min
433 }
434
435 // avoid divide by 0
436 if (findRange->second.second == 0)
437 {
George Liuce1abb62025-02-20 10:44:47 +0800438 lg2::error("Tach Max Set to 0, tachName: '{NAME}'", "NAME",
439 tachName);
James Feistb2eb3f52018-12-04 16:17:50 -0800440 return false;
441 }
442
443 double rpm = findReading->second;
444
445 // for now assume the min for a fan is always 0, divide by max to get
446 // percent and mult by 100
447 rpm /= findRange->second.second;
448 rpm *= 100;
449
Alexander Hansen89be6142025-09-18 15:36:16 +0200450 lg2::debug("Tach '{NAME}' at '{RPM}'", "NAME", tachName, "RPM", rpm);
James Feistb2eb3f52018-12-04 16:17:50 -0800451
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;
Alexander Hansen89be6142025-09-18 15:36:16 +0200477 lg2::debug(
478 "totalCFM = {CFM}, Ci = {CI}, MaxCFM = {MAXCFM}, rpm = {RPM}, c1 = {C1}"
479 ", c2 = {C2}, max = {MAX}, min = {MIN}",
480 "CFM", totalCFM, "CI", ci, "MAXCFM", maxCFM, "RPM", rpm, "C1", c1,
481 "C2", c2, "MAX", tachMaxPercent, "MIN", tachMinPercent);
James Feistb2eb3f52018-12-04 16:17:50 -0800482 }
483
484 // divide by 100 since rpm is in percent
485 value = totalCFM / 100;
Alexander Hansen89be6142025-09-18 15:36:16 +0200486 lg2::debug("cfm value = {VALUE}", "VALUE", value);
James Feist9566bfa2019-01-29 15:31:23 -0800487 return true;
James Feistb2eb3f52018-12-04 16:17:50 -0800488}
489
490static constexpr double exitAirMaxReading = 127;
491static constexpr double exitAirMinReading = -128;
James Feistbc896df2018-11-26 16:28:17 -0800492ExitAirTempSensor::ExitAirTempSensor(
493 std::shared_ptr<sdbusplus::asio::connection>& conn,
James Feistb2eb3f52018-12-04 16:17:50 -0800494 const std::string& sensorName, const std::string& sensorConfiguration,
James Feistbc896df2018-11-26 16:28:17 -0800495 sdbusplus::asio::object_server& objectServer,
James Feistb839c052019-05-15 10:25:24 -0700496 std::vector<thresholds::Threshold>&& thresholdData) :
Zhikui Renda98f092021-11-01 09:41:08 -0700497 Sensor(escapeName(sensorName), std::move(thresholdData),
Zev Weiss054aad82022-08-18 01:37:34 -0700498 sensorConfiguration, "ExitAirTemp", false, false, exitAirMaxReading,
499 exitAirMinReading, conn, PowerState::on),
Ed Tanous2049bd22022-07-09 07:20:26 -0700500 objServer(objectServer)
James Feistbc896df2018-11-26 16:28:17 -0800501{
502 sensorInterface = objectServer.add_interface(
503 "/xyz/openbmc_project/sensors/temperature/" + name,
504 "xyz.openbmc_project.Sensor.Value");
505
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530506 for (const auto& threshold : thresholds)
James Feistbc896df2018-11-26 16:28:17 -0800507 {
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530508 std::string interface = thresholds::getInterface(threshold.level);
509 thresholdInterfaces[static_cast<size_t>(threshold.level)] =
510 objectServer.add_interface(
511 "/xyz/openbmc_project/sensors/temperature/" + name, interface);
James Feistbc896df2018-11-26 16:28:17 -0800512 }
James Feist078f2322019-03-08 11:09:05 -0800513 association = objectServer.add_interface(
514 "/xyz/openbmc_project/sensors/temperature/" + name,
James Feist2adc95c2019-09-30 14:55:28 -0700515 association::interface);
Andrei Kartashev39287412022-02-04 16:04:47 +0300516 setInitialProperties(sensor_paths::unitDegreesC);
James Feistbc896df2018-11-26 16:28:17 -0800517}
518
519ExitAirTempSensor::~ExitAirTempSensor()
520{
Jayashree Dhanapal56678082022-01-04 17:27:20 +0530521 for (const auto& iface : thresholdInterfaces)
522 {
523 objServer.remove_interface(iface);
524 }
James Feist523828e2019-03-04 14:38:37 -0800525 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800526 objServer.remove_interface(association);
James Feistbc896df2018-11-26 16:28:17 -0800527}
528
Ed Tanous201a1012024-04-03 18:07:28 -0700529void ExitAirTempSensor::setupMatches()
James Feistbc896df2018-11-26 16:28:17 -0800530{
Brandon Kim66558232021-11-09 16:53:08 -0800531 constexpr const auto matchTypes{
532 std::to_array<const char*>({"power", inletTemperatureSensor})};
James Feistbc896df2018-11-26 16:28:17 -0800533
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700534 std::weak_ptr<ExitAirTempSensor> weakRef = weak_from_this();
Ed Tanous13b63f82021-05-11 16:12:52 -0700535 for (const std::string type : matchTypes)
James Feistbc896df2018-11-26 16:28:17 -0800536 {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400537 setupSensorMatch(
538 matches, *dbusConnection, type,
539 [weakRef,
540 type](const double& value, sdbusplus::message_t& message) {
541 auto self = weakRef.lock();
542 if (!self)
543 {
544 return;
545 }
546 if (type == "power")
547 {
548 std::string path = message.get_path();
549 if (path.find("PS") != std::string::npos &&
550 path.ends_with("Input_Power"))
551 {
552 self->powerReadings[message.get_path()] = value;
553 }
554 }
555 else if (type == inletTemperatureSensor)
556 {
557 self->inletTemp = value;
558 }
559 self->updateReading();
560 });
561 }
562 dbusConnection->async_method_call(
563 [weakRef](boost::system::error_code ec,
564 const std::variant<double>& value) {
565 if (ec)
566 {
567 // sensor not ready yet
568 return;
569 }
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700570 auto self = weakRef.lock();
571 if (!self)
572 {
573 return;
574 }
Patrick Williams2aaf7172024-08-16 15:20:40 -0400575 self->inletTemp = std::visit(VariantToDoubleVisitor(), value);
576 },
James Feist9566bfa2019-01-29 15:31:23 -0800577 "xyz.openbmc_project.HwmonTempSensor",
578 std::string("/xyz/openbmc_project/sensors/") + inletTemperatureSensor,
James Feista5e58722019-04-22 14:43:11 -0700579 properties::interface, properties::get, sensorValueInterface, "Value");
580 dbusConnection->async_method_call(
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700581 [weakRef](boost::system::error_code ec, const GetSubTreeType& subtree) {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400582 if (ec)
James Feista5e58722019-04-22 14:43:11 -0700583 {
George Liuce1abb62025-02-20 10:44:47 +0800584 lg2::error("Error contacting mapper");
Patrick Williams2aaf7172024-08-16 15:20:40 -0400585 return;
James Feista5e58722019-04-22 14:43:11 -0700586 }
Patrick Williams2aaf7172024-08-16 15:20:40 -0400587 auto self = weakRef.lock();
588 if (!self)
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700589 {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400590 return;
Zhikui Rendbb73aa2021-04-02 13:39:04 -0700591 }
Patrick Williams2aaf7172024-08-16 15:20:40 -0400592 for (const auto& [path, matches] : subtree)
593 {
594 size_t lastSlash = path.rfind('/');
595 if (lastSlash == std::string::npos ||
596 lastSlash == path.size() || matches.empty())
597 {
598 continue;
599 }
600 std::string sensorName = path.substr(lastSlash + 1);
601 if (sensorName.starts_with("PS") &&
602 sensorName.ends_with("Input_Power"))
603 {
604 // lambda capture requires a proper variable (not a
605 // structured binding)
606 const std::string& cbPath = path;
607 self->dbusConnection->async_method_call(
608 [weakRef, cbPath](boost::system::error_code ec,
609 const std::variant<double>& value) {
610 if (ec)
611 {
George Liuce1abb62025-02-20 10:44:47 +0800612 lg2::error("Error getting value from '{PATH}'",
613 "PATH", cbPath);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400614 }
615 auto self = weakRef.lock();
616 if (!self)
617 {
618 return;
619 }
620 double reading =
621 std::visit(VariantToDoubleVisitor(), value);
Alexander Hansen89be6142025-09-18 15:36:16 +0200622 lg2::debug("'{PATH}' reading '{VALUE}'", "PATH",
623 cbPath, "VALUE", reading);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400624 self->powerReadings[cbPath] = reading;
625 },
626 matches[0].first, cbPath, properties::interface,
627 properties::get, sensorValueInterface, "Value");
628 }
629 }
630 },
James Feista5e58722019-04-22 14:43:11 -0700631 mapper::busName, mapper::path, mapper::interface, mapper::subtree,
632 "/xyz/openbmc_project/sensors/power", 0,
633 std::array<const char*, 1>{sensorValueInterface});
James Feistbc896df2018-11-26 16:28:17 -0800634}
635
Ed Tanous201a1012024-04-03 18:07:28 -0700636void ExitAirTempSensor::updateReading()
James Feistbc896df2018-11-26 16:28:17 -0800637{
James Feistbc896df2018-11-26 16:28:17 -0800638 double val = 0.0;
639 if (calculate(val))
640 {
James Feist18af4232019-03-13 11:14:00 -0700641 val = std::floor(val + 0.5);
James Feistbc896df2018-11-26 16:28:17 -0800642 updateValue(val);
643 }
644 else
645 {
646 updateValue(std::numeric_limits<double>::quiet_NaN());
647 }
648}
649
Ed Tanous201a1012024-04-03 18:07:28 -0700650double ExitAirTempSensor::getTotalCFM()
James Feistbc896df2018-11-26 16:28:17 -0800651{
James Feistb2eb3f52018-12-04 16:17:50 -0800652 double sum = 0;
653 for (auto& sensor : cfmSensors)
James Feistbc896df2018-11-26 16:28:17 -0800654 {
James Feistb2eb3f52018-12-04 16:17:50 -0800655 double reading = 0;
656 if (!sensor->calculate(reading))
James Feistbc896df2018-11-26 16:28:17 -0800657 {
James Feistbc896df2018-11-26 16:28:17 -0800658 return -1;
659 }
James Feistb2eb3f52018-12-04 16:17:50 -0800660 sum += reading;
James Feistbc896df2018-11-26 16:28:17 -0800661 }
James Feistb2eb3f52018-12-04 16:17:50 -0800662
663 return sum;
James Feistbc896df2018-11-26 16:28:17 -0800664}
665
666bool ExitAirTempSensor::calculate(double& val)
667{
Zhikui Ren12e3d672020-12-03 15:14:49 -0800668 constexpr size_t maxErrorPrint = 5;
James Feistbc896df2018-11-26 16:28:17 -0800669 static bool firstRead = false;
James Feistae11cfc2019-05-07 15:01:20 -0700670 static size_t errorPrint = maxErrorPrint;
671
James Feistbc896df2018-11-26 16:28:17 -0800672 double cfm = getTotalCFM();
673 if (cfm <= 0)
674 {
George Liuce1abb62025-02-20 10:44:47 +0800675 lg2::error("Error getting cfm");
James Feistbc896df2018-11-26 16:28:17 -0800676 return false;
677 }
678
Zhikui Ren12e3d672020-12-03 15:14:49 -0800679 // Though cfm is not expected to be less than qMin normally,
680 // it is not a hard limit for exit air temp calculation.
681 // 50% qMin is chosen as a generic limit between providing
682 // a valid derived exit air temp and reporting exit air temp not available.
683 constexpr const double cfmLimitFactor = 0.5;
684 if (cfm < (qMin * cfmLimitFactor))
685 {
686 if (errorPrint > 0)
687 {
688 errorPrint--;
George Liuce1abb62025-02-20 10:44:47 +0800689 lg2::error("cfm '{CFM}' is too low, expected qMin '{QMIN}'", "CFM",
690 cfm, "QMIN", qMin);
Zhikui Ren12e3d672020-12-03 15:14:49 -0800691 }
692 val = 0;
693 return false;
694 }
695
James Feistbc896df2018-11-26 16:28:17 -0800696 // if there is an error getting inlet temp, return error
697 if (std::isnan(inletTemp))
698 {
James Feistae11cfc2019-05-07 15:01:20 -0700699 if (errorPrint > 0)
700 {
701 errorPrint--;
George Liuce1abb62025-02-20 10:44:47 +0800702 lg2::error("Cannot get inlet temp");
James Feistae11cfc2019-05-07 15:01:20 -0700703 }
James Feistbc896df2018-11-26 16:28:17 -0800704 val = 0;
705 return false;
706 }
707
708 // if fans are off, just make the exit temp equal to inlet
James Feist71d31b22019-01-02 16:57:54 -0800709 if (!isPowerOn())
James Feistbc896df2018-11-26 16:28:17 -0800710 {
711 val = inletTemp;
712 return true;
713 }
714
715 double totalPower = 0;
Zev Weiss72f322f2022-08-12 18:21:01 -0700716 for (const auto& [path, reading] : powerReadings)
James Feistbc896df2018-11-26 16:28:17 -0800717 {
Zev Weiss72f322f2022-08-12 18:21:01 -0700718 if (std::isnan(reading))
James Feistbc896df2018-11-26 16:28:17 -0800719 {
720 continue;
721 }
Zev Weiss72f322f2022-08-12 18:21:01 -0700722 totalPower += reading;
James Feistbc896df2018-11-26 16:28:17 -0800723 }
724
725 // Calculate power correction factor
726 // Ci = CL + (CH - CL)/(QMax - QMin) * (CFM - QMin)
Ed Tanous8a57ec02020-10-09 12:46:52 -0700727 double powerFactor = 0.0;
James Feistbc896df2018-11-26 16:28:17 -0800728 if (cfm <= qMin)
729 {
730 powerFactor = powerFactorMin;
731 }
732 else if (cfm >= qMax)
733 {
734 powerFactor = powerFactorMax;
735 }
736 else
737 {
738 powerFactor = powerFactorMin + ((powerFactorMax - powerFactorMin) /
739 (qMax - qMin) * (cfm - qMin));
740 }
741
Ed Tanous8a57ec02020-10-09 12:46:52 -0700742 totalPower *= powerFactor;
James Feistbc896df2018-11-26 16:28:17 -0800743 totalPower += pOffset;
744
745 if (totalPower == 0)
746 {
James Feistae11cfc2019-05-07 15:01:20 -0700747 if (errorPrint > 0)
748 {
749 errorPrint--;
George Liuce1abb62025-02-20 10:44:47 +0800750 lg2::error("total power 0");
James Feistae11cfc2019-05-07 15:01:20 -0700751 }
James Feistbc896df2018-11-26 16:28:17 -0800752 val = 0;
753 return false;
754 }
755
Alexander Hansen89be6142025-09-18 15:36:16 +0200756 lg2::debug(
757 "Power Factor: {POWER_FACTOR}, Inlet Temp: {INLET_TEMP}, Total Power: {TOTAL_POWER}",
758 "POWER_FACTOR", powerFactor, "INLET_TEMP", inletTemp, "TOTAL_POWER",
759 totalPower);
James Feistbc896df2018-11-26 16:28:17 -0800760
761 // Calculate the exit air temp
762 // Texit = Tfp + (1.76 * TotalPower / CFM * Faltitude)
Ed Tanous8a57ec02020-10-09 12:46:52 -0700763 double reading = 1.76 * totalPower * altitudeFactor;
James Feistbc896df2018-11-26 16:28:17 -0800764 reading /= cfm;
765 reading += inletTemp;
766
Alexander Hansen89be6142025-09-18 15:36:16 +0200767 lg2::debug("Reading 1: '{VALUE}'", "VALUE", reading);
James Feistbc896df2018-11-26 16:28:17 -0800768
769 // Now perform the exponential average
770 // Calculate alpha based on SDR values and CFM
771 // Ai = As + (Af - As)/(QMax - QMin) * (CFM - QMin)
772
773 double alpha = 0.0;
774 if (cfm < qMin)
775 {
776 alpha = alphaS;
777 }
778 else if (cfm >= qMax)
779 {
780 alpha = alphaF;
781 }
782 else
783 {
784 alpha = alphaS + ((alphaF - alphaS) * (cfm - qMin) / (qMax - qMin));
785 }
786
Zhikui Ren12e3d672020-12-03 15:14:49 -0800787 auto time = std::chrono::steady_clock::now();
James Feistbc896df2018-11-26 16:28:17 -0800788 if (!firstRead)
789 {
790 firstRead = true;
791 lastTime = time;
792 lastReading = reading;
793 }
794 double alphaDT =
795 std::chrono::duration_cast<std::chrono::seconds>(time - lastTime)
796 .count() *
797 alpha;
798
799 // cap at 1.0 or the below fails
800 if (alphaDT > 1.0)
801 {
802 alphaDT = 1.0;
803 }
804
Alexander Hansen89be6142025-09-18 15:36:16 +0200805 lg2::debug("AlphaDT: '{ADT}'", "ADT", alphaDT);
James Feistbc896df2018-11-26 16:28:17 -0800806
807 reading = ((reading * alphaDT) + (lastReading * (1.0 - alphaDT)));
808
Alexander Hansen89be6142025-09-18 15:36:16 +0200809 lg2::debug("Reading 2: '{VALUE}'", "VALUE", reading);
James Feistbc896df2018-11-26 16:28:17 -0800810
811 val = reading;
812 lastReading = reading;
813 lastTime = time;
James Feistae11cfc2019-05-07 15:01:20 -0700814 errorPrint = maxErrorPrint;
James Feistbc896df2018-11-26 16:28:17 -0800815 return true;
816}
817
Ed Tanous201a1012024-04-03 18:07:28 -0700818void ExitAirTempSensor::checkThresholds()
James Feistbc896df2018-11-26 16:28:17 -0800819{
820 thresholds::checkThresholds(this);
821}
822
Zev Weissafd15042022-07-18 12:28:40 -0700823static void loadVariantPathArray(const SensorBaseConfigMap& data,
824 const std::string& key,
825 std::vector<std::string>& resp)
James Feistbc896df2018-11-26 16:28:17 -0800826{
827 auto it = data.find(key);
828 if (it == data.end())
829 {
George Liuce1abb62025-02-20 10:44:47 +0800830 lg2::error("Configuration missing '{KEY}'", "KEY", key);
James Feistbc896df2018-11-26 16:28:17 -0800831 throw std::invalid_argument("Key Missing");
832 }
833 BasicVariantType copy = it->second;
James Feist3eb82622019-02-08 13:10:22 -0800834 std::vector<std::string> config = std::get<std::vector<std::string>>(copy);
James Feistbc896df2018-11-26 16:28:17 -0800835 for (auto& str : config)
836 {
837 boost::replace_all(str, " ", "_");
838 }
839 resp = std::move(config);
840}
841
842void createSensor(sdbusplus::asio::object_server& objectServer,
James Feistb2eb3f52018-12-04 16:17:50 -0800843 std::shared_ptr<ExitAirTempSensor>& exitAirSensor,
James Feistbc896df2018-11-26 16:28:17 -0800844 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
845{
846 if (!dbusConnection)
847 {
George Liuce1abb62025-02-20 10:44:47 +0800848 lg2::error("Connection not created");
James Feistbc896df2018-11-26 16:28:17 -0800849 return;
850 }
James Feist655f3762020-10-05 15:28:15 -0700851 auto getter = std::make_shared<GetSensorConfiguration>(
Patrick Williams597e8422023-10-20 11:19:01 -0500852 dbusConnection, [&objectServer, &dbusConnection,
853 &exitAirSensor](const ManagedObjectType& resp) {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400854 cfmSensors.clear();
855 for (const auto& [path, interfaces] : resp)
James Feistbc896df2018-11-26 16:28:17 -0800856 {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400857 for (const auto& [intf, cfg] : interfaces)
James Feistbc896df2018-11-26 16:28:17 -0800858 {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400859 if (intf == configInterfaceName(exitAirType))
860 {
861 // thresholds should be under the same path
862 std::vector<thresholds::Threshold> sensorThresholds;
863 parseThresholdsFromConfig(interfaces, sensorThresholds);
James Feistbc896df2018-11-26 16:28:17 -0800864
Patrick Williams2aaf7172024-08-16 15:20:40 -0400865 std::string name =
866 loadVariant<std::string>(cfg, "Name");
867 exitAirSensor = nullptr;
868 exitAirSensor = std::make_shared<ExitAirTempSensor>(
869 dbusConnection, name, path.str, objectServer,
870 std::move(sensorThresholds));
871 exitAirSensor->powerFactorMin =
872 loadVariant<double>(cfg, "PowerFactorMin");
873 exitAirSensor->powerFactorMax =
874 loadVariant<double>(cfg, "PowerFactorMax");
875 exitAirSensor->qMin = loadVariant<double>(cfg, "QMin");
876 exitAirSensor->qMax = loadVariant<double>(cfg, "QMax");
877 exitAirSensor->alphaS =
878 loadVariant<double>(cfg, "AlphaS");
879 exitAirSensor->alphaF =
880 loadVariant<double>(cfg, "AlphaF");
881 }
882 else if (intf == configInterfaceName(cfmType))
883 {
884 // thresholds should be under the same path
885 std::vector<thresholds::Threshold> sensorThresholds;
886 parseThresholdsFromConfig(interfaces, sensorThresholds);
887 std::string name =
888 loadVariant<std::string>(cfg, "Name");
889 auto sensor = std::make_shared<CFMSensor>(
890 dbusConnection, name, path.str, objectServer,
891 std::move(sensorThresholds), exitAirSensor);
892 loadVariantPathArray(cfg, "Tachs", sensor->tachs);
893 sensor->maxCFM = loadVariant<double>(cfg, "MaxCFM");
James Feistbc896df2018-11-26 16:28:17 -0800894
Patrick Williams2aaf7172024-08-16 15:20:40 -0400895 // change these into percent upon getting the data
896 sensor->c1 = loadVariant<double>(cfg, "C1") / 100;
897 sensor->c2 = loadVariant<double>(cfg, "C2") / 100;
898 sensor->tachMinPercent =
899 loadVariant<double>(cfg, "TachMinPercent");
900 sensor->tachMaxPercent =
901 loadVariant<double>(cfg, "TachMaxPercent");
902 sensor->createMaxCFMIface();
903 sensor->setupMatches();
James Feistbc896df2018-11-26 16:28:17 -0800904
Patrick Williams2aaf7172024-08-16 15:20:40 -0400905 cfmSensors.emplace_back(std::move(sensor));
906 }
James Feistbc896df2018-11-26 16:28:17 -0800907 }
908 }
Patrick Williams2aaf7172024-08-16 15:20:40 -0400909 if (exitAirSensor)
910 {
911 exitAirSensor->setupMatches();
912 exitAirSensor->updateReading();
913 }
914 });
James Feist655f3762020-10-05 15:28:15 -0700915 getter->getConfiguration(
Zev Weiss054aad82022-08-18 01:37:34 -0700916 std::vector<std::string>(monitorTypes.begin(), monitorTypes.end()));
James Feistbc896df2018-11-26 16:28:17 -0800917}
918
James Feistb6c0b912019-07-09 12:21:44 -0700919int main()
James Feistbc896df2018-11-26 16:28:17 -0800920{
Ed Tanous1f978632023-02-28 18:16:39 -0800921 boost::asio::io_context io;
James Feistbc896df2018-11-26 16:28:17 -0800922 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
Johnathan Mantey661d4372022-10-27 09:00:59 -0700923 sdbusplus::asio::object_server objectServer(systemBus, true);
924 objectServer.add_manager("/xyz/openbmc_project/sensors");
James Feistbc896df2018-11-26 16:28:17 -0800925 systemBus->request_name("xyz.openbmc_project.ExitAirTempSensor");
James Feistbc896df2018-11-26 16:28:17 -0800926 std::shared_ptr<ExitAirTempSensor> sensor =
927 nullptr; // wait until we find the config
James Feistbc896df2018-11-26 16:28:17 -0800928
Ed Tanous83db50c2023-03-01 10:20:24 -0800929 boost::asio::post(io,
930 [&]() { createSensor(objectServer, sensor, systemBus); });
James Feistbc896df2018-11-26 16:28:17 -0800931
Ed Tanous9b4a20e2022-09-06 08:47:11 -0700932 boost::asio::steady_timer configTimer(io);
James Feistbc896df2018-11-26 16:28:17 -0800933
Patrick Williams92f8f512022-07-22 19:26:55 -0500934 std::function<void(sdbusplus::message_t&)> eventHandler =
935 [&](sdbusplus::message_t&) {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400936 configTimer.expires_after(std::chrono::seconds(1));
937 // create a timer because normally multiple properties change
938 configTimer.async_wait([&](const boost::system::error_code& ec) {
939 if (ec == boost::asio::error::operation_aborted)
940 {
941 return; // we're being canceled
942 }
943 createSensor(objectServer, sensor, systemBus);
944 if (!sensor)
945 {
George Liuce1abb62025-02-20 10:44:47 +0800946 lg2::info("Configuration not detected");
Patrick Williams2aaf7172024-08-16 15:20:40 -0400947 }
948 });
949 };
Zev Weiss214d9712022-08-12 12:54:31 -0700950 std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
Zev Weiss054aad82022-08-18 01:37:34 -0700951 setupPropertiesChangedMatches(*systemBus, monitorTypes, eventHandler);
James Feistbc896df2018-11-26 16:28:17 -0800952
Bruce Lee913d4d02021-07-22 10:18:42 +0800953 setupManufacturingModeMatch(*systemBus);
James Feistbc896df2018-11-26 16:28:17 -0800954 io.run();
Zhikui Ren8685b172021-06-29 15:16:52 -0700955 return 0;
James Feistbc896df2018-11-26 16:28:17 -0800956}