blob: ce69291bcbf20cc76b610f550725ce28fb1d1eb6 [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>
9#include <iostream>
10
11static constexpr bool DEBUG = false;
12static constexpr auto busName = "xyz.openbmc_project.VirtualSensor";
13static constexpr auto sensorDbusPath = "/xyz/openbmc_project/sensors/";
Rashmica Guptae7efe132021-07-27 19:42:11 +100014static constexpr auto entityManagerBusName =
15 "xyz.openbmc_project.EntityManager";
16static constexpr auto vsThresholdsIfaceSuffix = ".Thresholds";
Rashmica Gupta304fd0e2021-08-10 16:50:44 +100017static constexpr std::array<const char*, 1> calculationIfaces = {
18 "xyz.openbmc_project.Configuration.ModifiedMedian"};
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +100019static constexpr auto defaultHysteresis = 0;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070020
Patrick Williams82b39c62021-07-28 16:22:27 -050021PHOSPHOR_LOG2_USING_WITH_FLAGS;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070022
Vijay Khemka51f898e2020-09-09 22:24:18 -070023int handleDbusSignal(sd_bus_message* msg, void* usrData, sd_bus_error*)
24{
25 if (usrData == nullptr)
26 {
27 throw std::runtime_error("Invalid match");
28 }
29
30 auto sdbpMsg = sdbusplus::message::message(msg);
31 std::string msgIfce;
32 std::map<std::string, std::variant<int64_t, double, bool>> msgData;
33
34 sdbpMsg.read(msgIfce, msgData);
35
36 if (msgData.find("Value") != msgData.end())
37 {
38 using namespace phosphor::virtualSensor;
39 VirtualSensor* obj = static_cast<VirtualSensor*>(usrData);
40 // TODO(openbmc/phosphor-virtual-sensor#1): updateVirtualSensor should
41 // be changed to take the information we got from the signal, to avoid
42 // having to do numerous dbus queries.
43 obj->updateVirtualSensor();
44 }
45 return 0;
46}
47
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070048namespace phosphor
49{
50namespace virtualSensor
51{
52
53void printParams(const VirtualSensor::ParamMap& paramMap)
54{
55 for (const auto& p : paramMap)
56 {
57 const auto& p1 = p.first;
58 const auto& p2 = p.second;
59 auto val = p2->getParamValue();
60 std::cout << p1 << " = " << val << "\n";
61 }
62}
63
64double SensorParam::getParamValue()
65{
66 switch (paramType)
67 {
68 case constParam:
69 return value;
70 break;
Vijay Khemka7452a862020-08-11 16:01:23 -070071 case dbusParam:
72 return dbusSensor->getSensorValue();
73 break;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070074 default:
75 throw std::invalid_argument("param type not supported");
76 }
77}
78
Lei YU0fcf0e12021-06-04 11:14:17 +080079using AssociationList =
80 std::vector<std::tuple<std::string, std::string, std::string>>;
81
82AssociationList getAssociationsFromJson(const Json& j)
83{
84 AssociationList assocs{};
85 try
86 {
87 j.get_to(assocs);
88 }
89 catch (const std::exception& ex)
90 {
Patrick Williams82b39c62021-07-28 16:22:27 -050091 error("Failed to parse association: {ERROR}", "ERROR", ex);
Lei YU0fcf0e12021-06-04 11:14:17 +080092 }
93 return assocs;
94}
95
Rashmica Guptae7efe132021-07-27 19:42:11 +100096template <typename U>
97struct VariantToNumber
98{
99 template <typename T>
100 U operator()(const T& t) const
101 {
102 if constexpr (std::is_convertible<T, U>::value)
103 {
104 return static_cast<U>(t);
105 }
106 throw std::invalid_argument("Invalid number type in config\n");
107 }
108};
109
110template <typename U>
111U getNumberFromConfig(const PropertyMap& map, const std::string& name,
112 bool required)
113{
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 }
123 return std::numeric_limits<U>::quiet_NaN();
124}
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,
138 uint64_t severity)
139{
140 std::string threshold;
141 std::string suffix;
142 static const std::array thresholdTypes{"Warning", "Critical",
143 "PerformanceLoss", "SoftShutdown",
144 "HardShutdown"};
145
146 if (severity >= thresholdTypes.size())
147 {
148 throw std::invalid_argument(
149 "Invalid threshold severity specified in entity manager");
150 }
151 threshold = thresholdTypes[severity];
152
153 if (direction == "less than")
154 {
155 suffix = "Low";
156 }
157 else if (direction == "greater than")
158 {
159 suffix = "High";
160 }
161 else
162 {
163 throw std::invalid_argument(
164 "Invalid threshold direction specified in entity manager");
165 }
166 return threshold + suffix;
167}
168
169void parseThresholds(Json& thresholds, const PropertyMap& propertyMap)
170{
171 std::string direction;
172
173 auto severity =
174 getNumberFromConfig<uint64_t>(propertyMap, "Severity", true);
175 auto value = getNumberFromConfig<double>(propertyMap, "Value", true);
176
177 auto itr = propertyMap.find("Direction");
178 if (itr != propertyMap.end())
179 {
180 direction = std::get<std::string>(itr->second);
181 }
182
183 auto threshold = getThresholdType(direction, severity);
184 thresholds[threshold] = value;
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000185
186 auto hysteresis =
187 getNumberFromConfig<double>(propertyMap, "Hysteresis", false);
188 if (hysteresis != std::numeric_limits<double>::quiet_NaN())
189 {
190 thresholds[threshold + "Hysteresis"] = hysteresis;
191 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000192}
193
194void VirtualSensor::parseConfigInterface(const PropertyMap& propertyMap,
195 const std::string& sensorType,
196 const std::string& interface)
197{
198 /* Parse sensors / DBus params */
199 if (auto itr = propertyMap.find("Sensors"); itr != propertyMap.end())
200 {
201 auto sensors = std::get<std::vector<std::string>>(itr->second);
202 for (auto sensor : sensors)
203 {
204 std::replace(sensor.begin(), sensor.end(), ' ', '_');
205 auto sensorObjPath = sensorDbusPath + sensorType + "/" + sensor;
206
207 auto paramPtr =
208 std::make_unique<SensorParam>(bus, sensorObjPath, this);
209 symbols.create_variable(sensor);
210 paramMap.emplace(std::move(sensor), std::move(paramPtr));
211 }
212 }
213 /* Get expression string */
214 if (!isCalculationType(interface))
215 {
216 throw std::invalid_argument("Invalid expression in interface");
217 }
218 exprStr = interface;
219
220 /* Get optional min and max input and output values */
221 ValueIface::maxValue(
222 getNumberFromConfig<double>(propertyMap, "MaxValue", false));
223 ValueIface::minValue(
224 getNumberFromConfig<double>(propertyMap, "MinValue", false));
225 maxValidInput =
226 getNumberFromConfig<double>(propertyMap, "MaxValidInput", false);
227 minValidInput =
228 getNumberFromConfig<double>(propertyMap, "MinValidInput", false);
229}
230
Matt Spinlerce675222021-01-14 16:38:09 -0600231void VirtualSensor::initVirtualSensor(const Json& sensorConfig,
232 const std::string& objPath)
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700233{
234
235 static const Json empty{};
236
237 /* Get threshold values if defined in config */
238 auto threshold = sensorConfig.value("Threshold", empty);
Matt Spinlerf15189e2021-01-15 10:13:28 -0600239
Rashmica Gupta3e999192021-06-09 16:17:04 +1000240 createThresholds(threshold, objPath);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700241
Harvey Wuf6443742021-04-09 16:47:36 +0800242 /* Get MaxValue, MinValue setting if defined in config */
243 auto confDesc = sensorConfig.value("Desc", empty);
244 if (auto maxConf = confDesc.find("MaxValue");
245 maxConf != confDesc.end() && maxConf->is_number())
246 {
247 ValueIface::maxValue(maxConf->get<double>());
248 }
249 if (auto minConf = confDesc.find("MinValue");
250 minConf != confDesc.end() && minConf->is_number())
251 {
252 ValueIface::minValue(minConf->get<double>());
253 }
254
Lei YU0fcf0e12021-06-04 11:14:17 +0800255 /* Get optional association */
256 auto assocJson = sensorConfig.value("Associations", empty);
257 if (!assocJson.empty())
258 {
259 auto assocs = getAssociationsFromJson(assocJson);
260 if (!assocs.empty())
261 {
262 associationIface =
263 std::make_unique<AssociationObject>(bus, objPath.c_str());
264 associationIface->associations(assocs);
265 }
266 }
267
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700268 /* Get expression string */
269 exprStr = sensorConfig.value("Expression", "");
270
271 /* Get all the parameter listed in configuration */
272 auto params = sensorConfig.value("Params", empty);
273
274 /* Check for constant parameter */
275 const auto& consParams = params.value("ConstParam", empty);
276 if (!consParams.empty())
277 {
278 for (auto& j : consParams)
279 {
280 if (j.find("ParamName") != j.end())
281 {
282 auto paramPtr = std::make_unique<SensorParam>(j["Value"]);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700283 std::string name = j["ParamName"];
284 symbols.create_variable(name);
285 paramMap.emplace(std::move(name), std::move(paramPtr));
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700286 }
287 else
288 {
289 /* Invalid configuration */
290 throw std::invalid_argument(
291 "ParamName not found in configuration");
292 }
293 }
294 }
295
Vijay Khemka7452a862020-08-11 16:01:23 -0700296 /* Check for dbus parameter */
297 auto dbusParams = params.value("DbusParam", empty);
298 if (!dbusParams.empty())
299 {
300 for (auto& j : dbusParams)
301 {
302 /* Get parameter dbus sensor descriptor */
303 auto desc = j.value("Desc", empty);
304 if ((!desc.empty()) && (j.find("ParamName") != j.end()))
305 {
306 std::string sensorType = desc.value("SensorType", "");
307 std::string name = desc.value("Name", "");
308
309 if (!sensorType.empty() && !name.empty())
310 {
Rashmica Gupta862c3d12021-08-06 12:19:31 +1000311 auto objPath = sensorDbusPath + sensorType + "/" + name;
Vijay Khemka7452a862020-08-11 16:01:23 -0700312
Vijay Khemka51f898e2020-09-09 22:24:18 -0700313 auto paramPtr =
314 std::make_unique<SensorParam>(bus, objPath, this);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700315 std::string name = j["ParamName"];
316 symbols.create_variable(name);
317 paramMap.emplace(std::move(name), std::move(paramPtr));
Vijay Khemka7452a862020-08-11 16:01:23 -0700318 }
319 }
320 }
321 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700322
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700323 symbols.add_constants();
Matt Spinler9f1ef4f2020-11-09 15:59:11 -0600324 symbols.add_package(vecopsPackage);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700325 expression.register_symbol_table(symbols);
326
327 /* parser from exprtk */
328 exprtk::parser<double> parser{};
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600329 if (!parser.compile(exprStr, expression))
330 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500331 error("Expression compilation failed");
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600332
333 for (std::size_t i = 0; i < parser.error_count(); ++i)
334 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500335 auto err = parser.get_error(i);
336 error("Error parsing token at {POSITION}: {ERROR}", "POSITION",
337 err.token.position, "TYPE",
338 exprtk::parser_error::to_str(err.mode), "ERROR",
339 err.diagnostic);
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600340 }
341 throw std::runtime_error("Expression compilation failed");
342 }
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700343
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700344 /* Print all parameters for debug purpose only */
345 if (DEBUG)
346 printParams(paramMap);
347}
348
Rashmica Guptae7efe132021-07-27 19:42:11 +1000349void VirtualSensor::initVirtualSensor(const InterfaceMap& interfaceMap,
350 const std::string& objPath,
351 const std::string& sensorType,
352 const std::string& calculationIface)
353{
354 Json thresholds;
355 const std::string vsThresholdsIntf =
356 calculationIface + vsThresholdsIfaceSuffix;
357
358 for (const auto& [interface, propertyMap] : interfaceMap)
359 {
360 /* Each threshold is on it's own interface with a number as a suffix
361 * eg xyz.openbmc_project.Configuration.ModifiedMedian.Thresholds1 */
362 if (interface.find(vsThresholdsIntf) != std::string::npos)
363 {
364 parseThresholds(thresholds, propertyMap);
365 }
366 else if (interface == calculationIface)
367 {
368 parseConfigInterface(propertyMap, sensorType, interface);
369 }
370 }
371
372 createThresholds(thresholds, objPath);
373 symbols.add_constants();
374 symbols.add_package(vecopsPackage);
375 expression.register_symbol_table(symbols);
376
377 /* Print all parameters for debug purpose only */
378 if (DEBUG)
379 {
380 printParams(paramMap);
381 }
382}
383
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700384void VirtualSensor::setSensorValue(double value)
385{
Patrick Williams543bf662021-04-29 09:03:53 -0500386 value = std::clamp(value, ValueIface::minValue(), ValueIface::maxValue());
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700387 ValueIface::value(value);
388}
389
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000390double VirtualSensor::calculateValue(const std::string& calculation,
391 const VirtualSensor::ParamMap& paramMap)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000392{
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000393 auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(),
394 calculation);
395 if (itr == calculationIfaces.end())
396 {
397 return std::numeric_limits<double>::quiet_NaN();
398 }
399 else if (calculation == "xyz.openbmc_project.Configuration.ModifiedMedian")
400 {
401 return calculateModifiedMedianValue(paramMap);
402 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000403 return std::numeric_limits<double>::quiet_NaN();
404}
405
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000406bool VirtualSensor::sensorInRange(double value)
407{
408 if (value <= this->maxValidInput && value >= this->minValidInput)
409 {
410 return true;
411 }
412 return false;
413}
414
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700415void VirtualSensor::updateVirtualSensor()
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700416{
417 for (auto& param : paramMap)
418 {
419 auto& name = param.first;
420 auto& data = param.second;
421 if (auto var = symbols.get_variable(name))
422 {
423 var->ref() = data->getParamValue();
424 }
425 else
426 {
427 /* Invalid parameter */
428 throw std::invalid_argument("ParamName not found in symbols");
429 }
430 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000431 auto itr =
432 std::find(calculationIfaces.begin(), calculationIfaces.end(), exprStr);
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000433 auto val = (itr == calculationIfaces.end())
434 ? expression.value()
435 : calculateValue(exprStr, paramMap);
Vijay Khemka32a71562020-09-10 15:29:18 -0700436
437 /* Set sensor value to dbus interface */
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700438 setSensorValue(val);
Vijay Khemka32a71562020-09-10 15:29:18 -0700439
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700440 if (DEBUG)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000441 {
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700442 std::cout << "Sensor value is " << val << "\n";
Rashmica Guptae7efe132021-07-27 19:42:11 +1000443 }
Vijay Khemka32a71562020-09-10 15:29:18 -0700444
Matt Spinler8f5e6112021-01-15 10:44:32 -0600445 /* Check sensor thresholds and log required message */
Matt Spinlerb306b032021-02-01 10:05:46 -0600446 checkThresholds(val, perfLossIface);
Patrick Williamsfdb826d2021-01-20 14:37:53 -0600447 checkThresholds(val, warningIface);
448 checkThresholds(val, criticalIface);
449 checkThresholds(val, softShutdownIface);
450 checkThresholds(val, hardShutdownIface);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700451}
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700452
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000453double VirtualSensor::calculateModifiedMedianValue(
454 const VirtualSensor::ParamMap& paramMap)
455{
456 std::vector<double> values;
457
458 for (auto& param : paramMap)
459 {
460 auto& name = param.first;
461 if (auto var = symbols.get_variable(name))
462 {
463 if (!sensorInRange(var->ref()))
464 {
465 continue;
466 }
467 values.push_back(var->ref());
468 }
469 }
470
471 size_t size = values.size();
472 std::sort(values.begin(), values.end());
473 switch (size)
474 {
475 case 2:
476 /* Choose biggest value */
477 return values.at(1);
478 case 0:
479 return std::numeric_limits<double>::quiet_NaN();
480 default:
481 /* Choose median value */
482 if (size % 2 == 0)
483 {
484 // Average of the two middle values
485 return (values.at(size / 2) + values.at(size / 2 - 1)) / 2;
486 }
487 else
488 {
489 return values.at((size - 1) / 2);
490 }
491 }
492}
493
Rashmica Gupta3e999192021-06-09 16:17:04 +1000494void VirtualSensor::createThresholds(const Json& threshold,
495 const std::string& objPath)
496{
497 if (threshold.empty())
498 {
499 return;
500 }
501 // Only create the threshold interfaces if
502 // at least one of their values is present.
503 if (threshold.contains("CriticalHigh") || threshold.contains("CriticalLow"))
504 {
505 criticalIface =
506 std::make_unique<Threshold<CriticalObject>>(bus, objPath.c_str());
507
508 criticalIface->criticalHigh(threshold.value(
509 "CriticalHigh", std::numeric_limits<double>::quiet_NaN()));
510 criticalIface->criticalLow(threshold.value(
511 "CriticalLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000512 criticalIface->setHighHysteresis(
513 threshold.value("CriticalHighHysteresis", defaultHysteresis));
514 criticalIface->setLowHysteresis(
515 threshold.value("CriticalLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000516 }
517
518 if (threshold.contains("WarningHigh") || threshold.contains("WarningLow"))
519 {
520 warningIface =
521 std::make_unique<Threshold<WarningObject>>(bus, objPath.c_str());
522
523 warningIface->warningHigh(threshold.value(
524 "WarningHigh", std::numeric_limits<double>::quiet_NaN()));
525 warningIface->warningLow(threshold.value(
526 "WarningLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000527 warningIface->setHighHysteresis(
528 threshold.value("WarningHighHysteresis", defaultHysteresis));
529 warningIface->setLowHysteresis(
530 threshold.value("WarningLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000531 }
532
533 if (threshold.contains("HardShutdownHigh") ||
534 threshold.contains("HardShutdownLow"))
535 {
536 hardShutdownIface = std::make_unique<Threshold<HardShutdownObject>>(
537 bus, objPath.c_str());
538
539 hardShutdownIface->hardShutdownHigh(threshold.value(
540 "HardShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
541 hardShutdownIface->hardShutdownLow(threshold.value(
542 "HardShutdownLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000543 hardShutdownIface->setHighHysteresis(
544 threshold.value("HardShutdownHighHysteresis", defaultHysteresis));
545 hardShutdownIface->setLowHysteresis(
546 threshold.value("HardShutdownLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000547 }
548
549 if (threshold.contains("SoftShutdownHigh") ||
550 threshold.contains("SoftShutdownLow"))
551 {
552 softShutdownIface = std::make_unique<Threshold<SoftShutdownObject>>(
553 bus, objPath.c_str());
554
555 softShutdownIface->softShutdownHigh(threshold.value(
556 "SoftShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
557 softShutdownIface->softShutdownLow(threshold.value(
558 "SoftShutdownLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000559 softShutdownIface->setHighHysteresis(
560 threshold.value("SoftShutdownHighHysteresis", defaultHysteresis));
561 softShutdownIface->setLowHysteresis(
562 threshold.value("SoftShutdownLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000563 }
564
565 if (threshold.contains("PerformanceLossHigh") ||
566 threshold.contains("PerformanceLossLow"))
567 {
568 perfLossIface = std::make_unique<Threshold<PerformanceLossObject>>(
569 bus, objPath.c_str());
570
571 perfLossIface->performanceLossHigh(threshold.value(
572 "PerformanceLossHigh", std::numeric_limits<double>::quiet_NaN()));
573 perfLossIface->performanceLossLow(threshold.value(
574 "PerformanceLossLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000575 perfLossIface->setHighHysteresis(threshold.value(
576 "PerformanceLossHighHysteresis", defaultHysteresis));
577 perfLossIface->setLowHysteresis(
578 threshold.value("PerformanceLossLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000579 }
580}
581
Rashmica Guptae7efe132021-07-27 19:42:11 +1000582ManagedObjectType VirtualSensors::getObjectsFromDBus()
583{
584 ManagedObjectType objects;
585
586 try
587 {
588 auto method = bus.new_method_call(entityManagerBusName, "/",
589 "org.freedesktop.DBus.ObjectManager",
590 "GetManagedObjects");
591 auto reply = bus.call(method);
592 reply.read(objects);
593 }
594 catch (const sdbusplus::exception::SdBusError& ex)
595 {
596 // If entity manager isn't running yet, keep going.
597 if (std::string("org.freedesktop.DBus.Error.ServiceUnknown") !=
598 ex.name())
599 {
600 throw ex.name();
601 }
602 }
603
604 return objects;
605}
606
607void VirtualSensors::propertiesChanged(sdbusplus::message::message& msg)
608{
609 std::string path;
610 PropertyMap properties;
611
612 msg.read(path, properties);
613
614 /* We get multiple callbacks for one sensor. 'Type' is a required field and
615 * is a unique label so use to to only proceed once per sensor */
616 if (properties.contains("Type"))
617 {
618 if (isCalculationType(path))
619 {
620 createVirtualSensorsFromDBus(path);
621 }
622 }
623}
624
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700625/** @brief Parsing Virtual Sensor config JSON file */
626Json VirtualSensors::parseConfigFile(const std::string configFile)
627{
628 std::ifstream jsonFile(configFile);
629 if (!jsonFile.is_open())
630 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500631 error("config JSON file {FILENAME} not found", "FILENAME", configFile);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000632 return {};
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700633 }
634
635 auto data = Json::parse(jsonFile, nullptr, false);
636 if (data.is_discarded())
637 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500638 error("config readings JSON parser failure with {FILENAME}", "FILENAME",
639 configFile);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700640 throw std::exception{};
641 }
642
643 return data;
644}
645
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700646std::map<std::string, ValueIface::Unit> unitMap = {
647 {"temperature", ValueIface::Unit::DegreesC},
648 {"fan_tach", ValueIface::Unit::RPMS},
649 {"voltage", ValueIface::Unit::Volts},
650 {"altitude", ValueIface::Unit::Meters},
651 {"current", ValueIface::Unit::Amperes},
652 {"power", ValueIface::Unit::Watts},
653 {"energy", ValueIface::Unit::Joules},
Kumar Thangavel2b56ddb2021-01-13 20:16:11 +0530654 {"utilization", ValueIface::Unit::Percent},
Rashmica Gupta4ac7a7f2021-07-08 12:30:50 +1000655 {"airflow", ValueIface::Unit::CFM},
656 {"pressure", ValueIface::Unit::Pascals}};
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700657
Rashmica Guptae7efe132021-07-27 19:42:11 +1000658const std::string getSensorTypeFromUnit(const std::string& unit)
659{
660 std::string unitPrefix = "xyz.openbmc_project.Sensor.Value.Unit.";
661 for (auto [type, unitObj] : unitMap)
662 {
663 auto unitPath = ValueIface::convertUnitToString(unitObj);
664 if (unitPath == (unitPrefix + unit))
665 {
666 return type;
667 }
668 }
669 return "";
670}
671
672void VirtualSensors::setupMatches()
673{
674 /* Already setup */
675 if (!this->matches.empty())
676 {
677 return;
678 }
679
680 /* Setup matches */
681 auto eventHandler = [this](sdbusplus::message::message& message) {
682 if (message.is_method_error())
683 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500684 error("Callback method error");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000685 return;
686 }
687 this->propertiesChanged(message);
688 };
689
690 for (const char* iface : calculationIfaces)
691 {
692 auto match = std::make_unique<sdbusplus::bus::match::match>(
693 bus,
694 sdbusplus::bus::match::rules::propertiesChangedNamespace(
695 "/xyz/openbmc_project/inventory", iface),
696 eventHandler);
697 this->matches.emplace_back(std::move(match));
698 }
699}
700
701void VirtualSensors::createVirtualSensorsFromDBus(
702 const std::string& calculationIface)
703{
704 if (calculationIface.empty())
705 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500706 error("No calculation type supplied");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000707 return;
708 }
709 auto objects = getObjectsFromDBus();
710
711 /* Get virtual sensors config data */
712 for (const auto& [path, interfaceMap] : objects)
713 {
714 auto objpath = static_cast<std::string>(path);
715 std::string name = path.filename();
716 std::string sensorType, sensorUnit;
717
718 /* Find Virtual Sensor interfaces */
719 if (!interfaceMap.contains(calculationIface))
720 {
721 continue;
722 }
723 if (name.empty())
724 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500725 error("Virtual Sensor name not found in entity manager config");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000726 continue;
727 }
728 if (virtualSensorsMap.contains(name))
729 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500730 error("A virtual sensor named {NAME} already exists", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000731 continue;
732 }
733
734 /* Extract the virtual sensor type as we need this to initialize the
735 * sensor */
736 for (const auto& [interface, propertyMap] : interfaceMap)
737 {
738 if (interface != calculationIface)
739 {
740 continue;
741 }
742 auto itr = propertyMap.find("Units");
743 if (itr != propertyMap.end())
744 {
745 sensorUnit = std::get<std::string>(itr->second);
746 break;
747 }
748 }
749 sensorType = getSensorTypeFromUnit(sensorUnit);
750 if (sensorType.empty())
751 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500752 error("Sensor unit type {TYPE} is not supported", "TYPE",
753 sensorUnit);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000754 continue;
755 }
756
757 try
758 {
759 auto virtObjPath = sensorDbusPath + sensorType + "/" + name;
760
761 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
762 bus, virtObjPath.c_str(), interfaceMap, name, sensorType,
763 calculationIface);
Patrick Williams82b39c62021-07-28 16:22:27 -0500764 info("Added a new virtual sensor: {NAME} {TYPE}", "NAME", name,
765 "TYPE", sensorType);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000766 virtualSensorPtr->updateVirtualSensor();
767
768 /* Initialize unit value for virtual sensor */
769 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
770 virtualSensorPtr->emit_object_added();
771
772 virtualSensorsMap.emplace(name, std::move(virtualSensorPtr));
773
774 /* Setup match for interfaces removed */
775 auto intfRemoved = [this, objpath,
776 name](sdbusplus::message::message& message) {
777 if (!virtualSensorsMap.contains(name))
778 {
779 return;
780 }
781 sdbusplus::message::object_path path;
782 message.read(path);
783 if (static_cast<const std::string&>(path) == objpath)
784 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500785 info("Removed a virtual sensor: {NAME}", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000786 virtualSensorsMap.erase(name);
787 }
788 };
789 auto matchOnRemove = std::make_unique<sdbusplus::bus::match::match>(
790 bus,
791 sdbusplus::bus::match::rules::interfacesRemoved() +
792 sdbusplus::bus::match::rules::argNpath(0, objpath),
793 intfRemoved);
794 /* TODO: slight race condition here. Check that the config still
795 * exists */
796 this->matches.emplace_back(std::move(matchOnRemove));
797 }
798 catch (std::invalid_argument& ia)
799 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500800 error("Failed to set up virtual sensor: {ERROR}", "ERROR", ia);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000801 }
802 }
803}
804
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700805void VirtualSensors::createVirtualSensors()
806{
807 static const Json empty{};
808
809 auto data = parseConfigFile(VIRTUAL_SENSOR_CONFIG_FILE);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000810
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700811 // print values
812 if (DEBUG)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000813 {
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700814 std::cout << "Config json data:\n" << data << "\n\n";
Rashmica Guptae7efe132021-07-27 19:42:11 +1000815 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700816
817 /* Get virtual sensors config data */
818 for (const auto& j : data)
819 {
820 auto desc = j.value("Desc", empty);
821 if (!desc.empty())
822 {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000823 if (desc.value("Config", "") == "D-Bus")
824 {
825 /* Look on D-Bus for a virtual sensor config. Set up matches
826 * first because the configs may not be on D-Bus yet and we
827 * don't want to miss them */
828 setupMatches();
829
830 if (desc.contains("Type"))
831 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500832 auto type = desc.value("Type", "");
833 auto path = "xyz.openbmc_project.Configuration." + type;
834
Rashmica Guptae7efe132021-07-27 19:42:11 +1000835 if (!isCalculationType(path))
836 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500837 error("Invalid calculation type {TYPE} supplied.",
838 "TYPE", type);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000839 continue;
840 }
841 createVirtualSensorsFromDBus(path);
842 }
843 continue;
844 }
845
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700846 std::string sensorType = desc.value("SensorType", "");
847 std::string name = desc.value("Name", "");
Rashmica Gupta665a0a22021-06-30 11:35:28 +1000848 std::replace(name.begin(), name.end(), ' ', '_');
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700849
850 if (!name.empty() && !sensorType.empty())
851 {
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700852 if (unitMap.find(sensorType) == unitMap.end())
853 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500854 error("Sensor type {TYPE} is not supported", "TYPE",
855 sensorType);
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700856 }
857 else
858 {
Rashmica Gupta67d8b9d2021-06-30 11:41:14 +1000859 if (virtualSensorsMap.find(name) != virtualSensorsMap.end())
860 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500861 error("A virtual sensor named {NAME} already exists",
862 "NAME", name);
Rashmica Gupta67d8b9d2021-06-30 11:41:14 +1000863 continue;
864 }
Rashmica Gupta862c3d12021-08-06 12:19:31 +1000865 auto objPath = sensorDbusPath + sensorType + "/" + name;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700866
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700867 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
868 bus, objPath.c_str(), j, name);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700869
Patrick Williams82b39c62021-07-28 16:22:27 -0500870 info("Added a new virtual sensor: {NAME}", "NAME", name);
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700871 virtualSensorPtr->updateVirtualSensor();
872
873 /* Initialize unit value for virtual sensor */
874 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
Rashmica Guptaa2fa63a2021-08-06 12:21:13 +1000875 virtualSensorPtr->emit_object_added();
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700876
877 virtualSensorsMap.emplace(std::move(name),
878 std::move(virtualSensorPtr));
879 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700880 }
881 else
882 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500883 error(
884 "Sensor type ({TYPE}) or name ({NAME}) not found in config file",
885 "NAME", name, "TYPE", sensorType);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700886 }
887 }
888 else
889 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500890 error("Descriptor for new virtual sensor not found in config file");
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700891 }
892 }
893}
894
895} // namespace virtualSensor
896} // namespace phosphor
897
898/**
899 * @brief Main
900 */
901int main()
902{
903
904 // Get a default event loop
905 auto event = sdeventplus::Event::get_default();
906
907 // Get a handle to system dbus
908 auto bus = sdbusplus::bus::new_default();
909
Matt Spinler6c19e7d2021-01-12 16:26:45 -0600910 // Add the ObjectManager interface
911 sdbusplus::server::manager::manager objManager(bus, "/");
912
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700913 // Create an virtual sensors object
914 phosphor::virtualSensor::VirtualSensors virtualSensors(bus);
915
916 // Request service bus name
917 bus.request_name(busName);
918
919 // Attach the bus to sd_event to service user requests
920 bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
921 event.loop();
922
923 return 0;
924}