blob: 304e8749ef98d1de335347cc6478148a625c04f6 [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
29 auto sdbpMsg = sdbusplus::message::message(msg);
30 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,
111 bool required)
112{
113 if (auto itr = map.find(name); itr != map.end())
114 {
115 return std::visit(VariantToNumber<U>(), itr->second);
116 }
117 else if (required)
118 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500119 error("Required field {NAME} missing in config", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000120 throw std::invalid_argument("Required field missing in config");
121 }
122 return std::numeric_limits<U>::quiet_NaN();
123}
124
125bool isCalculationType(const std::string& interface)
126{
127 auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(),
128 interface);
129 if (itr != calculationIfaces.end())
130 {
131 return true;
132 }
133 return false;
134}
135
136const std::string getThresholdType(const std::string& direction,
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100137 const std::string& severity)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000138{
Rashmica Guptae7efe132021-07-27 19:42:11 +1000139 std::string suffix;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000140
141 if (direction == "less than")
142 {
143 suffix = "Low";
144 }
145 else if (direction == "greater than")
146 {
147 suffix = "High";
148 }
149 else
150 {
151 throw std::invalid_argument(
152 "Invalid threshold direction specified in entity manager");
153 }
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100154 return severity + suffix;
155}
156
157std::string getSeverityField(const PropertyMap& propertyMap)
158{
159 static const std::array thresholdTypes{"Warning", "Critical",
160 "PerformanceLoss", "SoftShutdown",
161 "HardShutdown"};
162
163 std::string severity;
164 if (auto itr = propertyMap.find("Severity"); itr != propertyMap.end())
165 {
166 /* Severity should be a string, but can be an unsigned int */
167 if (std::holds_alternative<std::string>(itr->second))
168 {
169 severity = std::get<std::string>(itr->second);
170 if (0 == std::ranges::count(thresholdTypes, severity))
171 {
172 throw std::invalid_argument(
173 "Invalid threshold severity specified in entity manager");
174 }
175 }
176 else
177 {
178 auto sev =
179 getNumberFromConfig<uint64_t>(propertyMap, "Severity", true);
180 /* Checking bounds ourselves so we throw invalid argument on
181 * invalid user input */
182 if (sev >= thresholdTypes.size())
183 {
184 throw std::invalid_argument(
185 "Invalid threshold severity specified in entity manager");
186 }
187 severity = thresholdTypes.at(sev);
188 }
189 }
190 return severity;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000191}
192
193void parseThresholds(Json& thresholds, const PropertyMap& propertyMap)
194{
195 std::string direction;
196
Rashmica Guptae7efe132021-07-27 19:42:11 +1000197 auto value = getNumberFromConfig<double>(propertyMap, "Value", true);
198
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100199 auto severity = getSeverityField(propertyMap);
200
201 if (auto itr = propertyMap.find("Direction"); itr != propertyMap.end())
Rashmica Guptae7efe132021-07-27 19:42:11 +1000202 {
203 direction = std::get<std::string>(itr->second);
204 }
205
206 auto threshold = getThresholdType(direction, severity);
207 thresholds[threshold] = value;
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000208
209 auto hysteresis =
210 getNumberFromConfig<double>(propertyMap, "Hysteresis", false);
211 if (hysteresis != std::numeric_limits<double>::quiet_NaN())
212 {
213 thresholds[threshold + "Hysteresis"] = hysteresis;
214 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000215}
216
217void VirtualSensor::parseConfigInterface(const PropertyMap& propertyMap,
218 const std::string& sensorType,
219 const std::string& interface)
220{
221 /* Parse sensors / DBus params */
222 if (auto itr = propertyMap.find("Sensors"); itr != propertyMap.end())
223 {
224 auto sensors = std::get<std::vector<std::string>>(itr->second);
225 for (auto sensor : sensors)
226 {
227 std::replace(sensor.begin(), sensor.end(), ' ', '_');
228 auto sensorObjPath = sensorDbusPath + sensorType + "/" + sensor;
229
230 auto paramPtr =
231 std::make_unique<SensorParam>(bus, sensorObjPath, this);
232 symbols.create_variable(sensor);
233 paramMap.emplace(std::move(sensor), std::move(paramPtr));
234 }
235 }
236 /* Get expression string */
237 if (!isCalculationType(interface))
238 {
239 throw std::invalid_argument("Invalid expression in interface");
240 }
241 exprStr = interface;
242
243 /* Get optional min and max input and output values */
244 ValueIface::maxValue(
245 getNumberFromConfig<double>(propertyMap, "MaxValue", false));
246 ValueIface::minValue(
247 getNumberFromConfig<double>(propertyMap, "MinValue", false));
248 maxValidInput =
249 getNumberFromConfig<double>(propertyMap, "MaxValidInput", false);
250 minValidInput =
251 getNumberFromConfig<double>(propertyMap, "MinValidInput", false);
252}
253
Matt Spinlerce675222021-01-14 16:38:09 -0600254void VirtualSensor::initVirtualSensor(const Json& sensorConfig,
255 const std::string& objPath)
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700256{
257
258 static const Json empty{};
259
260 /* Get threshold values if defined in config */
261 auto threshold = sensorConfig.value("Threshold", empty);
Matt Spinlerf15189e2021-01-15 10:13:28 -0600262
Rashmica Gupta3e999192021-06-09 16:17:04 +1000263 createThresholds(threshold, objPath);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700264
Harvey Wuf6443742021-04-09 16:47:36 +0800265 /* Get MaxValue, MinValue setting if defined in config */
266 auto confDesc = sensorConfig.value("Desc", empty);
267 if (auto maxConf = confDesc.find("MaxValue");
268 maxConf != confDesc.end() && maxConf->is_number())
269 {
270 ValueIface::maxValue(maxConf->get<double>());
271 }
272 if (auto minConf = confDesc.find("MinValue");
273 minConf != confDesc.end() && minConf->is_number())
274 {
275 ValueIface::minValue(minConf->get<double>());
276 }
277
Lei YU0fcf0e12021-06-04 11:14:17 +0800278 /* Get optional association */
279 auto assocJson = sensorConfig.value("Associations", empty);
280 if (!assocJson.empty())
281 {
282 auto assocs = getAssociationsFromJson(assocJson);
283 if (!assocs.empty())
284 {
285 associationIface =
286 std::make_unique<AssociationObject>(bus, objPath.c_str());
287 associationIface->associations(assocs);
288 }
289 }
290
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700291 /* Get expression string */
Patrick Williams03c4c8e2022-04-14 22:19:44 -0500292 static constexpr auto exprKey = "Expression";
293 if (sensorConfig.contains(exprKey))
294 {
Patrick Williamsa9596782022-04-15 10:20:07 -0500295 auto& ref = sensorConfig.at(exprKey);
Patrick Williams03c4c8e2022-04-14 22:19:44 -0500296 if (ref.is_array())
297 {
298 exprStr = std::string{};
299 for (auto& s : ref)
300 {
301 exprStr += s;
302 }
303 }
304 else if (ref.is_string())
305 {
306 exprStr = std::string{ref};
307 }
308 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700309
310 /* Get all the parameter listed in configuration */
311 auto params = sensorConfig.value("Params", empty);
312
313 /* Check for constant parameter */
314 const auto& consParams = params.value("ConstParam", empty);
315 if (!consParams.empty())
316 {
317 for (auto& j : consParams)
318 {
319 if (j.find("ParamName") != j.end())
320 {
321 auto paramPtr = std::make_unique<SensorParam>(j["Value"]);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700322 std::string name = j["ParamName"];
323 symbols.create_variable(name);
324 paramMap.emplace(std::move(name), std::move(paramPtr));
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700325 }
326 else
327 {
328 /* Invalid configuration */
329 throw std::invalid_argument(
330 "ParamName not found in configuration");
331 }
332 }
333 }
334
Vijay Khemka7452a862020-08-11 16:01:23 -0700335 /* Check for dbus parameter */
336 auto dbusParams = params.value("DbusParam", empty);
337 if (!dbusParams.empty())
338 {
339 for (auto& j : dbusParams)
340 {
341 /* Get parameter dbus sensor descriptor */
342 auto desc = j.value("Desc", empty);
343 if ((!desc.empty()) && (j.find("ParamName") != j.end()))
344 {
345 std::string sensorType = desc.value("SensorType", "");
346 std::string name = desc.value("Name", "");
347
348 if (!sensorType.empty() && !name.empty())
349 {
George Liu1204b432021-12-29 17:24:48 +0800350 auto path = sensorDbusPath + sensorType + "/" + name;
Vijay Khemka7452a862020-08-11 16:01:23 -0700351
Vijay Khemka51f898e2020-09-09 22:24:18 -0700352 auto paramPtr =
George Liu1204b432021-12-29 17:24:48 +0800353 std::make_unique<SensorParam>(bus, path, this);
354 std::string paramName = j["ParamName"];
355 symbols.create_variable(paramName);
356 paramMap.emplace(std::move(paramName), std::move(paramPtr));
Vijay Khemka7452a862020-08-11 16:01:23 -0700357 }
358 }
359 }
360 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700361
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700362 symbols.add_constants();
Matt Spinler9f1ef4f2020-11-09 15:59:11 -0600363 symbols.add_package(vecopsPackage);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700364 expression.register_symbol_table(symbols);
365
366 /* parser from exprtk */
367 exprtk::parser<double> parser{};
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600368 if (!parser.compile(exprStr, expression))
369 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500370 error("Expression compilation failed");
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600371
372 for (std::size_t i = 0; i < parser.error_count(); ++i)
373 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500374 auto err = parser.get_error(i);
375 error("Error parsing token at {POSITION}: {ERROR}", "POSITION",
376 err.token.position, "TYPE",
377 exprtk::parser_error::to_str(err.mode), "ERROR",
378 err.diagnostic);
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600379 }
380 throw std::runtime_error("Expression compilation failed");
381 }
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700382
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700383 /* Print all parameters for debug purpose only */
384 if (DEBUG)
385 printParams(paramMap);
386}
387
Rashmica Guptae7efe132021-07-27 19:42:11 +1000388void VirtualSensor::initVirtualSensor(const InterfaceMap& interfaceMap,
389 const std::string& objPath,
390 const std::string& sensorType,
391 const std::string& calculationIface)
392{
393 Json thresholds;
394 const std::string vsThresholdsIntf =
395 calculationIface + vsThresholdsIfaceSuffix;
396
397 for (const auto& [interface, propertyMap] : interfaceMap)
398 {
399 /* Each threshold is on it's own interface with a number as a suffix
400 * eg xyz.openbmc_project.Configuration.ModifiedMedian.Thresholds1 */
401 if (interface.find(vsThresholdsIntf) != std::string::npos)
402 {
403 parseThresholds(thresholds, propertyMap);
404 }
405 else if (interface == calculationIface)
406 {
407 parseConfigInterface(propertyMap, sensorType, interface);
408 }
409 }
410
411 createThresholds(thresholds, objPath);
412 symbols.add_constants();
413 symbols.add_package(vecopsPackage);
414 expression.register_symbol_table(symbols);
415
416 /* Print all parameters for debug purpose only */
417 if (DEBUG)
418 {
419 printParams(paramMap);
420 }
421}
422
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700423void VirtualSensor::setSensorValue(double value)
424{
Patrick Williams543bf662021-04-29 09:03:53 -0500425 value = std::clamp(value, ValueIface::minValue(), ValueIface::maxValue());
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700426 ValueIface::value(value);
427}
428
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000429double VirtualSensor::calculateValue(const std::string& calculation,
430 const VirtualSensor::ParamMap& paramMap)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000431{
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000432 auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(),
433 calculation);
434 if (itr == calculationIfaces.end())
435 {
436 return std::numeric_limits<double>::quiet_NaN();
437 }
438 else if (calculation == "xyz.openbmc_project.Configuration.ModifiedMedian")
439 {
440 return calculateModifiedMedianValue(paramMap);
441 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000442 return std::numeric_limits<double>::quiet_NaN();
443}
444
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000445bool VirtualSensor::sensorInRange(double value)
446{
447 if (value <= this->maxValidInput && value >= this->minValidInput)
448 {
449 return true;
450 }
451 return false;
452}
453
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700454void VirtualSensor::updateVirtualSensor()
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700455{
456 for (auto& param : paramMap)
457 {
458 auto& name = param.first;
459 auto& data = param.second;
460 if (auto var = symbols.get_variable(name))
461 {
462 var->ref() = data->getParamValue();
463 }
464 else
465 {
466 /* Invalid parameter */
467 throw std::invalid_argument("ParamName not found in symbols");
468 }
469 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000470 auto itr =
471 std::find(calculationIfaces.begin(), calculationIfaces.end(), exprStr);
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000472 auto val = (itr == calculationIfaces.end())
473 ? expression.value()
474 : calculateValue(exprStr, paramMap);
Vijay Khemka32a71562020-09-10 15:29:18 -0700475
476 /* Set sensor value to dbus interface */
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700477 setSensorValue(val);
Vijay Khemka32a71562020-09-10 15:29:18 -0700478
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700479 if (DEBUG)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000480 {
Patrick Williamsfbd71452021-08-30 06:59:46 -0500481 debug("Sensor {NAME} = {VALUE}", "NAME", this->name, "VALUE", val);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000482 }
Vijay Khemka32a71562020-09-10 15:29:18 -0700483
Matt Spinler8f5e6112021-01-15 10:44:32 -0600484 /* Check sensor thresholds and log required message */
Matt Spinlerb306b032021-02-01 10:05:46 -0600485 checkThresholds(val, perfLossIface);
Patrick Williamsfdb826d2021-01-20 14:37:53 -0600486 checkThresholds(val, warningIface);
487 checkThresholds(val, criticalIface);
488 checkThresholds(val, softShutdownIface);
489 checkThresholds(val, hardShutdownIface);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700490}
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700491
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000492double VirtualSensor::calculateModifiedMedianValue(
493 const VirtualSensor::ParamMap& paramMap)
494{
495 std::vector<double> values;
496
497 for (auto& param : paramMap)
498 {
499 auto& name = param.first;
500 if (auto var = symbols.get_variable(name))
501 {
502 if (!sensorInRange(var->ref()))
503 {
504 continue;
505 }
506 values.push_back(var->ref());
507 }
508 }
509
510 size_t size = values.size();
511 std::sort(values.begin(), values.end());
512 switch (size)
513 {
514 case 2:
515 /* Choose biggest value */
516 return values.at(1);
517 case 0:
518 return std::numeric_limits<double>::quiet_NaN();
519 default:
520 /* Choose median value */
521 if (size % 2 == 0)
522 {
523 // Average of the two middle values
524 return (values.at(size / 2) + values.at(size / 2 - 1)) / 2;
525 }
526 else
527 {
528 return values.at((size - 1) / 2);
529 }
530 }
531}
532
Rashmica Gupta3e999192021-06-09 16:17:04 +1000533void VirtualSensor::createThresholds(const Json& threshold,
534 const std::string& objPath)
535{
536 if (threshold.empty())
537 {
538 return;
539 }
540 // Only create the threshold interfaces if
541 // at least one of their values is present.
542 if (threshold.contains("CriticalHigh") || threshold.contains("CriticalLow"))
543 {
544 criticalIface =
545 std::make_unique<Threshold<CriticalObject>>(bus, objPath.c_str());
546
547 criticalIface->criticalHigh(threshold.value(
548 "CriticalHigh", std::numeric_limits<double>::quiet_NaN()));
549 criticalIface->criticalLow(threshold.value(
550 "CriticalLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000551 criticalIface->setHighHysteresis(
552 threshold.value("CriticalHighHysteresis", defaultHysteresis));
553 criticalIface->setLowHysteresis(
554 threshold.value("CriticalLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000555 }
556
557 if (threshold.contains("WarningHigh") || threshold.contains("WarningLow"))
558 {
559 warningIface =
560 std::make_unique<Threshold<WarningObject>>(bus, objPath.c_str());
561
562 warningIface->warningHigh(threshold.value(
563 "WarningHigh", std::numeric_limits<double>::quiet_NaN()));
564 warningIface->warningLow(threshold.value(
565 "WarningLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000566 warningIface->setHighHysteresis(
567 threshold.value("WarningHighHysteresis", defaultHysteresis));
568 warningIface->setLowHysteresis(
569 threshold.value("WarningLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000570 }
571
572 if (threshold.contains("HardShutdownHigh") ||
573 threshold.contains("HardShutdownLow"))
574 {
575 hardShutdownIface = std::make_unique<Threshold<HardShutdownObject>>(
576 bus, objPath.c_str());
577
578 hardShutdownIface->hardShutdownHigh(threshold.value(
579 "HardShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
580 hardShutdownIface->hardShutdownLow(threshold.value(
581 "HardShutdownLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000582 hardShutdownIface->setHighHysteresis(
583 threshold.value("HardShutdownHighHysteresis", defaultHysteresis));
584 hardShutdownIface->setLowHysteresis(
585 threshold.value("HardShutdownLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000586 }
587
588 if (threshold.contains("SoftShutdownHigh") ||
589 threshold.contains("SoftShutdownLow"))
590 {
591 softShutdownIface = std::make_unique<Threshold<SoftShutdownObject>>(
592 bus, objPath.c_str());
593
594 softShutdownIface->softShutdownHigh(threshold.value(
595 "SoftShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
596 softShutdownIface->softShutdownLow(threshold.value(
597 "SoftShutdownLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000598 softShutdownIface->setHighHysteresis(
599 threshold.value("SoftShutdownHighHysteresis", defaultHysteresis));
600 softShutdownIface->setLowHysteresis(
601 threshold.value("SoftShutdownLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000602 }
603
604 if (threshold.contains("PerformanceLossHigh") ||
605 threshold.contains("PerformanceLossLow"))
606 {
607 perfLossIface = std::make_unique<Threshold<PerformanceLossObject>>(
608 bus, objPath.c_str());
609
610 perfLossIface->performanceLossHigh(threshold.value(
611 "PerformanceLossHigh", std::numeric_limits<double>::quiet_NaN()));
612 perfLossIface->performanceLossLow(threshold.value(
613 "PerformanceLossLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000614 perfLossIface->setHighHysteresis(threshold.value(
615 "PerformanceLossHighHysteresis", defaultHysteresis));
616 perfLossIface->setLowHysteresis(
617 threshold.value("PerformanceLossLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000618 }
619}
620
Rashmica Guptae7efe132021-07-27 19:42:11 +1000621ManagedObjectType VirtualSensors::getObjectsFromDBus()
622{
623 ManagedObjectType objects;
624
625 try
626 {
627 auto method = bus.new_method_call(entityManagerBusName, "/",
628 "org.freedesktop.DBus.ObjectManager",
629 "GetManagedObjects");
630 auto reply = bus.call(method);
631 reply.read(objects);
632 }
Patrick Williams74c1e7d2021-09-02 09:50:55 -0500633 catch (const sdbusplus::exception::exception& ex)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000634 {
635 // If entity manager isn't running yet, keep going.
636 if (std::string("org.freedesktop.DBus.Error.ServiceUnknown") !=
637 ex.name())
638 {
639 throw ex.name();
640 }
641 }
642
643 return objects;
644}
645
646void VirtualSensors::propertiesChanged(sdbusplus::message::message& msg)
647{
648 std::string path;
649 PropertyMap properties;
650
651 msg.read(path, properties);
652
653 /* We get multiple callbacks for one sensor. 'Type' is a required field and
654 * is a unique label so use to to only proceed once per sensor */
655 if (properties.contains("Type"))
656 {
657 if (isCalculationType(path))
658 {
659 createVirtualSensorsFromDBus(path);
660 }
661 }
662}
663
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700664/** @brief Parsing Virtual Sensor config JSON file */
George Liu1204b432021-12-29 17:24:48 +0800665Json VirtualSensors::parseConfigFile(const std::string& configFile)
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700666{
667 std::ifstream jsonFile(configFile);
668 if (!jsonFile.is_open())
669 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500670 error("config JSON file {FILENAME} not found", "FILENAME", configFile);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000671 return {};
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700672 }
673
674 auto data = Json::parse(jsonFile, nullptr, false);
675 if (data.is_discarded())
676 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500677 error("config readings JSON parser failure with {FILENAME}", "FILENAME",
678 configFile);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700679 throw std::exception{};
680 }
681
682 return data;
683}
684
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700685std::map<std::string, ValueIface::Unit> unitMap = {
686 {"temperature", ValueIface::Unit::DegreesC},
687 {"fan_tach", ValueIface::Unit::RPMS},
688 {"voltage", ValueIface::Unit::Volts},
689 {"altitude", ValueIface::Unit::Meters},
690 {"current", ValueIface::Unit::Amperes},
691 {"power", ValueIface::Unit::Watts},
692 {"energy", ValueIface::Unit::Joules},
Kumar Thangavel2b56ddb2021-01-13 20:16:11 +0530693 {"utilization", ValueIface::Unit::Percent},
Rashmica Gupta4ac7a7f2021-07-08 12:30:50 +1000694 {"airflow", ValueIface::Unit::CFM},
695 {"pressure", ValueIface::Unit::Pascals}};
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700696
Rashmica Guptae7efe132021-07-27 19:42:11 +1000697const std::string getSensorTypeFromUnit(const std::string& unit)
698{
699 std::string unitPrefix = "xyz.openbmc_project.Sensor.Value.Unit.";
700 for (auto [type, unitObj] : unitMap)
701 {
702 auto unitPath = ValueIface::convertUnitToString(unitObj);
703 if (unitPath == (unitPrefix + unit))
704 {
705 return type;
706 }
707 }
708 return "";
709}
710
711void VirtualSensors::setupMatches()
712{
713 /* Already setup */
714 if (!this->matches.empty())
715 {
716 return;
717 }
718
719 /* Setup matches */
720 auto eventHandler = [this](sdbusplus::message::message& message) {
721 if (message.is_method_error())
722 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500723 error("Callback method error");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000724 return;
725 }
726 this->propertiesChanged(message);
727 };
728
729 for (const char* iface : calculationIfaces)
730 {
731 auto match = std::make_unique<sdbusplus::bus::match::match>(
732 bus,
733 sdbusplus::bus::match::rules::propertiesChangedNamespace(
734 "/xyz/openbmc_project/inventory", iface),
735 eventHandler);
736 this->matches.emplace_back(std::move(match));
737 }
738}
739
740void VirtualSensors::createVirtualSensorsFromDBus(
741 const std::string& calculationIface)
742{
743 if (calculationIface.empty())
744 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500745 error("No calculation type supplied");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000746 return;
747 }
748 auto objects = getObjectsFromDBus();
749
750 /* Get virtual sensors config data */
751 for (const auto& [path, interfaceMap] : objects)
752 {
753 auto objpath = static_cast<std::string>(path);
754 std::string name = path.filename();
755 std::string sensorType, sensorUnit;
756
757 /* Find Virtual Sensor interfaces */
758 if (!interfaceMap.contains(calculationIface))
759 {
760 continue;
761 }
762 if (name.empty())
763 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500764 error("Virtual Sensor name not found in entity manager config");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000765 continue;
766 }
767 if (virtualSensorsMap.contains(name))
768 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500769 error("A virtual sensor named {NAME} already exists", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000770 continue;
771 }
772
773 /* Extract the virtual sensor type as we need this to initialize the
774 * sensor */
775 for (const auto& [interface, propertyMap] : interfaceMap)
776 {
777 if (interface != calculationIface)
778 {
779 continue;
780 }
781 auto itr = propertyMap.find("Units");
782 if (itr != propertyMap.end())
783 {
784 sensorUnit = std::get<std::string>(itr->second);
785 break;
786 }
787 }
788 sensorType = getSensorTypeFromUnit(sensorUnit);
789 if (sensorType.empty())
790 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500791 error("Sensor unit type {TYPE} is not supported", "TYPE",
792 sensorUnit);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000793 continue;
794 }
795
796 try
797 {
798 auto virtObjPath = sensorDbusPath + sensorType + "/" + name;
799
800 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
801 bus, virtObjPath.c_str(), interfaceMap, name, sensorType,
802 calculationIface);
Patrick Williams82b39c62021-07-28 16:22:27 -0500803 info("Added a new virtual sensor: {NAME} {TYPE}", "NAME", name,
804 "TYPE", sensorType);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000805 virtualSensorPtr->updateVirtualSensor();
806
807 /* Initialize unit value for virtual sensor */
808 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
809 virtualSensorPtr->emit_object_added();
810
811 virtualSensorsMap.emplace(name, std::move(virtualSensorPtr));
812
813 /* Setup match for interfaces removed */
814 auto intfRemoved = [this, objpath,
815 name](sdbusplus::message::message& message) {
816 if (!virtualSensorsMap.contains(name))
817 {
818 return;
819 }
820 sdbusplus::message::object_path path;
821 message.read(path);
822 if (static_cast<const std::string&>(path) == objpath)
823 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500824 info("Removed a virtual sensor: {NAME}", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000825 virtualSensorsMap.erase(name);
826 }
827 };
828 auto matchOnRemove = std::make_unique<sdbusplus::bus::match::match>(
829 bus,
830 sdbusplus::bus::match::rules::interfacesRemoved() +
831 sdbusplus::bus::match::rules::argNpath(0, objpath),
832 intfRemoved);
833 /* TODO: slight race condition here. Check that the config still
834 * exists */
835 this->matches.emplace_back(std::move(matchOnRemove));
836 }
Patrick Williamsdac26632021-10-06 14:38:47 -0500837 catch (const std::invalid_argument& ia)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000838 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500839 error("Failed to set up virtual sensor: {ERROR}", "ERROR", ia);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000840 }
841 }
842}
843
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700844void VirtualSensors::createVirtualSensors()
845{
846 static const Json empty{};
847
848 auto data = parseConfigFile(VIRTUAL_SENSOR_CONFIG_FILE);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000849
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700850 // print values
851 if (DEBUG)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000852 {
Patrick Williamsfbd71452021-08-30 06:59:46 -0500853 debug("JSON: {JSON}", "JSON", data.dump());
Rashmica Guptae7efe132021-07-27 19:42:11 +1000854 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700855
856 /* Get virtual sensors config data */
857 for (const auto& j : data)
858 {
859 auto desc = j.value("Desc", empty);
860 if (!desc.empty())
861 {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000862 if (desc.value("Config", "") == "D-Bus")
863 {
864 /* Look on D-Bus for a virtual sensor config. Set up matches
865 * first because the configs may not be on D-Bus yet and we
866 * don't want to miss them */
867 setupMatches();
868
869 if (desc.contains("Type"))
870 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500871 auto type = desc.value("Type", "");
872 auto path = "xyz.openbmc_project.Configuration." + type;
873
Rashmica Guptae7efe132021-07-27 19:42:11 +1000874 if (!isCalculationType(path))
875 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500876 error("Invalid calculation type {TYPE} supplied.",
877 "TYPE", type);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000878 continue;
879 }
880 createVirtualSensorsFromDBus(path);
881 }
882 continue;
883 }
884
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700885 std::string sensorType = desc.value("SensorType", "");
886 std::string name = desc.value("Name", "");
Rashmica Gupta665a0a22021-06-30 11:35:28 +1000887 std::replace(name.begin(), name.end(), ' ', '_');
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700888
889 if (!name.empty() && !sensorType.empty())
890 {
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700891 if (unitMap.find(sensorType) == unitMap.end())
892 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500893 error("Sensor type {TYPE} is not supported", "TYPE",
894 sensorType);
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700895 }
896 else
897 {
Rashmica Gupta67d8b9d2021-06-30 11:41:14 +1000898 if (virtualSensorsMap.find(name) != virtualSensorsMap.end())
899 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500900 error("A virtual sensor named {NAME} already exists",
901 "NAME", name);
Rashmica Gupta67d8b9d2021-06-30 11:41:14 +1000902 continue;
903 }
Rashmica Gupta862c3d12021-08-06 12:19:31 +1000904 auto objPath = sensorDbusPath + sensorType + "/" + name;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700905
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700906 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
907 bus, objPath.c_str(), j, name);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700908
Patrick Williams82b39c62021-07-28 16:22:27 -0500909 info("Added a new virtual sensor: {NAME}", "NAME", name);
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700910 virtualSensorPtr->updateVirtualSensor();
911
912 /* Initialize unit value for virtual sensor */
913 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
Rashmica Guptaa2fa63a2021-08-06 12:21:13 +1000914 virtualSensorPtr->emit_object_added();
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700915
916 virtualSensorsMap.emplace(std::move(name),
917 std::move(virtualSensorPtr));
918 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700919 }
920 else
921 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500922 error(
923 "Sensor type ({TYPE}) or name ({NAME}) not found in config file",
924 "NAME", name, "TYPE", sensorType);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700925 }
926 }
927 else
928 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500929 error("Descriptor for new virtual sensor not found in config file");
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700930 }
931 }
932}
933
934} // namespace virtualSensor
935} // namespace phosphor
936
937/**
938 * @brief Main
939 */
940int main()
941{
942
943 // Get a default event loop
944 auto event = sdeventplus::Event::get_default();
945
946 // Get a handle to system dbus
947 auto bus = sdbusplus::bus::new_default();
948
Matt Spinler6c19e7d2021-01-12 16:26:45 -0600949 // Add the ObjectManager interface
950 sdbusplus::server::manager::manager objManager(bus, "/");
951
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700952 // Create an virtual sensors object
953 phosphor::virtualSensor::VirtualSensors virtualSensors(bus);
954
955 // Request service bus name
956 bus.request_name(busName);
957
958 // Attach the bus to sd_event to service user requests
959 bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
960 event.loop();
961
962 return 0;
963}