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