blob: 5e9fc29698d01411650af27ed81405846ebb923f [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
17#include "ExitAirTempSensor.hpp"
18
19#include "Utils.hpp"
20#include "VariantVisitors.hpp"
21
22#include <math.h>
23
24#include <boost/algorithm/string/predicate.hpp>
25#include <boost/algorithm/string/replace.hpp>
26#include <chrono>
27#include <iostream>
28#include <limits>
29#include <numeric>
30#include <sdbusplus/asio/connection.hpp>
31#include <sdbusplus/asio/object_server.hpp>
32#include <vector>
33
34constexpr const float altitudeFactor = 1.14;
35constexpr const char* exitAirIface =
36 "xyz.openbmc_project.Configuration.ExitAirTempSensor";
37constexpr const char* cfmIface = "xyz.openbmc_project.Configuration.CFMSensor";
38
39// todo: this *might* need to be configurable
40constexpr const char* inletTemperatureSensor = "temperature/Front_Panel_Temp";
James Feist13452092019-03-07 16:38:12 -080041constexpr const char* pidConfigurationType =
42 "xyz.openbmc_project.Configuration.Pid";
43constexpr const char* settingsDaemon = "xyz.openbmc_project.Settings";
44constexpr const char* cfmSettingPath = "/xyz/openbmc_project/control/cfm_limit";
45constexpr const char* cfmSettingIface = "xyz.openbmc_project.Control.CFMLimit";
James Feistbc896df2018-11-26 16:28:17 -080046
47static constexpr bool DEBUG = false;
48
James Feistb2eb3f52018-12-04 16:17:50 -080049static constexpr double cfmMaxReading = 255;
50static constexpr double cfmMinReading = 0;
51
James Feist13452092019-03-07 16:38:12 -080052static constexpr size_t minSystemCfm = 50;
53
James Feistb2eb3f52018-12-04 16:17:50 -080054static void setupSensorMatch(
55 std::vector<sdbusplus::bus::match::match>& matches,
56 sdbusplus::bus::bus& connection, const std::string& type,
57 std::function<void(const double&, sdbusplus::message::message&)>&& callback)
58{
59
60 std::function<void(sdbusplus::message::message & message)> eventHandler =
61 [callback{std::move(callback)}](sdbusplus::message::message& message) {
62 std::string objectName;
James Feist3eb82622019-02-08 13:10:22 -080063 boost::container::flat_map<std::string,
64 std::variant<double, int64_t>>
James Feistb2eb3f52018-12-04 16:17:50 -080065 values;
66 message.read(objectName, values);
67 auto findValue = values.find("Value");
68 if (findValue == values.end())
69 {
70 return;
71 }
James Feist3eb82622019-02-08 13:10:22 -080072 double value =
73 std::visit(VariantToDoubleVisitor(), findValue->second);
James Feist9566bfa2019-01-29 15:31:23 -080074 if (std::isnan(value))
75 {
76 return;
77 }
78
James Feistb2eb3f52018-12-04 16:17:50 -080079 callback(value, message);
80 };
81 matches.emplace_back(connection,
82 "type='signal',"
83 "member='PropertiesChanged',interface='org."
84 "freedesktop.DBus.Properties',path_"
85 "namespace='/xyz/openbmc_project/sensors/" +
86 std::string(type) +
87 "',arg0='xyz.openbmc_project.Sensor.Value'",
88 std::move(eventHandler));
89}
90
James Feist13452092019-03-07 16:38:12 -080091static void setMaxPWM(const std::shared_ptr<sdbusplus::asio::connection>& conn,
92 double value)
93{
94 using GetSubTreeType = std::vector<std::pair<
95 std::string,
96 std::vector<std::pair<std::string, std::vector<std::string>>>>>;
97
98 conn->async_method_call(
99 [conn, value](const boost::system::error_code ec,
100 const GetSubTreeType& ret) {
101 if (ec)
102 {
103 std::cerr << "Error calling mapper\n";
104 return;
105 }
106 for (const auto& [path, objDict] : ret)
107 {
108 if (objDict.empty())
109 {
110 return;
111 }
112 const std::string& owner = objDict.begin()->first;
113
114 conn->async_method_call(
115 [conn, value, owner,
116 path](const boost::system::error_code ec,
117 const std::variant<std::string>& classType) {
118 if (ec)
119 {
120 std::cerr << "Error getting pid class\n";
121 return;
122 }
123 auto classStr = std::get_if<std::string>(&classType);
124 if (classStr == nullptr || *classStr != "fan")
125 {
126 return;
127 }
128 conn->async_method_call(
129 [](boost::system::error_code& ec) {
130 if (ec)
131 {
132 std::cerr << "Error setting pid class\n";
133 return;
134 }
135 },
136 owner, path, "org.freedesktop.DBus.Properties",
137 "Set", pidConfigurationType, "OutLimitMax",
138 std::variant<double>(value));
139 },
140 owner, path, "org.freedesktop.DBus.Properties", "Get",
141 pidConfigurationType, "Class");
142 }
143 },
144 "xyz.openbmc_project.ObjectMapper",
145 "/xyz/openbmc_project/object_mapper",
146 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0,
147 std::array<std::string, 1>{pidConfigurationType});
148}
149
James Feistb2eb3f52018-12-04 16:17:50 -0800150CFMSensor::CFMSensor(std::shared_ptr<sdbusplus::asio::connection>& conn,
151 const std::string& sensorName,
152 const std::string& sensorConfiguration,
153 sdbusplus::asio::object_server& objectServer,
154 std::vector<thresholds::Threshold>&& thresholds,
155 std::shared_ptr<ExitAirTempSensor>& parent) :
156 Sensor(boost::replace_all_copy(sensorName, " ", "_"),
157 "" /* todo: remove arg from base*/, std::move(thresholds),
158 sensorConfiguration, "xyz.openbmc_project.Configuration.ExitAirTemp",
159 cfmMaxReading, cfmMinReading),
James Feist9566bfa2019-01-29 15:31:23 -0800160 dbusConnection(conn), parent(parent), objServer(objectServer)
James Feistb2eb3f52018-12-04 16:17:50 -0800161{
162 sensorInterface =
163 objectServer.add_interface("/xyz/openbmc_project/sensors/cfm/" + name,
164 "xyz.openbmc_project.Sensor.Value");
165
166 if (thresholds::hasWarningInterface(thresholds))
167 {
168 thresholdInterfaceWarning = objectServer.add_interface(
169 "/xyz/openbmc_project/sensors/cfm/" + name,
170 "xyz.openbmc_project.Sensor.Threshold.Warning");
171 }
172 if (thresholds::hasCriticalInterface(thresholds))
173 {
174 thresholdInterfaceCritical = objectServer.add_interface(
175 "/xyz/openbmc_project/sensors/cfm/" + name,
176 "xyz.openbmc_project.Sensor.Threshold.Critical");
177 }
James Feist078f2322019-03-08 11:09:05 -0800178
179 association = objectServer.add_interface(
180 "/xyz/openbmc_project/sensors/voltage/" + name,
181 "org.openbmc.Associations");
182
James Feistb2eb3f52018-12-04 16:17:50 -0800183 setInitialProperties(conn);
184 setupSensorMatch(
185 matches, *dbusConnection, "fan_tach",
186 std::move(
187 [this](const double& value, sdbusplus::message::message& message) {
188 tachReadings[message.get_path()] = value;
189 if (tachRanges.find(message.get_path()) == tachRanges.end())
190 {
191 // calls update reading after updating ranges
192 addTachRanges(message.get_sender(), message.get_path());
193 }
194 else
195 {
196 updateReading();
197 }
198 }));
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");
205
206 conn->async_method_call(
207 [this, conn](const boost::system::error_code ec,
208 const std::variant<double> cfmVariant) {
209 uint64_t maxRpm = 100;
210 if (!ec)
211 {
212
213 auto cfm = std::get_if<double>(&cfmVariant);
214 if (cfm != nullptr || *cfm >= minSystemCfm)
215 {
216 maxRpm = getMaxRpm(*cfm);
217 }
218 }
219 pwmLimitIface->register_property("Limit", maxRpm);
220 pwmLimitIface->initialize();
221 setMaxPWM(conn, maxRpm);
222 },
223 settingsDaemon, cfmSettingPath, "org.freedesktop.DBus.Properties",
224 "Get", cfmSettingIface, "Limit");
225
226 matches.emplace_back(
227 *conn,
228 "type='signal',"
229 "member='PropertiesChanged',interface='org."
230 "freedesktop.DBus.Properties',path='" +
231 std::string(cfmSettingPath) + "',arg0='" +
232 std::string(cfmSettingIface) + "'",
233 [this, conn](sdbusplus::message::message& message) {
234 boost::container::flat_map<std::string, std::variant<double>>
235 values;
236 std::string objectName;
237 message.read(objectName, values);
238 const auto findValue = values.find("Limit");
239 if (findValue == values.end())
240 {
241 return;
242 }
243 const auto reading = std::get_if<double>(&(findValue->second));
244 if (reading == nullptr)
245 {
246 std::cerr << "Got CFM Limit of wrong type\n";
247 return;
248 }
249 if (*reading < minSystemCfm && *reading != 0)
250 {
251 std::cerr << "Illegal CFM setting detected\n";
252 return;
253 }
254 uint64_t maxRpm = getMaxRpm(*reading);
255 pwmLimitIface->set_property("Limit", maxRpm);
256 setMaxPWM(conn, maxRpm);
257 });
James Feistb2eb3f52018-12-04 16:17:50 -0800258}
259
James Feist9566bfa2019-01-29 15:31:23 -0800260CFMSensor::~CFMSensor()
261{
262 objServer.remove_interface(thresholdInterfaceWarning);
263 objServer.remove_interface(thresholdInterfaceCritical);
264 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800265 objServer.remove_interface(association);
James Feist13452092019-03-07 16:38:12 -0800266 objServer.remove_interface(cfmLimitIface);
267 objServer.remove_interface(pwmLimitIface);
268}
269
270void CFMSensor::createMaxCFMIface(void)
271{
272 cfmLimitIface->register_property("Limit", static_cast<double>(c2) * maxCFM *
273 tachs.size());
274 cfmLimitIface->initialize();
James Feist9566bfa2019-01-29 15:31:23 -0800275}
276
James Feistb2eb3f52018-12-04 16:17:50 -0800277void CFMSensor::addTachRanges(const std::string& serviceName,
278 const std::string& path)
279{
280 dbusConnection->async_method_call(
281 [this, path](const boost::system::error_code ec,
282 const boost::container::flat_map<std::string,
283 BasicVariantType>& data) {
284 if (ec)
285 {
286 std::cerr << "Error getting properties from " << path << "\n";
James Feist1ccdb5e2019-01-24 09:44:01 -0800287 return;
James Feistb2eb3f52018-12-04 16:17:50 -0800288 }
289
290 double max = loadVariant<double>(data, "MaxValue");
291 double min = loadVariant<double>(data, "MinValue");
292 tachRanges[path] = std::make_pair(min, max);
293 updateReading();
294 },
295 serviceName, path, "org.freedesktop.DBus.Properties", "GetAll",
296 "xyz.openbmc_project.Sensor.Value");
297}
298
299void CFMSensor::checkThresholds(void)
300{
301 thresholds::checkThresholds(this);
302}
303
304void CFMSensor::updateReading(void)
305{
306 double val = 0.0;
307 if (calculate(val))
308 {
309 if (value != val && parent)
310 {
311 parent->updateReading();
312 }
313 updateValue(val);
314 }
315 else
316 {
317 updateValue(std::numeric_limits<double>::quiet_NaN());
318 }
319}
320
James Feist13452092019-03-07 16:38:12 -0800321uint64_t CFMSensor::getMaxRpm(uint64_t cfmMaxSetting)
322{
323 uint64_t pwmPercent = 100;
324 double totalCFM = std::numeric_limits<double>::max();
325 if (cfmMaxSetting == 0)
326 {
327 return pwmPercent;
328 }
329
James Feist52427952019-04-05 14:23:35 -0700330 bool firstLoop = true;
James Feist13452092019-03-07 16:38:12 -0800331 while (totalCFM > cfmMaxSetting)
332 {
James Feist52427952019-04-05 14:23:35 -0700333 if (firstLoop)
334 {
335 firstLoop = false;
336 }
337 else
338 {
339 pwmPercent--;
340 }
341
James Feist13452092019-03-07 16:38:12 -0800342 double ci = 0;
343 if (pwmPercent == 0)
344 {
345 ci = 0;
346 }
347 else if (pwmPercent < tachMinPercent)
348 {
349 ci = c1;
350 }
351 else if (pwmPercent > tachMaxPercent)
352 {
353 ci = c2;
354 }
355 else
356 {
357 ci = c1 + (((c2 - c1) * (pwmPercent - tachMinPercent)) /
358 (tachMaxPercent - tachMinPercent));
359 }
360
361 // Now calculate the CFM for this tach
362 // CFMi = Ci * Qmaxi * TACHi
363 totalCFM = ci * maxCFM * pwmPercent;
364 totalCFM *= tachs.size();
365 // divide by 100 since pwm is in percent
366 totalCFM /= 100;
367
James Feist13452092019-03-07 16:38:12 -0800368 if (pwmPercent <= 0)
369 {
370 break;
371 }
372 }
James Feist52427952019-04-05 14:23:35 -0700373
James Feist13452092019-03-07 16:38:12 -0800374 return pwmPercent;
375}
376
James Feistb2eb3f52018-12-04 16:17:50 -0800377bool CFMSensor::calculate(double& value)
378{
379 double totalCFM = 0;
380 for (const std::string& tachName : tachs)
381 {
James Feist9566bfa2019-01-29 15:31:23 -0800382
James Feistb2eb3f52018-12-04 16:17:50 -0800383 auto findReading = std::find_if(
384 tachReadings.begin(), tachReadings.end(), [&](const auto& item) {
385 return boost::ends_with(item.first, tachName);
386 });
387 auto findRange = std::find_if(
388 tachRanges.begin(), tachRanges.end(), [&](const auto& item) {
389 return boost::ends_with(item.first, tachName);
390 });
391 if (findReading == tachReadings.end())
392 {
James Feista96329f2019-01-24 10:08:27 -0800393 if (DEBUG)
394 {
395 std::cerr << "Can't find " << tachName << "in readings\n";
396 }
James Feist9566bfa2019-01-29 15:31:23 -0800397 continue; // haven't gotten a reading
James Feistb2eb3f52018-12-04 16:17:50 -0800398 }
399
400 if (findRange == tachRanges.end())
401 {
James Feist523828e2019-03-04 14:38:37 -0800402 std::cerr << "Can't find " << tachName << " in ranges\n";
James Feistb2eb3f52018-12-04 16:17:50 -0800403 return false; // haven't gotten a max / min
404 }
405
406 // avoid divide by 0
407 if (findRange->second.second == 0)
408 {
409 std::cerr << "Tach Max Set to 0 " << tachName << "\n";
410 return false;
411 }
412
413 double rpm = findReading->second;
414
415 // for now assume the min for a fan is always 0, divide by max to get
416 // percent and mult by 100
417 rpm /= findRange->second.second;
418 rpm *= 100;
419
420 if constexpr (DEBUG)
421 {
422 std::cout << "Tach " << tachName << "at " << rpm << "\n";
423 }
424
425 // Do a linear interpolation to get Ci
426 // Ci = C1 + (C2 - C1)/(RPM2 - RPM1) * (TACHi - TACH1)
427
428 double ci = 0;
429 if (rpm == 0)
430 {
431 ci = 0;
432 }
433 else if (rpm < tachMinPercent)
434 {
435 ci = c1;
436 }
437 else if (rpm > tachMaxPercent)
438 {
439 ci = c2;
440 }
441 else
442 {
443 ci = c1 + (((c2 - c1) * (rpm - tachMinPercent)) /
444 (tachMaxPercent - tachMinPercent));
445 }
446
447 // Now calculate the CFM for this tach
448 // CFMi = Ci * Qmaxi * TACHi
449 totalCFM += ci * maxCFM * rpm;
450 }
451
452 // divide by 100 since rpm is in percent
453 value = totalCFM / 100;
James Feist9566bfa2019-01-29 15:31:23 -0800454 return true;
James Feistb2eb3f52018-12-04 16:17:50 -0800455}
456
457static constexpr double exitAirMaxReading = 127;
458static constexpr double exitAirMinReading = -128;
James Feistbc896df2018-11-26 16:28:17 -0800459ExitAirTempSensor::ExitAirTempSensor(
460 std::shared_ptr<sdbusplus::asio::connection>& conn,
James Feistb2eb3f52018-12-04 16:17:50 -0800461 const std::string& sensorName, const std::string& sensorConfiguration,
James Feistbc896df2018-11-26 16:28:17 -0800462 sdbusplus::asio::object_server& objectServer,
463 std::vector<thresholds::Threshold>&& thresholds) :
James Feistb2eb3f52018-12-04 16:17:50 -0800464 Sensor(boost::replace_all_copy(sensorName, " ", "_"),
465 "" /* todo: remove arg from base*/, std::move(thresholds),
466 sensorConfiguration, "xyz.openbmc_project.Configuration.ExitAirTemp",
467 exitAirMaxReading, exitAirMinReading),
James Feist523828e2019-03-04 14:38:37 -0800468 dbusConnection(conn), objServer(objectServer)
James Feistbc896df2018-11-26 16:28:17 -0800469{
470 sensorInterface = objectServer.add_interface(
471 "/xyz/openbmc_project/sensors/temperature/" + name,
472 "xyz.openbmc_project.Sensor.Value");
473
474 if (thresholds::hasWarningInterface(thresholds))
475 {
476 thresholdInterfaceWarning = objectServer.add_interface(
477 "/xyz/openbmc_project/sensors/temperature/" + name,
478 "xyz.openbmc_project.Sensor.Threshold.Warning");
479 }
480 if (thresholds::hasCriticalInterface(thresholds))
481 {
482 thresholdInterfaceCritical = objectServer.add_interface(
483 "/xyz/openbmc_project/sensors/temperature/" + name,
484 "xyz.openbmc_project.Sensor.Threshold.Critical");
485 }
James Feist078f2322019-03-08 11:09:05 -0800486 association = objectServer.add_interface(
487 "/xyz/openbmc_project/sensors/temperature/" + name,
488 "org.openbmc.Associations");
James Feistbc896df2018-11-26 16:28:17 -0800489 setInitialProperties(conn);
490 setupMatches();
James Feist71d31b22019-01-02 16:57:54 -0800491 setupPowerMatch(conn);
James Feistbc896df2018-11-26 16:28:17 -0800492}
493
494ExitAirTempSensor::~ExitAirTempSensor()
495{
James Feist523828e2019-03-04 14:38:37 -0800496 objServer.remove_interface(thresholdInterfaceWarning);
497 objServer.remove_interface(thresholdInterfaceCritical);
498 objServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800499 objServer.remove_interface(association);
James Feistbc896df2018-11-26 16:28:17 -0800500}
501
502void ExitAirTempSensor::setupMatches(void)
503{
504
James Feistb2eb3f52018-12-04 16:17:50 -0800505 constexpr const std::array<const char*, 2> matchTypes = {
506 "power", inletTemperatureSensor};
James Feistbc896df2018-11-26 16:28:17 -0800507
James Feistb2eb3f52018-12-04 16:17:50 -0800508 for (const std::string& type : matchTypes)
James Feistbc896df2018-11-26 16:28:17 -0800509 {
James Feistb2eb3f52018-12-04 16:17:50 -0800510 setupSensorMatch(matches, *dbusConnection, type,
511 [this, type](const double& value,
512 sdbusplus::message::message& message) {
513 if (type == "power")
514 {
515 powerReadings[message.get_path()] = value;
516 }
517 else if (type == inletTemperatureSensor)
518 {
519 inletTemp = value;
520 }
521 updateReading();
522 });
James Feistbc896df2018-11-26 16:28:17 -0800523 }
James Feist9566bfa2019-01-29 15:31:23 -0800524 dbusConnection->async_method_call(
525 [this](boost::system::error_code ec,
526 const std::variant<double>& value) {
527 if (ec)
528 {
529 // sensor not ready yet
530 return;
531 }
532
James Feist3eb82622019-02-08 13:10:22 -0800533 inletTemp = std::visit(VariantToDoubleVisitor(), value);
James Feist9566bfa2019-01-29 15:31:23 -0800534 },
535 "xyz.openbmc_project.HwmonTempSensor",
536 std::string("/xyz/openbmc_project/sensors/") + inletTemperatureSensor,
537 "org.freedesktop.DBus.Properties", "Get",
538 "xyz.openbmc_project.Sensor.Value", "Value");
James Feistbc896df2018-11-26 16:28:17 -0800539}
540
541void ExitAirTempSensor::updateReading(void)
542{
543
544 double val = 0.0;
545 if (calculate(val))
546 {
James Feist18af4232019-03-13 11:14:00 -0700547 val = std::floor(val + 0.5);
James Feistbc896df2018-11-26 16:28:17 -0800548 updateValue(val);
549 }
550 else
551 {
552 updateValue(std::numeric_limits<double>::quiet_NaN());
553 }
554}
555
James Feistb2eb3f52018-12-04 16:17:50 -0800556double ExitAirTempSensor::getTotalCFM(void)
James Feistbc896df2018-11-26 16:28:17 -0800557{
James Feistb2eb3f52018-12-04 16:17:50 -0800558 double sum = 0;
559 for (auto& sensor : cfmSensors)
James Feistbc896df2018-11-26 16:28:17 -0800560 {
James Feistb2eb3f52018-12-04 16:17:50 -0800561 double reading = 0;
562 if (!sensor->calculate(reading))
James Feistbc896df2018-11-26 16:28:17 -0800563 {
James Feistbc896df2018-11-26 16:28:17 -0800564 return -1;
565 }
James Feistb2eb3f52018-12-04 16:17:50 -0800566 sum += reading;
James Feistbc896df2018-11-26 16:28:17 -0800567 }
James Feistb2eb3f52018-12-04 16:17:50 -0800568
569 return sum;
James Feistbc896df2018-11-26 16:28:17 -0800570}
571
572bool ExitAirTempSensor::calculate(double& val)
573{
574 static bool firstRead = false;
575 double cfm = getTotalCFM();
576 if (cfm <= 0)
577 {
578 std::cerr << "Error getting cfm\n";
579 return false;
580 }
581
582 // if there is an error getting inlet temp, return error
583 if (std::isnan(inletTemp))
584 {
585 std::cerr << "Cannot get inlet temp\n";
586 val = 0;
587 return false;
588 }
589
590 // if fans are off, just make the exit temp equal to inlet
James Feist71d31b22019-01-02 16:57:54 -0800591 if (!isPowerOn())
James Feistbc896df2018-11-26 16:28:17 -0800592 {
593 val = inletTemp;
594 return true;
595 }
596
597 double totalPower = 0;
598 for (const auto& reading : powerReadings)
599 {
600 if (std::isnan(reading.second))
601 {
602 continue;
603 }
604 totalPower += reading.second;
605 }
606
607 // Calculate power correction factor
608 // Ci = CL + (CH - CL)/(QMax - QMin) * (CFM - QMin)
609 float powerFactor = 0.0;
610 if (cfm <= qMin)
611 {
612 powerFactor = powerFactorMin;
613 }
614 else if (cfm >= qMax)
615 {
616 powerFactor = powerFactorMax;
617 }
618 else
619 {
620 powerFactor = powerFactorMin + ((powerFactorMax - powerFactorMin) /
621 (qMax - qMin) * (cfm - qMin));
622 }
623
624 totalPower *= powerFactor;
625 totalPower += pOffset;
626
627 if (totalPower == 0)
628 {
629 std::cerr << "total power 0\n";
630 val = 0;
631 return false;
632 }
633
634 if constexpr (DEBUG)
635 {
636 std::cout << "Power Factor " << powerFactor << "\n";
637 std::cout << "Inlet Temp " << inletTemp << "\n";
638 std::cout << "Total Power" << totalPower << "\n";
639 }
640
641 // Calculate the exit air temp
642 // Texit = Tfp + (1.76 * TotalPower / CFM * Faltitude)
643 double reading = 1.76 * totalPower * altitudeFactor;
644 reading /= cfm;
645 reading += inletTemp;
646
647 if constexpr (DEBUG)
648 {
649 std::cout << "Reading 1: " << reading << "\n";
650 }
651
652 // Now perform the exponential average
653 // Calculate alpha based on SDR values and CFM
654 // Ai = As + (Af - As)/(QMax - QMin) * (CFM - QMin)
655
656 double alpha = 0.0;
657 if (cfm < qMin)
658 {
659 alpha = alphaS;
660 }
661 else if (cfm >= qMax)
662 {
663 alpha = alphaF;
664 }
665 else
666 {
667 alpha = alphaS + ((alphaF - alphaS) * (cfm - qMin) / (qMax - qMin));
668 }
669
670 auto time = std::chrono::system_clock::now();
671 if (!firstRead)
672 {
673 firstRead = true;
674 lastTime = time;
675 lastReading = reading;
676 }
677 double alphaDT =
678 std::chrono::duration_cast<std::chrono::seconds>(time - lastTime)
679 .count() *
680 alpha;
681
682 // cap at 1.0 or the below fails
683 if (alphaDT > 1.0)
684 {
685 alphaDT = 1.0;
686 }
687
688 if constexpr (DEBUG)
689 {
690 std::cout << "AlphaDT: " << alphaDT << "\n";
691 }
692
693 reading = ((reading * alphaDT) + (lastReading * (1.0 - alphaDT)));
694
695 if constexpr (DEBUG)
696 {
697 std::cout << "Reading 2: " << reading << "\n";
698 }
699
700 val = reading;
701 lastReading = reading;
702 lastTime = time;
703 return true;
704}
705
706void ExitAirTempSensor::checkThresholds(void)
707{
708 thresholds::checkThresholds(this);
709}
710
James Feistbc896df2018-11-26 16:28:17 -0800711static void loadVariantPathArray(
712 const boost::container::flat_map<std::string, BasicVariantType>& data,
713 const std::string& key, std::vector<std::string>& resp)
714{
715 auto it = data.find(key);
716 if (it == data.end())
717 {
718 std::cerr << "Configuration missing " << key << "\n";
719 throw std::invalid_argument("Key Missing");
720 }
721 BasicVariantType copy = it->second;
James Feist3eb82622019-02-08 13:10:22 -0800722 std::vector<std::string> config = std::get<std::vector<std::string>>(copy);
James Feistbc896df2018-11-26 16:28:17 -0800723 for (auto& str : config)
724 {
725 boost::replace_all(str, " ", "_");
726 }
727 resp = std::move(config);
728}
729
730void createSensor(sdbusplus::asio::object_server& objectServer,
James Feistb2eb3f52018-12-04 16:17:50 -0800731 std::shared_ptr<ExitAirTempSensor>& exitAirSensor,
James Feistbc896df2018-11-26 16:28:17 -0800732 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
733{
734 if (!dbusConnection)
735 {
736 std::cerr << "Connection not created\n";
737 return;
738 }
739 dbusConnection->async_method_call(
740 [&](boost::system::error_code ec, const ManagedObjectType& resp) {
741 if (ec)
742 {
743 std::cerr << "Error contacting entity manager\n";
744 return;
745 }
James Feistb2eb3f52018-12-04 16:17:50 -0800746 std::vector<std::unique_ptr<CFMSensor>> cfmSensors;
James Feistbc896df2018-11-26 16:28:17 -0800747 for (const auto& pathPair : resp)
748 {
749 for (const auto& entry : pathPair.second)
750 {
751 if (entry.first == exitAirIface)
752 {
James Feistbc896df2018-11-26 16:28:17 -0800753 // thresholds should be under the same path
754 std::vector<thresholds::Threshold> sensorThresholds;
755 parseThresholdsFromConfig(pathPair.second,
756 sensorThresholds);
James Feistbc896df2018-11-26 16:28:17 -0800757
James Feist523828e2019-03-04 14:38:37 -0800758 std::string name =
759 loadVariant<std::string>(entry.second, "Name");
760 exitAirSensor = std::make_shared<ExitAirTempSensor>(
761 dbusConnection, name, pathPair.first.str,
762 objectServer, std::move(sensorThresholds));
James Feistb2eb3f52018-12-04 16:17:50 -0800763 exitAirSensor->powerFactorMin =
764 loadVariant<double>(entry.second, "PowerFactorMin");
765 exitAirSensor->powerFactorMax =
766 loadVariant<double>(entry.second, "PowerFactorMax");
767 exitAirSensor->qMin =
768 loadVariant<double>(entry.second, "QMin");
769 exitAirSensor->qMax =
770 loadVariant<double>(entry.second, "QMax");
771 exitAirSensor->alphaS =
772 loadVariant<double>(entry.second, "AlphaS");
773 exitAirSensor->alphaF =
774 loadVariant<double>(entry.second, "AlphaF");
James Feistbc896df2018-11-26 16:28:17 -0800775 }
776 else if (entry.first == cfmIface)
777
778 {
James Feistb2eb3f52018-12-04 16:17:50 -0800779 // thresholds should be under the same path
780 std::vector<thresholds::Threshold> sensorThresholds;
781 parseThresholdsFromConfig(pathPair.second,
782 sensorThresholds);
783 std::string name =
784 loadVariant<std::string>(entry.second, "Name");
785 auto sensor = std::make_unique<CFMSensor>(
786 dbusConnection, name, pathPair.first.str,
787 objectServer, std::move(sensorThresholds),
788 exitAirSensor);
789 loadVariantPathArray(entry.second, "Tachs",
790 sensor->tachs);
791 sensor->maxCFM =
792 loadVariant<double>(entry.second, "MaxCFM");
James Feistbc896df2018-11-26 16:28:17 -0800793
794 // change these into percent upon getting the data
James Feistb2eb3f52018-12-04 16:17:50 -0800795 sensor->c1 =
796 loadVariant<double>(entry.second, "C1") / 100;
797 sensor->c2 =
798 loadVariant<double>(entry.second, "C2") / 100;
799 sensor->tachMinPercent =
800 loadVariant<double>(entry.second,
801 "TachMinPercent") /
James Feistbc896df2018-11-26 16:28:17 -0800802 100;
James Feistb2eb3f52018-12-04 16:17:50 -0800803 sensor->tachMaxPercent =
804 loadVariant<double>(entry.second,
805 "TachMaxPercent") /
James Feistbc896df2018-11-26 16:28:17 -0800806 100;
James Feist13452092019-03-07 16:38:12 -0800807 sensor->createMaxCFMIface();
James Feistbc896df2018-11-26 16:28:17 -0800808
James Feistb2eb3f52018-12-04 16:17:50 -0800809 cfmSensors.emplace_back(std::move(sensor));
James Feistbc896df2018-11-26 16:28:17 -0800810 }
811 }
812 }
James Feistb2eb3f52018-12-04 16:17:50 -0800813 if (exitAirSensor)
James Feistbc896df2018-11-26 16:28:17 -0800814 {
James Feistb2eb3f52018-12-04 16:17:50 -0800815 exitAirSensor->cfmSensors = std::move(cfmSensors);
James Feistbc896df2018-11-26 16:28:17 -0800816
James Feistb2eb3f52018-12-04 16:17:50 -0800817 // todo: when power sensors are done delete this fake
818 // reading
819 exitAirSensor->powerReadings["foo"] = 144.0;
James Feistbc896df2018-11-26 16:28:17 -0800820
James Feistb2eb3f52018-12-04 16:17:50 -0800821 exitAirSensor->updateReading();
James Feistbc896df2018-11-26 16:28:17 -0800822 }
823 },
824 entityManagerName, "/", "org.freedesktop.DBus.ObjectManager",
825 "GetManagedObjects");
826}
827
828int main(int argc, char** argv)
829{
830
831 boost::asio::io_service io;
832 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
833 systemBus->request_name("xyz.openbmc_project.ExitAirTempSensor");
834 sdbusplus::asio::object_server objectServer(systemBus);
835 std::shared_ptr<ExitAirTempSensor> sensor =
836 nullptr; // wait until we find the config
837 std::vector<std::unique_ptr<sdbusplus::bus::match::match>> matches;
838
839 io.post([&]() { createSensor(objectServer, sensor, systemBus); });
840
841 boost::asio::deadline_timer configTimer(io);
842
843 std::function<void(sdbusplus::message::message&)> eventHandler =
844 [&](sdbusplus::message::message& message) {
845 configTimer.expires_from_now(boost::posix_time::seconds(1));
846 // create a timer because normally multiple properties change
847 configTimer.async_wait([&](const boost::system::error_code& ec) {
848 if (ec == boost::asio::error::operation_aborted)
849 {
850 return; // we're being canceled
851 }
852 createSensor(objectServer, sensor, systemBus);
853 if (!sensor)
854 {
855 std::cout << "Configuration not detected\n";
856 }
857 });
858 };
859 constexpr const std::array<const char*, 2> monitorIfaces = {exitAirIface,
860 cfmIface};
861 for (const char* type : monitorIfaces)
862 {
863 auto match = std::make_unique<sdbusplus::bus::match::match>(
864 static_cast<sdbusplus::bus::bus&>(*systemBus),
865 "type='signal',member='PropertiesChanged',path_namespace='" +
866 std::string(inventoryPath) + "',arg0namespace='" + type + "'",
867 eventHandler);
868 matches.emplace_back(std::move(match));
869 }
870
871 io.run();
872}