blob: 063fb0f293767afe33bf2009a831f74eec8bd2b2 [file] [log] [blame]
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001#include "virtualSensor.hpp"
2
3#include "config.hpp"
4
Patrick Williams82b39c62021-07-28 16:22:27 -05005#include <phosphor-logging/lg2.hpp>
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07006
7#include <fstream>
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07008
9static constexpr bool DEBUG = false;
10static constexpr auto busName = "xyz.openbmc_project.VirtualSensor";
11static constexpr auto sensorDbusPath = "/xyz/openbmc_project/sensors/";
Rashmica Guptae7efe132021-07-27 19:42:11 +100012static constexpr auto vsThresholdsIfaceSuffix = ".Thresholds";
Rashmica Gupta304fd0e2021-08-10 16:50:44 +100013static constexpr std::array<const char*, 1> calculationIfaces = {
14 "xyz.openbmc_project.Configuration.ModifiedMedian"};
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +100015static constexpr auto defaultHysteresis = 0;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070016
Patrick Williams82b39c62021-07-28 16:22:27 -050017PHOSPHOR_LOG2_USING_WITH_FLAGS;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070018
Vijay Khemka51f898e2020-09-09 22:24:18 -070019int handleDbusSignal(sd_bus_message* msg, void* usrData, sd_bus_error*)
20{
21 if (usrData == nullptr)
22 {
23 throw std::runtime_error("Invalid match");
24 }
25
Patrick Williams8e11ccc2022-07-22 19:26:57 -050026 auto sdbpMsg = sdbusplus::message_t(msg);
Vijay Khemka51f898e2020-09-09 22:24:18 -070027 std::string msgIfce;
28 std::map<std::string, std::variant<int64_t, double, bool>> msgData;
29
30 sdbpMsg.read(msgIfce, msgData);
31
32 if (msgData.find("Value") != msgData.end())
33 {
34 using namespace phosphor::virtualSensor;
35 VirtualSensor* obj = static_cast<VirtualSensor*>(usrData);
36 // TODO(openbmc/phosphor-virtual-sensor#1): updateVirtualSensor should
37 // be changed to take the information we got from the signal, to avoid
38 // having to do numerous dbus queries.
39 obj->updateVirtualSensor();
40 }
41 return 0;
42}
43
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070044namespace phosphor
45{
46namespace virtualSensor
47{
48
49void printParams(const VirtualSensor::ParamMap& paramMap)
50{
51 for (const auto& p : paramMap)
52 {
53 const auto& p1 = p.first;
54 const auto& p2 = p.second;
55 auto val = p2->getParamValue();
Patrick Williamsfbd71452021-08-30 06:59:46 -050056 debug("Parameter: {PARAM} = {VALUE}", "PARAM", p1, "VALUE", val);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070057 }
58}
59
60double SensorParam::getParamValue()
61{
62 switch (paramType)
63 {
64 case constParam:
65 return value;
66 break;
Vijay Khemka7452a862020-08-11 16:01:23 -070067 case dbusParam:
68 return dbusSensor->getSensorValue();
69 break;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070070 default:
71 throw std::invalid_argument("param type not supported");
72 }
73}
74
Lei YU0fcf0e12021-06-04 11:14:17 +080075using AssociationList =
76 std::vector<std::tuple<std::string, std::string, std::string>>;
77
78AssociationList getAssociationsFromJson(const Json& j)
79{
80 AssociationList assocs{};
81 try
82 {
83 j.get_to(assocs);
84 }
85 catch (const std::exception& ex)
86 {
Patrick Williams82b39c62021-07-28 16:22:27 -050087 error("Failed to parse association: {ERROR}", "ERROR", ex);
Lei YU0fcf0e12021-06-04 11:14:17 +080088 }
89 return assocs;
90}
91
Rashmica Guptae7efe132021-07-27 19:42:11 +100092template <typename U>
93struct VariantToNumber
94{
95 template <typename T>
96 U operator()(const T& t) const
97 {
98 if constexpr (std::is_convertible<T, U>::value)
99 {
100 return static_cast<U>(t);
101 }
102 throw std::invalid_argument("Invalid number type in config\n");
103 }
104};
105
106template <typename U>
107U getNumberFromConfig(const PropertyMap& map, const std::string& name,
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800108 bool required,
109 U defaultValue = std::numeric_limits<U>::quiet_NaN())
Rashmica Guptae7efe132021-07-27 19:42:11 +1000110{
111 if (auto itr = map.find(name); itr != map.end())
112 {
113 return std::visit(VariantToNumber<U>(), itr->second);
114 }
115 else if (required)
116 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500117 error("Required field {NAME} missing in config", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000118 throw std::invalid_argument("Required field missing in config");
119 }
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800120 return defaultValue;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000121}
122
123bool isCalculationType(const std::string& interface)
124{
125 auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(),
126 interface);
127 if (itr != calculationIfaces.end())
128 {
129 return true;
130 }
131 return false;
132}
133
134const std::string getThresholdType(const std::string& direction,
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100135 const std::string& severity)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000136{
Rashmica Guptae7efe132021-07-27 19:42:11 +1000137 std::string suffix;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000138
139 if (direction == "less than")
140 {
141 suffix = "Low";
142 }
143 else if (direction == "greater than")
144 {
145 suffix = "High";
146 }
147 else
148 {
149 throw std::invalid_argument(
150 "Invalid threshold direction specified in entity manager");
151 }
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100152 return severity + suffix;
153}
154
155std::string getSeverityField(const PropertyMap& propertyMap)
156{
157 static const std::array thresholdTypes{"Warning", "Critical",
158 "PerformanceLoss", "SoftShutdown",
159 "HardShutdown"};
160
161 std::string severity;
162 if (auto itr = propertyMap.find("Severity"); itr != propertyMap.end())
163 {
164 /* Severity should be a string, but can be an unsigned int */
165 if (std::holds_alternative<std::string>(itr->second))
166 {
167 severity = std::get<std::string>(itr->second);
168 if (0 == std::ranges::count(thresholdTypes, severity))
169 {
170 throw std::invalid_argument(
171 "Invalid threshold severity specified in entity manager");
172 }
173 }
174 else
175 {
176 auto sev =
177 getNumberFromConfig<uint64_t>(propertyMap, "Severity", true);
178 /* Checking bounds ourselves so we throw invalid argument on
179 * invalid user input */
180 if (sev >= thresholdTypes.size())
181 {
182 throw std::invalid_argument(
183 "Invalid threshold severity specified in entity manager");
184 }
185 severity = thresholdTypes.at(sev);
186 }
187 }
188 return severity;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000189}
190
Tao Lin91799db2022-07-27 21:02:20 +0800191void parseThresholds(Json& thresholds, const PropertyMap& propertyMap,
192 const std::string& entityInterface = "")
Rashmica Guptae7efe132021-07-27 19:42:11 +1000193{
194 std::string direction;
195
Rashmica Guptae7efe132021-07-27 19:42:11 +1000196 auto value = getNumberFromConfig<double>(propertyMap, "Value", true);
197
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100198 auto severity = getSeverityField(propertyMap);
199
200 if (auto itr = propertyMap.find("Direction"); itr != propertyMap.end())
Rashmica Guptae7efe132021-07-27 19:42:11 +1000201 {
202 direction = std::get<std::string>(itr->second);
203 }
204
205 auto threshold = getThresholdType(direction, severity);
206 thresholds[threshold] = value;
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000207
208 auto hysteresis =
209 getNumberFromConfig<double>(propertyMap, "Hysteresis", false);
210 if (hysteresis != std::numeric_limits<double>::quiet_NaN())
211 {
212 thresholds[threshold + "Hysteresis"] = hysteresis;
213 }
Tao Lin91799db2022-07-27 21:02:20 +0800214
215 if (!entityInterface.empty())
216 {
217 thresholds[threshold + "Direction"] = entityInterface;
218 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000219}
220
221void VirtualSensor::parseConfigInterface(const PropertyMap& propertyMap,
222 const std::string& sensorType,
223 const std::string& interface)
224{
225 /* Parse sensors / DBus params */
226 if (auto itr = propertyMap.find("Sensors"); itr != propertyMap.end())
227 {
228 auto sensors = std::get<std::vector<std::string>>(itr->second);
229 for (auto sensor : sensors)
230 {
231 std::replace(sensor.begin(), sensor.end(), ' ', '_');
232 auto sensorObjPath = sensorDbusPath + sensorType + "/" + sensor;
233
234 auto paramPtr =
235 std::make_unique<SensorParam>(bus, sensorObjPath, this);
236 symbols.create_variable(sensor);
237 paramMap.emplace(std::move(sensor), std::move(paramPtr));
238 }
239 }
240 /* Get expression string */
241 if (!isCalculationType(interface))
242 {
243 throw std::invalid_argument("Invalid expression in interface");
244 }
245 exprStr = interface;
246
247 /* Get optional min and max input and output values */
248 ValueIface::maxValue(
249 getNumberFromConfig<double>(propertyMap, "MaxValue", false));
250 ValueIface::minValue(
251 getNumberFromConfig<double>(propertyMap, "MinValue", false));
252 maxValidInput =
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800253 getNumberFromConfig<double>(propertyMap, "MaxValidInput", false,
254 std::numeric_limits<double>::infinity());
Rashmica Guptae7efe132021-07-27 19:42:11 +1000255 minValidInput =
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800256 getNumberFromConfig<double>(propertyMap, "MinValidInput", false,
257 -std::numeric_limits<double>::infinity());
Rashmica Guptae7efe132021-07-27 19:42:11 +1000258}
259
Matt Spinlerce675222021-01-14 16:38:09 -0600260void VirtualSensor::initVirtualSensor(const Json& sensorConfig,
261 const std::string& objPath)
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700262{
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700263 static const Json empty{};
264
265 /* Get threshold values if defined in config */
266 auto threshold = sensorConfig.value("Threshold", empty);
Matt Spinlerf15189e2021-01-15 10:13:28 -0600267
Rashmica Gupta3e999192021-06-09 16:17:04 +1000268 createThresholds(threshold, objPath);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700269
Harvey Wuf6443742021-04-09 16:47:36 +0800270 /* Get MaxValue, MinValue setting if defined in config */
271 auto confDesc = sensorConfig.value("Desc", empty);
272 if (auto maxConf = confDesc.find("MaxValue");
273 maxConf != confDesc.end() && maxConf->is_number())
274 {
275 ValueIface::maxValue(maxConf->get<double>());
276 }
277 if (auto minConf = confDesc.find("MinValue");
278 minConf != confDesc.end() && minConf->is_number())
279 {
280 ValueIface::minValue(minConf->get<double>());
281 }
282
Lei YU0fcf0e12021-06-04 11:14:17 +0800283 /* Get optional association */
284 auto assocJson = sensorConfig.value("Associations", empty);
285 if (!assocJson.empty())
286 {
287 auto assocs = getAssociationsFromJson(assocJson);
288 if (!assocs.empty())
289 {
290 associationIface =
291 std::make_unique<AssociationObject>(bus, objPath.c_str());
292 associationIface->associations(assocs);
293 }
294 }
295
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700296 /* Get expression string */
Patrick Williams03c4c8e2022-04-14 22:19:44 -0500297 static constexpr auto exprKey = "Expression";
298 if (sensorConfig.contains(exprKey))
299 {
Patrick Williamsa9596782022-04-15 10:20:07 -0500300 auto& ref = sensorConfig.at(exprKey);
Patrick Williams03c4c8e2022-04-14 22:19:44 -0500301 if (ref.is_array())
302 {
303 exprStr = std::string{};
304 for (auto& s : ref)
305 {
306 exprStr += s;
307 }
308 }
309 else if (ref.is_string())
310 {
311 exprStr = std::string{ref};
312 }
313 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700314
315 /* Get all the parameter listed in configuration */
316 auto params = sensorConfig.value("Params", empty);
317
318 /* Check for constant parameter */
319 const auto& consParams = params.value("ConstParam", empty);
320 if (!consParams.empty())
321 {
322 for (auto& j : consParams)
323 {
324 if (j.find("ParamName") != j.end())
325 {
326 auto paramPtr = std::make_unique<SensorParam>(j["Value"]);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700327 std::string name = j["ParamName"];
328 symbols.create_variable(name);
329 paramMap.emplace(std::move(name), std::move(paramPtr));
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700330 }
331 else
332 {
333 /* Invalid configuration */
334 throw std::invalid_argument(
335 "ParamName not found in configuration");
336 }
337 }
338 }
339
Vijay Khemka7452a862020-08-11 16:01:23 -0700340 /* Check for dbus parameter */
341 auto dbusParams = params.value("DbusParam", empty);
342 if (!dbusParams.empty())
343 {
344 for (auto& j : dbusParams)
345 {
346 /* Get parameter dbus sensor descriptor */
347 auto desc = j.value("Desc", empty);
348 if ((!desc.empty()) && (j.find("ParamName") != j.end()))
349 {
350 std::string sensorType = desc.value("SensorType", "");
351 std::string name = desc.value("Name", "");
352
353 if (!sensorType.empty() && !name.empty())
354 {
George Liu1204b432021-12-29 17:24:48 +0800355 auto path = sensorDbusPath + sensorType + "/" + name;
Vijay Khemka7452a862020-08-11 16:01:23 -0700356
Vijay Khemka51f898e2020-09-09 22:24:18 -0700357 auto paramPtr =
George Liu1204b432021-12-29 17:24:48 +0800358 std::make_unique<SensorParam>(bus, path, this);
359 std::string paramName = j["ParamName"];
360 symbols.create_variable(paramName);
361 paramMap.emplace(std::move(paramName), std::move(paramPtr));
Vijay Khemka7452a862020-08-11 16:01:23 -0700362 }
363 }
364 }
365 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700366
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700367 symbols.add_constants();
Matt Spinler9f1ef4f2020-11-09 15:59:11 -0600368 symbols.add_package(vecopsPackage);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700369 expression.register_symbol_table(symbols);
370
371 /* parser from exprtk */
372 exprtk::parser<double> parser{};
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600373 if (!parser.compile(exprStr, expression))
374 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500375 error("Expression compilation failed");
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600376
377 for (std::size_t i = 0; i < parser.error_count(); ++i)
378 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500379 auto err = parser.get_error(i);
380 error("Error parsing token at {POSITION}: {ERROR}", "POSITION",
381 err.token.position, "TYPE",
382 exprtk::parser_error::to_str(err.mode), "ERROR",
383 err.diagnostic);
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600384 }
385 throw std::runtime_error("Expression compilation failed");
386 }
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700387
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700388 /* Print all parameters for debug purpose only */
389 if (DEBUG)
390 printParams(paramMap);
391}
392
Tao Lindc777012022-07-27 20:41:46 +0800393void VirtualSensor::createAssociation(const std::string& objPath,
394 const std::string& entityPath)
395{
396 if (objPath.empty() || entityPath.empty())
397 {
398 return;
399 }
400
401 std::filesystem::path p(entityPath);
402 auto assocsDbus =
403 AssociationList{{"chassis", "all_sensors", p.parent_path().string()}};
404 associationIface =
405 std::make_unique<AssociationObject>(bus, objPath.c_str());
406 associationIface->associations(assocsDbus);
407}
408
Rashmica Guptae7efe132021-07-27 19:42:11 +1000409void VirtualSensor::initVirtualSensor(const InterfaceMap& interfaceMap,
410 const std::string& objPath,
411 const std::string& sensorType,
412 const std::string& calculationIface)
413{
414 Json thresholds;
415 const std::string vsThresholdsIntf =
416 calculationIface + vsThresholdsIfaceSuffix;
417
418 for (const auto& [interface, propertyMap] : interfaceMap)
419 {
420 /* Each threshold is on it's own interface with a number as a suffix
421 * eg xyz.openbmc_project.Configuration.ModifiedMedian.Thresholds1 */
422 if (interface.find(vsThresholdsIntf) != std::string::npos)
423 {
Tao Lin91799db2022-07-27 21:02:20 +0800424 parseThresholds(thresholds, propertyMap, interface);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000425 }
426 else if (interface == calculationIface)
427 {
428 parseConfigInterface(propertyMap, sensorType, interface);
429 }
430 }
431
432 createThresholds(thresholds, objPath);
433 symbols.add_constants();
434 symbols.add_package(vecopsPackage);
435 expression.register_symbol_table(symbols);
436
Tao Lindc777012022-07-27 20:41:46 +0800437 createAssociation(objPath, entityPath);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000438 /* Print all parameters for debug purpose only */
439 if (DEBUG)
440 {
441 printParams(paramMap);
442 }
443}
444
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700445void VirtualSensor::setSensorValue(double value)
446{
Patrick Williams543bf662021-04-29 09:03:53 -0500447 value = std::clamp(value, ValueIface::minValue(), ValueIface::maxValue());
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700448 ValueIface::value(value);
449}
450
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000451double VirtualSensor::calculateValue(const std::string& calculation,
452 const VirtualSensor::ParamMap& paramMap)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000453{
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000454 auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(),
455 calculation);
456 if (itr == calculationIfaces.end())
457 {
458 return std::numeric_limits<double>::quiet_NaN();
459 }
460 else if (calculation == "xyz.openbmc_project.Configuration.ModifiedMedian")
461 {
462 return calculateModifiedMedianValue(paramMap);
463 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000464 return std::numeric_limits<double>::quiet_NaN();
465}
466
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000467bool VirtualSensor::sensorInRange(double value)
468{
469 if (value <= this->maxValidInput && value >= this->minValidInput)
470 {
471 return true;
472 }
473 return false;
474}
475
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700476void VirtualSensor::updateVirtualSensor()
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700477{
478 for (auto& param : paramMap)
479 {
480 auto& name = param.first;
481 auto& data = param.second;
482 if (auto var = symbols.get_variable(name))
483 {
484 var->ref() = data->getParamValue();
485 }
486 else
487 {
488 /* Invalid parameter */
489 throw std::invalid_argument("ParamName not found in symbols");
490 }
491 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000492 auto itr =
493 std::find(calculationIfaces.begin(), calculationIfaces.end(), exprStr);
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000494 auto val = (itr == calculationIfaces.end())
495 ? expression.value()
496 : calculateValue(exprStr, paramMap);
Vijay Khemka32a71562020-09-10 15:29:18 -0700497
498 /* Set sensor value to dbus interface */
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700499 setSensorValue(val);
Vijay Khemka32a71562020-09-10 15:29:18 -0700500
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700501 if (DEBUG)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000502 {
Patrick Williamsfbd71452021-08-30 06:59:46 -0500503 debug("Sensor {NAME} = {VALUE}", "NAME", this->name, "VALUE", val);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000504 }
Vijay Khemka32a71562020-09-10 15:29:18 -0700505
Matt Spinler8f5e6112021-01-15 10:44:32 -0600506 /* Check sensor thresholds and log required message */
Matt Spinlerb306b032021-02-01 10:05:46 -0600507 checkThresholds(val, perfLossIface);
Patrick Williamsfdb826d2021-01-20 14:37:53 -0600508 checkThresholds(val, warningIface);
509 checkThresholds(val, criticalIface);
510 checkThresholds(val, softShutdownIface);
511 checkThresholds(val, hardShutdownIface);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700512}
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700513
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000514double VirtualSensor::calculateModifiedMedianValue(
515 const VirtualSensor::ParamMap& paramMap)
516{
517 std::vector<double> values;
518
519 for (auto& param : paramMap)
520 {
521 auto& name = param.first;
522 if (auto var = symbols.get_variable(name))
523 {
524 if (!sensorInRange(var->ref()))
525 {
526 continue;
527 }
528 values.push_back(var->ref());
529 }
530 }
531
532 size_t size = values.size();
533 std::sort(values.begin(), values.end());
534 switch (size)
535 {
536 case 2:
537 /* Choose biggest value */
538 return values.at(1);
539 case 0:
540 return std::numeric_limits<double>::quiet_NaN();
541 default:
542 /* Choose median value */
543 if (size % 2 == 0)
544 {
545 // Average of the two middle values
546 return (values.at(size / 2) + values.at(size / 2 - 1)) / 2;
547 }
548 else
549 {
550 return values.at((size - 1) / 2);
551 }
552 }
553}
554
Rashmica Gupta3e999192021-06-09 16:17:04 +1000555void VirtualSensor::createThresholds(const Json& threshold,
556 const std::string& objPath)
557{
558 if (threshold.empty())
559 {
560 return;
561 }
562 // Only create the threshold interfaces if
563 // at least one of their values is present.
564 if (threshold.contains("CriticalHigh") || threshold.contains("CriticalLow"))
565 {
566 criticalIface =
567 std::make_unique<Threshold<CriticalObject>>(bus, objPath.c_str());
568
569 criticalIface->criticalHigh(threshold.value(
570 "CriticalHigh", std::numeric_limits<double>::quiet_NaN()));
571 criticalIface->criticalLow(threshold.value(
572 "CriticalLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000573 criticalIface->setHighHysteresis(
574 threshold.value("CriticalHighHysteresis", defaultHysteresis));
575 criticalIface->setLowHysteresis(
576 threshold.value("CriticalLowHysteresis", defaultHysteresis));
Tao Lin91799db2022-07-27 21:02:20 +0800577
578 if (threshold.contains("CriticalHigh"))
579 {
580 criticalIface->setEntityInterfaceHigh(
581 threshold.value("CriticalHighDirection", ""));
582 if (DEBUG)
583 {
584 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
585 "INTF", threshold.value("CriticalHighDirection", ""));
586 }
587 }
588 if (threshold.contains("CriticalLow"))
589 {
590 criticalIface->setEntityInterfaceLow(
591 threshold.value("CriticalLowDirection", ""));
592 if (DEBUG)
593 {
594 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
595 "INTF", threshold.value("CriticalLowDirection", ""));
596 }
597 }
598
599 criticalIface->setEntityPath(entityPath);
600 if (DEBUG)
601 {
602 debug("Sensor Threshold:{NAME} = path:{PATH}", "NAME", objPath,
603 "PATH", entityPath);
604 }
Rashmica Gupta3e999192021-06-09 16:17:04 +1000605 }
606
607 if (threshold.contains("WarningHigh") || threshold.contains("WarningLow"))
608 {
609 warningIface =
610 std::make_unique<Threshold<WarningObject>>(bus, objPath.c_str());
611
612 warningIface->warningHigh(threshold.value(
613 "WarningHigh", std::numeric_limits<double>::quiet_NaN()));
614 warningIface->warningLow(threshold.value(
615 "WarningLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000616 warningIface->setHighHysteresis(
617 threshold.value("WarningHighHysteresis", defaultHysteresis));
618 warningIface->setLowHysteresis(
619 threshold.value("WarningLowHysteresis", defaultHysteresis));
Tao Lin91799db2022-07-27 21:02:20 +0800620
621 if (threshold.contains("WarningHigh"))
622 {
623 warningIface->setEntityInterfaceHigh(
624 threshold.value("WarningHighDirection", ""));
625 if (DEBUG)
626 {
627 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
628 "INTF", threshold.value("WarningHighDirection", ""));
629 }
630 }
631 if (threshold.contains("WarningLow"))
632 {
633 warningIface->setEntityInterfaceLow(
634 threshold.value("WarningLowDirection", ""));
635 if (DEBUG)
636 {
637 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
638 "INTF", threshold.value("WarningLowDirection", ""));
639 }
640 }
641
642 warningIface->setEntityPath(entityPath);
643 if (DEBUG)
644 {
645 debug("Sensor Threshold:{NAME} = path:{PATH}", "NAME", objPath,
646 "PATH", entityPath);
647 }
Rashmica Gupta3e999192021-06-09 16:17:04 +1000648 }
649
650 if (threshold.contains("HardShutdownHigh") ||
651 threshold.contains("HardShutdownLow"))
652 {
653 hardShutdownIface = std::make_unique<Threshold<HardShutdownObject>>(
654 bus, objPath.c_str());
655
656 hardShutdownIface->hardShutdownHigh(threshold.value(
657 "HardShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
658 hardShutdownIface->hardShutdownLow(threshold.value(
659 "HardShutdownLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000660 hardShutdownIface->setHighHysteresis(
661 threshold.value("HardShutdownHighHysteresis", defaultHysteresis));
662 hardShutdownIface->setLowHysteresis(
663 threshold.value("HardShutdownLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000664 }
665
666 if (threshold.contains("SoftShutdownHigh") ||
667 threshold.contains("SoftShutdownLow"))
668 {
669 softShutdownIface = std::make_unique<Threshold<SoftShutdownObject>>(
670 bus, objPath.c_str());
671
672 softShutdownIface->softShutdownHigh(threshold.value(
673 "SoftShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
674 softShutdownIface->softShutdownLow(threshold.value(
675 "SoftShutdownLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000676 softShutdownIface->setHighHysteresis(
677 threshold.value("SoftShutdownHighHysteresis", defaultHysteresis));
678 softShutdownIface->setLowHysteresis(
679 threshold.value("SoftShutdownLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000680 }
681
682 if (threshold.contains("PerformanceLossHigh") ||
683 threshold.contains("PerformanceLossLow"))
684 {
685 perfLossIface = std::make_unique<Threshold<PerformanceLossObject>>(
686 bus, objPath.c_str());
687
688 perfLossIface->performanceLossHigh(threshold.value(
689 "PerformanceLossHigh", std::numeric_limits<double>::quiet_NaN()));
690 perfLossIface->performanceLossLow(threshold.value(
691 "PerformanceLossLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000692 perfLossIface->setHighHysteresis(threshold.value(
693 "PerformanceLossHighHysteresis", defaultHysteresis));
694 perfLossIface->setLowHysteresis(
695 threshold.value("PerformanceLossLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000696 }
697}
698
Rashmica Guptae7efe132021-07-27 19:42:11 +1000699ManagedObjectType VirtualSensors::getObjectsFromDBus()
700{
701 ManagedObjectType objects;
702
703 try
704 {
Nan Zhouf6825b92022-09-20 20:52:43 +0000705 auto method = bus.new_method_call("xyz.openbmc_project.EntityManager",
706 "/xyz/openbmc_project/inventory",
Rashmica Guptae7efe132021-07-27 19:42:11 +1000707 "org.freedesktop.DBus.ObjectManager",
708 "GetManagedObjects");
709 auto reply = bus.call(method);
710 reply.read(objects);
711 }
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500712 catch (const sdbusplus::exception_t& ex)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000713 {
714 // If entity manager isn't running yet, keep going.
715 if (std::string("org.freedesktop.DBus.Error.ServiceUnknown") !=
716 ex.name())
717 {
Matt Spinler71b9c112022-10-18 09:14:45 -0500718 error("Could not reach entity-manager: {ERROR}", "ERROR", ex);
719 throw;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000720 }
721 }
722
723 return objects;
724}
725
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500726void VirtualSensors::propertiesChanged(sdbusplus::message_t& msg)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000727{
728 std::string path;
729 PropertyMap properties;
730
731 msg.read(path, properties);
732
733 /* We get multiple callbacks for one sensor. 'Type' is a required field and
734 * is a unique label so use to to only proceed once per sensor */
735 if (properties.contains("Type"))
736 {
737 if (isCalculationType(path))
738 {
739 createVirtualSensorsFromDBus(path);
740 }
741 }
742}
743
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700744/** @brief Parsing Virtual Sensor config JSON file */
George Liu1204b432021-12-29 17:24:48 +0800745Json VirtualSensors::parseConfigFile(const std::string& configFile)
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700746{
747 std::ifstream jsonFile(configFile);
748 if (!jsonFile.is_open())
749 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500750 error("config JSON file {FILENAME} not found", "FILENAME", configFile);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000751 return {};
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700752 }
753
754 auto data = Json::parse(jsonFile, nullptr, false);
755 if (data.is_discarded())
756 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500757 error("config readings JSON parser failure with {FILENAME}", "FILENAME",
758 configFile);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700759 throw std::exception{};
760 }
761
762 return data;
763}
764
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700765std::map<std::string, ValueIface::Unit> unitMap = {
766 {"temperature", ValueIface::Unit::DegreesC},
767 {"fan_tach", ValueIface::Unit::RPMS},
768 {"voltage", ValueIface::Unit::Volts},
769 {"altitude", ValueIface::Unit::Meters},
770 {"current", ValueIface::Unit::Amperes},
771 {"power", ValueIface::Unit::Watts},
772 {"energy", ValueIface::Unit::Joules},
Kumar Thangavel2b56ddb2021-01-13 20:16:11 +0530773 {"utilization", ValueIface::Unit::Percent},
Rashmica Gupta4ac7a7f2021-07-08 12:30:50 +1000774 {"airflow", ValueIface::Unit::CFM},
775 {"pressure", ValueIface::Unit::Pascals}};
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700776
Rashmica Guptae7efe132021-07-27 19:42:11 +1000777const std::string getSensorTypeFromUnit(const std::string& unit)
778{
779 std::string unitPrefix = "xyz.openbmc_project.Sensor.Value.Unit.";
780 for (auto [type, unitObj] : unitMap)
781 {
782 auto unitPath = ValueIface::convertUnitToString(unitObj);
783 if (unitPath == (unitPrefix + unit))
784 {
785 return type;
786 }
787 }
788 return "";
789}
790
791void VirtualSensors::setupMatches()
792{
793 /* Already setup */
794 if (!this->matches.empty())
795 {
796 return;
797 }
798
799 /* Setup matches */
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500800 auto eventHandler = [this](sdbusplus::message_t& message) {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000801 if (message.is_method_error())
802 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500803 error("Callback method error");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000804 return;
805 }
806 this->propertiesChanged(message);
807 };
808
809 for (const char* iface : calculationIfaces)
810 {
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500811 auto match = std::make_unique<sdbusplus::bus::match_t>(
Rashmica Guptae7efe132021-07-27 19:42:11 +1000812 bus,
813 sdbusplus::bus::match::rules::propertiesChangedNamespace(
814 "/xyz/openbmc_project/inventory", iface),
815 eventHandler);
816 this->matches.emplace_back(std::move(match));
817 }
818}
819
820void VirtualSensors::createVirtualSensorsFromDBus(
821 const std::string& calculationIface)
822{
823 if (calculationIface.empty())
824 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500825 error("No calculation type supplied");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000826 return;
827 }
828 auto objects = getObjectsFromDBus();
829
830 /* Get virtual sensors config data */
831 for (const auto& [path, interfaceMap] : objects)
832 {
833 auto objpath = static_cast<std::string>(path);
834 std::string name = path.filename();
835 std::string sensorType, sensorUnit;
836
837 /* Find Virtual Sensor interfaces */
838 if (!interfaceMap.contains(calculationIface))
839 {
840 continue;
841 }
842 if (name.empty())
843 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500844 error("Virtual Sensor name not found in entity manager config");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000845 continue;
846 }
847 if (virtualSensorsMap.contains(name))
848 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500849 error("A virtual sensor named {NAME} already exists", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000850 continue;
851 }
852
853 /* Extract the virtual sensor type as we need this to initialize the
854 * sensor */
855 for (const auto& [interface, propertyMap] : interfaceMap)
856 {
857 if (interface != calculationIface)
858 {
859 continue;
860 }
861 auto itr = propertyMap.find("Units");
862 if (itr != propertyMap.end())
863 {
864 sensorUnit = std::get<std::string>(itr->second);
865 break;
866 }
867 }
868 sensorType = getSensorTypeFromUnit(sensorUnit);
869 if (sensorType.empty())
870 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500871 error("Sensor unit type {TYPE} is not supported", "TYPE",
872 sensorUnit);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000873 continue;
874 }
875
876 try
877 {
878 auto virtObjPath = sensorDbusPath + sensorType + "/" + name;
879
880 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
881 bus, virtObjPath.c_str(), interfaceMap, name, sensorType,
Tao Lindc777012022-07-27 20:41:46 +0800882 calculationIface, objpath);
Patrick Williams82b39c62021-07-28 16:22:27 -0500883 info("Added a new virtual sensor: {NAME} {TYPE}", "NAME", name,
884 "TYPE", sensorType);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000885 virtualSensorPtr->updateVirtualSensor();
886
887 /* Initialize unit value for virtual sensor */
888 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
889 virtualSensorPtr->emit_object_added();
890
891 virtualSensorsMap.emplace(name, std::move(virtualSensorPtr));
892
893 /* Setup match for interfaces removed */
894 auto intfRemoved = [this, objpath,
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500895 name](sdbusplus::message_t& message) {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000896 if (!virtualSensorsMap.contains(name))
897 {
898 return;
899 }
900 sdbusplus::message::object_path path;
901 message.read(path);
902 if (static_cast<const std::string&>(path) == objpath)
903 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500904 info("Removed a virtual sensor: {NAME}", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000905 virtualSensorsMap.erase(name);
906 }
907 };
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500908 auto matchOnRemove = std::make_unique<sdbusplus::bus::match_t>(
Rashmica Guptae7efe132021-07-27 19:42:11 +1000909 bus,
910 sdbusplus::bus::match::rules::interfacesRemoved() +
911 sdbusplus::bus::match::rules::argNpath(0, objpath),
912 intfRemoved);
913 /* TODO: slight race condition here. Check that the config still
914 * exists */
915 this->matches.emplace_back(std::move(matchOnRemove));
916 }
Patrick Williamsdac26632021-10-06 14:38:47 -0500917 catch (const std::invalid_argument& ia)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000918 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500919 error("Failed to set up virtual sensor: {ERROR}", "ERROR", ia);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000920 }
921 }
922}
923
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700924void VirtualSensors::createVirtualSensors()
925{
926 static const Json empty{};
927
928 auto data = parseConfigFile(VIRTUAL_SENSOR_CONFIG_FILE);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000929
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700930 // print values
931 if (DEBUG)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000932 {
Patrick Williamsfbd71452021-08-30 06:59:46 -0500933 debug("JSON: {JSON}", "JSON", data.dump());
Rashmica Guptae7efe132021-07-27 19:42:11 +1000934 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700935
936 /* Get virtual sensors config data */
937 for (const auto& j : data)
938 {
939 auto desc = j.value("Desc", empty);
940 if (!desc.empty())
941 {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000942 if (desc.value("Config", "") == "D-Bus")
943 {
944 /* Look on D-Bus for a virtual sensor config. Set up matches
945 * first because the configs may not be on D-Bus yet and we
946 * don't want to miss them */
947 setupMatches();
948
949 if (desc.contains("Type"))
950 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500951 auto type = desc.value("Type", "");
952 auto path = "xyz.openbmc_project.Configuration." + type;
953
Rashmica Guptae7efe132021-07-27 19:42:11 +1000954 if (!isCalculationType(path))
955 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500956 error("Invalid calculation type {TYPE} supplied.",
957 "TYPE", type);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000958 continue;
959 }
960 createVirtualSensorsFromDBus(path);
961 }
962 continue;
963 }
964
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700965 std::string sensorType = desc.value("SensorType", "");
966 std::string name = desc.value("Name", "");
Rashmica Gupta665a0a22021-06-30 11:35:28 +1000967 std::replace(name.begin(), name.end(), ' ', '_');
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700968
969 if (!name.empty() && !sensorType.empty())
970 {
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700971 if (unitMap.find(sensorType) == unitMap.end())
972 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500973 error("Sensor type {TYPE} is not supported", "TYPE",
974 sensorType);
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700975 }
976 else
977 {
Rashmica Gupta67d8b9d2021-06-30 11:41:14 +1000978 if (virtualSensorsMap.find(name) != virtualSensorsMap.end())
979 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500980 error("A virtual sensor named {NAME} already exists",
981 "NAME", name);
Rashmica Gupta67d8b9d2021-06-30 11:41:14 +1000982 continue;
983 }
Rashmica Gupta862c3d12021-08-06 12:19:31 +1000984 auto objPath = sensorDbusPath + sensorType + "/" + name;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700985
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700986 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
987 bus, objPath.c_str(), j, name);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700988
Patrick Williams82b39c62021-07-28 16:22:27 -0500989 info("Added a new virtual sensor: {NAME}", "NAME", name);
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700990 virtualSensorPtr->updateVirtualSensor();
991
992 /* Initialize unit value for virtual sensor */
993 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
Rashmica Guptaa2fa63a2021-08-06 12:21:13 +1000994 virtualSensorPtr->emit_object_added();
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700995
996 virtualSensorsMap.emplace(std::move(name),
997 std::move(virtualSensorPtr));
998 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700999 }
1000 else
1001 {
Patrick Williams82b39c62021-07-28 16:22:27 -05001002 error(
1003 "Sensor type ({TYPE}) or name ({NAME}) not found in config file",
1004 "NAME", name, "TYPE", sensorType);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001005 }
1006 }
1007 else
1008 {
Patrick Williams82b39c62021-07-28 16:22:27 -05001009 error("Descriptor for new virtual sensor not found in config file");
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001010 }
1011 }
1012}
1013
1014} // namespace virtualSensor
1015} // namespace phosphor
1016
1017/**
1018 * @brief Main
1019 */
1020int main()
1021{
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001022 // Get a handle to system dbus
1023 auto bus = sdbusplus::bus::new_default();
1024
Matt Spinler6c19e7d2021-01-12 16:26:45 -06001025 // Add the ObjectManager interface
Ed Tanousf7ec40a2022-10-04 17:39:40 -07001026 sdbusplus::server::manager_t objManager(bus,
1027 "/xyz/openbmc_project/sensors");
Matt Spinler6c19e7d2021-01-12 16:26:45 -06001028
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001029 // Create an virtual sensors object
1030 phosphor::virtualSensor::VirtualSensors virtualSensors(bus);
1031
1032 // Request service bus name
1033 bus.request_name(busName);
1034
Patrick Williamse6672392022-09-02 09:03:24 -05001035 // Run the dbus loop.
1036 bus.process_loop();
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001037
1038 return 0;
1039}