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