blob: db075488375736f7f71d416618d82e0f03555a69 [file] [log] [blame]
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001#include "virtualSensor.hpp"
2
3#include "config.hpp"
4
Patrick Williams82b39c62021-07-28 16:22:27 -05005#include <phosphor-logging/lg2.hpp>
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07006
7#include <fstream>
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07008
9static constexpr bool DEBUG = false;
10static constexpr auto busName = "xyz.openbmc_project.VirtualSensor";
11static constexpr auto sensorDbusPath = "/xyz/openbmc_project/sensors/";
Rashmica Guptae7efe132021-07-27 19:42:11 +100012static constexpr auto vsThresholdsIfaceSuffix = ".Thresholds";
Rashmica Gupta304fd0e2021-08-10 16:50:44 +100013static constexpr std::array<const char*, 1> calculationIfaces = {
14 "xyz.openbmc_project.Configuration.ModifiedMedian"};
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +100015static constexpr auto defaultHysteresis = 0;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070016
Patrick Williams82b39c62021-07-28 16:22:27 -050017PHOSPHOR_LOG2_USING_WITH_FLAGS;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070018
Vijay Khemka51f898e2020-09-09 22:24:18 -070019int handleDbusSignal(sd_bus_message* msg, void* usrData, sd_bus_error*)
20{
21 if (usrData == nullptr)
22 {
23 throw std::runtime_error("Invalid match");
24 }
25
Patrick Williams8e11ccc2022-07-22 19:26:57 -050026 auto sdbpMsg = sdbusplus::message_t(msg);
Vijay Khemka51f898e2020-09-09 22:24:18 -070027 std::string msgIfce;
28 std::map<std::string, std::variant<int64_t, double, bool>> msgData;
29
30 sdbpMsg.read(msgIfce, msgData);
31
32 if (msgData.find("Value") != msgData.end())
33 {
34 using namespace phosphor::virtualSensor;
35 VirtualSensor* obj = static_cast<VirtualSensor*>(usrData);
36 // TODO(openbmc/phosphor-virtual-sensor#1): updateVirtualSensor should
37 // be changed to take the information we got from the signal, to avoid
38 // having to do numerous dbus queries.
39 obj->updateVirtualSensor();
40 }
41 return 0;
42}
43
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070044namespace phosphor
45{
46namespace virtualSensor
47{
48
49void printParams(const VirtualSensor::ParamMap& paramMap)
50{
51 for (const auto& p : paramMap)
52 {
53 const auto& p1 = p.first;
54 const auto& p2 = p.second;
55 auto val = p2->getParamValue();
Patrick Williamsfbd71452021-08-30 06:59:46 -050056 debug("Parameter: {PARAM} = {VALUE}", "PARAM", p1, "VALUE", val);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070057 }
58}
59
60double SensorParam::getParamValue()
61{
62 switch (paramType)
63 {
64 case constParam:
65 return value;
66 break;
Vijay Khemka7452a862020-08-11 16:01:23 -070067 case dbusParam:
68 return dbusSensor->getSensorValue();
69 break;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070070 default:
71 throw std::invalid_argument("param type not supported");
72 }
73}
74
Lei YU0fcf0e12021-06-04 11:14:17 +080075using AssociationList =
76 std::vector<std::tuple<std::string, std::string, std::string>>;
77
78AssociationList getAssociationsFromJson(const Json& j)
79{
80 AssociationList assocs{};
81 try
82 {
83 j.get_to(assocs);
84 }
85 catch (const std::exception& ex)
86 {
Patrick Williams82b39c62021-07-28 16:22:27 -050087 error("Failed to parse association: {ERROR}", "ERROR", ex);
Lei YU0fcf0e12021-06-04 11:14:17 +080088 }
89 return assocs;
90}
91
Rashmica Guptae7efe132021-07-27 19:42:11 +100092template <typename U>
93struct VariantToNumber
94{
95 template <typename T>
96 U operator()(const T& t) const
97 {
98 if constexpr (std::is_convertible<T, U>::value)
99 {
100 return static_cast<U>(t);
101 }
102 throw std::invalid_argument("Invalid number type in config\n");
103 }
104};
105
106template <typename U>
107U getNumberFromConfig(const PropertyMap& map, const std::string& name,
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800108 bool required,
109 U defaultValue = std::numeric_limits<U>::quiet_NaN())
Rashmica Guptae7efe132021-07-27 19:42:11 +1000110{
111 if (auto itr = map.find(name); itr != map.end())
112 {
113 return std::visit(VariantToNumber<U>(), itr->second);
114 }
115 else if (required)
116 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500117 error("Required field {NAME} missing in config", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000118 throw std::invalid_argument("Required field missing in config");
119 }
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800120 return defaultValue;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000121}
122
123bool isCalculationType(const std::string& interface)
124{
125 auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(),
126 interface);
127 if (itr != calculationIfaces.end())
128 {
129 return true;
130 }
131 return false;
132}
133
134const std::string getThresholdType(const std::string& direction,
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100135 const std::string& severity)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000136{
Rashmica Guptae7efe132021-07-27 19:42:11 +1000137 std::string suffix;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000138
139 if (direction == "less than")
140 {
141 suffix = "Low";
142 }
143 else if (direction == "greater than")
144 {
145 suffix = "High";
146 }
147 else
148 {
149 throw std::invalid_argument(
150 "Invalid threshold direction specified in entity manager");
151 }
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100152 return severity + suffix;
153}
154
155std::string getSeverityField(const PropertyMap& propertyMap)
156{
157 static const std::array thresholdTypes{"Warning", "Critical",
158 "PerformanceLoss", "SoftShutdown",
159 "HardShutdown"};
160
161 std::string severity;
162 if (auto itr = propertyMap.find("Severity"); itr != propertyMap.end())
163 {
164 /* Severity should be a string, but can be an unsigned int */
165 if (std::holds_alternative<std::string>(itr->second))
166 {
167 severity = std::get<std::string>(itr->second);
168 if (0 == std::ranges::count(thresholdTypes, severity))
169 {
170 throw std::invalid_argument(
171 "Invalid threshold severity specified in entity manager");
172 }
173 }
174 else
175 {
176 auto sev =
177 getNumberFromConfig<uint64_t>(propertyMap, "Severity", true);
178 /* Checking bounds ourselves so we throw invalid argument on
179 * invalid user input */
180 if (sev >= thresholdTypes.size())
181 {
182 throw std::invalid_argument(
183 "Invalid threshold severity specified in entity manager");
184 }
185 severity = thresholdTypes.at(sev);
186 }
187 }
188 return severity;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000189}
190
191void parseThresholds(Json& thresholds, const PropertyMap& propertyMap)
192{
193 std::string direction;
194
Rashmica Guptae7efe132021-07-27 19:42:11 +1000195 auto value = getNumberFromConfig<double>(propertyMap, "Value", true);
196
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100197 auto severity = getSeverityField(propertyMap);
198
199 if (auto itr = propertyMap.find("Direction"); itr != propertyMap.end())
Rashmica Guptae7efe132021-07-27 19:42:11 +1000200 {
201 direction = std::get<std::string>(itr->second);
202 }
203
204 auto threshold = getThresholdType(direction, severity);
205 thresholds[threshold] = value;
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000206
207 auto hysteresis =
208 getNumberFromConfig<double>(propertyMap, "Hysteresis", false);
209 if (hysteresis != std::numeric_limits<double>::quiet_NaN())
210 {
211 thresholds[threshold + "Hysteresis"] = hysteresis;
212 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000213}
214
215void VirtualSensor::parseConfigInterface(const PropertyMap& propertyMap,
216 const std::string& sensorType,
217 const std::string& interface)
218{
219 /* Parse sensors / DBus params */
220 if (auto itr = propertyMap.find("Sensors"); itr != propertyMap.end())
221 {
222 auto sensors = std::get<std::vector<std::string>>(itr->second);
223 for (auto sensor : sensors)
224 {
225 std::replace(sensor.begin(), sensor.end(), ' ', '_');
226 auto sensorObjPath = sensorDbusPath + sensorType + "/" + sensor;
227
228 auto paramPtr =
229 std::make_unique<SensorParam>(bus, sensorObjPath, this);
230 symbols.create_variable(sensor);
231 paramMap.emplace(std::move(sensor), std::move(paramPtr));
232 }
233 }
234 /* Get expression string */
235 if (!isCalculationType(interface))
236 {
237 throw std::invalid_argument("Invalid expression in interface");
238 }
239 exprStr = interface;
240
241 /* Get optional min and max input and output values */
242 ValueIface::maxValue(
243 getNumberFromConfig<double>(propertyMap, "MaxValue", false));
244 ValueIface::minValue(
245 getNumberFromConfig<double>(propertyMap, "MinValue", false));
246 maxValidInput =
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800247 getNumberFromConfig<double>(propertyMap, "MaxValidInput", false,
248 std::numeric_limits<double>::infinity());
Rashmica Guptae7efe132021-07-27 19:42:11 +1000249 minValidInput =
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800250 getNumberFromConfig<double>(propertyMap, "MinValidInput", false,
251 -std::numeric_limits<double>::infinity());
Rashmica Guptae7efe132021-07-27 19:42:11 +1000252}
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{
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700257 static const Json empty{};
258
259 /* Get threshold values if defined in config */
260 auto threshold = sensorConfig.value("Threshold", empty);
Matt Spinlerf15189e2021-01-15 10:13:28 -0600261
Rashmica Gupta3e999192021-06-09 16:17:04 +1000262 createThresholds(threshold, objPath);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700263
Harvey Wuf6443742021-04-09 16:47:36 +0800264 /* Get MaxValue, MinValue setting if defined in config */
265 auto confDesc = sensorConfig.value("Desc", empty);
266 if (auto maxConf = confDesc.find("MaxValue");
267 maxConf != confDesc.end() && maxConf->is_number())
268 {
269 ValueIface::maxValue(maxConf->get<double>());
270 }
271 if (auto minConf = confDesc.find("MinValue");
272 minConf != confDesc.end() && minConf->is_number())
273 {
274 ValueIface::minValue(minConf->get<double>());
275 }
276
Lei YU0fcf0e12021-06-04 11:14:17 +0800277 /* Get optional association */
278 auto assocJson = sensorConfig.value("Associations", empty);
279 if (!assocJson.empty())
280 {
281 auto assocs = getAssociationsFromJson(assocJson);
282 if (!assocs.empty())
283 {
284 associationIface =
285 std::make_unique<AssociationObject>(bus, objPath.c_str());
286 associationIface->associations(assocs);
287 }
288 }
289
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700290 /* Get expression string */
Patrick Williams03c4c8e2022-04-14 22:19:44 -0500291 static constexpr auto exprKey = "Expression";
292 if (sensorConfig.contains(exprKey))
293 {
Patrick Williamsa9596782022-04-15 10:20:07 -0500294 auto& ref = sensorConfig.at(exprKey);
Patrick Williams03c4c8e2022-04-14 22:19:44 -0500295 if (ref.is_array())
296 {
297 exprStr = std::string{};
298 for (auto& s : ref)
299 {
300 exprStr += s;
301 }
302 }
303 else if (ref.is_string())
304 {
305 exprStr = std::string{ref};
306 }
307 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700308
309 /* Get all the parameter listed in configuration */
310 auto params = sensorConfig.value("Params", empty);
311
312 /* Check for constant parameter */
313 const auto& consParams = params.value("ConstParam", empty);
314 if (!consParams.empty())
315 {
316 for (auto& j : consParams)
317 {
318 if (j.find("ParamName") != j.end())
319 {
320 auto paramPtr = std::make_unique<SensorParam>(j["Value"]);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700321 std::string name = j["ParamName"];
322 symbols.create_variable(name);
323 paramMap.emplace(std::move(name), std::move(paramPtr));
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700324 }
325 else
326 {
327 /* Invalid configuration */
328 throw std::invalid_argument(
329 "ParamName not found in configuration");
330 }
331 }
332 }
333
Vijay Khemka7452a862020-08-11 16:01:23 -0700334 /* Check for dbus parameter */
335 auto dbusParams = params.value("DbusParam", empty);
336 if (!dbusParams.empty())
337 {
338 for (auto& j : dbusParams)
339 {
340 /* Get parameter dbus sensor descriptor */
341 auto desc = j.value("Desc", empty);
342 if ((!desc.empty()) && (j.find("ParamName") != j.end()))
343 {
344 std::string sensorType = desc.value("SensorType", "");
345 std::string name = desc.value("Name", "");
346
347 if (!sensorType.empty() && !name.empty())
348 {
George Liu1204b432021-12-29 17:24:48 +0800349 auto path = sensorDbusPath + sensorType + "/" + name;
Vijay Khemka7452a862020-08-11 16:01:23 -0700350
Vijay Khemka51f898e2020-09-09 22:24:18 -0700351 auto paramPtr =
George Liu1204b432021-12-29 17:24:48 +0800352 std::make_unique<SensorParam>(bus, path, this);
353 std::string paramName = j["ParamName"];
354 symbols.create_variable(paramName);
355 paramMap.emplace(std::move(paramName), std::move(paramPtr));
Vijay Khemka7452a862020-08-11 16:01:23 -0700356 }
357 }
358 }
359 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700360
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700361 symbols.add_constants();
Matt Spinler9f1ef4f2020-11-09 15:59:11 -0600362 symbols.add_package(vecopsPackage);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700363 expression.register_symbol_table(symbols);
364
365 /* parser from exprtk */
366 exprtk::parser<double> parser{};
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600367 if (!parser.compile(exprStr, expression))
368 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500369 error("Expression compilation failed");
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600370
371 for (std::size_t i = 0; i < parser.error_count(); ++i)
372 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500373 auto err = parser.get_error(i);
374 error("Error parsing token at {POSITION}: {ERROR}", "POSITION",
375 err.token.position, "TYPE",
376 exprtk::parser_error::to_str(err.mode), "ERROR",
377 err.diagnostic);
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600378 }
379 throw std::runtime_error("Expression compilation failed");
380 }
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700381
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700382 /* Print all parameters for debug purpose only */
383 if (DEBUG)
384 printParams(paramMap);
385}
386
Tao Lindc777012022-07-27 20:41:46 +0800387void VirtualSensor::createAssociation(const std::string& objPath,
388 const std::string& entityPath)
389{
390 if (objPath.empty() || entityPath.empty())
391 {
392 return;
393 }
394
395 std::filesystem::path p(entityPath);
396 auto assocsDbus =
397 AssociationList{{"chassis", "all_sensors", p.parent_path().string()}};
398 associationIface =
399 std::make_unique<AssociationObject>(bus, objPath.c_str());
400 associationIface->associations(assocsDbus);
401}
402
Rashmica Guptae7efe132021-07-27 19:42:11 +1000403void VirtualSensor::initVirtualSensor(const InterfaceMap& interfaceMap,
404 const std::string& objPath,
405 const std::string& sensorType,
406 const std::string& calculationIface)
407{
408 Json thresholds;
409 const std::string vsThresholdsIntf =
410 calculationIface + vsThresholdsIfaceSuffix;
411
412 for (const auto& [interface, propertyMap] : interfaceMap)
413 {
414 /* Each threshold is on it's own interface with a number as a suffix
415 * eg xyz.openbmc_project.Configuration.ModifiedMedian.Thresholds1 */
416 if (interface.find(vsThresholdsIntf) != std::string::npos)
417 {
418 parseThresholds(thresholds, propertyMap);
419 }
420 else if (interface == calculationIface)
421 {
422 parseConfigInterface(propertyMap, sensorType, interface);
423 }
424 }
425
426 createThresholds(thresholds, objPath);
427 symbols.add_constants();
428 symbols.add_package(vecopsPackage);
429 expression.register_symbol_table(symbols);
430
Tao Lindc777012022-07-27 20:41:46 +0800431 createAssociation(objPath, entityPath);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000432 /* Print all parameters for debug purpose only */
433 if (DEBUG)
434 {
435 printParams(paramMap);
436 }
437}
438
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700439void VirtualSensor::setSensorValue(double value)
440{
Patrick Williams543bf662021-04-29 09:03:53 -0500441 value = std::clamp(value, ValueIface::minValue(), ValueIface::maxValue());
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700442 ValueIface::value(value);
443}
444
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000445double VirtualSensor::calculateValue(const std::string& calculation,
446 const VirtualSensor::ParamMap& paramMap)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000447{
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000448 auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(),
449 calculation);
450 if (itr == calculationIfaces.end())
451 {
452 return std::numeric_limits<double>::quiet_NaN();
453 }
454 else if (calculation == "xyz.openbmc_project.Configuration.ModifiedMedian")
455 {
456 return calculateModifiedMedianValue(paramMap);
457 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000458 return std::numeric_limits<double>::quiet_NaN();
459}
460
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000461bool VirtualSensor::sensorInRange(double value)
462{
463 if (value <= this->maxValidInput && value >= this->minValidInput)
464 {
465 return true;
466 }
467 return false;
468}
469
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700470void VirtualSensor::updateVirtualSensor()
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700471{
472 for (auto& param : paramMap)
473 {
474 auto& name = param.first;
475 auto& data = param.second;
476 if (auto var = symbols.get_variable(name))
477 {
478 var->ref() = data->getParamValue();
479 }
480 else
481 {
482 /* Invalid parameter */
483 throw std::invalid_argument("ParamName not found in symbols");
484 }
485 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000486 auto itr =
487 std::find(calculationIfaces.begin(), calculationIfaces.end(), exprStr);
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000488 auto val = (itr == calculationIfaces.end())
489 ? expression.value()
490 : calculateValue(exprStr, paramMap);
Vijay Khemka32a71562020-09-10 15:29:18 -0700491
492 /* Set sensor value to dbus interface */
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700493 setSensorValue(val);
Vijay Khemka32a71562020-09-10 15:29:18 -0700494
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700495 if (DEBUG)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000496 {
Patrick Williamsfbd71452021-08-30 06:59:46 -0500497 debug("Sensor {NAME} = {VALUE}", "NAME", this->name, "VALUE", val);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000498 }
Vijay Khemka32a71562020-09-10 15:29:18 -0700499
Matt Spinler8f5e6112021-01-15 10:44:32 -0600500 /* Check sensor thresholds and log required message */
Matt Spinlerb306b032021-02-01 10:05:46 -0600501 checkThresholds(val, perfLossIface);
Patrick Williamsfdb826d2021-01-20 14:37:53 -0600502 checkThresholds(val, warningIface);
503 checkThresholds(val, criticalIface);
504 checkThresholds(val, softShutdownIface);
505 checkThresholds(val, hardShutdownIface);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700506}
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700507
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000508double VirtualSensor::calculateModifiedMedianValue(
509 const VirtualSensor::ParamMap& paramMap)
510{
511 std::vector<double> values;
512
513 for (auto& param : paramMap)
514 {
515 auto& name = param.first;
516 if (auto var = symbols.get_variable(name))
517 {
518 if (!sensorInRange(var->ref()))
519 {
520 continue;
521 }
522 values.push_back(var->ref());
523 }
524 }
525
526 size_t size = values.size();
527 std::sort(values.begin(), values.end());
528 switch (size)
529 {
530 case 2:
531 /* Choose biggest value */
532 return values.at(1);
533 case 0:
534 return std::numeric_limits<double>::quiet_NaN();
535 default:
536 /* Choose median value */
537 if (size % 2 == 0)
538 {
539 // Average of the two middle values
540 return (values.at(size / 2) + values.at(size / 2 - 1)) / 2;
541 }
542 else
543 {
544 return values.at((size - 1) / 2);
545 }
546 }
547}
548
Rashmica Gupta3e999192021-06-09 16:17:04 +1000549void VirtualSensor::createThresholds(const Json& threshold,
550 const std::string& objPath)
551{
552 if (threshold.empty())
553 {
554 return;
555 }
556 // Only create the threshold interfaces if
557 // at least one of their values is present.
558 if (threshold.contains("CriticalHigh") || threshold.contains("CriticalLow"))
559 {
560 criticalIface =
561 std::make_unique<Threshold<CriticalObject>>(bus, objPath.c_str());
562
563 criticalIface->criticalHigh(threshold.value(
564 "CriticalHigh", std::numeric_limits<double>::quiet_NaN()));
565 criticalIface->criticalLow(threshold.value(
566 "CriticalLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000567 criticalIface->setHighHysteresis(
568 threshold.value("CriticalHighHysteresis", defaultHysteresis));
569 criticalIface->setLowHysteresis(
570 threshold.value("CriticalLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000571 }
572
573 if (threshold.contains("WarningHigh") || threshold.contains("WarningLow"))
574 {
575 warningIface =
576 std::make_unique<Threshold<WarningObject>>(bus, objPath.c_str());
577
578 warningIface->warningHigh(threshold.value(
579 "WarningHigh", std::numeric_limits<double>::quiet_NaN()));
580 warningIface->warningLow(threshold.value(
581 "WarningLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000582 warningIface->setHighHysteresis(
583 threshold.value("WarningHighHysteresis", defaultHysteresis));
584 warningIface->setLowHysteresis(
585 threshold.value("WarningLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000586 }
587
588 if (threshold.contains("HardShutdownHigh") ||
589 threshold.contains("HardShutdownLow"))
590 {
591 hardShutdownIface = std::make_unique<Threshold<HardShutdownObject>>(
592 bus, objPath.c_str());
593
594 hardShutdownIface->hardShutdownHigh(threshold.value(
595 "HardShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
596 hardShutdownIface->hardShutdownLow(threshold.value(
597 "HardShutdownLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000598 hardShutdownIface->setHighHysteresis(
599 threshold.value("HardShutdownHighHysteresis", defaultHysteresis));
600 hardShutdownIface->setLowHysteresis(
601 threshold.value("HardShutdownLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000602 }
603
604 if (threshold.contains("SoftShutdownHigh") ||
605 threshold.contains("SoftShutdownLow"))
606 {
607 softShutdownIface = std::make_unique<Threshold<SoftShutdownObject>>(
608 bus, objPath.c_str());
609
610 softShutdownIface->softShutdownHigh(threshold.value(
611 "SoftShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
612 softShutdownIface->softShutdownLow(threshold.value(
613 "SoftShutdownLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000614 softShutdownIface->setHighHysteresis(
615 threshold.value("SoftShutdownHighHysteresis", defaultHysteresis));
616 softShutdownIface->setLowHysteresis(
617 threshold.value("SoftShutdownLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000618 }
619
620 if (threshold.contains("PerformanceLossHigh") ||
621 threshold.contains("PerformanceLossLow"))
622 {
623 perfLossIface = std::make_unique<Threshold<PerformanceLossObject>>(
624 bus, objPath.c_str());
625
626 perfLossIface->performanceLossHigh(threshold.value(
627 "PerformanceLossHigh", std::numeric_limits<double>::quiet_NaN()));
628 perfLossIface->performanceLossLow(threshold.value(
629 "PerformanceLossLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000630 perfLossIface->setHighHysteresis(threshold.value(
631 "PerformanceLossHighHysteresis", defaultHysteresis));
632 perfLossIface->setLowHysteresis(
633 threshold.value("PerformanceLossLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000634 }
635}
636
Rashmica Guptae7efe132021-07-27 19:42:11 +1000637ManagedObjectType VirtualSensors::getObjectsFromDBus()
638{
639 ManagedObjectType objects;
640
641 try
642 {
Nan Zhouf6825b92022-09-20 20:52:43 +0000643 auto method = bus.new_method_call("xyz.openbmc_project.EntityManager",
644 "/xyz/openbmc_project/inventory",
Rashmica Guptae7efe132021-07-27 19:42:11 +1000645 "org.freedesktop.DBus.ObjectManager",
646 "GetManagedObjects");
647 auto reply = bus.call(method);
648 reply.read(objects);
649 }
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500650 catch (const sdbusplus::exception_t& ex)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000651 {
652 // If entity manager isn't running yet, keep going.
653 if (std::string("org.freedesktop.DBus.Error.ServiceUnknown") !=
654 ex.name())
655 {
656 throw ex.name();
657 }
658 }
659
660 return objects;
661}
662
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500663void VirtualSensors::propertiesChanged(sdbusplus::message_t& msg)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000664{
665 std::string path;
666 PropertyMap properties;
667
668 msg.read(path, properties);
669
670 /* We get multiple callbacks for one sensor. 'Type' is a required field and
671 * is a unique label so use to to only proceed once per sensor */
672 if (properties.contains("Type"))
673 {
674 if (isCalculationType(path))
675 {
676 createVirtualSensorsFromDBus(path);
677 }
678 }
679}
680
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700681/** @brief Parsing Virtual Sensor config JSON file */
George Liu1204b432021-12-29 17:24:48 +0800682Json VirtualSensors::parseConfigFile(const std::string& configFile)
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700683{
684 std::ifstream jsonFile(configFile);
685 if (!jsonFile.is_open())
686 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500687 error("config JSON file {FILENAME} not found", "FILENAME", configFile);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000688 return {};
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700689 }
690
691 auto data = Json::parse(jsonFile, nullptr, false);
692 if (data.is_discarded())
693 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500694 error("config readings JSON parser failure with {FILENAME}", "FILENAME",
695 configFile);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700696 throw std::exception{};
697 }
698
699 return data;
700}
701
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700702std::map<std::string, ValueIface::Unit> unitMap = {
703 {"temperature", ValueIface::Unit::DegreesC},
704 {"fan_tach", ValueIface::Unit::RPMS},
705 {"voltage", ValueIface::Unit::Volts},
706 {"altitude", ValueIface::Unit::Meters},
707 {"current", ValueIface::Unit::Amperes},
708 {"power", ValueIface::Unit::Watts},
709 {"energy", ValueIface::Unit::Joules},
Kumar Thangavel2b56ddb2021-01-13 20:16:11 +0530710 {"utilization", ValueIface::Unit::Percent},
Rashmica Gupta4ac7a7f2021-07-08 12:30:50 +1000711 {"airflow", ValueIface::Unit::CFM},
712 {"pressure", ValueIface::Unit::Pascals}};
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700713
Rashmica Guptae7efe132021-07-27 19:42:11 +1000714const std::string getSensorTypeFromUnit(const std::string& unit)
715{
716 std::string unitPrefix = "xyz.openbmc_project.Sensor.Value.Unit.";
717 for (auto [type, unitObj] : unitMap)
718 {
719 auto unitPath = ValueIface::convertUnitToString(unitObj);
720 if (unitPath == (unitPrefix + unit))
721 {
722 return type;
723 }
724 }
725 return "";
726}
727
728void VirtualSensors::setupMatches()
729{
730 /* Already setup */
731 if (!this->matches.empty())
732 {
733 return;
734 }
735
736 /* Setup matches */
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500737 auto eventHandler = [this](sdbusplus::message_t& message) {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000738 if (message.is_method_error())
739 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500740 error("Callback method error");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000741 return;
742 }
743 this->propertiesChanged(message);
744 };
745
746 for (const char* iface : calculationIfaces)
747 {
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500748 auto match = std::make_unique<sdbusplus::bus::match_t>(
Rashmica Guptae7efe132021-07-27 19:42:11 +1000749 bus,
750 sdbusplus::bus::match::rules::propertiesChangedNamespace(
751 "/xyz/openbmc_project/inventory", iface),
752 eventHandler);
753 this->matches.emplace_back(std::move(match));
754 }
755}
756
757void VirtualSensors::createVirtualSensorsFromDBus(
758 const std::string& calculationIface)
759{
760 if (calculationIface.empty())
761 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500762 error("No calculation type supplied");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000763 return;
764 }
765 auto objects = getObjectsFromDBus();
766
767 /* Get virtual sensors config data */
768 for (const auto& [path, interfaceMap] : objects)
769 {
770 auto objpath = static_cast<std::string>(path);
771 std::string name = path.filename();
772 std::string sensorType, sensorUnit;
773
774 /* Find Virtual Sensor interfaces */
775 if (!interfaceMap.contains(calculationIface))
776 {
777 continue;
778 }
779 if (name.empty())
780 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500781 error("Virtual Sensor name not found in entity manager config");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000782 continue;
783 }
784 if (virtualSensorsMap.contains(name))
785 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500786 error("A virtual sensor named {NAME} already exists", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000787 continue;
788 }
789
790 /* Extract the virtual sensor type as we need this to initialize the
791 * sensor */
792 for (const auto& [interface, propertyMap] : interfaceMap)
793 {
794 if (interface != calculationIface)
795 {
796 continue;
797 }
798 auto itr = propertyMap.find("Units");
799 if (itr != propertyMap.end())
800 {
801 sensorUnit = std::get<std::string>(itr->second);
802 break;
803 }
804 }
805 sensorType = getSensorTypeFromUnit(sensorUnit);
806 if (sensorType.empty())
807 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500808 error("Sensor unit type {TYPE} is not supported", "TYPE",
809 sensorUnit);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000810 continue;
811 }
812
813 try
814 {
815 auto virtObjPath = sensorDbusPath + sensorType + "/" + name;
816
817 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
818 bus, virtObjPath.c_str(), interfaceMap, name, sensorType,
Tao Lindc777012022-07-27 20:41:46 +0800819 calculationIface, objpath);
Patrick Williams82b39c62021-07-28 16:22:27 -0500820 info("Added a new virtual sensor: {NAME} {TYPE}", "NAME", name,
821 "TYPE", sensorType);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000822 virtualSensorPtr->updateVirtualSensor();
823
824 /* Initialize unit value for virtual sensor */
825 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
826 virtualSensorPtr->emit_object_added();
827
828 virtualSensorsMap.emplace(name, std::move(virtualSensorPtr));
829
830 /* Setup match for interfaces removed */
831 auto intfRemoved = [this, objpath,
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500832 name](sdbusplus::message_t& message) {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000833 if (!virtualSensorsMap.contains(name))
834 {
835 return;
836 }
837 sdbusplus::message::object_path path;
838 message.read(path);
839 if (static_cast<const std::string&>(path) == objpath)
840 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500841 info("Removed a virtual sensor: {NAME}", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000842 virtualSensorsMap.erase(name);
843 }
844 };
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500845 auto matchOnRemove = std::make_unique<sdbusplus::bus::match_t>(
Rashmica Guptae7efe132021-07-27 19:42:11 +1000846 bus,
847 sdbusplus::bus::match::rules::interfacesRemoved() +
848 sdbusplus::bus::match::rules::argNpath(0, objpath),
849 intfRemoved);
850 /* TODO: slight race condition here. Check that the config still
851 * exists */
852 this->matches.emplace_back(std::move(matchOnRemove));
853 }
Patrick Williamsdac26632021-10-06 14:38:47 -0500854 catch (const std::invalid_argument& ia)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000855 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500856 error("Failed to set up virtual sensor: {ERROR}", "ERROR", ia);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000857 }
858 }
859}
860
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700861void VirtualSensors::createVirtualSensors()
862{
863 static const Json empty{};
864
865 auto data = parseConfigFile(VIRTUAL_SENSOR_CONFIG_FILE);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000866
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700867 // print values
868 if (DEBUG)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000869 {
Patrick Williamsfbd71452021-08-30 06:59:46 -0500870 debug("JSON: {JSON}", "JSON", data.dump());
Rashmica Guptae7efe132021-07-27 19:42:11 +1000871 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700872
873 /* Get virtual sensors config data */
874 for (const auto& j : data)
875 {
876 auto desc = j.value("Desc", empty);
877 if (!desc.empty())
878 {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000879 if (desc.value("Config", "") == "D-Bus")
880 {
881 /* Look on D-Bus for a virtual sensor config. Set up matches
882 * first because the configs may not be on D-Bus yet and we
883 * don't want to miss them */
884 setupMatches();
885
886 if (desc.contains("Type"))
887 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500888 auto type = desc.value("Type", "");
889 auto path = "xyz.openbmc_project.Configuration." + type;
890
Rashmica Guptae7efe132021-07-27 19:42:11 +1000891 if (!isCalculationType(path))
892 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500893 error("Invalid calculation type {TYPE} supplied.",
894 "TYPE", type);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000895 continue;
896 }
897 createVirtualSensorsFromDBus(path);
898 }
899 continue;
900 }
901
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700902 std::string sensorType = desc.value("SensorType", "");
903 std::string name = desc.value("Name", "");
Rashmica Gupta665a0a22021-06-30 11:35:28 +1000904 std::replace(name.begin(), name.end(), ' ', '_');
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700905
906 if (!name.empty() && !sensorType.empty())
907 {
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700908 if (unitMap.find(sensorType) == unitMap.end())
909 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500910 error("Sensor type {TYPE} is not supported", "TYPE",
911 sensorType);
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700912 }
913 else
914 {
Rashmica Gupta67d8b9d2021-06-30 11:41:14 +1000915 if (virtualSensorsMap.find(name) != virtualSensorsMap.end())
916 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500917 error("A virtual sensor named {NAME} already exists",
918 "NAME", name);
Rashmica Gupta67d8b9d2021-06-30 11:41:14 +1000919 continue;
920 }
Rashmica Gupta862c3d12021-08-06 12:19:31 +1000921 auto objPath = sensorDbusPath + sensorType + "/" + name;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700922
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700923 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
924 bus, objPath.c_str(), j, name);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700925
Patrick Williams82b39c62021-07-28 16:22:27 -0500926 info("Added a new virtual sensor: {NAME}", "NAME", name);
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700927 virtualSensorPtr->updateVirtualSensor();
928
929 /* Initialize unit value for virtual sensor */
930 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
Rashmica Guptaa2fa63a2021-08-06 12:21:13 +1000931 virtualSensorPtr->emit_object_added();
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700932
933 virtualSensorsMap.emplace(std::move(name),
934 std::move(virtualSensorPtr));
935 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700936 }
937 else
938 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500939 error(
940 "Sensor type ({TYPE}) or name ({NAME}) not found in config file",
941 "NAME", name, "TYPE", sensorType);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700942 }
943 }
944 else
945 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500946 error("Descriptor for new virtual sensor not found in config file");
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700947 }
948 }
949}
950
951} // namespace virtualSensor
952} // namespace phosphor
953
954/**
955 * @brief Main
956 */
957int main()
958{
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700959 // Get a handle to system dbus
960 auto bus = sdbusplus::bus::new_default();
961
Matt Spinler6c19e7d2021-01-12 16:26:45 -0600962 // Add the ObjectManager interface
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500963 sdbusplus::server::manager_t objManager(bus, "/");
Matt Spinler6c19e7d2021-01-12 16:26:45 -0600964
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700965 // Create an virtual sensors object
966 phosphor::virtualSensor::VirtualSensors virtualSensors(bus);
967
968 // Request service bus name
969 bus.request_name(busName);
970
Patrick Williamse6672392022-09-02 09:03:24 -0500971 // Run the dbus loop.
972 bus.process_loop();
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700973
974 return 0;
975}