blob: cbf389163020be4b5bd1b81c091bc95bef9ea8a0 [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 entityManagerBusName =
13 "xyz.openbmc_project.EntityManager";
14static constexpr auto vsThresholdsIfaceSuffix = ".Thresholds";
Rashmica Gupta304fd0e2021-08-10 16:50:44 +100015static constexpr std::array<const char*, 1> calculationIfaces = {
16 "xyz.openbmc_project.Configuration.ModifiedMedian"};
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +100017static constexpr auto defaultHysteresis = 0;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070018
Patrick Williams82b39c62021-07-28 16:22:27 -050019PHOSPHOR_LOG2_USING_WITH_FLAGS;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070020
Vijay Khemka51f898e2020-09-09 22:24:18 -070021int handleDbusSignal(sd_bus_message* msg, void* usrData, sd_bus_error*)
22{
23 if (usrData == nullptr)
24 {
25 throw std::runtime_error("Invalid match");
26 }
27
Patrick Williams8e11ccc2022-07-22 19:26:57 -050028 auto sdbpMsg = sdbusplus::message_t(msg);
Vijay Khemka51f898e2020-09-09 22:24:18 -070029 std::string msgIfce;
30 std::map<std::string, std::variant<int64_t, double, bool>> msgData;
31
32 sdbpMsg.read(msgIfce, msgData);
33
34 if (msgData.find("Value") != msgData.end())
35 {
36 using namespace phosphor::virtualSensor;
37 VirtualSensor* obj = static_cast<VirtualSensor*>(usrData);
38 // TODO(openbmc/phosphor-virtual-sensor#1): updateVirtualSensor should
39 // be changed to take the information we got from the signal, to avoid
40 // having to do numerous dbus queries.
41 obj->updateVirtualSensor();
42 }
43 return 0;
44}
45
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070046namespace phosphor
47{
48namespace virtualSensor
49{
50
51void printParams(const VirtualSensor::ParamMap& paramMap)
52{
53 for (const auto& p : paramMap)
54 {
55 const auto& p1 = p.first;
56 const auto& p2 = p.second;
57 auto val = p2->getParamValue();
Patrick Williamsfbd71452021-08-30 06:59:46 -050058 debug("Parameter: {PARAM} = {VALUE}", "PARAM", p1, "VALUE", val);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070059 }
60}
61
62double SensorParam::getParamValue()
63{
64 switch (paramType)
65 {
66 case constParam:
67 return value;
68 break;
Vijay Khemka7452a862020-08-11 16:01:23 -070069 case dbusParam:
70 return dbusSensor->getSensorValue();
71 break;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070072 default:
73 throw std::invalid_argument("param type not supported");
74 }
75}
76
Lei YU0fcf0e12021-06-04 11:14:17 +080077using AssociationList =
78 std::vector<std::tuple<std::string, std::string, std::string>>;
79
80AssociationList getAssociationsFromJson(const Json& j)
81{
82 AssociationList assocs{};
83 try
84 {
85 j.get_to(assocs);
86 }
87 catch (const std::exception& ex)
88 {
Patrick Williams82b39c62021-07-28 16:22:27 -050089 error("Failed to parse association: {ERROR}", "ERROR", ex);
Lei YU0fcf0e12021-06-04 11:14:17 +080090 }
91 return assocs;
92}
93
Rashmica Guptae7efe132021-07-27 19:42:11 +100094template <typename U>
95struct VariantToNumber
96{
97 template <typename T>
98 U operator()(const T& t) const
99 {
100 if constexpr (std::is_convertible<T, U>::value)
101 {
102 return static_cast<U>(t);
103 }
104 throw std::invalid_argument("Invalid number type in config\n");
105 }
106};
107
108template <typename U>
109U getNumberFromConfig(const PropertyMap& map, const std::string& name,
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800110 bool required,
111 U defaultValue = std::numeric_limits<U>::quiet_NaN())
Rashmica Guptae7efe132021-07-27 19:42:11 +1000112{
113 if (auto itr = map.find(name); itr != map.end())
114 {
115 return std::visit(VariantToNumber<U>(), itr->second);
116 }
117 else if (required)
118 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500119 error("Required field {NAME} missing in config", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000120 throw std::invalid_argument("Required field missing in config");
121 }
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800122 return defaultValue;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000123}
124
125bool isCalculationType(const std::string& interface)
126{
127 auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(),
128 interface);
129 if (itr != calculationIfaces.end())
130 {
131 return true;
132 }
133 return false;
134}
135
136const std::string getThresholdType(const std::string& direction,
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100137 const std::string& severity)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000138{
Rashmica Guptae7efe132021-07-27 19:42:11 +1000139 std::string suffix;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000140
141 if (direction == "less than")
142 {
143 suffix = "Low";
144 }
145 else if (direction == "greater than")
146 {
147 suffix = "High";
148 }
149 else
150 {
151 throw std::invalid_argument(
152 "Invalid threshold direction specified in entity manager");
153 }
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100154 return severity + suffix;
155}
156
157std::string getSeverityField(const PropertyMap& propertyMap)
158{
159 static const std::array thresholdTypes{"Warning", "Critical",
160 "PerformanceLoss", "SoftShutdown",
161 "HardShutdown"};
162
163 std::string severity;
164 if (auto itr = propertyMap.find("Severity"); itr != propertyMap.end())
165 {
166 /* Severity should be a string, but can be an unsigned int */
167 if (std::holds_alternative<std::string>(itr->second))
168 {
169 severity = std::get<std::string>(itr->second);
170 if (0 == std::ranges::count(thresholdTypes, severity))
171 {
172 throw std::invalid_argument(
173 "Invalid threshold severity specified in entity manager");
174 }
175 }
176 else
177 {
178 auto sev =
179 getNumberFromConfig<uint64_t>(propertyMap, "Severity", true);
180 /* Checking bounds ourselves so we throw invalid argument on
181 * invalid user input */
182 if (sev >= thresholdTypes.size())
183 {
184 throw std::invalid_argument(
185 "Invalid threshold severity specified in entity manager");
186 }
187 severity = thresholdTypes.at(sev);
188 }
189 }
190 return severity;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000191}
192
193void parseThresholds(Json& thresholds, const PropertyMap& propertyMap)
194{
195 std::string direction;
196
Rashmica Guptae7efe132021-07-27 19:42:11 +1000197 auto value = getNumberFromConfig<double>(propertyMap, "Value", true);
198
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100199 auto severity = getSeverityField(propertyMap);
200
201 if (auto itr = propertyMap.find("Direction"); itr != propertyMap.end())
Rashmica Guptae7efe132021-07-27 19:42:11 +1000202 {
203 direction = std::get<std::string>(itr->second);
204 }
205
206 auto threshold = getThresholdType(direction, severity);
207 thresholds[threshold] = value;
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000208
209 auto hysteresis =
210 getNumberFromConfig<double>(propertyMap, "Hysteresis", false);
211 if (hysteresis != std::numeric_limits<double>::quiet_NaN())
212 {
213 thresholds[threshold + "Hysteresis"] = hysteresis;
214 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000215}
216
217void VirtualSensor::parseConfigInterface(const PropertyMap& propertyMap,
218 const std::string& sensorType,
219 const std::string& interface)
220{
221 /* Parse sensors / DBus params */
222 if (auto itr = propertyMap.find("Sensors"); itr != propertyMap.end())
223 {
224 auto sensors = std::get<std::vector<std::string>>(itr->second);
225 for (auto sensor : sensors)
226 {
227 std::replace(sensor.begin(), sensor.end(), ' ', '_');
228 auto sensorObjPath = sensorDbusPath + sensorType + "/" + sensor;
229
230 auto paramPtr =
231 std::make_unique<SensorParam>(bus, sensorObjPath, this);
232 symbols.create_variable(sensor);
233 paramMap.emplace(std::move(sensor), std::move(paramPtr));
234 }
235 }
236 /* Get expression string */
237 if (!isCalculationType(interface))
238 {
239 throw std::invalid_argument("Invalid expression in interface");
240 }
241 exprStr = interface;
242
243 /* Get optional min and max input and output values */
244 ValueIface::maxValue(
245 getNumberFromConfig<double>(propertyMap, "MaxValue", false));
246 ValueIface::minValue(
247 getNumberFromConfig<double>(propertyMap, "MinValue", false));
248 maxValidInput =
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800249 getNumberFromConfig<double>(propertyMap, "MaxValidInput", false,
250 std::numeric_limits<double>::infinity());
Rashmica Guptae7efe132021-07-27 19:42:11 +1000251 minValidInput =
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800252 getNumberFromConfig<double>(propertyMap, "MinValidInput", false,
253 -std::numeric_limits<double>::infinity());
Rashmica Guptae7efe132021-07-27 19:42:11 +1000254}
255
Matt Spinlerce675222021-01-14 16:38:09 -0600256void VirtualSensor::initVirtualSensor(const Json& sensorConfig,
257 const std::string& objPath)
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700258{
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700259 static const Json empty{};
260
261 /* Get threshold values if defined in config */
262 auto threshold = sensorConfig.value("Threshold", empty);
Matt Spinlerf15189e2021-01-15 10:13:28 -0600263
Rashmica Gupta3e999192021-06-09 16:17:04 +1000264 createThresholds(threshold, objPath);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700265
Harvey Wuf6443742021-04-09 16:47:36 +0800266 /* Get MaxValue, MinValue setting if defined in config */
267 auto confDesc = sensorConfig.value("Desc", empty);
268 if (auto maxConf = confDesc.find("MaxValue");
269 maxConf != confDesc.end() && maxConf->is_number())
270 {
271 ValueIface::maxValue(maxConf->get<double>());
272 }
273 if (auto minConf = confDesc.find("MinValue");
274 minConf != confDesc.end() && minConf->is_number())
275 {
276 ValueIface::minValue(minConf->get<double>());
277 }
278
Lei YU0fcf0e12021-06-04 11:14:17 +0800279 /* Get optional association */
280 auto assocJson = sensorConfig.value("Associations", empty);
281 if (!assocJson.empty())
282 {
283 auto assocs = getAssociationsFromJson(assocJson);
284 if (!assocs.empty())
285 {
286 associationIface =
287 std::make_unique<AssociationObject>(bus, objPath.c_str());
288 associationIface->associations(assocs);
289 }
290 }
291
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700292 /* Get expression string */
Patrick Williams03c4c8e2022-04-14 22:19:44 -0500293 static constexpr auto exprKey = "Expression";
294 if (sensorConfig.contains(exprKey))
295 {
Patrick Williamsa9596782022-04-15 10:20:07 -0500296 auto& ref = sensorConfig.at(exprKey);
Patrick Williams03c4c8e2022-04-14 22:19:44 -0500297 if (ref.is_array())
298 {
299 exprStr = std::string{};
300 for (auto& s : ref)
301 {
302 exprStr += s;
303 }
304 }
305 else if (ref.is_string())
306 {
307 exprStr = std::string{ref};
308 }
309 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700310
311 /* Get all the parameter listed in configuration */
312 auto params = sensorConfig.value("Params", empty);
313
314 /* Check for constant parameter */
315 const auto& consParams = params.value("ConstParam", empty);
316 if (!consParams.empty())
317 {
318 for (auto& j : consParams)
319 {
320 if (j.find("ParamName") != j.end())
321 {
322 auto paramPtr = std::make_unique<SensorParam>(j["Value"]);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700323 std::string name = j["ParamName"];
324 symbols.create_variable(name);
325 paramMap.emplace(std::move(name), std::move(paramPtr));
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700326 }
327 else
328 {
329 /* Invalid configuration */
330 throw std::invalid_argument(
331 "ParamName not found in configuration");
332 }
333 }
334 }
335
Vijay Khemka7452a862020-08-11 16:01:23 -0700336 /* Check for dbus parameter */
337 auto dbusParams = params.value("DbusParam", empty);
338 if (!dbusParams.empty())
339 {
340 for (auto& j : dbusParams)
341 {
342 /* Get parameter dbus sensor descriptor */
343 auto desc = j.value("Desc", empty);
344 if ((!desc.empty()) && (j.find("ParamName") != j.end()))
345 {
346 std::string sensorType = desc.value("SensorType", "");
347 std::string name = desc.value("Name", "");
348
349 if (!sensorType.empty() && !name.empty())
350 {
George Liu1204b432021-12-29 17:24:48 +0800351 auto path = sensorDbusPath + sensorType + "/" + name;
Vijay Khemka7452a862020-08-11 16:01:23 -0700352
Vijay Khemka51f898e2020-09-09 22:24:18 -0700353 auto paramPtr =
George Liu1204b432021-12-29 17:24:48 +0800354 std::make_unique<SensorParam>(bus, path, this);
355 std::string paramName = j["ParamName"];
356 symbols.create_variable(paramName);
357 paramMap.emplace(std::move(paramName), std::move(paramPtr));
Vijay Khemka7452a862020-08-11 16:01:23 -0700358 }
359 }
360 }
361 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700362
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700363 symbols.add_constants();
Matt Spinler9f1ef4f2020-11-09 15:59:11 -0600364 symbols.add_package(vecopsPackage);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700365 expression.register_symbol_table(symbols);
366
367 /* parser from exprtk */
368 exprtk::parser<double> parser{};
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600369 if (!parser.compile(exprStr, expression))
370 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500371 error("Expression compilation failed");
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600372
373 for (std::size_t i = 0; i < parser.error_count(); ++i)
374 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500375 auto err = parser.get_error(i);
376 error("Error parsing token at {POSITION}: {ERROR}", "POSITION",
377 err.token.position, "TYPE",
378 exprtk::parser_error::to_str(err.mode), "ERROR",
379 err.diagnostic);
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600380 }
381 throw std::runtime_error("Expression compilation failed");
382 }
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700383
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700384 /* Print all parameters for debug purpose only */
385 if (DEBUG)
386 printParams(paramMap);
387}
388
Rashmica Guptae7efe132021-07-27 19:42:11 +1000389void VirtualSensor::initVirtualSensor(const InterfaceMap& interfaceMap,
390 const std::string& objPath,
391 const std::string& sensorType,
392 const std::string& calculationIface)
393{
394 Json thresholds;
395 const std::string vsThresholdsIntf =
396 calculationIface + vsThresholdsIfaceSuffix;
397
398 for (const auto& [interface, propertyMap] : interfaceMap)
399 {
400 /* Each threshold is on it's own interface with a number as a suffix
401 * eg xyz.openbmc_project.Configuration.ModifiedMedian.Thresholds1 */
402 if (interface.find(vsThresholdsIntf) != std::string::npos)
403 {
404 parseThresholds(thresholds, propertyMap);
405 }
406 else if (interface == calculationIface)
407 {
408 parseConfigInterface(propertyMap, sensorType, interface);
409 }
410 }
411
412 createThresholds(thresholds, objPath);
413 symbols.add_constants();
414 symbols.add_package(vecopsPackage);
415 expression.register_symbol_table(symbols);
416
417 /* Print all parameters for debug purpose only */
418 if (DEBUG)
419 {
420 printParams(paramMap);
421 }
422}
423
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700424void VirtualSensor::setSensorValue(double value)
425{
Patrick Williams543bf662021-04-29 09:03:53 -0500426 value = std::clamp(value, ValueIface::minValue(), ValueIface::maxValue());
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700427 ValueIface::value(value);
428}
429
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000430double VirtualSensor::calculateValue(const std::string& calculation,
431 const VirtualSensor::ParamMap& paramMap)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000432{
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000433 auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(),
434 calculation);
435 if (itr == calculationIfaces.end())
436 {
437 return std::numeric_limits<double>::quiet_NaN();
438 }
439 else if (calculation == "xyz.openbmc_project.Configuration.ModifiedMedian")
440 {
441 return calculateModifiedMedianValue(paramMap);
442 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000443 return std::numeric_limits<double>::quiet_NaN();
444}
445
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000446bool VirtualSensor::sensorInRange(double value)
447{
448 if (value <= this->maxValidInput && value >= this->minValidInput)
449 {
450 return true;
451 }
452 return false;
453}
454
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700455void VirtualSensor::updateVirtualSensor()
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700456{
457 for (auto& param : paramMap)
458 {
459 auto& name = param.first;
460 auto& data = param.second;
461 if (auto var = symbols.get_variable(name))
462 {
463 var->ref() = data->getParamValue();
464 }
465 else
466 {
467 /* Invalid parameter */
468 throw std::invalid_argument("ParamName not found in symbols");
469 }
470 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000471 auto itr =
472 std::find(calculationIfaces.begin(), calculationIfaces.end(), exprStr);
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000473 auto val = (itr == calculationIfaces.end())
474 ? expression.value()
475 : calculateValue(exprStr, paramMap);
Vijay Khemka32a71562020-09-10 15:29:18 -0700476
477 /* Set sensor value to dbus interface */
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700478 setSensorValue(val);
Vijay Khemka32a71562020-09-10 15:29:18 -0700479
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700480 if (DEBUG)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000481 {
Patrick Williamsfbd71452021-08-30 06:59:46 -0500482 debug("Sensor {NAME} = {VALUE}", "NAME", this->name, "VALUE", val);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000483 }
Vijay Khemka32a71562020-09-10 15:29:18 -0700484
Matt Spinler8f5e6112021-01-15 10:44:32 -0600485 /* Check sensor thresholds and log required message */
Matt Spinlerb306b032021-02-01 10:05:46 -0600486 checkThresholds(val, perfLossIface);
Patrick Williamsfdb826d2021-01-20 14:37:53 -0600487 checkThresholds(val, warningIface);
488 checkThresholds(val, criticalIface);
489 checkThresholds(val, softShutdownIface);
490 checkThresholds(val, hardShutdownIface);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700491}
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700492
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000493double VirtualSensor::calculateModifiedMedianValue(
494 const VirtualSensor::ParamMap& paramMap)
495{
496 std::vector<double> values;
497
498 for (auto& param : paramMap)
499 {
500 auto& name = param.first;
501 if (auto var = symbols.get_variable(name))
502 {
503 if (!sensorInRange(var->ref()))
504 {
505 continue;
506 }
507 values.push_back(var->ref());
508 }
509 }
510
511 size_t size = values.size();
512 std::sort(values.begin(), values.end());
513 switch (size)
514 {
515 case 2:
516 /* Choose biggest value */
517 return values.at(1);
518 case 0:
519 return std::numeric_limits<double>::quiet_NaN();
520 default:
521 /* Choose median value */
522 if (size % 2 == 0)
523 {
524 // Average of the two middle values
525 return (values.at(size / 2) + values.at(size / 2 - 1)) / 2;
526 }
527 else
528 {
529 return values.at((size - 1) / 2);
530 }
531 }
532}
533
Rashmica Gupta3e999192021-06-09 16:17:04 +1000534void VirtualSensor::createThresholds(const Json& threshold,
535 const std::string& objPath)
536{
537 if (threshold.empty())
538 {
539 return;
540 }
541 // Only create the threshold interfaces if
542 // at least one of their values is present.
543 if (threshold.contains("CriticalHigh") || threshold.contains("CriticalLow"))
544 {
545 criticalIface =
546 std::make_unique<Threshold<CriticalObject>>(bus, objPath.c_str());
547
548 criticalIface->criticalHigh(threshold.value(
549 "CriticalHigh", std::numeric_limits<double>::quiet_NaN()));
550 criticalIface->criticalLow(threshold.value(
551 "CriticalLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000552 criticalIface->setHighHysteresis(
553 threshold.value("CriticalHighHysteresis", defaultHysteresis));
554 criticalIface->setLowHysteresis(
555 threshold.value("CriticalLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000556 }
557
558 if (threshold.contains("WarningHigh") || threshold.contains("WarningLow"))
559 {
560 warningIface =
561 std::make_unique<Threshold<WarningObject>>(bus, objPath.c_str());
562
563 warningIface->warningHigh(threshold.value(
564 "WarningHigh", std::numeric_limits<double>::quiet_NaN()));
565 warningIface->warningLow(threshold.value(
566 "WarningLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000567 warningIface->setHighHysteresis(
568 threshold.value("WarningHighHysteresis", defaultHysteresis));
569 warningIface->setLowHysteresis(
570 threshold.value("WarningLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000571 }
572
573 if (threshold.contains("HardShutdownHigh") ||
574 threshold.contains("HardShutdownLow"))
575 {
576 hardShutdownIface = std::make_unique<Threshold<HardShutdownObject>>(
577 bus, objPath.c_str());
578
579 hardShutdownIface->hardShutdownHigh(threshold.value(
580 "HardShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
581 hardShutdownIface->hardShutdownLow(threshold.value(
582 "HardShutdownLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000583 hardShutdownIface->setHighHysteresis(
584 threshold.value("HardShutdownHighHysteresis", defaultHysteresis));
585 hardShutdownIface->setLowHysteresis(
586 threshold.value("HardShutdownLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000587 }
588
589 if (threshold.contains("SoftShutdownHigh") ||
590 threshold.contains("SoftShutdownLow"))
591 {
592 softShutdownIface = std::make_unique<Threshold<SoftShutdownObject>>(
593 bus, objPath.c_str());
594
595 softShutdownIface->softShutdownHigh(threshold.value(
596 "SoftShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
597 softShutdownIface->softShutdownLow(threshold.value(
598 "SoftShutdownLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000599 softShutdownIface->setHighHysteresis(
600 threshold.value("SoftShutdownHighHysteresis", defaultHysteresis));
601 softShutdownIface->setLowHysteresis(
602 threshold.value("SoftShutdownLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000603 }
604
605 if (threshold.contains("PerformanceLossHigh") ||
606 threshold.contains("PerformanceLossLow"))
607 {
608 perfLossIface = std::make_unique<Threshold<PerformanceLossObject>>(
609 bus, objPath.c_str());
610
611 perfLossIface->performanceLossHigh(threshold.value(
612 "PerformanceLossHigh", std::numeric_limits<double>::quiet_NaN()));
613 perfLossIface->performanceLossLow(threshold.value(
614 "PerformanceLossLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000615 perfLossIface->setHighHysteresis(threshold.value(
616 "PerformanceLossHighHysteresis", defaultHysteresis));
617 perfLossIface->setLowHysteresis(
618 threshold.value("PerformanceLossLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000619 }
620}
621
Rashmica Guptae7efe132021-07-27 19:42:11 +1000622ManagedObjectType VirtualSensors::getObjectsFromDBus()
623{
624 ManagedObjectType objects;
625
626 try
627 {
628 auto method = bus.new_method_call(entityManagerBusName, "/",
629 "org.freedesktop.DBus.ObjectManager",
630 "GetManagedObjects");
631 auto reply = bus.call(method);
632 reply.read(objects);
633 }
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500634 catch (const sdbusplus::exception_t& ex)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000635 {
636 // If entity manager isn't running yet, keep going.
637 if (std::string("org.freedesktop.DBus.Error.ServiceUnknown") !=
638 ex.name())
639 {
640 throw ex.name();
641 }
642 }
643
644 return objects;
645}
646
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500647void VirtualSensors::propertiesChanged(sdbusplus::message_t& msg)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000648{
649 std::string path;
650 PropertyMap properties;
651
652 msg.read(path, properties);
653
654 /* We get multiple callbacks for one sensor. 'Type' is a required field and
655 * is a unique label so use to to only proceed once per sensor */
656 if (properties.contains("Type"))
657 {
658 if (isCalculationType(path))
659 {
660 createVirtualSensorsFromDBus(path);
661 }
662 }
663}
664
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700665/** @brief Parsing Virtual Sensor config JSON file */
George Liu1204b432021-12-29 17:24:48 +0800666Json VirtualSensors::parseConfigFile(const std::string& configFile)
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700667{
668 std::ifstream jsonFile(configFile);
669 if (!jsonFile.is_open())
670 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500671 error("config JSON file {FILENAME} not found", "FILENAME", configFile);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000672 return {};
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700673 }
674
675 auto data = Json::parse(jsonFile, nullptr, false);
676 if (data.is_discarded())
677 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500678 error("config readings JSON parser failure with {FILENAME}", "FILENAME",
679 configFile);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700680 throw std::exception{};
681 }
682
683 return data;
684}
685
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700686std::map<std::string, ValueIface::Unit> unitMap = {
687 {"temperature", ValueIface::Unit::DegreesC},
688 {"fan_tach", ValueIface::Unit::RPMS},
689 {"voltage", ValueIface::Unit::Volts},
690 {"altitude", ValueIface::Unit::Meters},
691 {"current", ValueIface::Unit::Amperes},
692 {"power", ValueIface::Unit::Watts},
693 {"energy", ValueIface::Unit::Joules},
Kumar Thangavel2b56ddb2021-01-13 20:16:11 +0530694 {"utilization", ValueIface::Unit::Percent},
Rashmica Gupta4ac7a7f2021-07-08 12:30:50 +1000695 {"airflow", ValueIface::Unit::CFM},
696 {"pressure", ValueIface::Unit::Pascals}};
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700697
Rashmica Guptae7efe132021-07-27 19:42:11 +1000698const std::string getSensorTypeFromUnit(const std::string& unit)
699{
700 std::string unitPrefix = "xyz.openbmc_project.Sensor.Value.Unit.";
701 for (auto [type, unitObj] : unitMap)
702 {
703 auto unitPath = ValueIface::convertUnitToString(unitObj);
704 if (unitPath == (unitPrefix + unit))
705 {
706 return type;
707 }
708 }
709 return "";
710}
711
712void VirtualSensors::setupMatches()
713{
714 /* Already setup */
715 if (!this->matches.empty())
716 {
717 return;
718 }
719
720 /* Setup matches */
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500721 auto eventHandler = [this](sdbusplus::message_t& message) {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000722 if (message.is_method_error())
723 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500724 error("Callback method error");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000725 return;
726 }
727 this->propertiesChanged(message);
728 };
729
730 for (const char* iface : calculationIfaces)
731 {
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500732 auto match = std::make_unique<sdbusplus::bus::match_t>(
Rashmica Guptae7efe132021-07-27 19:42:11 +1000733 bus,
734 sdbusplus::bus::match::rules::propertiesChangedNamespace(
735 "/xyz/openbmc_project/inventory", iface),
736 eventHandler);
737 this->matches.emplace_back(std::move(match));
738 }
739}
740
741void VirtualSensors::createVirtualSensorsFromDBus(
742 const std::string& calculationIface)
743{
744 if (calculationIface.empty())
745 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500746 error("No calculation type supplied");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000747 return;
748 }
749 auto objects = getObjectsFromDBus();
750
751 /* Get virtual sensors config data */
752 for (const auto& [path, interfaceMap] : objects)
753 {
754 auto objpath = static_cast<std::string>(path);
755 std::string name = path.filename();
756 std::string sensorType, sensorUnit;
757
758 /* Find Virtual Sensor interfaces */
759 if (!interfaceMap.contains(calculationIface))
760 {
761 continue;
762 }
763 if (name.empty())
764 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500765 error("Virtual Sensor name not found in entity manager config");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000766 continue;
767 }
768 if (virtualSensorsMap.contains(name))
769 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500770 error("A virtual sensor named {NAME} already exists", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000771 continue;
772 }
773
774 /* Extract the virtual sensor type as we need this to initialize the
775 * sensor */
776 for (const auto& [interface, propertyMap] : interfaceMap)
777 {
778 if (interface != calculationIface)
779 {
780 continue;
781 }
782 auto itr = propertyMap.find("Units");
783 if (itr != propertyMap.end())
784 {
785 sensorUnit = std::get<std::string>(itr->second);
786 break;
787 }
788 }
789 sensorType = getSensorTypeFromUnit(sensorUnit);
790 if (sensorType.empty())
791 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500792 error("Sensor unit type {TYPE} is not supported", "TYPE",
793 sensorUnit);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000794 continue;
795 }
796
797 try
798 {
799 auto virtObjPath = sensorDbusPath + sensorType + "/" + name;
800
801 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
802 bus, virtObjPath.c_str(), interfaceMap, name, sensorType,
803 calculationIface);
Patrick Williams82b39c62021-07-28 16:22:27 -0500804 info("Added a new virtual sensor: {NAME} {TYPE}", "NAME", name,
805 "TYPE", sensorType);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000806 virtualSensorPtr->updateVirtualSensor();
807
808 /* Initialize unit value for virtual sensor */
809 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
810 virtualSensorPtr->emit_object_added();
811
812 virtualSensorsMap.emplace(name, std::move(virtualSensorPtr));
813
814 /* Setup match for interfaces removed */
815 auto intfRemoved = [this, objpath,
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500816 name](sdbusplus::message_t& message) {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000817 if (!virtualSensorsMap.contains(name))
818 {
819 return;
820 }
821 sdbusplus::message::object_path path;
822 message.read(path);
823 if (static_cast<const std::string&>(path) == objpath)
824 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500825 info("Removed a virtual sensor: {NAME}", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000826 virtualSensorsMap.erase(name);
827 }
828 };
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500829 auto matchOnRemove = std::make_unique<sdbusplus::bus::match_t>(
Rashmica Guptae7efe132021-07-27 19:42:11 +1000830 bus,
831 sdbusplus::bus::match::rules::interfacesRemoved() +
832 sdbusplus::bus::match::rules::argNpath(0, objpath),
833 intfRemoved);
834 /* TODO: slight race condition here. Check that the config still
835 * exists */
836 this->matches.emplace_back(std::move(matchOnRemove));
837 }
Patrick Williamsdac26632021-10-06 14:38:47 -0500838 catch (const std::invalid_argument& ia)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000839 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500840 error("Failed to set up virtual sensor: {ERROR}", "ERROR", ia);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000841 }
842 }
843}
844
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700845void VirtualSensors::createVirtualSensors()
846{
847 static const Json empty{};
848
849 auto data = parseConfigFile(VIRTUAL_SENSOR_CONFIG_FILE);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000850
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700851 // print values
852 if (DEBUG)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000853 {
Patrick Williamsfbd71452021-08-30 06:59:46 -0500854 debug("JSON: {JSON}", "JSON", data.dump());
Rashmica Guptae7efe132021-07-27 19:42:11 +1000855 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700856
857 /* Get virtual sensors config data */
858 for (const auto& j : data)
859 {
860 auto desc = j.value("Desc", empty);
861 if (!desc.empty())
862 {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000863 if (desc.value("Config", "") == "D-Bus")
864 {
865 /* Look on D-Bus for a virtual sensor config. Set up matches
866 * first because the configs may not be on D-Bus yet and we
867 * don't want to miss them */
868 setupMatches();
869
870 if (desc.contains("Type"))
871 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500872 auto type = desc.value("Type", "");
873 auto path = "xyz.openbmc_project.Configuration." + type;
874
Rashmica Guptae7efe132021-07-27 19:42:11 +1000875 if (!isCalculationType(path))
876 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500877 error("Invalid calculation type {TYPE} supplied.",
878 "TYPE", type);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000879 continue;
880 }
881 createVirtualSensorsFromDBus(path);
882 }
883 continue;
884 }
885
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700886 std::string sensorType = desc.value("SensorType", "");
887 std::string name = desc.value("Name", "");
Rashmica Gupta665a0a22021-06-30 11:35:28 +1000888 std::replace(name.begin(), name.end(), ' ', '_');
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700889
890 if (!name.empty() && !sensorType.empty())
891 {
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700892 if (unitMap.find(sensorType) == unitMap.end())
893 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500894 error("Sensor type {TYPE} is not supported", "TYPE",
895 sensorType);
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700896 }
897 else
898 {
Rashmica Gupta67d8b9d2021-06-30 11:41:14 +1000899 if (virtualSensorsMap.find(name) != virtualSensorsMap.end())
900 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500901 error("A virtual sensor named {NAME} already exists",
902 "NAME", name);
Rashmica Gupta67d8b9d2021-06-30 11:41:14 +1000903 continue;
904 }
Rashmica Gupta862c3d12021-08-06 12:19:31 +1000905 auto objPath = sensorDbusPath + sensorType + "/" + name;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700906
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700907 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
908 bus, objPath.c_str(), j, name);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700909
Patrick Williams82b39c62021-07-28 16:22:27 -0500910 info("Added a new virtual sensor: {NAME}", "NAME", name);
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700911 virtualSensorPtr->updateVirtualSensor();
912
913 /* Initialize unit value for virtual sensor */
914 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
Rashmica Guptaa2fa63a2021-08-06 12:21:13 +1000915 virtualSensorPtr->emit_object_added();
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700916
917 virtualSensorsMap.emplace(std::move(name),
918 std::move(virtualSensorPtr));
919 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700920 }
921 else
922 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500923 error(
924 "Sensor type ({TYPE}) or name ({NAME}) not found in config file",
925 "NAME", name, "TYPE", sensorType);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700926 }
927 }
928 else
929 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500930 error("Descriptor for new virtual sensor not found in config file");
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700931 }
932 }
933}
934
935} // namespace virtualSensor
936} // namespace phosphor
937
938/**
939 * @brief Main
940 */
941int main()
942{
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700943 // Get a handle to system dbus
944 auto bus = sdbusplus::bus::new_default();
945
Matt Spinler6c19e7d2021-01-12 16:26:45 -0600946 // Add the ObjectManager interface
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500947 sdbusplus::server::manager_t objManager(bus, "/");
Matt Spinler6c19e7d2021-01-12 16:26:45 -0600948
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700949 // Create an virtual sensors object
950 phosphor::virtualSensor::VirtualSensors virtualSensors(bus);
951
952 // Request service bus name
953 bus.request_name(busName);
954
Patrick Williamse6672392022-09-02 09:03:24 -0500955 // Run the dbus loop.
956 bus.process_loop();
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700957
958 return 0;
959}