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