blob: d1302009f396ed7fe71f9e456c0d6cb2f5d0c90a [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";
Tao Linf6b7e0a2022-10-09 09:35:44 +080013static constexpr std::array<const char*, 2> calculationIfaces = {
14 "xyz.openbmc_project.Configuration.ModifiedMedian",
15 "xyz.openbmc_project.Configuration.Maximum"};
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +100016static constexpr auto defaultHysteresis = 0;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070017
Patrick Williams82b39c62021-07-28 16:22:27 -050018PHOSPHOR_LOG2_USING_WITH_FLAGS;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070019
Vijay Khemka51f898e2020-09-09 22:24:18 -070020int handleDbusSignal(sd_bus_message* msg, void* usrData, sd_bus_error*)
21{
22 if (usrData == nullptr)
23 {
24 throw std::runtime_error("Invalid match");
25 }
26
Patrick Williams8e11ccc2022-07-22 19:26:57 -050027 auto sdbpMsg = sdbusplus::message_t(msg);
Vijay Khemka51f898e2020-09-09 22:24:18 -070028 std::string msgIfce;
29 std::map<std::string, std::variant<int64_t, double, bool>> msgData;
30
31 sdbpMsg.read(msgIfce, msgData);
32
33 if (msgData.find("Value") != msgData.end())
34 {
35 using namespace phosphor::virtualSensor;
36 VirtualSensor* obj = static_cast<VirtualSensor*>(usrData);
37 // TODO(openbmc/phosphor-virtual-sensor#1): updateVirtualSensor should
38 // be changed to take the information we got from the signal, to avoid
39 // having to do numerous dbus queries.
40 obj->updateVirtualSensor();
41 }
42 return 0;
43}
44
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070045namespace phosphor
46{
47namespace virtualSensor
48{
49
50void printParams(const VirtualSensor::ParamMap& paramMap)
51{
52 for (const auto& p : paramMap)
53 {
54 const auto& p1 = p.first;
55 const auto& p2 = p.second;
56 auto val = p2->getParamValue();
Patrick Williamsfbd71452021-08-30 06:59:46 -050057 debug("Parameter: {PARAM} = {VALUE}", "PARAM", p1, "VALUE", val);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070058 }
59}
60
61double SensorParam::getParamValue()
62{
63 switch (paramType)
64 {
65 case constParam:
66 return value;
67 break;
Vijay Khemka7452a862020-08-11 16:01:23 -070068 case dbusParam:
69 return dbusSensor->getSensorValue();
70 break;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070071 default:
72 throw std::invalid_argument("param type not supported");
73 }
74}
75
Lei YU0fcf0e12021-06-04 11:14:17 +080076using AssociationList =
77 std::vector<std::tuple<std::string, std::string, std::string>>;
78
79AssociationList getAssociationsFromJson(const Json& j)
80{
81 AssociationList assocs{};
82 try
83 {
84 j.get_to(assocs);
85 }
86 catch (const std::exception& ex)
87 {
Patrick Williams82b39c62021-07-28 16:22:27 -050088 error("Failed to parse association: {ERROR}", "ERROR", ex);
Lei YU0fcf0e12021-06-04 11:14:17 +080089 }
90 return assocs;
91}
92
Rashmica Guptae7efe132021-07-27 19:42:11 +100093template <typename U>
94struct VariantToNumber
95{
96 template <typename T>
97 U operator()(const T& t) const
98 {
99 if constexpr (std::is_convertible<T, U>::value)
100 {
101 return static_cast<U>(t);
102 }
103 throw std::invalid_argument("Invalid number type in config\n");
104 }
105};
106
107template <typename U>
108U getNumberFromConfig(const PropertyMap& map, const std::string& name,
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800109 bool required,
110 U defaultValue = std::numeric_limits<U>::quiet_NaN())
Rashmica Guptae7efe132021-07-27 19:42:11 +1000111{
112 if (auto itr = map.find(name); itr != map.end())
113 {
114 return std::visit(VariantToNumber<U>(), itr->second);
115 }
116 else if (required)
117 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500118 error("Required field {NAME} missing in config", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000119 throw std::invalid_argument("Required field missing in config");
120 }
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800121 return defaultValue;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000122}
123
124bool isCalculationType(const std::string& interface)
125{
126 auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(),
127 interface);
128 if (itr != calculationIfaces.end())
129 {
130 return true;
131 }
132 return false;
133}
134
135const std::string getThresholdType(const std::string& direction,
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100136 const std::string& severity)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000137{
Rashmica Guptae7efe132021-07-27 19:42:11 +1000138 std::string suffix;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000139
140 if (direction == "less than")
141 {
142 suffix = "Low";
143 }
144 else if (direction == "greater than")
145 {
146 suffix = "High";
147 }
148 else
149 {
150 throw std::invalid_argument(
151 "Invalid threshold direction specified in entity manager");
152 }
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100153 return severity + suffix;
154}
155
156std::string getSeverityField(const PropertyMap& propertyMap)
157{
158 static const std::array thresholdTypes{"Warning", "Critical",
159 "PerformanceLoss", "SoftShutdown",
160 "HardShutdown"};
161
162 std::string severity;
163 if (auto itr = propertyMap.find("Severity"); itr != propertyMap.end())
164 {
165 /* Severity should be a string, but can be an unsigned int */
166 if (std::holds_alternative<std::string>(itr->second))
167 {
168 severity = std::get<std::string>(itr->second);
169 if (0 == std::ranges::count(thresholdTypes, severity))
170 {
171 throw std::invalid_argument(
172 "Invalid threshold severity specified in entity manager");
173 }
174 }
175 else
176 {
177 auto sev =
178 getNumberFromConfig<uint64_t>(propertyMap, "Severity", true);
179 /* Checking bounds ourselves so we throw invalid argument on
180 * invalid user input */
181 if (sev >= thresholdTypes.size())
182 {
183 throw std::invalid_argument(
184 "Invalid threshold severity specified in entity manager");
185 }
186 severity = thresholdTypes.at(sev);
187 }
188 }
189 return severity;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000190}
191
Tao Lin91799db2022-07-27 21:02:20 +0800192void parseThresholds(Json& thresholds, const PropertyMap& propertyMap,
193 const std::string& entityInterface = "")
Rashmica Guptae7efe132021-07-27 19:42:11 +1000194{
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 }
Tao Lin91799db2022-07-27 21:02:20 +0800215
216 if (!entityInterface.empty())
217 {
218 thresholds[threshold + "Direction"] = entityInterface;
219 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000220}
221
222void VirtualSensor::parseConfigInterface(const PropertyMap& propertyMap,
223 const std::string& sensorType,
224 const std::string& interface)
225{
226 /* Parse sensors / DBus params */
227 if (auto itr = propertyMap.find("Sensors"); itr != propertyMap.end())
228 {
229 auto sensors = std::get<std::vector<std::string>>(itr->second);
230 for (auto sensor : sensors)
231 {
232 std::replace(sensor.begin(), sensor.end(), ' ', '_');
233 auto sensorObjPath = sensorDbusPath + sensorType + "/" + sensor;
234
235 auto paramPtr =
236 std::make_unique<SensorParam>(bus, sensorObjPath, this);
237 symbols.create_variable(sensor);
238 paramMap.emplace(std::move(sensor), std::move(paramPtr));
239 }
240 }
241 /* Get expression string */
242 if (!isCalculationType(interface))
243 {
244 throw std::invalid_argument("Invalid expression in interface");
245 }
246 exprStr = interface;
247
248 /* Get optional min and max input and output values */
249 ValueIface::maxValue(
250 getNumberFromConfig<double>(propertyMap, "MaxValue", false));
251 ValueIface::minValue(
252 getNumberFromConfig<double>(propertyMap, "MinValue", false));
253 maxValidInput =
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800254 getNumberFromConfig<double>(propertyMap, "MaxValidInput", false,
255 std::numeric_limits<double>::infinity());
Rashmica Guptae7efe132021-07-27 19:42:11 +1000256 minValidInput =
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800257 getNumberFromConfig<double>(propertyMap, "MinValidInput", false,
258 -std::numeric_limits<double>::infinity());
Rashmica Guptae7efe132021-07-27 19:42:11 +1000259}
260
Matt Spinlerce675222021-01-14 16:38:09 -0600261void VirtualSensor::initVirtualSensor(const Json& sensorConfig,
262 const std::string& objPath)
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700263{
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700264 static const Json empty{};
265
266 /* Get threshold values if defined in config */
267 auto threshold = sensorConfig.value("Threshold", empty);
Matt Spinlerf15189e2021-01-15 10:13:28 -0600268
Rashmica Gupta3e999192021-06-09 16:17:04 +1000269 createThresholds(threshold, objPath);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700270
Harvey Wuf6443742021-04-09 16:47:36 +0800271 /* Get MaxValue, MinValue setting if defined in config */
272 auto confDesc = sensorConfig.value("Desc", empty);
273 if (auto maxConf = confDesc.find("MaxValue");
274 maxConf != confDesc.end() && maxConf->is_number())
275 {
276 ValueIface::maxValue(maxConf->get<double>());
277 }
278 if (auto minConf = confDesc.find("MinValue");
279 minConf != confDesc.end() && minConf->is_number())
280 {
281 ValueIface::minValue(minConf->get<double>());
282 }
283
Lei YU0fcf0e12021-06-04 11:14:17 +0800284 /* Get optional association */
285 auto assocJson = sensorConfig.value("Associations", empty);
286 if (!assocJson.empty())
287 {
288 auto assocs = getAssociationsFromJson(assocJson);
289 if (!assocs.empty())
290 {
291 associationIface =
292 std::make_unique<AssociationObject>(bus, objPath.c_str());
293 associationIface->associations(assocs);
294 }
295 }
296
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700297 /* Get expression string */
Patrick Williams03c4c8e2022-04-14 22:19:44 -0500298 static constexpr auto exprKey = "Expression";
299 if (sensorConfig.contains(exprKey))
300 {
Patrick Williamsa9596782022-04-15 10:20:07 -0500301 auto& ref = sensorConfig.at(exprKey);
Patrick Williams03c4c8e2022-04-14 22:19:44 -0500302 if (ref.is_array())
303 {
304 exprStr = std::string{};
305 for (auto& s : ref)
306 {
307 exprStr += s;
308 }
309 }
310 else if (ref.is_string())
311 {
312 exprStr = std::string{ref};
313 }
314 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700315
316 /* Get all the parameter listed in configuration */
317 auto params = sensorConfig.value("Params", empty);
318
319 /* Check for constant parameter */
320 const auto& consParams = params.value("ConstParam", empty);
321 if (!consParams.empty())
322 {
323 for (auto& j : consParams)
324 {
325 if (j.find("ParamName") != j.end())
326 {
327 auto paramPtr = std::make_unique<SensorParam>(j["Value"]);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700328 std::string name = j["ParamName"];
329 symbols.create_variable(name);
330 paramMap.emplace(std::move(name), std::move(paramPtr));
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700331 }
332 else
333 {
334 /* Invalid configuration */
335 throw std::invalid_argument(
336 "ParamName not found in configuration");
337 }
338 }
339 }
340
Vijay Khemka7452a862020-08-11 16:01:23 -0700341 /* Check for dbus parameter */
342 auto dbusParams = params.value("DbusParam", empty);
343 if (!dbusParams.empty())
344 {
345 for (auto& j : dbusParams)
346 {
347 /* Get parameter dbus sensor descriptor */
348 auto desc = j.value("Desc", empty);
349 if ((!desc.empty()) && (j.find("ParamName") != j.end()))
350 {
351 std::string sensorType = desc.value("SensorType", "");
352 std::string name = desc.value("Name", "");
353
354 if (!sensorType.empty() && !name.empty())
355 {
George Liu1204b432021-12-29 17:24:48 +0800356 auto path = sensorDbusPath + sensorType + "/" + name;
Vijay Khemka7452a862020-08-11 16:01:23 -0700357
Vijay Khemka51f898e2020-09-09 22:24:18 -0700358 auto paramPtr =
George Liu1204b432021-12-29 17:24:48 +0800359 std::make_unique<SensorParam>(bus, path, this);
360 std::string paramName = j["ParamName"];
361 symbols.create_variable(paramName);
362 paramMap.emplace(std::move(paramName), std::move(paramPtr));
Vijay Khemka7452a862020-08-11 16:01:23 -0700363 }
364 }
365 }
366 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700367
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700368 symbols.add_constants();
Matt Spinler9f1ef4f2020-11-09 15:59:11 -0600369 symbols.add_package(vecopsPackage);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700370 expression.register_symbol_table(symbols);
371
372 /* parser from exprtk */
373 exprtk::parser<double> parser{};
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600374 if (!parser.compile(exprStr, expression))
375 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500376 error("Expression compilation failed");
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600377
378 for (std::size_t i = 0; i < parser.error_count(); ++i)
379 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500380 auto err = parser.get_error(i);
381 error("Error parsing token at {POSITION}: {ERROR}", "POSITION",
382 err.token.position, "TYPE",
383 exprtk::parser_error::to_str(err.mode), "ERROR",
384 err.diagnostic);
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600385 }
386 throw std::runtime_error("Expression compilation failed");
387 }
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700388
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700389 /* Print all parameters for debug purpose only */
390 if (DEBUG)
391 printParams(paramMap);
392}
393
Tao Lindc777012022-07-27 20:41:46 +0800394void VirtualSensor::createAssociation(const std::string& objPath,
395 const std::string& entityPath)
396{
397 if (objPath.empty() || entityPath.empty())
398 {
399 return;
400 }
401
402 std::filesystem::path p(entityPath);
403 auto assocsDbus =
404 AssociationList{{"chassis", "all_sensors", p.parent_path().string()}};
405 associationIface =
406 std::make_unique<AssociationObject>(bus, objPath.c_str());
407 associationIface->associations(assocsDbus);
408}
409
Rashmica Guptae7efe132021-07-27 19:42:11 +1000410void VirtualSensor::initVirtualSensor(const InterfaceMap& interfaceMap,
411 const std::string& objPath,
412 const std::string& sensorType,
413 const std::string& calculationIface)
414{
415 Json thresholds;
416 const std::string vsThresholdsIntf =
417 calculationIface + vsThresholdsIfaceSuffix;
418
419 for (const auto& [interface, propertyMap] : interfaceMap)
420 {
421 /* Each threshold is on it's own interface with a number as a suffix
422 * eg xyz.openbmc_project.Configuration.ModifiedMedian.Thresholds1 */
423 if (interface.find(vsThresholdsIntf) != std::string::npos)
424 {
Tao Lin91799db2022-07-27 21:02:20 +0800425 parseThresholds(thresholds, propertyMap, interface);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000426 }
427 else if (interface == calculationIface)
428 {
429 parseConfigInterface(propertyMap, sensorType, interface);
430 }
431 }
432
433 createThresholds(thresholds, objPath);
434 symbols.add_constants();
435 symbols.add_package(vecopsPackage);
436 expression.register_symbol_table(symbols);
437
Tao Lindc777012022-07-27 20:41:46 +0800438 createAssociation(objPath, entityPath);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000439 /* Print all parameters for debug purpose only */
440 if (DEBUG)
441 {
442 printParams(paramMap);
443 }
444}
445
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700446void VirtualSensor::setSensorValue(double value)
447{
Patrick Williams543bf662021-04-29 09:03:53 -0500448 value = std::clamp(value, ValueIface::minValue(), ValueIface::maxValue());
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700449 ValueIface::value(value);
450}
451
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000452double VirtualSensor::calculateValue(const std::string& calculation,
453 const VirtualSensor::ParamMap& paramMap)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000454{
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000455 auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(),
456 calculation);
457 if (itr == calculationIfaces.end())
458 {
459 return std::numeric_limits<double>::quiet_NaN();
460 }
461 else if (calculation == "xyz.openbmc_project.Configuration.ModifiedMedian")
462 {
463 return calculateModifiedMedianValue(paramMap);
464 }
Tao Linf6b7e0a2022-10-09 09:35:44 +0800465 else if (calculation == "xyz.openbmc_project.Configuration.Maximum")
466 {
467 return calculateMaximumValue(paramMap);
468 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000469 return std::numeric_limits<double>::quiet_NaN();
470}
471
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000472bool VirtualSensor::sensorInRange(double value)
473{
474 if (value <= this->maxValidInput && value >= this->minValidInput)
475 {
476 return true;
477 }
478 return false;
479}
480
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700481void VirtualSensor::updateVirtualSensor()
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700482{
483 for (auto& param : paramMap)
484 {
485 auto& name = param.first;
486 auto& data = param.second;
487 if (auto var = symbols.get_variable(name))
488 {
489 var->ref() = data->getParamValue();
490 }
491 else
492 {
493 /* Invalid parameter */
494 throw std::invalid_argument("ParamName not found in symbols");
495 }
496 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000497 auto itr =
498 std::find(calculationIfaces.begin(), calculationIfaces.end(), exprStr);
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000499 auto val = (itr == calculationIfaces.end())
500 ? expression.value()
501 : calculateValue(exprStr, paramMap);
Vijay Khemka32a71562020-09-10 15:29:18 -0700502
503 /* Set sensor value to dbus interface */
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700504 setSensorValue(val);
Vijay Khemka32a71562020-09-10 15:29:18 -0700505
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700506 if (DEBUG)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000507 {
Patrick Williamsfbd71452021-08-30 06:59:46 -0500508 debug("Sensor {NAME} = {VALUE}", "NAME", this->name, "VALUE", val);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000509 }
Vijay Khemka32a71562020-09-10 15:29:18 -0700510
Matt Spinler8f5e6112021-01-15 10:44:32 -0600511 /* Check sensor thresholds and log required message */
Matt Spinlerb306b032021-02-01 10:05:46 -0600512 checkThresholds(val, perfLossIface);
Patrick Williamsfdb826d2021-01-20 14:37:53 -0600513 checkThresholds(val, warningIface);
514 checkThresholds(val, criticalIface);
515 checkThresholds(val, softShutdownIface);
516 checkThresholds(val, hardShutdownIface);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700517}
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700518
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000519double VirtualSensor::calculateModifiedMedianValue(
520 const VirtualSensor::ParamMap& paramMap)
521{
522 std::vector<double> values;
523
524 for (auto& param : paramMap)
525 {
526 auto& name = param.first;
527 if (auto var = symbols.get_variable(name))
528 {
529 if (!sensorInRange(var->ref()))
530 {
531 continue;
532 }
533 values.push_back(var->ref());
534 }
535 }
536
537 size_t size = values.size();
538 std::sort(values.begin(), values.end());
539 switch (size)
540 {
541 case 2:
542 /* Choose biggest value */
543 return values.at(1);
544 case 0:
545 return std::numeric_limits<double>::quiet_NaN();
546 default:
547 /* Choose median value */
548 if (size % 2 == 0)
549 {
550 // Average of the two middle values
551 return (values.at(size / 2) + values.at(size / 2 - 1)) / 2;
552 }
553 else
554 {
555 return values.at((size - 1) / 2);
556 }
557 }
558}
559
Tao Linf6b7e0a2022-10-09 09:35:44 +0800560double VirtualSensor::calculateMaximumValue(
561 const VirtualSensor::ParamMap& paramMap)
562{
563 std::vector<double> values;
564
565 for (auto& param : paramMap)
566 {
567 auto& name = param.first;
568 if (auto var = symbols.get_variable(name))
569 {
570 if (!sensorInRange(var->ref()))
571 {
572 continue;
573 }
574 values.push_back(var->ref());
575 }
576 }
577 auto maxIt = std::max_element(values.begin(), values.end());
578 if (maxIt == values.end())
579 {
580 return std::numeric_limits<double>::quiet_NaN();
581 }
582 return *maxIt;
583}
584
Rashmica Gupta3e999192021-06-09 16:17:04 +1000585void VirtualSensor::createThresholds(const Json& threshold,
586 const std::string& objPath)
587{
588 if (threshold.empty())
589 {
590 return;
591 }
592 // Only create the threshold interfaces if
593 // at least one of their values is present.
594 if (threshold.contains("CriticalHigh") || threshold.contains("CriticalLow"))
595 {
596 criticalIface =
597 std::make_unique<Threshold<CriticalObject>>(bus, objPath.c_str());
598
599 criticalIface->criticalHigh(threshold.value(
600 "CriticalHigh", std::numeric_limits<double>::quiet_NaN()));
601 criticalIface->criticalLow(threshold.value(
602 "CriticalLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000603 criticalIface->setHighHysteresis(
604 threshold.value("CriticalHighHysteresis", defaultHysteresis));
605 criticalIface->setLowHysteresis(
606 threshold.value("CriticalLowHysteresis", defaultHysteresis));
Tao Lin91799db2022-07-27 21:02:20 +0800607
608 if (threshold.contains("CriticalHigh"))
609 {
610 criticalIface->setEntityInterfaceHigh(
611 threshold.value("CriticalHighDirection", ""));
612 if (DEBUG)
613 {
614 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
615 "INTF", threshold.value("CriticalHighDirection", ""));
616 }
617 }
618 if (threshold.contains("CriticalLow"))
619 {
620 criticalIface->setEntityInterfaceLow(
621 threshold.value("CriticalLowDirection", ""));
622 if (DEBUG)
623 {
624 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
625 "INTF", threshold.value("CriticalLowDirection", ""));
626 }
627 }
628
629 criticalIface->setEntityPath(entityPath);
630 if (DEBUG)
631 {
632 debug("Sensor Threshold:{NAME} = path:{PATH}", "NAME", objPath,
633 "PATH", entityPath);
634 }
Rashmica Gupta3e999192021-06-09 16:17:04 +1000635 }
636
637 if (threshold.contains("WarningHigh") || threshold.contains("WarningLow"))
638 {
639 warningIface =
640 std::make_unique<Threshold<WarningObject>>(bus, objPath.c_str());
641
642 warningIface->warningHigh(threshold.value(
643 "WarningHigh", std::numeric_limits<double>::quiet_NaN()));
644 warningIface->warningLow(threshold.value(
645 "WarningLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000646 warningIface->setHighHysteresis(
647 threshold.value("WarningHighHysteresis", defaultHysteresis));
648 warningIface->setLowHysteresis(
649 threshold.value("WarningLowHysteresis", defaultHysteresis));
Tao Lin91799db2022-07-27 21:02:20 +0800650
651 if (threshold.contains("WarningHigh"))
652 {
653 warningIface->setEntityInterfaceHigh(
654 threshold.value("WarningHighDirection", ""));
655 if (DEBUG)
656 {
657 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
658 "INTF", threshold.value("WarningHighDirection", ""));
659 }
660 }
661 if (threshold.contains("WarningLow"))
662 {
663 warningIface->setEntityInterfaceLow(
664 threshold.value("WarningLowDirection", ""));
665 if (DEBUG)
666 {
667 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
668 "INTF", threshold.value("WarningLowDirection", ""));
669 }
670 }
671
672 warningIface->setEntityPath(entityPath);
673 if (DEBUG)
674 {
675 debug("Sensor Threshold:{NAME} = path:{PATH}", "NAME", objPath,
676 "PATH", entityPath);
677 }
Rashmica Gupta3e999192021-06-09 16:17:04 +1000678 }
679
680 if (threshold.contains("HardShutdownHigh") ||
681 threshold.contains("HardShutdownLow"))
682 {
683 hardShutdownIface = std::make_unique<Threshold<HardShutdownObject>>(
684 bus, objPath.c_str());
685
686 hardShutdownIface->hardShutdownHigh(threshold.value(
687 "HardShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
688 hardShutdownIface->hardShutdownLow(threshold.value(
689 "HardShutdownLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000690 hardShutdownIface->setHighHysteresis(
691 threshold.value("HardShutdownHighHysteresis", defaultHysteresis));
692 hardShutdownIface->setLowHysteresis(
693 threshold.value("HardShutdownLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000694 }
695
696 if (threshold.contains("SoftShutdownHigh") ||
697 threshold.contains("SoftShutdownLow"))
698 {
699 softShutdownIface = std::make_unique<Threshold<SoftShutdownObject>>(
700 bus, objPath.c_str());
701
702 softShutdownIface->softShutdownHigh(threshold.value(
703 "SoftShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
704 softShutdownIface->softShutdownLow(threshold.value(
705 "SoftShutdownLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000706 softShutdownIface->setHighHysteresis(
707 threshold.value("SoftShutdownHighHysteresis", defaultHysteresis));
708 softShutdownIface->setLowHysteresis(
709 threshold.value("SoftShutdownLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000710 }
711
712 if (threshold.contains("PerformanceLossHigh") ||
713 threshold.contains("PerformanceLossLow"))
714 {
715 perfLossIface = std::make_unique<Threshold<PerformanceLossObject>>(
716 bus, objPath.c_str());
717
718 perfLossIface->performanceLossHigh(threshold.value(
719 "PerformanceLossHigh", std::numeric_limits<double>::quiet_NaN()));
720 perfLossIface->performanceLossLow(threshold.value(
721 "PerformanceLossLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000722 perfLossIface->setHighHysteresis(threshold.value(
723 "PerformanceLossHighHysteresis", defaultHysteresis));
724 perfLossIface->setLowHysteresis(
725 threshold.value("PerformanceLossLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000726 }
727}
728
Rashmica Guptae7efe132021-07-27 19:42:11 +1000729ManagedObjectType VirtualSensors::getObjectsFromDBus()
730{
731 ManagedObjectType objects;
732
733 try
734 {
Nan Zhouf6825b92022-09-20 20:52:43 +0000735 auto method = bus.new_method_call("xyz.openbmc_project.EntityManager",
736 "/xyz/openbmc_project/inventory",
Rashmica Guptae7efe132021-07-27 19:42:11 +1000737 "org.freedesktop.DBus.ObjectManager",
738 "GetManagedObjects");
739 auto reply = bus.call(method);
740 reply.read(objects);
741 }
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500742 catch (const sdbusplus::exception_t& ex)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000743 {
744 // If entity manager isn't running yet, keep going.
745 if (std::string("org.freedesktop.DBus.Error.ServiceUnknown") !=
746 ex.name())
747 {
Matt Spinler71b9c112022-10-18 09:14:45 -0500748 error("Could not reach entity-manager: {ERROR}", "ERROR", ex);
749 throw;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000750 }
751 }
752
753 return objects;
754}
755
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500756void VirtualSensors::propertiesChanged(sdbusplus::message_t& msg)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000757{
758 std::string path;
759 PropertyMap properties;
760
761 msg.read(path, properties);
762
763 /* We get multiple callbacks for one sensor. 'Type' is a required field and
764 * is a unique label so use to to only proceed once per sensor */
765 if (properties.contains("Type"))
766 {
767 if (isCalculationType(path))
768 {
769 createVirtualSensorsFromDBus(path);
770 }
771 }
772}
773
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700774/** @brief Parsing Virtual Sensor config JSON file */
George Liu1204b432021-12-29 17:24:48 +0800775Json VirtualSensors::parseConfigFile(const std::string& configFile)
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700776{
777 std::ifstream jsonFile(configFile);
778 if (!jsonFile.is_open())
779 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500780 error("config JSON file {FILENAME} not found", "FILENAME", configFile);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000781 return {};
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700782 }
783
784 auto data = Json::parse(jsonFile, nullptr, false);
785 if (data.is_discarded())
786 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500787 error("config readings JSON parser failure with {FILENAME}", "FILENAME",
788 configFile);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700789 throw std::exception{};
790 }
791
792 return data;
793}
794
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700795std::map<std::string, ValueIface::Unit> unitMap = {
796 {"temperature", ValueIface::Unit::DegreesC},
797 {"fan_tach", ValueIface::Unit::RPMS},
798 {"voltage", ValueIface::Unit::Volts},
799 {"altitude", ValueIface::Unit::Meters},
800 {"current", ValueIface::Unit::Amperes},
801 {"power", ValueIface::Unit::Watts},
802 {"energy", ValueIface::Unit::Joules},
Kumar Thangavel2b56ddb2021-01-13 20:16:11 +0530803 {"utilization", ValueIface::Unit::Percent},
Rashmica Gupta4ac7a7f2021-07-08 12:30:50 +1000804 {"airflow", ValueIface::Unit::CFM},
805 {"pressure", ValueIface::Unit::Pascals}};
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700806
Rashmica Guptae7efe132021-07-27 19:42:11 +1000807const std::string getSensorTypeFromUnit(const std::string& unit)
808{
809 std::string unitPrefix = "xyz.openbmc_project.Sensor.Value.Unit.";
810 for (auto [type, unitObj] : unitMap)
811 {
812 auto unitPath = ValueIface::convertUnitToString(unitObj);
813 if (unitPath == (unitPrefix + unit))
814 {
815 return type;
816 }
817 }
818 return "";
819}
820
821void VirtualSensors::setupMatches()
822{
823 /* Already setup */
824 if (!this->matches.empty())
825 {
826 return;
827 }
828
829 /* Setup matches */
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500830 auto eventHandler = [this](sdbusplus::message_t& message) {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000831 if (message.is_method_error())
832 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500833 error("Callback method error");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000834 return;
835 }
836 this->propertiesChanged(message);
837 };
838
839 for (const char* iface : calculationIfaces)
840 {
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500841 auto match = std::make_unique<sdbusplus::bus::match_t>(
Rashmica Guptae7efe132021-07-27 19:42:11 +1000842 bus,
843 sdbusplus::bus::match::rules::propertiesChangedNamespace(
844 "/xyz/openbmc_project/inventory", iface),
845 eventHandler);
846 this->matches.emplace_back(std::move(match));
847 }
848}
849
850void VirtualSensors::createVirtualSensorsFromDBus(
851 const std::string& calculationIface)
852{
853 if (calculationIface.empty())
854 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500855 error("No calculation type supplied");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000856 return;
857 }
858 auto objects = getObjectsFromDBus();
859
860 /* Get virtual sensors config data */
861 for (const auto& [path, interfaceMap] : objects)
862 {
863 auto objpath = static_cast<std::string>(path);
864 std::string name = path.filename();
865 std::string sensorType, sensorUnit;
866
867 /* Find Virtual Sensor interfaces */
868 if (!interfaceMap.contains(calculationIface))
869 {
870 continue;
871 }
872 if (name.empty())
873 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500874 error("Virtual Sensor name not found in entity manager config");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000875 continue;
876 }
877 if (virtualSensorsMap.contains(name))
878 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500879 error("A virtual sensor named {NAME} already exists", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000880 continue;
881 }
882
883 /* Extract the virtual sensor type as we need this to initialize the
884 * sensor */
885 for (const auto& [interface, propertyMap] : interfaceMap)
886 {
887 if (interface != calculationIface)
888 {
889 continue;
890 }
891 auto itr = propertyMap.find("Units");
892 if (itr != propertyMap.end())
893 {
894 sensorUnit = std::get<std::string>(itr->second);
895 break;
896 }
897 }
898 sensorType = getSensorTypeFromUnit(sensorUnit);
899 if (sensorType.empty())
900 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500901 error("Sensor unit type {TYPE} is not supported", "TYPE",
902 sensorUnit);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000903 continue;
904 }
905
906 try
907 {
908 auto virtObjPath = sensorDbusPath + sensorType + "/" + name;
909
910 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
911 bus, virtObjPath.c_str(), interfaceMap, name, sensorType,
Tao Lindc777012022-07-27 20:41:46 +0800912 calculationIface, objpath);
Patrick Williams82b39c62021-07-28 16:22:27 -0500913 info("Added a new virtual sensor: {NAME} {TYPE}", "NAME", name,
914 "TYPE", sensorType);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000915 virtualSensorPtr->updateVirtualSensor();
916
917 /* Initialize unit value for virtual sensor */
918 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
919 virtualSensorPtr->emit_object_added();
920
921 virtualSensorsMap.emplace(name, std::move(virtualSensorPtr));
922
923 /* Setup match for interfaces removed */
924 auto intfRemoved = [this, objpath,
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500925 name](sdbusplus::message_t& message) {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000926 if (!virtualSensorsMap.contains(name))
927 {
928 return;
929 }
930 sdbusplus::message::object_path path;
931 message.read(path);
932 if (static_cast<const std::string&>(path) == objpath)
933 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500934 info("Removed a virtual sensor: {NAME}", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000935 virtualSensorsMap.erase(name);
936 }
937 };
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500938 auto matchOnRemove = std::make_unique<sdbusplus::bus::match_t>(
Rashmica Guptae7efe132021-07-27 19:42:11 +1000939 bus,
940 sdbusplus::bus::match::rules::interfacesRemoved() +
941 sdbusplus::bus::match::rules::argNpath(0, objpath),
942 intfRemoved);
943 /* TODO: slight race condition here. Check that the config still
944 * exists */
945 this->matches.emplace_back(std::move(matchOnRemove));
946 }
Patrick Williamsdac26632021-10-06 14:38:47 -0500947 catch (const std::invalid_argument& ia)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000948 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500949 error("Failed to set up virtual sensor: {ERROR}", "ERROR", ia);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000950 }
951 }
952}
953
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700954void VirtualSensors::createVirtualSensors()
955{
956 static const Json empty{};
957
958 auto data = parseConfigFile(VIRTUAL_SENSOR_CONFIG_FILE);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000959
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700960 // print values
961 if (DEBUG)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000962 {
Patrick Williamsfbd71452021-08-30 06:59:46 -0500963 debug("JSON: {JSON}", "JSON", data.dump());
Rashmica Guptae7efe132021-07-27 19:42:11 +1000964 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700965
966 /* Get virtual sensors config data */
967 for (const auto& j : data)
968 {
969 auto desc = j.value("Desc", empty);
970 if (!desc.empty())
971 {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000972 if (desc.value("Config", "") == "D-Bus")
973 {
974 /* Look on D-Bus for a virtual sensor config. Set up matches
975 * first because the configs may not be on D-Bus yet and we
976 * don't want to miss them */
977 setupMatches();
978
979 if (desc.contains("Type"))
980 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500981 auto type = desc.value("Type", "");
982 auto path = "xyz.openbmc_project.Configuration." + type;
983
Rashmica Guptae7efe132021-07-27 19:42:11 +1000984 if (!isCalculationType(path))
985 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500986 error("Invalid calculation type {TYPE} supplied.",
987 "TYPE", type);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000988 continue;
989 }
990 createVirtualSensorsFromDBus(path);
991 }
992 continue;
993 }
994
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700995 std::string sensorType = desc.value("SensorType", "");
996 std::string name = desc.value("Name", "");
Rashmica Gupta665a0a22021-06-30 11:35:28 +1000997 std::replace(name.begin(), name.end(), ' ', '_');
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700998
999 if (!name.empty() && !sensorType.empty())
1000 {
Vijay Khemkae0d371e2020-09-21 18:35:52 -07001001 if (unitMap.find(sensorType) == unitMap.end())
1002 {
Patrick Williams82b39c62021-07-28 16:22:27 -05001003 error("Sensor type {TYPE} is not supported", "TYPE",
1004 sensorType);
Vijay Khemkae0d371e2020-09-21 18:35:52 -07001005 }
1006 else
1007 {
Rashmica Gupta67d8b9d2021-06-30 11:41:14 +10001008 if (virtualSensorsMap.find(name) != virtualSensorsMap.end())
1009 {
Patrick Williams82b39c62021-07-28 16:22:27 -05001010 error("A virtual sensor named {NAME} already exists",
1011 "NAME", name);
Rashmica Gupta67d8b9d2021-06-30 11:41:14 +10001012 continue;
1013 }
Rashmica Gupta862c3d12021-08-06 12:19:31 +10001014 auto objPath = sensorDbusPath + sensorType + "/" + name;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001015
Vijay Khemkae0d371e2020-09-21 18:35:52 -07001016 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
1017 bus, objPath.c_str(), j, name);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001018
Patrick Williams82b39c62021-07-28 16:22:27 -05001019 info("Added a new virtual sensor: {NAME}", "NAME", name);
Vijay Khemkae0d371e2020-09-21 18:35:52 -07001020 virtualSensorPtr->updateVirtualSensor();
1021
1022 /* Initialize unit value for virtual sensor */
1023 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
Rashmica Guptaa2fa63a2021-08-06 12:21:13 +10001024 virtualSensorPtr->emit_object_added();
Vijay Khemkae0d371e2020-09-21 18:35:52 -07001025
1026 virtualSensorsMap.emplace(std::move(name),
1027 std::move(virtualSensorPtr));
1028 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001029 }
1030 else
1031 {
Patrick Williams82b39c62021-07-28 16:22:27 -05001032 error(
1033 "Sensor type ({TYPE}) or name ({NAME}) not found in config file",
1034 "NAME", name, "TYPE", sensorType);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001035 }
1036 }
1037 else
1038 {
Patrick Williams82b39c62021-07-28 16:22:27 -05001039 error("Descriptor for new virtual sensor not found in config file");
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001040 }
1041 }
1042}
1043
1044} // namespace virtualSensor
1045} // namespace phosphor
1046
1047/**
1048 * @brief Main
1049 */
1050int main()
1051{
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001052 // Get a handle to system dbus
1053 auto bus = sdbusplus::bus::new_default();
1054
Matt Spinler6c19e7d2021-01-12 16:26:45 -06001055 // Add the ObjectManager interface
Ed Tanousf7ec40a2022-10-04 17:39:40 -07001056 sdbusplus::server::manager_t objManager(bus,
1057 "/xyz/openbmc_project/sensors");
Matt Spinler6c19e7d2021-01-12 16:26:45 -06001058
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001059 // Create an virtual sensors object
1060 phosphor::virtualSensor::VirtualSensors virtualSensors(bus);
1061
1062 // Request service bus name
1063 bus.request_name(busName);
1064
Patrick Williamse6672392022-09-02 09:03:24 -05001065 // Run the dbus loop.
1066 bus.process_loop();
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001067
1068 return 0;
1069}