1 #include "virtualSensor.hpp"
2
3 #include "calculate.hpp"
4
5 #include <phosphor-logging/lg2.hpp>
6
7 #include <fstream>
8
9 static constexpr auto sensorDbusPath = "/xyz/openbmc_project/sensors/";
10 static constexpr auto vsThresholdsIfaceSuffix = ".Thresholds";
11 static constexpr auto defaultHysteresis = 0;
12
13 PHOSPHOR_LOG2_USING_WITH_FLAGS;
14
15 namespace phosphor::virtual_sensor
16 {
17
18 FuncMaxIgnoreNaN<double> VirtualSensor::funcMaxIgnoreNaN;
19 FuncSumIgnoreNaN<double> VirtualSensor::funcSumIgnoreNaN;
20 FuncIfNan<double> VirtualSensor::funcIfNan;
21
printParams(const VirtualSensor::ParamMap & paramMap)22 void printParams(const VirtualSensor::ParamMap& paramMap)
23 {
24 for (const auto& p : paramMap)
25 {
26 const auto& p1 = p.first;
27 const auto& p2 = p.second;
28 auto val = p2->getParamValue();
29 debug("Parameter: {PARAM} = {VALUE}", "PARAM", p1, "VALUE", val);
30 }
31 }
32
getParamValue()33 double SensorParam::getParamValue()
34 {
35 switch (paramType)
36 {
37 case constParam:
38 return value;
39 break;
40 case dbusParam:
41 return dbusSensor->getSensorValue();
42 break;
43 default:
44 throw std::invalid_argument("param type not supported");
45 }
46 }
47
48 using AssociationList =
49 std::vector<std::tuple<std::string, std::string, std::string>>;
50
getAssociationsFromJson(const Json & j)51 AssociationList getAssociationsFromJson(const Json& j)
52 {
53 AssociationList assocs{};
54 try
55 {
56 j.get_to(assocs);
57 }
58 catch (const std::exception& ex)
59 {
60 error("Failed to parse association: {ERROR}", "ERROR", ex);
61 }
62 return assocs;
63 }
64
65 template <typename U>
66 struct VariantToNumber
67 {
68 template <typename T>
operator ()phosphor::virtual_sensor::VariantToNumber69 U operator()(const T& t) const
70 {
71 if constexpr (std::is_convertible<T, U>::value)
72 {
73 return static_cast<U>(t);
74 }
75 throw std::invalid_argument("Invalid number type in config\n");
76 }
77 };
78
79 template <typename U>
getNumberFromConfig(const PropertyMap & map,const std::string & name,bool required,U defaultValue=std::numeric_limits<U>::quiet_NaN ())80 U getNumberFromConfig(const PropertyMap& map, const std::string& name,
81 bool required,
82 U defaultValue = std::numeric_limits<U>::quiet_NaN())
83 {
84 if (auto itr = map.find(name); itr != map.end())
85 {
86 return std::visit(VariantToNumber<U>(), itr->second);
87 }
88 else if (required)
89 {
90 error("Required field {NAME} missing in config", "NAME", name);
91 throw std::invalid_argument("Required field missing in config");
92 }
93 return defaultValue;
94 }
95
getThresholdType(const std::string & direction,const std::string & severity)96 const std::string getThresholdType(const std::string& direction,
97 const std::string& severity)
98 {
99 std::string suffix;
100
101 if (direction == "less than")
102 {
103 suffix = "Low";
104 }
105 else if (direction == "greater than")
106 {
107 suffix = "High";
108 }
109 else
110 {
111 throw std::invalid_argument(
112 "Invalid threshold direction specified in entity manager");
113 }
114 return severity + suffix;
115 }
116
getSeverityField(const PropertyMap & propertyMap)117 std::string getSeverityField(const PropertyMap& propertyMap)
118 {
119 static const std::array thresholdTypes{
120 "Warning", "Critical", "PerformanceLoss", "SoftShutdown",
121 "HardShutdown"};
122
123 std::string severity;
124 if (auto itr = propertyMap.find("Severity"); itr != propertyMap.end())
125 {
126 /* Severity should be a string, but can be an unsigned int */
127 if (std::holds_alternative<std::string>(itr->second))
128 {
129 severity = std::get<std::string>(itr->second);
130 if (0 == std::ranges::count(thresholdTypes, severity))
131 {
132 throw std::invalid_argument(
133 "Invalid threshold severity specified in entity manager");
134 }
135 }
136 else
137 {
138 auto sev =
139 getNumberFromConfig<uint64_t>(propertyMap, "Severity", true);
140 /* Checking bounds ourselves so we throw invalid argument on
141 * invalid user input */
142 if (sev >= thresholdTypes.size())
143 {
144 throw std::invalid_argument(
145 "Invalid threshold severity specified in entity manager");
146 }
147 severity = thresholdTypes.at(sev);
148 }
149 }
150 return severity;
151 }
152
parseThresholds(Json & thresholds,const PropertyMap & propertyMap,const std::string & entityInterface="")153 void parseThresholds(Json& thresholds, const PropertyMap& propertyMap,
154 const std::string& entityInterface = "")
155 {
156 std::string direction;
157
158 auto value = getNumberFromConfig<double>(propertyMap, "Value", true);
159
160 auto severity = getSeverityField(propertyMap);
161
162 if (auto itr = propertyMap.find("Direction"); itr != propertyMap.end())
163 {
164 direction = std::get<std::string>(itr->second);
165 }
166
167 auto threshold = getThresholdType(direction, severity);
168 thresholds[threshold] = value;
169
170 auto hysteresis =
171 getNumberFromConfig<double>(propertyMap, "Hysteresis", false);
172 if (hysteresis != std::numeric_limits<double>::quiet_NaN())
173 {
174 thresholds[threshold + "Hysteresis"] = hysteresis;
175 }
176
177 if (!entityInterface.empty())
178 {
179 thresholds[threshold + "Direction"] = entityInterface;
180 }
181 }
182
parseConfigInterface(const PropertyMap & propertyMap,const std::string & sensorType,const std::string & interface)183 void VirtualSensor::parseConfigInterface(const PropertyMap& propertyMap,
184 const std::string& sensorType,
185 const std::string& interface)
186 {
187 /* Parse sensors / DBus params */
188 if (auto itr = propertyMap.find("Sensors"); itr != propertyMap.end())
189 {
190 auto sensors = std::get<std::vector<std::string>>(itr->second);
191 for (auto sensor : sensors)
192 {
193 std::replace(sensor.begin(), sensor.end(), ' ', '_');
194 auto sensorObjPath = sensorDbusPath + sensorType + "/" + sensor;
195
196 auto paramPtr =
197 std::make_unique<SensorParam>(bus, sensorObjPath, *this);
198 symbols.create_variable(sensor);
199 paramMap.emplace(std::move(sensor), std::move(paramPtr));
200 }
201 }
202 /* Get expression string */
203 if (!calculationIfaces.contains(interface))
204 {
205 throw std::invalid_argument("Invalid expression in interface");
206 }
207 exprStr = interface;
208
209 /* Get optional min and max input and output values */
210 ValueIface::maxValue(
211 getNumberFromConfig<double>(propertyMap, "MaxValue", false));
212 ValueIface::minValue(
213 getNumberFromConfig<double>(propertyMap, "MinValue", false));
214 maxValidInput =
215 getNumberFromConfig<double>(propertyMap, "MaxValidInput", false,
216 std::numeric_limits<double>::infinity());
217 minValidInput =
218 getNumberFromConfig<double>(propertyMap, "MinValidInput", false,
219 -std::numeric_limits<double>::infinity());
220 }
221
initVirtualSensor(const Json & sensorConfig,const std::string & objPath)222 void VirtualSensor::initVirtualSensor(const Json& sensorConfig,
223 const std::string& objPath)
224 {
225 static const Json empty{};
226
227 /* Get threshold values if defined in config */
228 auto threshold = sensorConfig.value("Threshold", empty);
229
230 createThresholds(threshold, objPath);
231
232 /* Get MaxValue, MinValue setting if defined in config */
233 auto confDesc = sensorConfig.value("Desc", empty);
234 if (auto maxConf = confDesc.find("MaxValue");
235 maxConf != confDesc.end() && maxConf->is_number())
236 {
237 ValueIface::maxValue(maxConf->get<double>());
238 }
239 if (auto minConf = confDesc.find("MinValue");
240 minConf != confDesc.end() && minConf->is_number())
241 {
242 ValueIface::minValue(minConf->get<double>());
243 }
244
245 /* Get optional association */
246 auto assocJson = sensorConfig.value("Associations", empty);
247 if (!assocJson.empty())
248 {
249 auto assocs = getAssociationsFromJson(assocJson);
250 if (!assocs.empty())
251 {
252 associationIface =
253 std::make_unique<AssociationObject>(bus, objPath.c_str());
254 associationIface->associations(assocs);
255 }
256 }
257
258 /* Get expression string */
259 static constexpr auto exprKey = "Expression";
260 if (sensorConfig.contains(exprKey))
261 {
262 auto& ref = sensorConfig.at(exprKey);
263 if (ref.is_array())
264 {
265 exprStr = std::string{};
266 for (auto& s : ref)
267 {
268 exprStr += s;
269 }
270 }
271 else if (ref.is_string())
272 {
273 exprStr = std::string{ref};
274 }
275 }
276
277 /* Get all the parameter listed in configuration */
278 auto params = sensorConfig.value("Params", empty);
279
280 /* Check for constant parameter */
281 const auto& consParams = params.value("ConstParam", empty);
282 if (!consParams.empty())
283 {
284 for (auto& j : consParams)
285 {
286 if (j.find("ParamName") != j.end())
287 {
288 auto paramPtr = std::make_unique<SensorParam>(j["Value"]);
289 std::string name = j["ParamName"];
290 symbols.create_variable(name);
291 paramMap.emplace(std::move(name), std::move(paramPtr));
292 }
293 else
294 {
295 /* Invalid configuration */
296 throw std::invalid_argument(
297 "ParamName not found in configuration");
298 }
299 }
300 }
301
302 /* Check for dbus parameter */
303 auto dbusParams = params.value("DbusParam", empty);
304 if (!dbusParams.empty())
305 {
306 for (auto& j : dbusParams)
307 {
308 /* Get parameter dbus sensor descriptor */
309 auto desc = j.value("Desc", empty);
310 if ((!desc.empty()) && (j.find("ParamName") != j.end()))
311 {
312 std::string sensorType = desc.value("SensorType", "");
313 std::string name = desc.value("Name", "");
314
315 if (!sensorType.empty() && !name.empty())
316 {
317 auto path = sensorDbusPath + sensorType + "/" + name;
318
319 auto paramPtr =
320 std::make_unique<SensorParam>(bus, path, *this);
321 std::string paramName = j["ParamName"];
322 symbols.create_variable(paramName);
323 paramMap.emplace(std::move(paramName), std::move(paramPtr));
324 }
325 }
326 }
327 }
328
329 symbols.add_constants();
330 symbols.add_package(vecopsPackage);
331 symbols.add_function("maxIgnoreNaN", funcMaxIgnoreNaN);
332 symbols.add_function("sumIgnoreNaN", funcSumIgnoreNaN);
333 symbols.add_function("ifNan", funcIfNan);
334
335 expression.register_symbol_table(symbols);
336
337 /* parser from exprtk */
338 exprtk::parser<double> parser{};
339 if (!parser.compile(exprStr, expression))
340 {
341 error("Expression compilation failed");
342
343 for (std::size_t i = 0; i < parser.error_count(); ++i)
344 {
345 auto err = parser.get_error(i);
346 error("Error parsing token at {POSITION}: {ERROR}", "POSITION",
347 err.token.position, "TYPE",
348 exprtk::parser_error::to_str(err.mode), "ERROR",
349 err.diagnostic);
350 }
351 throw std::runtime_error("Expression compilation failed");
352 }
353
354 /* Print all parameters for debug purpose only */
355 printParams(paramMap);
356 }
357
createAssociation(const std::string & objPath,const std::string & entityPath)358 void VirtualSensor::createAssociation(const std::string& objPath,
359 const std::string& entityPath)
360 {
361 if (objPath.empty() || entityPath.empty())
362 {
363 return;
364 }
365
366 std::filesystem::path p(entityPath);
367 auto assocsDbus =
368 AssociationList{{"chassis", "all_sensors", p.parent_path().string()}};
369 associationIface =
370 std::make_unique<AssociationObject>(bus, objPath.c_str());
371 associationIface->associations(assocsDbus);
372 }
373
initVirtualSensor(const InterfaceMap & interfaceMap,const std::string & objPath,const std::string & sensorType,const std::string & calculationIface)374 void VirtualSensor::initVirtualSensor(
375 const InterfaceMap& interfaceMap, const std::string& objPath,
376 const std::string& sensorType, const std::string& calculationIface)
377 {
378 Json thresholds;
379 const std::string vsThresholdsIntf =
380 calculationIface + vsThresholdsIfaceSuffix;
381
382 for (const auto& [interface, propertyMap] : interfaceMap)
383 {
384 /* Each threshold is on it's own interface with a number as a suffix
385 * eg xyz.openbmc_project.Configuration.ModifiedMedian.Thresholds1 */
386 if (interface.find(vsThresholdsIntf) != std::string::npos)
387 {
388 parseThresholds(thresholds, propertyMap, interface);
389 }
390 else if (interface == calculationIface)
391 {
392 parseConfigInterface(propertyMap, sensorType, interface);
393 }
394 }
395
396 createThresholds(thresholds, objPath);
397 symbols.add_constants();
398 symbols.add_package(vecopsPackage);
399 expression.register_symbol_table(symbols);
400
401 createAssociation(objPath, entityPath);
402 /* Print all parameters for debug purpose only */
403 printParams(paramMap);
404 }
405
setSensorValue(double value)406 void VirtualSensor::setSensorValue(double value)
407 {
408 value = std::clamp(value, ValueIface::minValue(), ValueIface::maxValue());
409 ValueIface::value(value);
410 }
411
calculateValue(const std::string & calculation,const VirtualSensor::ParamMap & paramMap)412 double VirtualSensor::calculateValue(const std::string& calculation,
413 const VirtualSensor::ParamMap& paramMap)
414 {
415 auto iter = calculationIfaces.find(calculation);
416 if (iter == calculationIfaces.end())
417 {
418 return std::numeric_limits<double>::quiet_NaN();
419 }
420
421 std::vector<double> values;
422 for (auto& param : paramMap)
423 {
424 auto& name = param.first;
425 if (auto var = symbols.get_variable(name))
426 {
427 if (!sensorInRange(var->ref()))
428 {
429 continue;
430 }
431 values.push_back(var->ref());
432 }
433 }
434
435 return iter->second(values);
436 }
437
sensorInRange(double value)438 bool VirtualSensor::sensorInRange(double value)
439 {
440 if (value <= this->maxValidInput && value >= this->minValidInput)
441 {
442 return true;
443 }
444 return false;
445 }
446
updateVirtualSensor()447 void VirtualSensor::updateVirtualSensor()
448 {
449 for (auto& param : paramMap)
450 {
451 auto& name = param.first;
452 auto& data = param.second;
453 if (auto var = symbols.get_variable(name))
454 {
455 var->ref() = data->getParamValue();
456 }
457 else
458 {
459 /* Invalid parameter */
460 throw std::invalid_argument("ParamName not found in symbols");
461 }
462 }
463 auto val = (!calculationIfaces.contains(exprStr))
464 ? expression.value()
465 : calculateValue(exprStr, paramMap);
466
467 /* Set sensor value to dbus interface */
468 setSensorValue(val);
469 debug("Sensor {NAME} = {VALUE}", "NAME", this->name, "VALUE", val);
470
471 /* Check sensor thresholds and log required message */
472 checkThresholds(val, perfLossIface);
473 checkThresholds(val, warningIface);
474 checkThresholds(val, criticalIface);
475 checkThresholds(val, softShutdownIface);
476 checkThresholds(val, hardShutdownIface);
477 }
478
createThresholds(const Json & threshold,const std::string & objPath)479 void VirtualSensor::createThresholds(const Json& threshold,
480 const std::string& objPath)
481 {
482 if (threshold.empty())
483 {
484 return;
485 }
486 // Only create the threshold interfaces if
487 // at least one of their values is present.
488 if (threshold.contains("CriticalHigh") || threshold.contains("CriticalLow"))
489 {
490 criticalIface =
491 std::make_unique<Threshold<CriticalObject>>(bus, objPath.c_str());
492
493 if (threshold.contains("CriticalHigh"))
494 {
495 criticalIface->setEntityInterfaceHigh(
496 threshold.value("CriticalHighDirection", ""));
497 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
498 "INTF", threshold.value("CriticalHighDirection", ""));
499 }
500 if (threshold.contains("CriticalLow"))
501 {
502 criticalIface->setEntityInterfaceLow(
503 threshold.value("CriticalLowDirection", ""));
504 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
505 "INTF", threshold.value("CriticalLowDirection", ""));
506 }
507
508 criticalIface->setEntityPath(entityPath);
509 debug("Sensor Threshold:{NAME} = path:{PATH}", "NAME", objPath, "PATH",
510 entityPath);
511
512 criticalIface->criticalHigh(threshold.value(
513 "CriticalHigh", std::numeric_limits<double>::quiet_NaN()));
514 criticalIface->criticalLow(threshold.value(
515 "CriticalLow", std::numeric_limits<double>::quiet_NaN()));
516 criticalIface->setHighHysteresis(
517 threshold.value("CriticalHighHysteresis", defaultHysteresis));
518 criticalIface->setLowHysteresis(
519 threshold.value("CriticalLowHysteresis", defaultHysteresis));
520 }
521
522 if (threshold.contains("WarningHigh") || threshold.contains("WarningLow"))
523 {
524 warningIface =
525 std::make_unique<Threshold<WarningObject>>(bus, objPath.c_str());
526
527 if (threshold.contains("WarningHigh"))
528 {
529 warningIface->setEntityInterfaceHigh(
530 threshold.value("WarningHighDirection", ""));
531 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
532 "INTF", threshold.value("WarningHighDirection", ""));
533 }
534 if (threshold.contains("WarningLow"))
535 {
536 warningIface->setEntityInterfaceLow(
537 threshold.value("WarningLowDirection", ""));
538 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
539 "INTF", threshold.value("WarningLowDirection", ""));
540 }
541
542 warningIface->setEntityPath(entityPath);
543 debug("Sensor Threshold:{NAME} = path:{PATH}", "NAME", objPath, "PATH",
544 entityPath);
545
546 warningIface->warningHigh(threshold.value(
547 "WarningHigh", std::numeric_limits<double>::quiet_NaN()));
548 warningIface->warningLow(threshold.value(
549 "WarningLow", std::numeric_limits<double>::quiet_NaN()));
550 warningIface->setHighHysteresis(
551 threshold.value("WarningHighHysteresis", defaultHysteresis));
552 warningIface->setLowHysteresis(
553 threshold.value("WarningLowHysteresis", defaultHysteresis));
554 }
555
556 if (threshold.contains("HardShutdownHigh") ||
557 threshold.contains("HardShutdownLow"))
558 {
559 hardShutdownIface = std::make_unique<Threshold<HardShutdownObject>>(
560 bus, objPath.c_str());
561
562 hardShutdownIface->hardShutdownHigh(threshold.value(
563 "HardShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
564 hardShutdownIface->hardShutdownLow(threshold.value(
565 "HardShutdownLow", std::numeric_limits<double>::quiet_NaN()));
566 hardShutdownIface->setHighHysteresis(
567 threshold.value("HardShutdownHighHysteresis", defaultHysteresis));
568 hardShutdownIface->setLowHysteresis(
569 threshold.value("HardShutdownLowHysteresis", defaultHysteresis));
570 }
571
572 if (threshold.contains("SoftShutdownHigh") ||
573 threshold.contains("SoftShutdownLow"))
574 {
575 softShutdownIface = std::make_unique<Threshold<SoftShutdownObject>>(
576 bus, objPath.c_str());
577
578 softShutdownIface->softShutdownHigh(threshold.value(
579 "SoftShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
580 softShutdownIface->softShutdownLow(threshold.value(
581 "SoftShutdownLow", std::numeric_limits<double>::quiet_NaN()));
582 softShutdownIface->setHighHysteresis(
583 threshold.value("SoftShutdownHighHysteresis", defaultHysteresis));
584 softShutdownIface->setLowHysteresis(
585 threshold.value("SoftShutdownLowHysteresis", defaultHysteresis));
586 }
587
588 if (threshold.contains("PerformanceLossHigh") ||
589 threshold.contains("PerformanceLossLow"))
590 {
591 perfLossIface = std::make_unique<Threshold<PerformanceLossObject>>(
592 bus, objPath.c_str());
593
594 perfLossIface->performanceLossHigh(threshold.value(
595 "PerformanceLossHigh", std::numeric_limits<double>::quiet_NaN()));
596 perfLossIface->performanceLossLow(threshold.value(
597 "PerformanceLossLow", std::numeric_limits<double>::quiet_NaN()));
598 perfLossIface->setHighHysteresis(threshold.value(
599 "PerformanceLossHighHysteresis", defaultHysteresis));
600 perfLossIface->setLowHysteresis(
601 threshold.value("PerformanceLossLowHysteresis", defaultHysteresis));
602 }
603 }
604
getObjectsFromDBus()605 ManagedObjectType VirtualSensors::getObjectsFromDBus()
606 {
607 ManagedObjectType objects;
608
609 try
610 {
611 auto method = bus.new_method_call(
612 "xyz.openbmc_project.EntityManager",
613 "/xyz/openbmc_project/inventory",
614 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
615 auto reply = bus.call(method);
616 reply.read(objects);
617 }
618 catch (const sdbusplus::exception_t& ex)
619 {
620 // If entity manager isn't running yet, keep going.
621 if (std::string("org.freedesktop.DBus.Error.ServiceUnknown") !=
622 ex.name())
623 {
624 error("Could not reach entity-manager: {ERROR}", "ERROR", ex);
625 throw;
626 }
627 }
628
629 return objects;
630 }
631
propertiesChanged(sdbusplus::message_t & msg)632 void VirtualSensors::propertiesChanged(sdbusplus::message_t& msg)
633 {
634 std::string interface;
635 PropertyMap properties;
636
637 msg.read(interface, properties);
638
639 /* We get multiple callbacks for one sensor. 'Type' is a required field and
640 * is a unique label so use to to only proceed once per sensor */
641 if (properties.contains("Type"))
642 {
643 if (calculationIfaces.contains(interface))
644 {
645 createVirtualSensorsFromDBus(interface);
646 }
647 }
648 }
649
650 /** @brief Parsing Virtual Sensor config JSON file */
parseConfigFile()651 Json VirtualSensors::parseConfigFile()
652 {
653 using path = std::filesystem::path;
654 auto configFile = []() -> path {
655 static constexpr auto name = "virtual_sensor_config.json";
656
657 for (auto pathSeg : {std::filesystem::current_path(),
658 path{"/var/lib/phosphor-virtual-sensor"},
659 path{"/usr/share/phosphor-virtual-sensor"}})
660 {
661 auto file = pathSeg / name;
662 if (std::filesystem::exists(file))
663 {
664 return file;
665 }
666 }
667 return name;
668 }();
669
670 std::ifstream jsonFile(configFile);
671 if (!jsonFile.is_open())
672 {
673 error("config JSON file {FILENAME} not found", "FILENAME", configFile);
674 return {};
675 }
676
677 auto data = Json::parse(jsonFile, nullptr, false);
678 if (data.is_discarded())
679 {
680 error("config readings JSON parser failure with {FILENAME}", "FILENAME",
681 configFile);
682 throw std::exception{};
683 }
684
685 return data;
686 }
687
688 std::map<std::string, ValueIface::Unit> unitMap = {
689 {"temperature", ValueIface::Unit::DegreesC},
690 {"fan_tach", ValueIface::Unit::RPMS},
691 {"fan_pwm", ValueIface::Unit::Percent},
692 {"voltage", ValueIface::Unit::Volts},
693 {"altitude", ValueIface::Unit::Meters},
694 {"current", ValueIface::Unit::Amperes},
695 {"power", ValueIface::Unit::Watts},
696 {"energy", ValueIface::Unit::Joules},
697 {"utilization", ValueIface::Unit::Percent},
698 {"airflow", ValueIface::Unit::CFM},
699 {"pressure", ValueIface::Unit::Pascals}};
700
getSensorTypeFromUnit(const std::string & unit)701 const std::string getSensorTypeFromUnit(const std::string& unit)
702 {
703 std::string unitPrefix = "xyz.openbmc_project.Sensor.Value.Unit.";
704 for (auto [type, unitObj] : unitMap)
705 {
706 auto unitPath = ValueIface::convertUnitToString(unitObj);
707 if (unitPath == (unitPrefix + unit))
708 {
709 return type;
710 }
711 }
712 return "";
713 }
714
setupMatches()715 void VirtualSensors::setupMatches()
716 {
717 /* Already setup */
718 if (!this->matches.empty())
719 {
720 return;
721 }
722
723 /* Setup matches */
724 auto eventHandler = [this](sdbusplus::message_t& message) {
725 if (message.is_method_error())
726 {
727 error("Callback method error");
728 return;
729 }
730 this->propertiesChanged(message);
731 };
732
733 for (const auto& [iface, _] : calculationIfaces)
734 {
735 auto match = std::make_unique<sdbusplus::bus::match_t>(
736 bus,
737 sdbusplus::bus::match::rules::propertiesChangedNamespace(
738 "/xyz/openbmc_project/inventory", iface),
739 eventHandler);
740 this->matches.emplace_back(std::move(match));
741 }
742 }
743
createVirtualSensorsFromDBus(const std::string & calculationIface)744 void VirtualSensors::createVirtualSensorsFromDBus(
745 const std::string& calculationIface)
746 {
747 if (calculationIface.empty())
748 {
749 error("No calculation type supplied");
750 return;
751 }
752 auto objects = getObjectsFromDBus();
753
754 /* Get virtual sensors config data */
755 for (const auto& [path, interfaceMap] : objects)
756 {
757 /* Find Virtual Sensor interfaces */
758 auto intfIter = interfaceMap.find(calculationIface);
759 if (intfIter == interfaceMap.end())
760 {
761 continue;
762 }
763
764 std::string name = path.filename();
765 if (name.empty())
766 {
767 error("Virtual Sensor name not found in entity manager config");
768 continue;
769 }
770 if (virtualSensorsMap.contains(name))
771 {
772 error("A virtual sensor named {NAME} already exists", "NAME", name);
773 continue;
774 }
775
776 /* Extract the virtual sensor type as we need this to initialize the
777 * sensor */
778 std::string sensorType, sensorUnit;
779 auto propertyMap = intfIter->second;
780 auto proIter = propertyMap.find("Units");
781 if (proIter != propertyMap.end())
782 {
783 sensorUnit = std::get<std::string>(proIter->second);
784 }
785 sensorType = getSensorTypeFromUnit(sensorUnit);
786 if (sensorType.empty())
787 {
788 error("Sensor unit type {TYPE} is not supported", "TYPE",
789 sensorUnit);
790 continue;
791 }
792
793 try
794 {
795 auto objpath = static_cast<std::string>(path);
796 auto virtObjPath = sensorDbusPath + sensorType + "/" + name;
797
798 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
799 bus, virtObjPath.c_str(), interfaceMap, name, sensorType,
800 calculationIface, objpath);
801 info("Added a new virtual sensor: {NAME} {TYPE}", "NAME", name,
802 "TYPE", sensorType);
803 virtualSensorPtr->updateVirtualSensor();
804
805 /* Initialize unit value for virtual sensor */
806 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
807 virtualSensorPtr->emit_object_added();
808
809 virtualSensorsMap.emplace(name, std::move(virtualSensorPtr));
810
811 /* Setup match for interfaces removed */
812 auto intfRemoved = [this, objpath,
813 name](sdbusplus::message_t& message) {
814 if (!virtualSensorsMap.contains(name))
815 {
816 return;
817 }
818 sdbusplus::message::object_path path;
819 message.read(path);
820 if (static_cast<const std::string&>(path) == objpath)
821 {
822 info("Removed a virtual sensor: {NAME}", "NAME", name);
823 virtualSensorsMap.erase(name);
824 }
825 };
826 auto matchOnRemove = std::make_unique<sdbusplus::bus::match_t>(
827 bus,
828 sdbusplus::bus::match::rules::interfacesRemoved() +
829 sdbusplus::bus::match::rules::argNpath(0, objpath),
830 intfRemoved);
831 /* TODO: slight race condition here. Check that the config still
832 * exists */
833 this->matches.emplace_back(std::move(matchOnRemove));
834 }
835 catch (const std::invalid_argument& ia)
836 {
837 error("Failed to set up virtual sensor: {ERROR}", "ERROR", ia);
838 }
839 }
840 }
841
createVirtualSensors()842 void VirtualSensors::createVirtualSensors()
843 {
844 static const Json empty{};
845
846 auto data = parseConfigFile();
847
848 // print values
849 debug("JSON: {JSON}", "JSON", data.dump());
850
851 /* Get virtual sensors config data */
852 for (const auto& j : data)
853 {
854 auto desc = j.value("Desc", empty);
855 if (!desc.empty())
856 {
857 if (desc.value("Config", "") == "D-Bus")
858 {
859 /* Look on D-Bus for a virtual sensor config. Set up matches
860 * first because the configs may not be on D-Bus yet and we
861 * don't want to miss them */
862 setupMatches();
863
864 if (desc.contains("Type"))
865 {
866 auto type = desc.value("Type", "");
867 auto intf = "xyz.openbmc_project.Configuration." + type;
868
869 if (!calculationIfaces.contains(intf))
870 {
871 error("Invalid calculation type {TYPE} supplied.",
872 "TYPE", type);
873 continue;
874 }
875 createVirtualSensorsFromDBus(intf);
876 }
877 continue;
878 }
879
880 std::string sensorType = desc.value("SensorType", "");
881 std::string name = desc.value("Name", "");
882 std::replace(name.begin(), name.end(), ' ', '_');
883
884 if (!name.empty() && !sensorType.empty())
885 {
886 if (unitMap.find(sensorType) == unitMap.end())
887 {
888 error("Sensor type {TYPE} is not supported", "TYPE",
889 sensorType);
890 }
891 else
892 {
893 if (virtualSensorsMap.find(name) != virtualSensorsMap.end())
894 {
895 error("A virtual sensor named {NAME} already exists",
896 "NAME", name);
897 continue;
898 }
899 auto objPath = sensorDbusPath + sensorType + "/" + name;
900
901 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
902 bus, objPath.c_str(), j, name);
903
904 info("Added a new virtual sensor: {NAME}", "NAME", name);
905 virtualSensorPtr->updateVirtualSensor();
906
907 /* Initialize unit value for virtual sensor */
908 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
909 virtualSensorPtr->emit_object_added();
910
911 virtualSensorsMap.emplace(std::move(name),
912 std::move(virtualSensorPtr));
913 }
914 }
915 else
916 {
917 error(
918 "Sensor type ({TYPE}) or name ({NAME}) not found in config file",
919 "NAME", name, "TYPE", sensorType);
920 }
921 }
922 else
923 {
924 error("Descriptor for new virtual sensor not found in config file");
925 }
926 }
927 }
928
929 } // namespace phosphor::virtual_sensor
930