blob: bf95bc4001f224f9edcd52a9e53e956ca74c9868 [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#include <sdeventplus/event.hpp>
7
8#include <fstream>
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07009
10static constexpr bool DEBUG = false;
11static constexpr auto busName = "xyz.openbmc_project.VirtualSensor";
12static constexpr auto sensorDbusPath = "/xyz/openbmc_project/sensors/";
Rashmica Guptae7efe132021-07-27 19:42:11 +100013static constexpr auto entityManagerBusName =
14 "xyz.openbmc_project.EntityManager";
15static constexpr auto vsThresholdsIfaceSuffix = ".Thresholds";
Rashmica Gupta304fd0e2021-08-10 16:50:44 +100016static constexpr std::array<const char*, 1> calculationIfaces = {
17 "xyz.openbmc_project.Configuration.ModifiedMedian"};
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +100018static constexpr auto defaultHysteresis = 0;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070019
Patrick Williams82b39c62021-07-28 16:22:27 -050020PHOSPHOR_LOG2_USING_WITH_FLAGS;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070021
Vijay Khemka51f898e2020-09-09 22:24:18 -070022int handleDbusSignal(sd_bus_message* msg, void* usrData, sd_bus_error*)
23{
24 if (usrData == nullptr)
25 {
26 throw std::runtime_error("Invalid match");
27 }
28
Patrick Williams8e11ccc2022-07-22 19:26:57 -050029 auto sdbpMsg = sdbusplus::message_t(msg);
Vijay Khemka51f898e2020-09-09 22:24:18 -070030 std::string msgIfce;
31 std::map<std::string, std::variant<int64_t, double, bool>> msgData;
32
33 sdbpMsg.read(msgIfce, msgData);
34
35 if (msgData.find("Value") != msgData.end())
36 {
37 using namespace phosphor::virtualSensor;
38 VirtualSensor* obj = static_cast<VirtualSensor*>(usrData);
39 // TODO(openbmc/phosphor-virtual-sensor#1): updateVirtualSensor should
40 // be changed to take the information we got from the signal, to avoid
41 // having to do numerous dbus queries.
42 obj->updateVirtualSensor();
43 }
44 return 0;
45}
46
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070047namespace phosphor
48{
49namespace virtualSensor
50{
51
52void printParams(const VirtualSensor::ParamMap& paramMap)
53{
54 for (const auto& p : paramMap)
55 {
56 const auto& p1 = p.first;
57 const auto& p2 = p.second;
58 auto val = p2->getParamValue();
Patrick Williamsfbd71452021-08-30 06:59:46 -050059 debug("Parameter: {PARAM} = {VALUE}", "PARAM", p1, "VALUE", val);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070060 }
61}
62
63double SensorParam::getParamValue()
64{
65 switch (paramType)
66 {
67 case constParam:
68 return value;
69 break;
Vijay Khemka7452a862020-08-11 16:01:23 -070070 case dbusParam:
71 return dbusSensor->getSensorValue();
72 break;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070073 default:
74 throw std::invalid_argument("param type not supported");
75 }
76}
77
Lei YU0fcf0e12021-06-04 11:14:17 +080078using AssociationList =
79 std::vector<std::tuple<std::string, std::string, std::string>>;
80
81AssociationList getAssociationsFromJson(const Json& j)
82{
83 AssociationList assocs{};
84 try
85 {
86 j.get_to(assocs);
87 }
88 catch (const std::exception& ex)
89 {
Patrick Williams82b39c62021-07-28 16:22:27 -050090 error("Failed to parse association: {ERROR}", "ERROR", ex);
Lei YU0fcf0e12021-06-04 11:14:17 +080091 }
92 return assocs;
93}
94
Rashmica Guptae7efe132021-07-27 19:42:11 +100095template <typename U>
96struct VariantToNumber
97{
98 template <typename T>
99 U operator()(const T& t) const
100 {
101 if constexpr (std::is_convertible<T, U>::value)
102 {
103 return static_cast<U>(t);
104 }
105 throw std::invalid_argument("Invalid number type in config\n");
106 }
107};
108
109template <typename U>
110U getNumberFromConfig(const PropertyMap& map, const std::string& name,
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800111 bool required,
112 U defaultValue = std::numeric_limits<U>::quiet_NaN())
Rashmica Guptae7efe132021-07-27 19:42:11 +1000113{
114 if (auto itr = map.find(name); itr != map.end())
115 {
116 return std::visit(VariantToNumber<U>(), itr->second);
117 }
118 else if (required)
119 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500120 error("Required field {NAME} missing in config", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000121 throw std::invalid_argument("Required field missing in config");
122 }
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800123 return defaultValue;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000124}
125
126bool isCalculationType(const std::string& interface)
127{
128 auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(),
129 interface);
130 if (itr != calculationIfaces.end())
131 {
132 return true;
133 }
134 return false;
135}
136
137const std::string getThresholdType(const std::string& direction,
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100138 const std::string& severity)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000139{
Rashmica Guptae7efe132021-07-27 19:42:11 +1000140 std::string suffix;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000141
142 if (direction == "less than")
143 {
144 suffix = "Low";
145 }
146 else if (direction == "greater than")
147 {
148 suffix = "High";
149 }
150 else
151 {
152 throw std::invalid_argument(
153 "Invalid threshold direction specified in entity manager");
154 }
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100155 return severity + suffix;
156}
157
158std::string getSeverityField(const PropertyMap& propertyMap)
159{
160 static const std::array thresholdTypes{"Warning", "Critical",
161 "PerformanceLoss", "SoftShutdown",
162 "HardShutdown"};
163
164 std::string severity;
165 if (auto itr = propertyMap.find("Severity"); itr != propertyMap.end())
166 {
167 /* Severity should be a string, but can be an unsigned int */
168 if (std::holds_alternative<std::string>(itr->second))
169 {
170 severity = std::get<std::string>(itr->second);
171 if (0 == std::ranges::count(thresholdTypes, severity))
172 {
173 throw std::invalid_argument(
174 "Invalid threshold severity specified in entity manager");
175 }
176 }
177 else
178 {
179 auto sev =
180 getNumberFromConfig<uint64_t>(propertyMap, "Severity", true);
181 /* Checking bounds ourselves so we throw invalid argument on
182 * invalid user input */
183 if (sev >= thresholdTypes.size())
184 {
185 throw std::invalid_argument(
186 "Invalid threshold severity specified in entity manager");
187 }
188 severity = thresholdTypes.at(sev);
189 }
190 }
191 return severity;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000192}
193
194void parseThresholds(Json& thresholds, const PropertyMap& propertyMap)
195{
196 std::string direction;
197
Rashmica Guptae7efe132021-07-27 19:42:11 +1000198 auto value = getNumberFromConfig<double>(propertyMap, "Value", true);
199
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100200 auto severity = getSeverityField(propertyMap);
201
202 if (auto itr = propertyMap.find("Direction"); itr != propertyMap.end())
Rashmica Guptae7efe132021-07-27 19:42:11 +1000203 {
204 direction = std::get<std::string>(itr->second);
205 }
206
207 auto threshold = getThresholdType(direction, severity);
208 thresholds[threshold] = value;
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000209
210 auto hysteresis =
211 getNumberFromConfig<double>(propertyMap, "Hysteresis", false);
212 if (hysteresis != std::numeric_limits<double>::quiet_NaN())
213 {
214 thresholds[threshold + "Hysteresis"] = hysteresis;
215 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000216}
217
218void VirtualSensor::parseConfigInterface(const PropertyMap& propertyMap,
219 const std::string& sensorType,
220 const std::string& interface)
221{
222 /* Parse sensors / DBus params */
223 if (auto itr = propertyMap.find("Sensors"); itr != propertyMap.end())
224 {
225 auto sensors = std::get<std::vector<std::string>>(itr->second);
226 for (auto sensor : sensors)
227 {
228 std::replace(sensor.begin(), sensor.end(), ' ', '_');
229 auto sensorObjPath = sensorDbusPath + sensorType + "/" + sensor;
230
231 auto paramPtr =
232 std::make_unique<SensorParam>(bus, sensorObjPath, this);
233 symbols.create_variable(sensor);
234 paramMap.emplace(std::move(sensor), std::move(paramPtr));
235 }
236 }
237 /* Get expression string */
238 if (!isCalculationType(interface))
239 {
240 throw std::invalid_argument("Invalid expression in interface");
241 }
242 exprStr = interface;
243
244 /* Get optional min and max input and output values */
245 ValueIface::maxValue(
246 getNumberFromConfig<double>(propertyMap, "MaxValue", false));
247 ValueIface::minValue(
248 getNumberFromConfig<double>(propertyMap, "MinValue", false));
249 maxValidInput =
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800250 getNumberFromConfig<double>(propertyMap, "MaxValidInput", false,
251 std::numeric_limits<double>::infinity());
Rashmica Guptae7efe132021-07-27 19:42:11 +1000252 minValidInput =
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800253 getNumberFromConfig<double>(propertyMap, "MinValidInput", false,
254 -std::numeric_limits<double>::infinity());
Rashmica Guptae7efe132021-07-27 19:42:11 +1000255}
256
Matt Spinlerce675222021-01-14 16:38:09 -0600257void VirtualSensor::initVirtualSensor(const Json& sensorConfig,
258 const std::string& objPath)
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700259{
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700260 static const Json empty{};
261
262 /* Get threshold values if defined in config */
263 auto threshold = sensorConfig.value("Threshold", empty);
Matt Spinlerf15189e2021-01-15 10:13:28 -0600264
Rashmica Gupta3e999192021-06-09 16:17:04 +1000265 createThresholds(threshold, objPath);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700266
Harvey Wuf6443742021-04-09 16:47:36 +0800267 /* Get MaxValue, MinValue setting if defined in config */
268 auto confDesc = sensorConfig.value("Desc", empty);
269 if (auto maxConf = confDesc.find("MaxValue");
270 maxConf != confDesc.end() && maxConf->is_number())
271 {
272 ValueIface::maxValue(maxConf->get<double>());
273 }
274 if (auto minConf = confDesc.find("MinValue");
275 minConf != confDesc.end() && minConf->is_number())
276 {
277 ValueIface::minValue(minConf->get<double>());
278 }
279
Lei YU0fcf0e12021-06-04 11:14:17 +0800280 /* Get optional association */
281 auto assocJson = sensorConfig.value("Associations", empty);
282 if (!assocJson.empty())
283 {
284 auto assocs = getAssociationsFromJson(assocJson);
285 if (!assocs.empty())
286 {
287 associationIface =
288 std::make_unique<AssociationObject>(bus, objPath.c_str());
289 associationIface->associations(assocs);
290 }
291 }
292
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700293 /* Get expression string */
Patrick Williams03c4c8e2022-04-14 22:19:44 -0500294 static constexpr auto exprKey = "Expression";
295 if (sensorConfig.contains(exprKey))
296 {
Patrick Williamsa9596782022-04-15 10:20:07 -0500297 auto& ref = sensorConfig.at(exprKey);
Patrick Williams03c4c8e2022-04-14 22:19:44 -0500298 if (ref.is_array())
299 {
300 exprStr = std::string{};
301 for (auto& s : ref)
302 {
303 exprStr += s;
304 }
305 }
306 else if (ref.is_string())
307 {
308 exprStr = std::string{ref};
309 }
310 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700311
312 /* Get all the parameter listed in configuration */
313 auto params = sensorConfig.value("Params", empty);
314
315 /* Check for constant parameter */
316 const auto& consParams = params.value("ConstParam", empty);
317 if (!consParams.empty())
318 {
319 for (auto& j : consParams)
320 {
321 if (j.find("ParamName") != j.end())
322 {
323 auto paramPtr = std::make_unique<SensorParam>(j["Value"]);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700324 std::string name = j["ParamName"];
325 symbols.create_variable(name);
326 paramMap.emplace(std::move(name), std::move(paramPtr));
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700327 }
328 else
329 {
330 /* Invalid configuration */
331 throw std::invalid_argument(
332 "ParamName not found in configuration");
333 }
334 }
335 }
336
Vijay Khemka7452a862020-08-11 16:01:23 -0700337 /* Check for dbus parameter */
338 auto dbusParams = params.value("DbusParam", empty);
339 if (!dbusParams.empty())
340 {
341 for (auto& j : dbusParams)
342 {
343 /* Get parameter dbus sensor descriptor */
344 auto desc = j.value("Desc", empty);
345 if ((!desc.empty()) && (j.find("ParamName") != j.end()))
346 {
347 std::string sensorType = desc.value("SensorType", "");
348 std::string name = desc.value("Name", "");
349
350 if (!sensorType.empty() && !name.empty())
351 {
George Liu1204b432021-12-29 17:24:48 +0800352 auto path = sensorDbusPath + sensorType + "/" + name;
Vijay Khemka7452a862020-08-11 16:01:23 -0700353
Vijay Khemka51f898e2020-09-09 22:24:18 -0700354 auto paramPtr =
George Liu1204b432021-12-29 17:24:48 +0800355 std::make_unique<SensorParam>(bus, path, this);
356 std::string paramName = j["ParamName"];
357 symbols.create_variable(paramName);
358 paramMap.emplace(std::move(paramName), std::move(paramPtr));
Vijay Khemka7452a862020-08-11 16:01:23 -0700359 }
360 }
361 }
362 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700363
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700364 symbols.add_constants();
Matt Spinler9f1ef4f2020-11-09 15:59:11 -0600365 symbols.add_package(vecopsPackage);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700366 expression.register_symbol_table(symbols);
367
368 /* parser from exprtk */
369 exprtk::parser<double> parser{};
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600370 if (!parser.compile(exprStr, expression))
371 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500372 error("Expression compilation failed");
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600373
374 for (std::size_t i = 0; i < parser.error_count(); ++i)
375 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500376 auto err = parser.get_error(i);
377 error("Error parsing token at {POSITION}: {ERROR}", "POSITION",
378 err.token.position, "TYPE",
379 exprtk::parser_error::to_str(err.mode), "ERROR",
380 err.diagnostic);
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600381 }
382 throw std::runtime_error("Expression compilation failed");
383 }
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700384
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700385 /* Print all parameters for debug purpose only */
386 if (DEBUG)
387 printParams(paramMap);
388}
389
Rashmica Guptae7efe132021-07-27 19:42:11 +1000390void VirtualSensor::initVirtualSensor(const InterfaceMap& interfaceMap,
391 const std::string& objPath,
392 const std::string& sensorType,
393 const std::string& calculationIface)
394{
395 Json thresholds;
396 const std::string vsThresholdsIntf =
397 calculationIface + vsThresholdsIfaceSuffix;
398
399 for (const auto& [interface, propertyMap] : interfaceMap)
400 {
401 /* Each threshold is on it's own interface with a number as a suffix
402 * eg xyz.openbmc_project.Configuration.ModifiedMedian.Thresholds1 */
403 if (interface.find(vsThresholdsIntf) != std::string::npos)
404 {
405 parseThresholds(thresholds, propertyMap);
406 }
407 else if (interface == calculationIface)
408 {
409 parseConfigInterface(propertyMap, sensorType, interface);
410 }
411 }
412
413 createThresholds(thresholds, objPath);
414 symbols.add_constants();
415 symbols.add_package(vecopsPackage);
416 expression.register_symbol_table(symbols);
417
418 /* Print all parameters for debug purpose only */
419 if (DEBUG)
420 {
421 printParams(paramMap);
422 }
423}
424
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700425void VirtualSensor::setSensorValue(double value)
426{
Patrick Williams543bf662021-04-29 09:03:53 -0500427 value = std::clamp(value, ValueIface::minValue(), ValueIface::maxValue());
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700428 ValueIface::value(value);
429}
430
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000431double VirtualSensor::calculateValue(const std::string& calculation,
432 const VirtualSensor::ParamMap& paramMap)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000433{
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000434 auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(),
435 calculation);
436 if (itr == calculationIfaces.end())
437 {
438 return std::numeric_limits<double>::quiet_NaN();
439 }
440 else if (calculation == "xyz.openbmc_project.Configuration.ModifiedMedian")
441 {
442 return calculateModifiedMedianValue(paramMap);
443 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000444 return std::numeric_limits<double>::quiet_NaN();
445}
446
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000447bool VirtualSensor::sensorInRange(double value)
448{
449 if (value <= this->maxValidInput && value >= this->minValidInput)
450 {
451 return true;
452 }
453 return false;
454}
455
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700456void VirtualSensor::updateVirtualSensor()
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700457{
458 for (auto& param : paramMap)
459 {
460 auto& name = param.first;
461 auto& data = param.second;
462 if (auto var = symbols.get_variable(name))
463 {
464 var->ref() = data->getParamValue();
465 }
466 else
467 {
468 /* Invalid parameter */
469 throw std::invalid_argument("ParamName not found in symbols");
470 }
471 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000472 auto itr =
473 std::find(calculationIfaces.begin(), calculationIfaces.end(), exprStr);
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000474 auto val = (itr == calculationIfaces.end())
475 ? expression.value()
476 : calculateValue(exprStr, paramMap);
Vijay Khemka32a71562020-09-10 15:29:18 -0700477
478 /* Set sensor value to dbus interface */
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700479 setSensorValue(val);
Vijay Khemka32a71562020-09-10 15:29:18 -0700480
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700481 if (DEBUG)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000482 {
Patrick Williamsfbd71452021-08-30 06:59:46 -0500483 debug("Sensor {NAME} = {VALUE}", "NAME", this->name, "VALUE", val);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000484 }
Vijay Khemka32a71562020-09-10 15:29:18 -0700485
Matt Spinler8f5e6112021-01-15 10:44:32 -0600486 /* Check sensor thresholds and log required message */
Matt Spinlerb306b032021-02-01 10:05:46 -0600487 checkThresholds(val, perfLossIface);
Patrick Williamsfdb826d2021-01-20 14:37:53 -0600488 checkThresholds(val, warningIface);
489 checkThresholds(val, criticalIface);
490 checkThresholds(val, softShutdownIface);
491 checkThresholds(val, hardShutdownIface);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700492}
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700493
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000494double VirtualSensor::calculateModifiedMedianValue(
495 const VirtualSensor::ParamMap& paramMap)
496{
497 std::vector<double> values;
498
499 for (auto& param : paramMap)
500 {
501 auto& name = param.first;
502 if (auto var = symbols.get_variable(name))
503 {
504 if (!sensorInRange(var->ref()))
505 {
506 continue;
507 }
508 values.push_back(var->ref());
509 }
510 }
511
512 size_t size = values.size();
513 std::sort(values.begin(), values.end());
514 switch (size)
515 {
516 case 2:
517 /* Choose biggest value */
518 return values.at(1);
519 case 0:
520 return std::numeric_limits<double>::quiet_NaN();
521 default:
522 /* Choose median value */
523 if (size % 2 == 0)
524 {
525 // Average of the two middle values
526 return (values.at(size / 2) + values.at(size / 2 - 1)) / 2;
527 }
528 else
529 {
530 return values.at((size - 1) / 2);
531 }
532 }
533}
534
Rashmica Gupta3e999192021-06-09 16:17:04 +1000535void VirtualSensor::createThresholds(const Json& threshold,
536 const std::string& objPath)
537{
538 if (threshold.empty())
539 {
540 return;
541 }
542 // Only create the threshold interfaces if
543 // at least one of their values is present.
544 if (threshold.contains("CriticalHigh") || threshold.contains("CriticalLow"))
545 {
546 criticalIface =
547 std::make_unique<Threshold<CriticalObject>>(bus, objPath.c_str());
548
549 criticalIface->criticalHigh(threshold.value(
550 "CriticalHigh", std::numeric_limits<double>::quiet_NaN()));
551 criticalIface->criticalLow(threshold.value(
552 "CriticalLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000553 criticalIface->setHighHysteresis(
554 threshold.value("CriticalHighHysteresis", defaultHysteresis));
555 criticalIface->setLowHysteresis(
556 threshold.value("CriticalLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000557 }
558
559 if (threshold.contains("WarningHigh") || threshold.contains("WarningLow"))
560 {
561 warningIface =
562 std::make_unique<Threshold<WarningObject>>(bus, objPath.c_str());
563
564 warningIface->warningHigh(threshold.value(
565 "WarningHigh", std::numeric_limits<double>::quiet_NaN()));
566 warningIface->warningLow(threshold.value(
567 "WarningLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000568 warningIface->setHighHysteresis(
569 threshold.value("WarningHighHysteresis", defaultHysteresis));
570 warningIface->setLowHysteresis(
571 threshold.value("WarningLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000572 }
573
574 if (threshold.contains("HardShutdownHigh") ||
575 threshold.contains("HardShutdownLow"))
576 {
577 hardShutdownIface = std::make_unique<Threshold<HardShutdownObject>>(
578 bus, objPath.c_str());
579
580 hardShutdownIface->hardShutdownHigh(threshold.value(
581 "HardShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
582 hardShutdownIface->hardShutdownLow(threshold.value(
583 "HardShutdownLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000584 hardShutdownIface->setHighHysteresis(
585 threshold.value("HardShutdownHighHysteresis", defaultHysteresis));
586 hardShutdownIface->setLowHysteresis(
587 threshold.value("HardShutdownLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000588 }
589
590 if (threshold.contains("SoftShutdownHigh") ||
591 threshold.contains("SoftShutdownLow"))
592 {
593 softShutdownIface = std::make_unique<Threshold<SoftShutdownObject>>(
594 bus, objPath.c_str());
595
596 softShutdownIface->softShutdownHigh(threshold.value(
597 "SoftShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
598 softShutdownIface->softShutdownLow(threshold.value(
599 "SoftShutdownLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000600 softShutdownIface->setHighHysteresis(
601 threshold.value("SoftShutdownHighHysteresis", defaultHysteresis));
602 softShutdownIface->setLowHysteresis(
603 threshold.value("SoftShutdownLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000604 }
605
606 if (threshold.contains("PerformanceLossHigh") ||
607 threshold.contains("PerformanceLossLow"))
608 {
609 perfLossIface = std::make_unique<Threshold<PerformanceLossObject>>(
610 bus, objPath.c_str());
611
612 perfLossIface->performanceLossHigh(threshold.value(
613 "PerformanceLossHigh", std::numeric_limits<double>::quiet_NaN()));
614 perfLossIface->performanceLossLow(threshold.value(
615 "PerformanceLossLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000616 perfLossIface->setHighHysteresis(threshold.value(
617 "PerformanceLossHighHysteresis", defaultHysteresis));
618 perfLossIface->setLowHysteresis(
619 threshold.value("PerformanceLossLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000620 }
621}
622
Rashmica Guptae7efe132021-07-27 19:42:11 +1000623ManagedObjectType VirtualSensors::getObjectsFromDBus()
624{
625 ManagedObjectType objects;
626
627 try
628 {
629 auto method = bus.new_method_call(entityManagerBusName, "/",
630 "org.freedesktop.DBus.ObjectManager",
631 "GetManagedObjects");
632 auto reply = bus.call(method);
633 reply.read(objects);
634 }
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500635 catch (const sdbusplus::exception_t& ex)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000636 {
637 // If entity manager isn't running yet, keep going.
638 if (std::string("org.freedesktop.DBus.Error.ServiceUnknown") !=
639 ex.name())
640 {
641 throw ex.name();
642 }
643 }
644
645 return objects;
646}
647
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500648void VirtualSensors::propertiesChanged(sdbusplus::message_t& msg)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000649{
650 std::string path;
651 PropertyMap properties;
652
653 msg.read(path, properties);
654
655 /* We get multiple callbacks for one sensor. 'Type' is a required field and
656 * is a unique label so use to to only proceed once per sensor */
657 if (properties.contains("Type"))
658 {
659 if (isCalculationType(path))
660 {
661 createVirtualSensorsFromDBus(path);
662 }
663 }
664}
665
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700666/** @brief Parsing Virtual Sensor config JSON file */
George Liu1204b432021-12-29 17:24:48 +0800667Json VirtualSensors::parseConfigFile(const std::string& configFile)
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700668{
669 std::ifstream jsonFile(configFile);
670 if (!jsonFile.is_open())
671 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500672 error("config JSON file {FILENAME} not found", "FILENAME", configFile);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000673 return {};
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700674 }
675
676 auto data = Json::parse(jsonFile, nullptr, false);
677 if (data.is_discarded())
678 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500679 error("config readings JSON parser failure with {FILENAME}", "FILENAME",
680 configFile);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700681 throw std::exception{};
682 }
683
684 return data;
685}
686
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700687std::map<std::string, ValueIface::Unit> unitMap = {
688 {"temperature", ValueIface::Unit::DegreesC},
689 {"fan_tach", ValueIface::Unit::RPMS},
690 {"voltage", ValueIface::Unit::Volts},
691 {"altitude", ValueIface::Unit::Meters},
692 {"current", ValueIface::Unit::Amperes},
693 {"power", ValueIface::Unit::Watts},
694 {"energy", ValueIface::Unit::Joules},
Kumar Thangavel2b56ddb2021-01-13 20:16:11 +0530695 {"utilization", ValueIface::Unit::Percent},
Rashmica Gupta4ac7a7f2021-07-08 12:30:50 +1000696 {"airflow", ValueIface::Unit::CFM},
697 {"pressure", ValueIface::Unit::Pascals}};
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700698
Rashmica Guptae7efe132021-07-27 19:42:11 +1000699const std::string getSensorTypeFromUnit(const std::string& unit)
700{
701 std::string unitPrefix = "xyz.openbmc_project.Sensor.Value.Unit.";
702 for (auto [type, unitObj] : unitMap)
703 {
704 auto unitPath = ValueIface::convertUnitToString(unitObj);
705 if (unitPath == (unitPrefix + unit))
706 {
707 return type;
708 }
709 }
710 return "";
711}
712
713void VirtualSensors::setupMatches()
714{
715 /* Already setup */
716 if (!this->matches.empty())
717 {
718 return;
719 }
720
721 /* Setup matches */
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500722 auto eventHandler = [this](sdbusplus::message_t& message) {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000723 if (message.is_method_error())
724 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500725 error("Callback method error");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000726 return;
727 }
728 this->propertiesChanged(message);
729 };
730
731 for (const char* iface : calculationIfaces)
732 {
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500733 auto match = std::make_unique<sdbusplus::bus::match_t>(
Rashmica Guptae7efe132021-07-27 19:42:11 +1000734 bus,
735 sdbusplus::bus::match::rules::propertiesChangedNamespace(
736 "/xyz/openbmc_project/inventory", iface),
737 eventHandler);
738 this->matches.emplace_back(std::move(match));
739 }
740}
741
742void VirtualSensors::createVirtualSensorsFromDBus(
743 const std::string& calculationIface)
744{
745 if (calculationIface.empty())
746 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500747 error("No calculation type supplied");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000748 return;
749 }
750 auto objects = getObjectsFromDBus();
751
752 /* Get virtual sensors config data */
753 for (const auto& [path, interfaceMap] : objects)
754 {
755 auto objpath = static_cast<std::string>(path);
756 std::string name = path.filename();
757 std::string sensorType, sensorUnit;
758
759 /* Find Virtual Sensor interfaces */
760 if (!interfaceMap.contains(calculationIface))
761 {
762 continue;
763 }
764 if (name.empty())
765 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500766 error("Virtual Sensor name not found in entity manager config");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000767 continue;
768 }
769 if (virtualSensorsMap.contains(name))
770 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500771 error("A virtual sensor named {NAME} already exists", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000772 continue;
773 }
774
775 /* Extract the virtual sensor type as we need this to initialize the
776 * sensor */
777 for (const auto& [interface, propertyMap] : interfaceMap)
778 {
779 if (interface != calculationIface)
780 {
781 continue;
782 }
783 auto itr = propertyMap.find("Units");
784 if (itr != propertyMap.end())
785 {
786 sensorUnit = std::get<std::string>(itr->second);
787 break;
788 }
789 }
790 sensorType = getSensorTypeFromUnit(sensorUnit);
791 if (sensorType.empty())
792 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500793 error("Sensor unit type {TYPE} is not supported", "TYPE",
794 sensorUnit);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000795 continue;
796 }
797
798 try
799 {
800 auto virtObjPath = sensorDbusPath + sensorType + "/" + name;
801
802 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
803 bus, virtObjPath.c_str(), interfaceMap, name, sensorType,
804 calculationIface);
Patrick Williams82b39c62021-07-28 16:22:27 -0500805 info("Added a new virtual sensor: {NAME} {TYPE}", "NAME", name,
806 "TYPE", sensorType);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000807 virtualSensorPtr->updateVirtualSensor();
808
809 /* Initialize unit value for virtual sensor */
810 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
811 virtualSensorPtr->emit_object_added();
812
813 virtualSensorsMap.emplace(name, std::move(virtualSensorPtr));
814
815 /* Setup match for interfaces removed */
816 auto intfRemoved = [this, objpath,
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500817 name](sdbusplus::message_t& message) {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000818 if (!virtualSensorsMap.contains(name))
819 {
820 return;
821 }
822 sdbusplus::message::object_path path;
823 message.read(path);
824 if (static_cast<const std::string&>(path) == objpath)
825 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500826 info("Removed a virtual sensor: {NAME}", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000827 virtualSensorsMap.erase(name);
828 }
829 };
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500830 auto matchOnRemove = std::make_unique<sdbusplus::bus::match_t>(
Rashmica Guptae7efe132021-07-27 19:42:11 +1000831 bus,
832 sdbusplus::bus::match::rules::interfacesRemoved() +
833 sdbusplus::bus::match::rules::argNpath(0, objpath),
834 intfRemoved);
835 /* TODO: slight race condition here. Check that the config still
836 * exists */
837 this->matches.emplace_back(std::move(matchOnRemove));
838 }
Patrick Williamsdac26632021-10-06 14:38:47 -0500839 catch (const std::invalid_argument& ia)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000840 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500841 error("Failed to set up virtual sensor: {ERROR}", "ERROR", ia);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000842 }
843 }
844}
845
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700846void VirtualSensors::createVirtualSensors()
847{
848 static const Json empty{};
849
850 auto data = parseConfigFile(VIRTUAL_SENSOR_CONFIG_FILE);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000851
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700852 // print values
853 if (DEBUG)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000854 {
Patrick Williamsfbd71452021-08-30 06:59:46 -0500855 debug("JSON: {JSON}", "JSON", data.dump());
Rashmica Guptae7efe132021-07-27 19:42:11 +1000856 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700857
858 /* Get virtual sensors config data */
859 for (const auto& j : data)
860 {
861 auto desc = j.value("Desc", empty);
862 if (!desc.empty())
863 {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000864 if (desc.value("Config", "") == "D-Bus")
865 {
866 /* Look on D-Bus for a virtual sensor config. Set up matches
867 * first because the configs may not be on D-Bus yet and we
868 * don't want to miss them */
869 setupMatches();
870
871 if (desc.contains("Type"))
872 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500873 auto type = desc.value("Type", "");
874 auto path = "xyz.openbmc_project.Configuration." + type;
875
Rashmica Guptae7efe132021-07-27 19:42:11 +1000876 if (!isCalculationType(path))
877 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500878 error("Invalid calculation type {TYPE} supplied.",
879 "TYPE", type);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000880 continue;
881 }
882 createVirtualSensorsFromDBus(path);
883 }
884 continue;
885 }
886
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700887 std::string sensorType = desc.value("SensorType", "");
888 std::string name = desc.value("Name", "");
Rashmica Gupta665a0a22021-06-30 11:35:28 +1000889 std::replace(name.begin(), name.end(), ' ', '_');
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700890
891 if (!name.empty() && !sensorType.empty())
892 {
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700893 if (unitMap.find(sensorType) == unitMap.end())
894 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500895 error("Sensor type {TYPE} is not supported", "TYPE",
896 sensorType);
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700897 }
898 else
899 {
Rashmica Gupta67d8b9d2021-06-30 11:41:14 +1000900 if (virtualSensorsMap.find(name) != virtualSensorsMap.end())
901 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500902 error("A virtual sensor named {NAME} already exists",
903 "NAME", name);
Rashmica Gupta67d8b9d2021-06-30 11:41:14 +1000904 continue;
905 }
Rashmica Gupta862c3d12021-08-06 12:19:31 +1000906 auto objPath = sensorDbusPath + sensorType + "/" + name;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700907
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700908 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
909 bus, objPath.c_str(), j, name);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700910
Patrick Williams82b39c62021-07-28 16:22:27 -0500911 info("Added a new virtual sensor: {NAME}", "NAME", name);
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700912 virtualSensorPtr->updateVirtualSensor();
913
914 /* Initialize unit value for virtual sensor */
915 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
Rashmica Guptaa2fa63a2021-08-06 12:21:13 +1000916 virtualSensorPtr->emit_object_added();
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700917
918 virtualSensorsMap.emplace(std::move(name),
919 std::move(virtualSensorPtr));
920 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700921 }
922 else
923 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500924 error(
925 "Sensor type ({TYPE}) or name ({NAME}) not found in config file",
926 "NAME", name, "TYPE", sensorType);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700927 }
928 }
929 else
930 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500931 error("Descriptor for new virtual sensor not found in config file");
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700932 }
933 }
934}
935
936} // namespace virtualSensor
937} // namespace phosphor
938
939/**
940 * @brief Main
941 */
942int main()
943{
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700944 // Get a default event loop
945 auto event = sdeventplus::Event::get_default();
946
947 // Get a handle to system dbus
948 auto bus = sdbusplus::bus::new_default();
949
Matt Spinler6c19e7d2021-01-12 16:26:45 -0600950 // Add the ObjectManager interface
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500951 sdbusplus::server::manager_t objManager(bus, "/");
Matt Spinler6c19e7d2021-01-12 16:26:45 -0600952
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700953 // Create an virtual sensors object
954 phosphor::virtualSensor::VirtualSensors virtualSensors(bus);
955
956 // Request service bus name
957 bus.request_name(busName);
958
959 // Attach the bus to sd_event to service user requests
960 bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
961 event.loop();
962
963 return 0;
964}