blob: 4e9f50afafadeff0101fa8a425e324ae34a20522 [file] [log] [blame]
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001#include "virtualSensor.hpp"
2
3#include "config.hpp"
4
Matt Spinlerddc6dcd2020-11-09 11:16:31 -06005#include <fmt/format.h>
6
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07007#include <phosphor-logging/log.hpp>
8#include <sdeventplus/event.hpp>
9
10#include <fstream>
11#include <iostream>
12
13static constexpr bool DEBUG = false;
14static constexpr auto busName = "xyz.openbmc_project.VirtualSensor";
15static constexpr auto sensorDbusPath = "/xyz/openbmc_project/sensors/";
Rashmica Guptae7efe132021-07-27 19:42:11 +100016static constexpr auto entityManagerBusName =
17 "xyz.openbmc_project.EntityManager";
18static constexpr auto vsThresholdsIfaceSuffix = ".Thresholds";
Rashmica Gupta304fd0e2021-08-10 16:50:44 +100019static constexpr std::array<const char*, 1> calculationIfaces = {
20 "xyz.openbmc_project.Configuration.ModifiedMedian"};
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070021
22using namespace phosphor::logging;
23
Vijay Khemka51f898e2020-09-09 22:24:18 -070024int handleDbusSignal(sd_bus_message* msg, void* usrData, sd_bus_error*)
25{
26 if (usrData == nullptr)
27 {
28 throw std::runtime_error("Invalid match");
29 }
30
31 auto sdbpMsg = sdbusplus::message::message(msg);
32 std::string msgIfce;
33 std::map<std::string, std::variant<int64_t, double, bool>> msgData;
34
35 sdbpMsg.read(msgIfce, msgData);
36
37 if (msgData.find("Value") != msgData.end())
38 {
39 using namespace phosphor::virtualSensor;
40 VirtualSensor* obj = static_cast<VirtualSensor*>(usrData);
41 // TODO(openbmc/phosphor-virtual-sensor#1): updateVirtualSensor should
42 // be changed to take the information we got from the signal, to avoid
43 // having to do numerous dbus queries.
44 obj->updateVirtualSensor();
45 }
46 return 0;
47}
48
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070049namespace phosphor
50{
51namespace virtualSensor
52{
53
54void printParams(const VirtualSensor::ParamMap& paramMap)
55{
56 for (const auto& p : paramMap)
57 {
58 const auto& p1 = p.first;
59 const auto& p2 = p.second;
60 auto val = p2->getParamValue();
61 std::cout << p1 << " = " << val << "\n";
62 }
63}
64
65double SensorParam::getParamValue()
66{
67 switch (paramType)
68 {
69 case constParam:
70 return value;
71 break;
Vijay Khemka7452a862020-08-11 16:01:23 -070072 case dbusParam:
73 return dbusSensor->getSensorValue();
74 break;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070075 default:
76 throw std::invalid_argument("param type not supported");
77 }
78}
79
Lei YU0fcf0e12021-06-04 11:14:17 +080080using AssociationList =
81 std::vector<std::tuple<std::string, std::string, std::string>>;
82
83AssociationList getAssociationsFromJson(const Json& j)
84{
85 AssociationList assocs{};
86 try
87 {
88 j.get_to(assocs);
89 }
90 catch (const std::exception& ex)
91 {
92 log<level::ERR>("Failed to parse association",
93 entry("EX=%s", ex.what()));
94 }
95 return assocs;
96}
97
Rashmica Guptae7efe132021-07-27 19:42:11 +100098template <typename U>
99struct VariantToNumber
100{
101 template <typename T>
102 U operator()(const T& t) const
103 {
104 if constexpr (std::is_convertible<T, U>::value)
105 {
106 return static_cast<U>(t);
107 }
108 throw std::invalid_argument("Invalid number type in config\n");
109 }
110};
111
112template <typename U>
113U getNumberFromConfig(const PropertyMap& map, const std::string& name,
114 bool required)
115{
116 if (auto itr = map.find(name); itr != map.end())
117 {
118 return std::visit(VariantToNumber<U>(), itr->second);
119 }
120 else if (required)
121 {
122 log<level::ERR>("Required field missing in config",
123 entry("NAME=%s", name.c_str()));
124 throw std::invalid_argument("Required field missing in config");
125 }
126 return std::numeric_limits<U>::quiet_NaN();
127}
128
129bool isCalculationType(const std::string& interface)
130{
131 auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(),
132 interface);
133 if (itr != calculationIfaces.end())
134 {
135 return true;
136 }
137 return false;
138}
139
140const std::string getThresholdType(const std::string& direction,
141 uint64_t severity)
142{
143 std::string threshold;
144 std::string suffix;
145 static const std::array thresholdTypes{"Warning", "Critical",
146 "PerformanceLoss", "SoftShutdown",
147 "HardShutdown"};
148
149 if (severity >= thresholdTypes.size())
150 {
151 throw std::invalid_argument(
152 "Invalid threshold severity specified in entity manager");
153 }
154 threshold = thresholdTypes[severity];
155
156 if (direction == "less than")
157 {
158 suffix = "Low";
159 }
160 else if (direction == "greater than")
161 {
162 suffix = "High";
163 }
164 else
165 {
166 throw std::invalid_argument(
167 "Invalid threshold direction specified in entity manager");
168 }
169 return threshold + suffix;
170}
171
172void parseThresholds(Json& thresholds, const PropertyMap& propertyMap)
173{
174 std::string direction;
175
176 auto severity =
177 getNumberFromConfig<uint64_t>(propertyMap, "Severity", true);
178 auto value = getNumberFromConfig<double>(propertyMap, "Value", true);
179
180 auto itr = propertyMap.find("Direction");
181 if (itr != propertyMap.end())
182 {
183 direction = std::get<std::string>(itr->second);
184 }
185
186 auto threshold = getThresholdType(direction, severity);
187 thresholds[threshold] = value;
188}
189
190void VirtualSensor::parseConfigInterface(const PropertyMap& propertyMap,
191 const std::string& sensorType,
192 const std::string& interface)
193{
194 /* Parse sensors / DBus params */
195 if (auto itr = propertyMap.find("Sensors"); itr != propertyMap.end())
196 {
197 auto sensors = std::get<std::vector<std::string>>(itr->second);
198 for (auto sensor : sensors)
199 {
200 std::replace(sensor.begin(), sensor.end(), ' ', '_');
201 auto sensorObjPath = sensorDbusPath + sensorType + "/" + sensor;
202
203 auto paramPtr =
204 std::make_unique<SensorParam>(bus, sensorObjPath, this);
205 symbols.create_variable(sensor);
206 paramMap.emplace(std::move(sensor), std::move(paramPtr));
207 }
208 }
209 /* Get expression string */
210 if (!isCalculationType(interface))
211 {
212 throw std::invalid_argument("Invalid expression in interface");
213 }
214 exprStr = interface;
215
216 /* Get optional min and max input and output values */
217 ValueIface::maxValue(
218 getNumberFromConfig<double>(propertyMap, "MaxValue", false));
219 ValueIface::minValue(
220 getNumberFromConfig<double>(propertyMap, "MinValue", false));
221 maxValidInput =
222 getNumberFromConfig<double>(propertyMap, "MaxValidInput", false);
223 minValidInput =
224 getNumberFromConfig<double>(propertyMap, "MinValidInput", false);
225}
226
Matt Spinlerce675222021-01-14 16:38:09 -0600227void VirtualSensor::initVirtualSensor(const Json& sensorConfig,
228 const std::string& objPath)
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700229{
230
231 static const Json empty{};
232
233 /* Get threshold values if defined in config */
234 auto threshold = sensorConfig.value("Threshold", empty);
Matt Spinlerf15189e2021-01-15 10:13:28 -0600235
Rashmica Gupta3e999192021-06-09 16:17:04 +1000236 createThresholds(threshold, objPath);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700237
Harvey Wuf6443742021-04-09 16:47:36 +0800238 /* Get MaxValue, MinValue setting if defined in config */
239 auto confDesc = sensorConfig.value("Desc", empty);
240 if (auto maxConf = confDesc.find("MaxValue");
241 maxConf != confDesc.end() && maxConf->is_number())
242 {
243 ValueIface::maxValue(maxConf->get<double>());
244 }
245 if (auto minConf = confDesc.find("MinValue");
246 minConf != confDesc.end() && minConf->is_number())
247 {
248 ValueIface::minValue(minConf->get<double>());
249 }
250
Lei YU0fcf0e12021-06-04 11:14:17 +0800251 /* Get optional association */
252 auto assocJson = sensorConfig.value("Associations", empty);
253 if (!assocJson.empty())
254 {
255 auto assocs = getAssociationsFromJson(assocJson);
256 if (!assocs.empty())
257 {
258 associationIface =
259 std::make_unique<AssociationObject>(bus, objPath.c_str());
260 associationIface->associations(assocs);
261 }
262 }
263
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700264 /* Get expression string */
265 exprStr = sensorConfig.value("Expression", "");
266
267 /* Get all the parameter listed in configuration */
268 auto params = sensorConfig.value("Params", empty);
269
270 /* Check for constant parameter */
271 const auto& consParams = params.value("ConstParam", empty);
272 if (!consParams.empty())
273 {
274 for (auto& j : consParams)
275 {
276 if (j.find("ParamName") != j.end())
277 {
278 auto paramPtr = std::make_unique<SensorParam>(j["Value"]);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700279 std::string name = j["ParamName"];
280 symbols.create_variable(name);
281 paramMap.emplace(std::move(name), std::move(paramPtr));
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700282 }
283 else
284 {
285 /* Invalid configuration */
286 throw std::invalid_argument(
287 "ParamName not found in configuration");
288 }
289 }
290 }
291
Vijay Khemka7452a862020-08-11 16:01:23 -0700292 /* Check for dbus parameter */
293 auto dbusParams = params.value("DbusParam", empty);
294 if (!dbusParams.empty())
295 {
296 for (auto& j : dbusParams)
297 {
298 /* Get parameter dbus sensor descriptor */
299 auto desc = j.value("Desc", empty);
300 if ((!desc.empty()) && (j.find("ParamName") != j.end()))
301 {
302 std::string sensorType = desc.value("SensorType", "");
303 std::string name = desc.value("Name", "");
304
305 if (!sensorType.empty() && !name.empty())
306 {
Rashmica Gupta862c3d12021-08-06 12:19:31 +1000307 auto objPath = sensorDbusPath + sensorType + "/" + name;
Vijay Khemka7452a862020-08-11 16:01:23 -0700308
Vijay Khemka51f898e2020-09-09 22:24:18 -0700309 auto paramPtr =
310 std::make_unique<SensorParam>(bus, objPath, this);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700311 std::string name = j["ParamName"];
312 symbols.create_variable(name);
313 paramMap.emplace(std::move(name), std::move(paramPtr));
Vijay Khemka7452a862020-08-11 16:01:23 -0700314 }
315 }
316 }
317 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700318
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700319 symbols.add_constants();
Matt Spinler9f1ef4f2020-11-09 15:59:11 -0600320 symbols.add_package(vecopsPackage);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700321 expression.register_symbol_table(symbols);
322
323 /* parser from exprtk */
324 exprtk::parser<double> parser{};
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600325 if (!parser.compile(exprStr, expression))
326 {
327 log<level::ERR>("Expression compilation failed");
328
329 for (std::size_t i = 0; i < parser.error_count(); ++i)
330 {
331 auto error = parser.get_error(i);
332
333 log<level::ERR>(
334 fmt::format(
335 "Position: {} Type: {} Message: {}", error.token.position,
336 exprtk::parser_error::to_str(error.mode), error.diagnostic)
337 .c_str());
338 }
339 throw std::runtime_error("Expression compilation failed");
340 }
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700341
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700342 /* Print all parameters for debug purpose only */
343 if (DEBUG)
344 printParams(paramMap);
345}
346
Rashmica Guptae7efe132021-07-27 19:42:11 +1000347void VirtualSensor::initVirtualSensor(const InterfaceMap& interfaceMap,
348 const std::string& objPath,
349 const std::string& sensorType,
350 const std::string& calculationIface)
351{
352 Json thresholds;
353 const std::string vsThresholdsIntf =
354 calculationIface + vsThresholdsIfaceSuffix;
355
356 for (const auto& [interface, propertyMap] : interfaceMap)
357 {
358 /* Each threshold is on it's own interface with a number as a suffix
359 * eg xyz.openbmc_project.Configuration.ModifiedMedian.Thresholds1 */
360 if (interface.find(vsThresholdsIntf) != std::string::npos)
361 {
362 parseThresholds(thresholds, propertyMap);
363 }
364 else if (interface == calculationIface)
365 {
366 parseConfigInterface(propertyMap, sensorType, interface);
367 }
368 }
369
370 createThresholds(thresholds, objPath);
371 symbols.add_constants();
372 symbols.add_package(vecopsPackage);
373 expression.register_symbol_table(symbols);
374
375 /* Print all parameters for debug purpose only */
376 if (DEBUG)
377 {
378 printParams(paramMap);
379 }
380}
381
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700382void VirtualSensor::setSensorValue(double value)
383{
Patrick Williams543bf662021-04-29 09:03:53 -0500384 value = std::clamp(value, ValueIface::minValue(), ValueIface::maxValue());
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700385 ValueIface::value(value);
386}
387
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000388double VirtualSensor::calculateValue(const std::string& calculation,
389 const VirtualSensor::ParamMap& paramMap)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000390{
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000391 auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(),
392 calculation);
393 if (itr == calculationIfaces.end())
394 {
395 return std::numeric_limits<double>::quiet_NaN();
396 }
397 else if (calculation == "xyz.openbmc_project.Configuration.ModifiedMedian")
398 {
399 return calculateModifiedMedianValue(paramMap);
400 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000401 return std::numeric_limits<double>::quiet_NaN();
402}
403
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000404bool VirtualSensor::sensorInRange(double value)
405{
406 if (value <= this->maxValidInput && value >= this->minValidInput)
407 {
408 return true;
409 }
410 return false;
411}
412
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700413void VirtualSensor::updateVirtualSensor()
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700414{
415 for (auto& param : paramMap)
416 {
417 auto& name = param.first;
418 auto& data = param.second;
419 if (auto var = symbols.get_variable(name))
420 {
421 var->ref() = data->getParamValue();
422 }
423 else
424 {
425 /* Invalid parameter */
426 throw std::invalid_argument("ParamName not found in symbols");
427 }
428 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000429 auto itr =
430 std::find(calculationIfaces.begin(), calculationIfaces.end(), exprStr);
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000431 auto val = (itr == calculationIfaces.end())
432 ? expression.value()
433 : calculateValue(exprStr, paramMap);
Vijay Khemka32a71562020-09-10 15:29:18 -0700434
435 /* Set sensor value to dbus interface */
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700436 setSensorValue(val);
Vijay Khemka32a71562020-09-10 15:29:18 -0700437
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700438 if (DEBUG)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000439 {
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700440 std::cout << "Sensor value is " << val << "\n";
Rashmica Guptae7efe132021-07-27 19:42:11 +1000441 }
Vijay Khemka32a71562020-09-10 15:29:18 -0700442
Matt Spinler8f5e6112021-01-15 10:44:32 -0600443 /* Check sensor thresholds and log required message */
Matt Spinlerb306b032021-02-01 10:05:46 -0600444 checkThresholds(val, perfLossIface);
Patrick Williamsfdb826d2021-01-20 14:37:53 -0600445 checkThresholds(val, warningIface);
446 checkThresholds(val, criticalIface);
447 checkThresholds(val, softShutdownIface);
448 checkThresholds(val, hardShutdownIface);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700449}
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700450
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000451double VirtualSensor::calculateModifiedMedianValue(
452 const VirtualSensor::ParamMap& paramMap)
453{
454 std::vector<double> values;
455
456 for (auto& param : paramMap)
457 {
458 auto& name = param.first;
459 if (auto var = symbols.get_variable(name))
460 {
461 if (!sensorInRange(var->ref()))
462 {
463 continue;
464 }
465 values.push_back(var->ref());
466 }
467 }
468
469 size_t size = values.size();
470 std::sort(values.begin(), values.end());
471 switch (size)
472 {
473 case 2:
474 /* Choose biggest value */
475 return values.at(1);
476 case 0:
477 return std::numeric_limits<double>::quiet_NaN();
478 default:
479 /* Choose median value */
480 if (size % 2 == 0)
481 {
482 // Average of the two middle values
483 return (values.at(size / 2) + values.at(size / 2 - 1)) / 2;
484 }
485 else
486 {
487 return values.at((size - 1) / 2);
488 }
489 }
490}
491
Rashmica Gupta3e999192021-06-09 16:17:04 +1000492void VirtualSensor::createThresholds(const Json& threshold,
493 const std::string& objPath)
494{
495 if (threshold.empty())
496 {
497 return;
498 }
499 // Only create the threshold interfaces if
500 // at least one of their values is present.
501 if (threshold.contains("CriticalHigh") || threshold.contains("CriticalLow"))
502 {
503 criticalIface =
504 std::make_unique<Threshold<CriticalObject>>(bus, objPath.c_str());
505
506 criticalIface->criticalHigh(threshold.value(
507 "CriticalHigh", std::numeric_limits<double>::quiet_NaN()));
508 criticalIface->criticalLow(threshold.value(
509 "CriticalLow", std::numeric_limits<double>::quiet_NaN()));
510 }
511
512 if (threshold.contains("WarningHigh") || threshold.contains("WarningLow"))
513 {
514 warningIface =
515 std::make_unique<Threshold<WarningObject>>(bus, objPath.c_str());
516
517 warningIface->warningHigh(threshold.value(
518 "WarningHigh", std::numeric_limits<double>::quiet_NaN()));
519 warningIface->warningLow(threshold.value(
520 "WarningLow", std::numeric_limits<double>::quiet_NaN()));
521 }
522
523 if (threshold.contains("HardShutdownHigh") ||
524 threshold.contains("HardShutdownLow"))
525 {
526 hardShutdownIface = std::make_unique<Threshold<HardShutdownObject>>(
527 bus, objPath.c_str());
528
529 hardShutdownIface->hardShutdownHigh(threshold.value(
530 "HardShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
531 hardShutdownIface->hardShutdownLow(threshold.value(
532 "HardShutdownLow", std::numeric_limits<double>::quiet_NaN()));
533 }
534
535 if (threshold.contains("SoftShutdownHigh") ||
536 threshold.contains("SoftShutdownLow"))
537 {
538 softShutdownIface = std::make_unique<Threshold<SoftShutdownObject>>(
539 bus, objPath.c_str());
540
541 softShutdownIface->softShutdownHigh(threshold.value(
542 "SoftShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
543 softShutdownIface->softShutdownLow(threshold.value(
544 "SoftShutdownLow", std::numeric_limits<double>::quiet_NaN()));
545 }
546
547 if (threshold.contains("PerformanceLossHigh") ||
548 threshold.contains("PerformanceLossLow"))
549 {
550 perfLossIface = std::make_unique<Threshold<PerformanceLossObject>>(
551 bus, objPath.c_str());
552
553 perfLossIface->performanceLossHigh(threshold.value(
554 "PerformanceLossHigh", std::numeric_limits<double>::quiet_NaN()));
555 perfLossIface->performanceLossLow(threshold.value(
556 "PerformanceLossLow", std::numeric_limits<double>::quiet_NaN()));
557 }
558}
559
Rashmica Guptae7efe132021-07-27 19:42:11 +1000560ManagedObjectType VirtualSensors::getObjectsFromDBus()
561{
562 ManagedObjectType objects;
563
564 try
565 {
566 auto method = bus.new_method_call(entityManagerBusName, "/",
567 "org.freedesktop.DBus.ObjectManager",
568 "GetManagedObjects");
569 auto reply = bus.call(method);
570 reply.read(objects);
571 }
572 catch (const sdbusplus::exception::SdBusError& ex)
573 {
574 // If entity manager isn't running yet, keep going.
575 if (std::string("org.freedesktop.DBus.Error.ServiceUnknown") !=
576 ex.name())
577 {
578 throw ex.name();
579 }
580 }
581
582 return objects;
583}
584
585void VirtualSensors::propertiesChanged(sdbusplus::message::message& msg)
586{
587 std::string path;
588 PropertyMap properties;
589
590 msg.read(path, properties);
591
592 /* We get multiple callbacks for one sensor. 'Type' is a required field and
593 * is a unique label so use to to only proceed once per sensor */
594 if (properties.contains("Type"))
595 {
596 if (isCalculationType(path))
597 {
598 createVirtualSensorsFromDBus(path);
599 }
600 }
601}
602
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700603/** @brief Parsing Virtual Sensor config JSON file */
604Json VirtualSensors::parseConfigFile(const std::string configFile)
605{
606 std::ifstream jsonFile(configFile);
607 if (!jsonFile.is_open())
608 {
609 log<level::ERR>("config JSON file not found",
Patrick Williams1846d822021-06-23 14:44:07 -0500610 entry("FILENAME=%s", configFile.c_str()));
Rashmica Guptae7efe132021-07-27 19:42:11 +1000611 return {};
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700612 }
613
614 auto data = Json::parse(jsonFile, nullptr, false);
615 if (data.is_discarded())
616 {
617 log<level::ERR>("config readings JSON parser failure",
Patrick Williams1846d822021-06-23 14:44:07 -0500618 entry("FILENAME=%s", configFile.c_str()));
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700619 throw std::exception{};
620 }
621
622 return data;
623}
624
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700625std::map<std::string, ValueIface::Unit> unitMap = {
626 {"temperature", ValueIface::Unit::DegreesC},
627 {"fan_tach", ValueIface::Unit::RPMS},
628 {"voltage", ValueIface::Unit::Volts},
629 {"altitude", ValueIface::Unit::Meters},
630 {"current", ValueIface::Unit::Amperes},
631 {"power", ValueIface::Unit::Watts},
632 {"energy", ValueIface::Unit::Joules},
Kumar Thangavel2b56ddb2021-01-13 20:16:11 +0530633 {"utilization", ValueIface::Unit::Percent},
Rashmica Gupta4ac7a7f2021-07-08 12:30:50 +1000634 {"airflow", ValueIface::Unit::CFM},
635 {"pressure", ValueIface::Unit::Pascals}};
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700636
Rashmica Guptae7efe132021-07-27 19:42:11 +1000637const std::string getSensorTypeFromUnit(const std::string& unit)
638{
639 std::string unitPrefix = "xyz.openbmc_project.Sensor.Value.Unit.";
640 for (auto [type, unitObj] : unitMap)
641 {
642 auto unitPath = ValueIface::convertUnitToString(unitObj);
643 if (unitPath == (unitPrefix + unit))
644 {
645 return type;
646 }
647 }
648 return "";
649}
650
651void VirtualSensors::setupMatches()
652{
653 /* Already setup */
654 if (!this->matches.empty())
655 {
656 return;
657 }
658
659 /* Setup matches */
660 auto eventHandler = [this](sdbusplus::message::message& message) {
661 if (message.is_method_error())
662 {
663 log<level::ERR>("Callback method error");
664 return;
665 }
666 this->propertiesChanged(message);
667 };
668
669 for (const char* iface : calculationIfaces)
670 {
671 auto match = std::make_unique<sdbusplus::bus::match::match>(
672 bus,
673 sdbusplus::bus::match::rules::propertiesChangedNamespace(
674 "/xyz/openbmc_project/inventory", iface),
675 eventHandler);
676 this->matches.emplace_back(std::move(match));
677 }
678}
679
680void VirtualSensors::createVirtualSensorsFromDBus(
681 const std::string& calculationIface)
682{
683 if (calculationIface.empty())
684 {
685 log<level::ERR>("No calculation type supplied");
686 return;
687 }
688 auto objects = getObjectsFromDBus();
689
690 /* Get virtual sensors config data */
691 for (const auto& [path, interfaceMap] : objects)
692 {
693 auto objpath = static_cast<std::string>(path);
694 std::string name = path.filename();
695 std::string sensorType, sensorUnit;
696
697 /* Find Virtual Sensor interfaces */
698 if (!interfaceMap.contains(calculationIface))
699 {
700 continue;
701 }
702 if (name.empty())
703 {
704 log<level::ERR>(
705 "Virtual Sensor name not found in entity manager config");
706 continue;
707 }
708 if (virtualSensorsMap.contains(name))
709 {
710 log<level::ERR>("A virtual sensor with this name already exists",
711 entry("NAME=%s", name.c_str()));
712 continue;
713 }
714
715 /* Extract the virtual sensor type as we need this to initialize the
716 * sensor */
717 for (const auto& [interface, propertyMap] : interfaceMap)
718 {
719 if (interface != calculationIface)
720 {
721 continue;
722 }
723 auto itr = propertyMap.find("Units");
724 if (itr != propertyMap.end())
725 {
726 sensorUnit = std::get<std::string>(itr->second);
727 break;
728 }
729 }
730 sensorType = getSensorTypeFromUnit(sensorUnit);
731 if (sensorType.empty())
732 {
733 log<level::ERR>("Sensor unit is not supported",
734 entry("TYPE=%s", sensorUnit.c_str()));
735 continue;
736 }
737
738 try
739 {
740 auto virtObjPath = sensorDbusPath + sensorType + "/" + name;
741
742 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
743 bus, virtObjPath.c_str(), interfaceMap, name, sensorType,
744 calculationIface);
745 log<level::INFO>("Added a new virtual sensor",
746 entry("NAME=%s", name.c_str()));
747 virtualSensorPtr->updateVirtualSensor();
748
749 /* Initialize unit value for virtual sensor */
750 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
751 virtualSensorPtr->emit_object_added();
752
753 virtualSensorsMap.emplace(name, std::move(virtualSensorPtr));
754
755 /* Setup match for interfaces removed */
756 auto intfRemoved = [this, objpath,
757 name](sdbusplus::message::message& message) {
758 if (!virtualSensorsMap.contains(name))
759 {
760 return;
761 }
762 sdbusplus::message::object_path path;
763 message.read(path);
764 if (static_cast<const std::string&>(path) == objpath)
765 {
766 log<level::INFO>("Removed a virtual sensor",
767 entry("NAME=%s", name.c_str()));
768 virtualSensorsMap.erase(name);
769 }
770 };
771 auto matchOnRemove = std::make_unique<sdbusplus::bus::match::match>(
772 bus,
773 sdbusplus::bus::match::rules::interfacesRemoved() +
774 sdbusplus::bus::match::rules::argNpath(0, objpath),
775 intfRemoved);
776 /* TODO: slight race condition here. Check that the config still
777 * exists */
778 this->matches.emplace_back(std::move(matchOnRemove));
779 }
780 catch (std::invalid_argument& ia)
781 {
782 log<level::ERR>("Failed to set up virtual sensor",
783 entry("Error=%s", ia.what()));
784 }
785 }
786}
787
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700788void VirtualSensors::createVirtualSensors()
789{
790 static const Json empty{};
791
792 auto data = parseConfigFile(VIRTUAL_SENSOR_CONFIG_FILE);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000793
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700794 // print values
795 if (DEBUG)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000796 {
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700797 std::cout << "Config json data:\n" << data << "\n\n";
Rashmica Guptae7efe132021-07-27 19:42:11 +1000798 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700799
800 /* Get virtual sensors config data */
801 for (const auto& j : data)
802 {
803 auto desc = j.value("Desc", empty);
804 if (!desc.empty())
805 {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000806 if (desc.value("Config", "") == "D-Bus")
807 {
808 /* Look on D-Bus for a virtual sensor config. Set up matches
809 * first because the configs may not be on D-Bus yet and we
810 * don't want to miss them */
811 setupMatches();
812
813 if (desc.contains("Type"))
814 {
815 auto path = "xyz.openbmc_project.Configuration." +
816 desc.value("Type", "");
817 if (!isCalculationType(path))
818 {
819 log<level::ERR>(
820 "Invalid calculation type supplied\n",
821 entry("TYPE=%s", desc.value("Type", "").c_str()));
822 continue;
823 }
824 createVirtualSensorsFromDBus(path);
825 }
826 continue;
827 }
828
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700829 std::string sensorType = desc.value("SensorType", "");
830 std::string name = desc.value("Name", "");
Rashmica Gupta665a0a22021-06-30 11:35:28 +1000831 std::replace(name.begin(), name.end(), ' ', '_');
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700832
833 if (!name.empty() && !sensorType.empty())
834 {
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700835 if (unitMap.find(sensorType) == unitMap.end())
836 {
837 log<level::ERR>("Sensor type is not supported",
Patrick Williams1846d822021-06-23 14:44:07 -0500838 entry("TYPE=%s", sensorType.c_str()));
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700839 }
840 else
841 {
Rashmica Gupta67d8b9d2021-06-30 11:41:14 +1000842 if (virtualSensorsMap.find(name) != virtualSensorsMap.end())
843 {
844 log<level::ERR>(
845 "A virtual sensor with this name already exists",
846 entry("TYPE=%s", name.c_str()));
847 continue;
848 }
Rashmica Gupta862c3d12021-08-06 12:19:31 +1000849 auto objPath = sensorDbusPath + sensorType + "/" + name;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700850
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700851 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
852 bus, objPath.c_str(), j, name);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700853
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700854 log<level::INFO>("Added a new virtual sensor",
Patrick Williams1846d822021-06-23 14:44:07 -0500855 entry("NAME=%s", name.c_str()));
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700856 virtualSensorPtr->updateVirtualSensor();
857
858 /* Initialize unit value for virtual sensor */
859 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
Rashmica Guptaa2fa63a2021-08-06 12:21:13 +1000860 virtualSensorPtr->emit_object_added();
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700861
862 virtualSensorsMap.emplace(std::move(name),
863 std::move(virtualSensorPtr));
864 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700865 }
866 else
867 {
868 log<level::ERR>("Sensor type or name not found in config file");
869 }
870 }
871 else
872 {
873 log<level::ERR>(
874 "Descriptor for new virtual sensor not found in config file");
875 }
876 }
877}
878
879} // namespace virtualSensor
880} // namespace phosphor
881
882/**
883 * @brief Main
884 */
885int main()
886{
887
888 // Get a default event loop
889 auto event = sdeventplus::Event::get_default();
890
891 // Get a handle to system dbus
892 auto bus = sdbusplus::bus::new_default();
893
Matt Spinler6c19e7d2021-01-12 16:26:45 -0600894 // Add the ObjectManager interface
895 sdbusplus::server::manager::manager objManager(bus, "/");
896
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700897 // Create an virtual sensors object
898 phosphor::virtualSensor::VirtualSensors virtualSensors(bus);
899
900 // Request service bus name
901 bus.request_name(busName);
902
903 // Attach the bus to sd_event to service user requests
904 bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
905 event.loop();
906
907 return 0;
908}