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