blob: 0f270190a5b312a0db7993e1445b53016a9ee33d [file] [log] [blame]
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001#include "virtualSensor.hpp"
2
3#include "config.hpp"
4
Matt Spinlerddc6dcd2020-11-09 11:16:31 -06005#include <fmt/format.h>
6
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07007#include <phosphor-logging/log.hpp>
8#include <sdeventplus/event.hpp>
9
10#include <fstream>
11#include <iostream>
12
13static constexpr bool DEBUG = false;
14static constexpr auto busName = "xyz.openbmc_project.VirtualSensor";
15static constexpr auto sensorDbusPath = "/xyz/openbmc_project/sensors/";
Rashmica Guptae7efe132021-07-27 19:42:11 +100016static constexpr auto entityManagerBusName =
17 "xyz.openbmc_project.EntityManager";
18static constexpr auto vsThresholdsIfaceSuffix = ".Thresholds";
Rashmica Gupta304fd0e2021-08-10 16:50:44 +100019static constexpr std::array<const char*, 1> calculationIfaces = {
20 "xyz.openbmc_project.Configuration.ModifiedMedian"};
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +100021static constexpr auto defaultHysteresis = 0;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070022
23using namespace phosphor::logging;
24
Vijay Khemka51f898e2020-09-09 22:24:18 -070025int handleDbusSignal(sd_bus_message* msg, void* usrData, sd_bus_error*)
26{
27 if (usrData == nullptr)
28 {
29 throw std::runtime_error("Invalid match");
30 }
31
32 auto sdbpMsg = sdbusplus::message::message(msg);
33 std::string msgIfce;
34 std::map<std::string, std::variant<int64_t, double, bool>> msgData;
35
36 sdbpMsg.read(msgIfce, msgData);
37
38 if (msgData.find("Value") != msgData.end())
39 {
40 using namespace phosphor::virtualSensor;
41 VirtualSensor* obj = static_cast<VirtualSensor*>(usrData);
42 // TODO(openbmc/phosphor-virtual-sensor#1): updateVirtualSensor should
43 // be changed to take the information we got from the signal, to avoid
44 // having to do numerous dbus queries.
45 obj->updateVirtualSensor();
46 }
47 return 0;
48}
49
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070050namespace phosphor
51{
52namespace virtualSensor
53{
54
55void printParams(const VirtualSensor::ParamMap& paramMap)
56{
57 for (const auto& p : paramMap)
58 {
59 const auto& p1 = p.first;
60 const auto& p2 = p.second;
61 auto val = p2->getParamValue();
62 std::cout << p1 << " = " << val << "\n";
63 }
64}
65
66double SensorParam::getParamValue()
67{
68 switch (paramType)
69 {
70 case constParam:
71 return value;
72 break;
Vijay Khemka7452a862020-08-11 16:01:23 -070073 case dbusParam:
74 return dbusSensor->getSensorValue();
75 break;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070076 default:
77 throw std::invalid_argument("param type not supported");
78 }
79}
80
Lei YU0fcf0e12021-06-04 11:14:17 +080081using AssociationList =
82 std::vector<std::tuple<std::string, std::string, std::string>>;
83
84AssociationList getAssociationsFromJson(const Json& j)
85{
86 AssociationList assocs{};
87 try
88 {
89 j.get_to(assocs);
90 }
91 catch (const std::exception& ex)
92 {
93 log<level::ERR>("Failed to parse association",
94 entry("EX=%s", ex.what()));
95 }
96 return assocs;
97}
98
Rashmica Guptae7efe132021-07-27 19:42:11 +100099template <typename U>
100struct VariantToNumber
101{
102 template <typename T>
103 U operator()(const T& t) const
104 {
105 if constexpr (std::is_convertible<T, U>::value)
106 {
107 return static_cast<U>(t);
108 }
109 throw std::invalid_argument("Invalid number type in config\n");
110 }
111};
112
113template <typename U>
114U getNumberFromConfig(const PropertyMap& map, const std::string& name,
115 bool required)
116{
117 if (auto itr = map.find(name); itr != map.end())
118 {
119 return std::visit(VariantToNumber<U>(), itr->second);
120 }
121 else if (required)
122 {
123 log<level::ERR>("Required field missing in config",
124 entry("NAME=%s", name.c_str()));
125 throw std::invalid_argument("Required field missing in config");
126 }
127 return std::numeric_limits<U>::quiet_NaN();
128}
129
130bool isCalculationType(const std::string& interface)
131{
132 auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(),
133 interface);
134 if (itr != calculationIfaces.end())
135 {
136 return true;
137 }
138 return false;
139}
140
141const std::string getThresholdType(const std::string& direction,
142 uint64_t severity)
143{
144 std::string threshold;
145 std::string suffix;
146 static const std::array thresholdTypes{"Warning", "Critical",
147 "PerformanceLoss", "SoftShutdown",
148 "HardShutdown"};
149
150 if (severity >= thresholdTypes.size())
151 {
152 throw std::invalid_argument(
153 "Invalid threshold severity specified in entity manager");
154 }
155 threshold = thresholdTypes[severity];
156
157 if (direction == "less than")
158 {
159 suffix = "Low";
160 }
161 else if (direction == "greater than")
162 {
163 suffix = "High";
164 }
165 else
166 {
167 throw std::invalid_argument(
168 "Invalid threshold direction specified in entity manager");
169 }
170 return threshold + suffix;
171}
172
173void parseThresholds(Json& thresholds, const PropertyMap& propertyMap)
174{
175 std::string direction;
176
177 auto severity =
178 getNumberFromConfig<uint64_t>(propertyMap, "Severity", true);
179 auto value = getNumberFromConfig<double>(propertyMap, "Value", true);
180
181 auto itr = propertyMap.find("Direction");
182 if (itr != propertyMap.end())
183 {
184 direction = std::get<std::string>(itr->second);
185 }
186
187 auto threshold = getThresholdType(direction, severity);
188 thresholds[threshold] = value;
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000189
190 auto hysteresis =
191 getNumberFromConfig<double>(propertyMap, "Hysteresis", false);
192 if (hysteresis != std::numeric_limits<double>::quiet_NaN())
193 {
194 thresholds[threshold + "Hysteresis"] = hysteresis;
195 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000196}
197
198void VirtualSensor::parseConfigInterface(const PropertyMap& propertyMap,
199 const std::string& sensorType,
200 const std::string& interface)
201{
202 /* Parse sensors / DBus params */
203 if (auto itr = propertyMap.find("Sensors"); itr != propertyMap.end())
204 {
205 auto sensors = std::get<std::vector<std::string>>(itr->second);
206 for (auto sensor : sensors)
207 {
208 std::replace(sensor.begin(), sensor.end(), ' ', '_');
209 auto sensorObjPath = sensorDbusPath + sensorType + "/" + sensor;
210
211 auto paramPtr =
212 std::make_unique<SensorParam>(bus, sensorObjPath, this);
213 symbols.create_variable(sensor);
214 paramMap.emplace(std::move(sensor), std::move(paramPtr));
215 }
216 }
217 /* Get expression string */
218 if (!isCalculationType(interface))
219 {
220 throw std::invalid_argument("Invalid expression in interface");
221 }
222 exprStr = interface;
223
224 /* Get optional min and max input and output values */
225 ValueIface::maxValue(
226 getNumberFromConfig<double>(propertyMap, "MaxValue", false));
227 ValueIface::minValue(
228 getNumberFromConfig<double>(propertyMap, "MinValue", false));
229 maxValidInput =
230 getNumberFromConfig<double>(propertyMap, "MaxValidInput", false);
231 minValidInput =
232 getNumberFromConfig<double>(propertyMap, "MinValidInput", false);
233}
234
Matt Spinlerce675222021-01-14 16:38:09 -0600235void VirtualSensor::initVirtualSensor(const Json& sensorConfig,
236 const std::string& objPath)
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700237{
238
239 static const Json empty{};
240
241 /* Get threshold values if defined in config */
242 auto threshold = sensorConfig.value("Threshold", empty);
Matt Spinlerf15189e2021-01-15 10:13:28 -0600243
Rashmica Gupta3e999192021-06-09 16:17:04 +1000244 createThresholds(threshold, objPath);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700245
Harvey Wuf6443742021-04-09 16:47:36 +0800246 /* Get MaxValue, MinValue setting if defined in config */
247 auto confDesc = sensorConfig.value("Desc", empty);
248 if (auto maxConf = confDesc.find("MaxValue");
249 maxConf != confDesc.end() && maxConf->is_number())
250 {
251 ValueIface::maxValue(maxConf->get<double>());
252 }
253 if (auto minConf = confDesc.find("MinValue");
254 minConf != confDesc.end() && minConf->is_number())
255 {
256 ValueIface::minValue(minConf->get<double>());
257 }
258
Lei YU0fcf0e12021-06-04 11:14:17 +0800259 /* Get optional association */
260 auto assocJson = sensorConfig.value("Associations", empty);
261 if (!assocJson.empty())
262 {
263 auto assocs = getAssociationsFromJson(assocJson);
264 if (!assocs.empty())
265 {
266 associationIface =
267 std::make_unique<AssociationObject>(bus, objPath.c_str());
268 associationIface->associations(assocs);
269 }
270 }
271
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700272 /* Get expression string */
273 exprStr = sensorConfig.value("Expression", "");
274
275 /* Get all the parameter listed in configuration */
276 auto params = sensorConfig.value("Params", empty);
277
278 /* Check for constant parameter */
279 const auto& consParams = params.value("ConstParam", empty);
280 if (!consParams.empty())
281 {
282 for (auto& j : consParams)
283 {
284 if (j.find("ParamName") != j.end())
285 {
286 auto paramPtr = std::make_unique<SensorParam>(j["Value"]);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700287 std::string name = j["ParamName"];
288 symbols.create_variable(name);
289 paramMap.emplace(std::move(name), std::move(paramPtr));
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700290 }
291 else
292 {
293 /* Invalid configuration */
294 throw std::invalid_argument(
295 "ParamName not found in configuration");
296 }
297 }
298 }
299
Vijay Khemka7452a862020-08-11 16:01:23 -0700300 /* Check for dbus parameter */
301 auto dbusParams = params.value("DbusParam", empty);
302 if (!dbusParams.empty())
303 {
304 for (auto& j : dbusParams)
305 {
306 /* Get parameter dbus sensor descriptor */
307 auto desc = j.value("Desc", empty);
308 if ((!desc.empty()) && (j.find("ParamName") != j.end()))
309 {
310 std::string sensorType = desc.value("SensorType", "");
311 std::string name = desc.value("Name", "");
312
313 if (!sensorType.empty() && !name.empty())
314 {
Rashmica Gupta862c3d12021-08-06 12:19:31 +1000315 auto objPath = sensorDbusPath + sensorType + "/" + name;
Vijay Khemka7452a862020-08-11 16:01:23 -0700316
Vijay Khemka51f898e2020-09-09 22:24:18 -0700317 auto paramPtr =
318 std::make_unique<SensorParam>(bus, objPath, this);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700319 std::string name = j["ParamName"];
320 symbols.create_variable(name);
321 paramMap.emplace(std::move(name), std::move(paramPtr));
Vijay Khemka7452a862020-08-11 16:01:23 -0700322 }
323 }
324 }
325 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700326
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700327 symbols.add_constants();
Matt Spinler9f1ef4f2020-11-09 15:59:11 -0600328 symbols.add_package(vecopsPackage);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700329 expression.register_symbol_table(symbols);
330
331 /* parser from exprtk */
332 exprtk::parser<double> parser{};
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600333 if (!parser.compile(exprStr, expression))
334 {
335 log<level::ERR>("Expression compilation failed");
336
337 for (std::size_t i = 0; i < parser.error_count(); ++i)
338 {
339 auto error = parser.get_error(i);
340
341 log<level::ERR>(
342 fmt::format(
343 "Position: {} Type: {} Message: {}", error.token.position,
344 exprtk::parser_error::to_str(error.mode), error.diagnostic)
345 .c_str());
346 }
347 throw std::runtime_error("Expression compilation failed");
348 }
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700349
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700350 /* Print all parameters for debug purpose only */
351 if (DEBUG)
352 printParams(paramMap);
353}
354
Rashmica Guptae7efe132021-07-27 19:42:11 +1000355void VirtualSensor::initVirtualSensor(const InterfaceMap& interfaceMap,
356 const std::string& objPath,
357 const std::string& sensorType,
358 const std::string& calculationIface)
359{
360 Json thresholds;
361 const std::string vsThresholdsIntf =
362 calculationIface + vsThresholdsIfaceSuffix;
363
364 for (const auto& [interface, propertyMap] : interfaceMap)
365 {
366 /* Each threshold is on it's own interface with a number as a suffix
367 * eg xyz.openbmc_project.Configuration.ModifiedMedian.Thresholds1 */
368 if (interface.find(vsThresholdsIntf) != std::string::npos)
369 {
370 parseThresholds(thresholds, propertyMap);
371 }
372 else if (interface == calculationIface)
373 {
374 parseConfigInterface(propertyMap, sensorType, interface);
375 }
376 }
377
378 createThresholds(thresholds, objPath);
379 symbols.add_constants();
380 symbols.add_package(vecopsPackage);
381 expression.register_symbol_table(symbols);
382
383 /* Print all parameters for debug purpose only */
384 if (DEBUG)
385 {
386 printParams(paramMap);
387 }
388}
389
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700390void VirtualSensor::setSensorValue(double value)
391{
Patrick Williams543bf662021-04-29 09:03:53 -0500392 value = std::clamp(value, ValueIface::minValue(), ValueIface::maxValue());
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700393 ValueIface::value(value);
394}
395
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000396double VirtualSensor::calculateValue(const std::string& calculation,
397 const VirtualSensor::ParamMap& paramMap)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000398{
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000399 auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(),
400 calculation);
401 if (itr == calculationIfaces.end())
402 {
403 return std::numeric_limits<double>::quiet_NaN();
404 }
405 else if (calculation == "xyz.openbmc_project.Configuration.ModifiedMedian")
406 {
407 return calculateModifiedMedianValue(paramMap);
408 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000409 return std::numeric_limits<double>::quiet_NaN();
410}
411
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000412bool VirtualSensor::sensorInRange(double value)
413{
414 if (value <= this->maxValidInput && value >= this->minValidInput)
415 {
416 return true;
417 }
418 return false;
419}
420
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700421void VirtualSensor::updateVirtualSensor()
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700422{
423 for (auto& param : paramMap)
424 {
425 auto& name = param.first;
426 auto& data = param.second;
427 if (auto var = symbols.get_variable(name))
428 {
429 var->ref() = data->getParamValue();
430 }
431 else
432 {
433 /* Invalid parameter */
434 throw std::invalid_argument("ParamName not found in symbols");
435 }
436 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000437 auto itr =
438 std::find(calculationIfaces.begin(), calculationIfaces.end(), exprStr);
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000439 auto val = (itr == calculationIfaces.end())
440 ? expression.value()
441 : calculateValue(exprStr, paramMap);
Vijay Khemka32a71562020-09-10 15:29:18 -0700442
443 /* Set sensor value to dbus interface */
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700444 setSensorValue(val);
Vijay Khemka32a71562020-09-10 15:29:18 -0700445
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700446 if (DEBUG)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000447 {
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700448 std::cout << "Sensor value is " << val << "\n";
Rashmica Guptae7efe132021-07-27 19:42:11 +1000449 }
Vijay Khemka32a71562020-09-10 15:29:18 -0700450
Matt Spinler8f5e6112021-01-15 10:44:32 -0600451 /* Check sensor thresholds and log required message */
Matt Spinlerb306b032021-02-01 10:05:46 -0600452 checkThresholds(val, perfLossIface);
Patrick Williamsfdb826d2021-01-20 14:37:53 -0600453 checkThresholds(val, warningIface);
454 checkThresholds(val, criticalIface);
455 checkThresholds(val, softShutdownIface);
456 checkThresholds(val, hardShutdownIface);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700457}
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700458
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000459double VirtualSensor::calculateModifiedMedianValue(
460 const VirtualSensor::ParamMap& paramMap)
461{
462 std::vector<double> values;
463
464 for (auto& param : paramMap)
465 {
466 auto& name = param.first;
467 if (auto var = symbols.get_variable(name))
468 {
469 if (!sensorInRange(var->ref()))
470 {
471 continue;
472 }
473 values.push_back(var->ref());
474 }
475 }
476
477 size_t size = values.size();
478 std::sort(values.begin(), values.end());
479 switch (size)
480 {
481 case 2:
482 /* Choose biggest value */
483 return values.at(1);
484 case 0:
485 return std::numeric_limits<double>::quiet_NaN();
486 default:
487 /* Choose median value */
488 if (size % 2 == 0)
489 {
490 // Average of the two middle values
491 return (values.at(size / 2) + values.at(size / 2 - 1)) / 2;
492 }
493 else
494 {
495 return values.at((size - 1) / 2);
496 }
497 }
498}
499
Rashmica Gupta3e999192021-06-09 16:17:04 +1000500void VirtualSensor::createThresholds(const Json& threshold,
501 const std::string& objPath)
502{
503 if (threshold.empty())
504 {
505 return;
506 }
507 // Only create the threshold interfaces if
508 // at least one of their values is present.
509 if (threshold.contains("CriticalHigh") || threshold.contains("CriticalLow"))
510 {
511 criticalIface =
512 std::make_unique<Threshold<CriticalObject>>(bus, objPath.c_str());
513
514 criticalIface->criticalHigh(threshold.value(
515 "CriticalHigh", std::numeric_limits<double>::quiet_NaN()));
516 criticalIface->criticalLow(threshold.value(
517 "CriticalLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000518 criticalIface->setHighHysteresis(
519 threshold.value("CriticalHighHysteresis", defaultHysteresis));
520 criticalIface->setLowHysteresis(
521 threshold.value("CriticalLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000522 }
523
524 if (threshold.contains("WarningHigh") || threshold.contains("WarningLow"))
525 {
526 warningIface =
527 std::make_unique<Threshold<WarningObject>>(bus, objPath.c_str());
528
529 warningIface->warningHigh(threshold.value(
530 "WarningHigh", std::numeric_limits<double>::quiet_NaN()));
531 warningIface->warningLow(threshold.value(
532 "WarningLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000533 warningIface->setHighHysteresis(
534 threshold.value("WarningHighHysteresis", defaultHysteresis));
535 warningIface->setLowHysteresis(
536 threshold.value("WarningLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000537 }
538
539 if (threshold.contains("HardShutdownHigh") ||
540 threshold.contains("HardShutdownLow"))
541 {
542 hardShutdownIface = std::make_unique<Threshold<HardShutdownObject>>(
543 bus, objPath.c_str());
544
545 hardShutdownIface->hardShutdownHigh(threshold.value(
546 "HardShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
547 hardShutdownIface->hardShutdownLow(threshold.value(
548 "HardShutdownLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000549 hardShutdownIface->setHighHysteresis(
550 threshold.value("HardShutdownHighHysteresis", defaultHysteresis));
551 hardShutdownIface->setLowHysteresis(
552 threshold.value("HardShutdownLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000553 }
554
555 if (threshold.contains("SoftShutdownHigh") ||
556 threshold.contains("SoftShutdownLow"))
557 {
558 softShutdownIface = std::make_unique<Threshold<SoftShutdownObject>>(
559 bus, objPath.c_str());
560
561 softShutdownIface->softShutdownHigh(threshold.value(
562 "SoftShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
563 softShutdownIface->softShutdownLow(threshold.value(
564 "SoftShutdownLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000565 softShutdownIface->setHighHysteresis(
566 threshold.value("SoftShutdownHighHysteresis", defaultHysteresis));
567 softShutdownIface->setLowHysteresis(
568 threshold.value("SoftShutdownLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000569 }
570
571 if (threshold.contains("PerformanceLossHigh") ||
572 threshold.contains("PerformanceLossLow"))
573 {
574 perfLossIface = std::make_unique<Threshold<PerformanceLossObject>>(
575 bus, objPath.c_str());
576
577 perfLossIface->performanceLossHigh(threshold.value(
578 "PerformanceLossHigh", std::numeric_limits<double>::quiet_NaN()));
579 perfLossIface->performanceLossLow(threshold.value(
580 "PerformanceLossLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000581 perfLossIface->setHighHysteresis(threshold.value(
582 "PerformanceLossHighHysteresis", defaultHysteresis));
583 perfLossIface->setLowHysteresis(
584 threshold.value("PerformanceLossLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000585 }
586}
587
Rashmica Guptae7efe132021-07-27 19:42:11 +1000588ManagedObjectType VirtualSensors::getObjectsFromDBus()
589{
590 ManagedObjectType objects;
591
592 try
593 {
594 auto method = bus.new_method_call(entityManagerBusName, "/",
595 "org.freedesktop.DBus.ObjectManager",
596 "GetManagedObjects");
597 auto reply = bus.call(method);
598 reply.read(objects);
599 }
600 catch (const sdbusplus::exception::SdBusError& ex)
601 {
602 // If entity manager isn't running yet, keep going.
603 if (std::string("org.freedesktop.DBus.Error.ServiceUnknown") !=
604 ex.name())
605 {
606 throw ex.name();
607 }
608 }
609
610 return objects;
611}
612
613void VirtualSensors::propertiesChanged(sdbusplus::message::message& msg)
614{
615 std::string path;
616 PropertyMap properties;
617
618 msg.read(path, properties);
619
620 /* We get multiple callbacks for one sensor. 'Type' is a required field and
621 * is a unique label so use to to only proceed once per sensor */
622 if (properties.contains("Type"))
623 {
624 if (isCalculationType(path))
625 {
626 createVirtualSensorsFromDBus(path);
627 }
628 }
629}
630
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700631/** @brief Parsing Virtual Sensor config JSON file */
632Json VirtualSensors::parseConfigFile(const std::string configFile)
633{
634 std::ifstream jsonFile(configFile);
635 if (!jsonFile.is_open())
636 {
637 log<level::ERR>("config JSON file not found",
Patrick Williams1846d822021-06-23 14:44:07 -0500638 entry("FILENAME=%s", configFile.c_str()));
Rashmica Guptae7efe132021-07-27 19:42:11 +1000639 return {};
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700640 }
641
642 auto data = Json::parse(jsonFile, nullptr, false);
643 if (data.is_discarded())
644 {
645 log<level::ERR>("config readings JSON parser failure",
Patrick Williams1846d822021-06-23 14:44:07 -0500646 entry("FILENAME=%s", configFile.c_str()));
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700647 throw std::exception{};
648 }
649
650 return data;
651}
652
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700653std::map<std::string, ValueIface::Unit> unitMap = {
654 {"temperature", ValueIface::Unit::DegreesC},
655 {"fan_tach", ValueIface::Unit::RPMS},
656 {"voltage", ValueIface::Unit::Volts},
657 {"altitude", ValueIface::Unit::Meters},
658 {"current", ValueIface::Unit::Amperes},
659 {"power", ValueIface::Unit::Watts},
660 {"energy", ValueIface::Unit::Joules},
Kumar Thangavel2b56ddb2021-01-13 20:16:11 +0530661 {"utilization", ValueIface::Unit::Percent},
Rashmica Gupta4ac7a7f2021-07-08 12:30:50 +1000662 {"airflow", ValueIface::Unit::CFM},
663 {"pressure", ValueIface::Unit::Pascals}};
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700664
Rashmica Guptae7efe132021-07-27 19:42:11 +1000665const std::string getSensorTypeFromUnit(const std::string& unit)
666{
667 std::string unitPrefix = "xyz.openbmc_project.Sensor.Value.Unit.";
668 for (auto [type, unitObj] : unitMap)
669 {
670 auto unitPath = ValueIface::convertUnitToString(unitObj);
671 if (unitPath == (unitPrefix + unit))
672 {
673 return type;
674 }
675 }
676 return "";
677}
678
679void VirtualSensors::setupMatches()
680{
681 /* Already setup */
682 if (!this->matches.empty())
683 {
684 return;
685 }
686
687 /* Setup matches */
688 auto eventHandler = [this](sdbusplus::message::message& message) {
689 if (message.is_method_error())
690 {
691 log<level::ERR>("Callback method error");
692 return;
693 }
694 this->propertiesChanged(message);
695 };
696
697 for (const char* iface : calculationIfaces)
698 {
699 auto match = std::make_unique<sdbusplus::bus::match::match>(
700 bus,
701 sdbusplus::bus::match::rules::propertiesChangedNamespace(
702 "/xyz/openbmc_project/inventory", iface),
703 eventHandler);
704 this->matches.emplace_back(std::move(match));
705 }
706}
707
708void VirtualSensors::createVirtualSensorsFromDBus(
709 const std::string& calculationIface)
710{
711 if (calculationIface.empty())
712 {
713 log<level::ERR>("No calculation type supplied");
714 return;
715 }
716 auto objects = getObjectsFromDBus();
717
718 /* Get virtual sensors config data */
719 for (const auto& [path, interfaceMap] : objects)
720 {
721 auto objpath = static_cast<std::string>(path);
722 std::string name = path.filename();
723 std::string sensorType, sensorUnit;
724
725 /* Find Virtual Sensor interfaces */
726 if (!interfaceMap.contains(calculationIface))
727 {
728 continue;
729 }
730 if (name.empty())
731 {
732 log<level::ERR>(
733 "Virtual Sensor name not found in entity manager config");
734 continue;
735 }
736 if (virtualSensorsMap.contains(name))
737 {
738 log<level::ERR>("A virtual sensor with this name already exists",
739 entry("NAME=%s", name.c_str()));
740 continue;
741 }
742
743 /* Extract the virtual sensor type as we need this to initialize the
744 * sensor */
745 for (const auto& [interface, propertyMap] : interfaceMap)
746 {
747 if (interface != calculationIface)
748 {
749 continue;
750 }
751 auto itr = propertyMap.find("Units");
752 if (itr != propertyMap.end())
753 {
754 sensorUnit = std::get<std::string>(itr->second);
755 break;
756 }
757 }
758 sensorType = getSensorTypeFromUnit(sensorUnit);
759 if (sensorType.empty())
760 {
761 log<level::ERR>("Sensor unit is not supported",
762 entry("TYPE=%s", sensorUnit.c_str()));
763 continue;
764 }
765
766 try
767 {
768 auto virtObjPath = sensorDbusPath + sensorType + "/" + name;
769
770 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
771 bus, virtObjPath.c_str(), interfaceMap, name, sensorType,
772 calculationIface);
773 log<level::INFO>("Added a new virtual sensor",
774 entry("NAME=%s", name.c_str()));
775 virtualSensorPtr->updateVirtualSensor();
776
777 /* Initialize unit value for virtual sensor */
778 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
779 virtualSensorPtr->emit_object_added();
780
781 virtualSensorsMap.emplace(name, std::move(virtualSensorPtr));
782
783 /* Setup match for interfaces removed */
784 auto intfRemoved = [this, objpath,
785 name](sdbusplus::message::message& message) {
786 if (!virtualSensorsMap.contains(name))
787 {
788 return;
789 }
790 sdbusplus::message::object_path path;
791 message.read(path);
792 if (static_cast<const std::string&>(path) == objpath)
793 {
794 log<level::INFO>("Removed a virtual sensor",
795 entry("NAME=%s", name.c_str()));
796 virtualSensorsMap.erase(name);
797 }
798 };
799 auto matchOnRemove = std::make_unique<sdbusplus::bus::match::match>(
800 bus,
801 sdbusplus::bus::match::rules::interfacesRemoved() +
802 sdbusplus::bus::match::rules::argNpath(0, objpath),
803 intfRemoved);
804 /* TODO: slight race condition here. Check that the config still
805 * exists */
806 this->matches.emplace_back(std::move(matchOnRemove));
807 }
808 catch (std::invalid_argument& ia)
809 {
810 log<level::ERR>("Failed to set up virtual sensor",
811 entry("Error=%s", ia.what()));
812 }
813 }
814}
815
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700816void VirtualSensors::createVirtualSensors()
817{
818 static const Json empty{};
819
820 auto data = parseConfigFile(VIRTUAL_SENSOR_CONFIG_FILE);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000821
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700822 // print values
823 if (DEBUG)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000824 {
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700825 std::cout << "Config json data:\n" << data << "\n\n";
Rashmica Guptae7efe132021-07-27 19:42:11 +1000826 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700827
828 /* Get virtual sensors config data */
829 for (const auto& j : data)
830 {
831 auto desc = j.value("Desc", empty);
832 if (!desc.empty())
833 {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000834 if (desc.value("Config", "") == "D-Bus")
835 {
836 /* Look on D-Bus for a virtual sensor config. Set up matches
837 * first because the configs may not be on D-Bus yet and we
838 * don't want to miss them */
839 setupMatches();
840
841 if (desc.contains("Type"))
842 {
843 auto path = "xyz.openbmc_project.Configuration." +
844 desc.value("Type", "");
845 if (!isCalculationType(path))
846 {
847 log<level::ERR>(
848 "Invalid calculation type supplied\n",
849 entry("TYPE=%s", desc.value("Type", "").c_str()));
850 continue;
851 }
852 createVirtualSensorsFromDBus(path);
853 }
854 continue;
855 }
856
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700857 std::string sensorType = desc.value("SensorType", "");
858 std::string name = desc.value("Name", "");
Rashmica Gupta665a0a22021-06-30 11:35:28 +1000859 std::replace(name.begin(), name.end(), ' ', '_');
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700860
861 if (!name.empty() && !sensorType.empty())
862 {
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700863 if (unitMap.find(sensorType) == unitMap.end())
864 {
865 log<level::ERR>("Sensor type is not supported",
Patrick Williams1846d822021-06-23 14:44:07 -0500866 entry("TYPE=%s", sensorType.c_str()));
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700867 }
868 else
869 {
Rashmica Gupta67d8b9d2021-06-30 11:41:14 +1000870 if (virtualSensorsMap.find(name) != virtualSensorsMap.end())
871 {
872 log<level::ERR>(
873 "A virtual sensor with this name already exists",
874 entry("TYPE=%s", name.c_str()));
875 continue;
876 }
Rashmica Gupta862c3d12021-08-06 12:19:31 +1000877 auto objPath = sensorDbusPath + sensorType + "/" + name;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700878
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700879 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
880 bus, objPath.c_str(), j, name);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700881
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700882 log<level::INFO>("Added a new virtual sensor",
Patrick Williams1846d822021-06-23 14:44:07 -0500883 entry("NAME=%s", name.c_str()));
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700884 virtualSensorPtr->updateVirtualSensor();
885
886 /* Initialize unit value for virtual sensor */
887 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
Rashmica Guptaa2fa63a2021-08-06 12:21:13 +1000888 virtualSensorPtr->emit_object_added();
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700889
890 virtualSensorsMap.emplace(std::move(name),
891 std::move(virtualSensorPtr));
892 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700893 }
894 else
895 {
896 log<level::ERR>("Sensor type or name not found in config file");
897 }
898 }
899 else
900 {
901 log<level::ERR>(
902 "Descriptor for new virtual sensor not found in config file");
903 }
904 }
905}
906
907} // namespace virtualSensor
908} // namespace phosphor
909
910/**
911 * @brief Main
912 */
913int main()
914{
915
916 // Get a default event loop
917 auto event = sdeventplus::Event::get_default();
918
919 // Get a handle to system dbus
920 auto bus = sdbusplus::bus::new_default();
921
Matt Spinler6c19e7d2021-01-12 16:26:45 -0600922 // Add the ObjectManager interface
923 sdbusplus::server::manager::manager objManager(bus, "/");
924
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700925 // Create an virtual sensors object
926 phosphor::virtualSensor::VirtualSensors virtualSensors(bus);
927
928 // Request service bus name
929 bus.request_name(busName);
930
931 // Attach the bus to sd_event to service user requests
932 bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
933 event.loop();
934
935 return 0;
936}