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{
133         "Warning", "Critical", "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 =
152                 getNumberFromConfig<uint64_t>(propertyMap, "Severity", 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 =
184         getNumberFromConfig<double>(propertyMap, "Hysteresis", 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 =
210                 std::make_unique<SensorParam>(bus, sensorObjPath, *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 =
333                         std::make_unique<SensorParam>(bus, path, *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 =
384         std::make_unique<AssociationObject>(bus, objPath.c_str());
385     associationIface->associations(assocsDbus);
386 }
387 
388 void VirtualSensor::initVirtualSensor(
389     const InterfaceMap& interfaceMap, const std::string& objPath,
390     const std::string& sensorType, const std::string& calculationIface)
391 {
392     Json thresholds;
393     const std::string vsThresholdsIntf =
394         calculationIface + vsThresholdsIfaceSuffix;
395 
396     for (const auto& [interface, propertyMap] : interfaceMap)
397     {
398         /* Each threshold is on it's own interface with a number as a suffix
399          * eg xyz.openbmc_project.Configuration.ModifiedMedian.Thresholds1 */
400         if (interface.find(vsThresholdsIntf) != std::string::npos)
401         {
402             parseThresholds(thresholds, propertyMap, interface);
403         }
404         else if (interface == calculationIface)
405         {
406             parseConfigInterface(propertyMap, sensorType, interface);
407         }
408     }
409 
410     createThresholds(thresholds, objPath);
411     symbols.add_constants();
412     symbols.add_package(vecopsPackage);
413     expression.register_symbol_table(symbols);
414 
415     createAssociation(objPath, entityPath);
416     /* Print all parameters for debug purpose only */
417     if (DEBUG)
418     {
419         printParams(paramMap);
420     }
421 }
422 
423 void VirtualSensor::setSensorValue(double value)
424 {
425     value = std::clamp(value, ValueIface::minValue(), ValueIface::maxValue());
426     ValueIface::value(value);
427 }
428 
429 double VirtualSensor::calculateValue(const std::string& calculation,
430                                      const VirtualSensor::ParamMap& paramMap)
431 {
432     auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(),
433                          calculation);
434     if (itr == calculationIfaces.end())
435     {
436         return std::numeric_limits<double>::quiet_NaN();
437     }
438     else if (calculation == "xyz.openbmc_project.Configuration.ModifiedMedian")
439     {
440         return calculateModifiedMedianValue(paramMap);
441     }
442     else if (calculation == "xyz.openbmc_project.Configuration.Maximum")
443     {
444         return calculateMaximumValue(paramMap);
445     }
446     return std::numeric_limits<double>::quiet_NaN();
447 }
448 
449 bool VirtualSensor::sensorInRange(double value)
450 {
451     if (value <= this->maxValidInput && value >= this->minValidInput)
452     {
453         return true;
454     }
455     return false;
456 }
457 
458 void VirtualSensor::updateVirtualSensor()
459 {
460     for (auto& param : paramMap)
461     {
462         auto& name = param.first;
463         auto& data = param.second;
464         if (auto var = symbols.get_variable(name))
465         {
466             var->ref() = data->getParamValue();
467         }
468         else
469         {
470             /* Invalid parameter */
471             throw std::invalid_argument("ParamName not found in symbols");
472         }
473     }
474     auto itr =
475         std::find(calculationIfaces.begin(), calculationIfaces.end(), exprStr);
476     auto val = (itr == calculationIfaces.end())
477                    ? expression.value()
478                    : calculateValue(exprStr, paramMap);
479 
480     /* Set sensor value to dbus interface */
481     setSensorValue(val);
482 
483     if (DEBUG)
484     {
485         debug("Sensor {NAME} = {VALUE}", "NAME", this->name, "VALUE", val);
486     }
487 
488     /* Check sensor thresholds and log required message */
489     checkThresholds(val, perfLossIface);
490     checkThresholds(val, warningIface);
491     checkThresholds(val, criticalIface);
492     checkThresholds(val, softShutdownIface);
493     checkThresholds(val, hardShutdownIface);
494 }
495 
496 double VirtualSensor::calculateModifiedMedianValue(
497     const VirtualSensor::ParamMap& paramMap)
498 {
499     std::vector<double> values;
500 
501     for (auto& param : paramMap)
502     {
503         auto& name = param.first;
504         if (auto var = symbols.get_variable(name))
505         {
506             if (!sensorInRange(var->ref()))
507             {
508                 continue;
509             }
510             values.push_back(var->ref());
511         }
512     }
513 
514     size_t size = values.size();
515     std::sort(values.begin(), values.end());
516     switch (size)
517     {
518         case 2:
519             /* Choose biggest value */
520             return values.at(1);
521         case 0:
522             return std::numeric_limits<double>::quiet_NaN();
523         default:
524             /* Choose median value */
525             if (size % 2 == 0)
526             {
527                 // Average of the two middle values
528                 return (values.at(size / 2) + values.at(size / 2 - 1)) / 2;
529             }
530             else
531             {
532                 return values.at((size - 1) / 2);
533             }
534     }
535 }
536 
537 double VirtualSensor::calculateMaximumValue(
538     const VirtualSensor::ParamMap& paramMap)
539 {
540     std::vector<double> values;
541 
542     for (auto& param : paramMap)
543     {
544         auto& name = param.first;
545         if (auto var = symbols.get_variable(name))
546         {
547             if (!sensorInRange(var->ref()))
548             {
549                 continue;
550             }
551             values.push_back(var->ref());
552         }
553     }
554     auto maxIt = std::max_element(values.begin(), values.end());
555     if (maxIt == values.end())
556     {
557         return std::numeric_limits<double>::quiet_NaN();
558     }
559     return *maxIt;
560 }
561 
562 void VirtualSensor::createThresholds(const Json& threshold,
563                                      const std::string& objPath)
564 {
565     if (threshold.empty())
566     {
567         return;
568     }
569     // Only create the threshold interfaces if
570     // at least one of their values is present.
571     if (threshold.contains("CriticalHigh") || threshold.contains("CriticalLow"))
572     {
573         criticalIface =
574             std::make_unique<Threshold<CriticalObject>>(bus, objPath.c_str());
575 
576         if (threshold.contains("CriticalHigh"))
577         {
578             criticalIface->setEntityInterfaceHigh(
579                 threshold.value("CriticalHighDirection", ""));
580             if (DEBUG)
581             {
582                 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
583                       "INTF", threshold.value("CriticalHighDirection", ""));
584             }
585         }
586         if (threshold.contains("CriticalLow"))
587         {
588             criticalIface->setEntityInterfaceLow(
589                 threshold.value("CriticalLowDirection", ""));
590             if (DEBUG)
591             {
592                 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
593                       "INTF", threshold.value("CriticalLowDirection", ""));
594             }
595         }
596 
597         criticalIface->setEntityPath(entityPath);
598         if (DEBUG)
599         {
600             debug("Sensor Threshold:{NAME} = path:{PATH}", "NAME", objPath,
601                   "PATH", entityPath);
602         }
603 
604         criticalIface->criticalHigh(threshold.value(
605             "CriticalHigh", std::numeric_limits<double>::quiet_NaN()));
606         criticalIface->criticalLow(threshold.value(
607             "CriticalLow", std::numeric_limits<double>::quiet_NaN()));
608         criticalIface->setHighHysteresis(
609             threshold.value("CriticalHighHysteresis", defaultHysteresis));
610         criticalIface->setLowHysteresis(
611             threshold.value("CriticalLowHysteresis", defaultHysteresis));
612     }
613 
614     if (threshold.contains("WarningHigh") || threshold.contains("WarningLow"))
615     {
616         warningIface =
617             std::make_unique<Threshold<WarningObject>>(bus, objPath.c_str());
618 
619         if (threshold.contains("WarningHigh"))
620         {
621             warningIface->setEntityInterfaceHigh(
622                 threshold.value("WarningHighDirection", ""));
623             if (DEBUG)
624             {
625                 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
626                       "INTF", threshold.value("WarningHighDirection", ""));
627             }
628         }
629         if (threshold.contains("WarningLow"))
630         {
631             warningIface->setEntityInterfaceLow(
632                 threshold.value("WarningLowDirection", ""));
633             if (DEBUG)
634             {
635                 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
636                       "INTF", threshold.value("WarningLowDirection", ""));
637             }
638         }
639 
640         warningIface->setEntityPath(entityPath);
641         if (DEBUG)
642         {
643             debug("Sensor Threshold:{NAME} = path:{PATH}", "NAME", objPath,
644                   "PATH", entityPath);
645         }
646 
647         warningIface->warningHigh(threshold.value(
648             "WarningHigh", std::numeric_limits<double>::quiet_NaN()));
649         warningIface->warningLow(threshold.value(
650             "WarningLow", std::numeric_limits<double>::quiet_NaN()));
651         warningIface->setHighHysteresis(
652             threshold.value("WarningHighHysteresis", defaultHysteresis));
653         warningIface->setLowHysteresis(
654             threshold.value("WarningLowHysteresis", defaultHysteresis));
655     }
656 
657     if (threshold.contains("HardShutdownHigh") ||
658         threshold.contains("HardShutdownLow"))
659     {
660         hardShutdownIface = std::make_unique<Threshold<HardShutdownObject>>(
661             bus, objPath.c_str());
662 
663         hardShutdownIface->hardShutdownHigh(threshold.value(
664             "HardShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
665         hardShutdownIface->hardShutdownLow(threshold.value(
666             "HardShutdownLow", std::numeric_limits<double>::quiet_NaN()));
667         hardShutdownIface->setHighHysteresis(
668             threshold.value("HardShutdownHighHysteresis", defaultHysteresis));
669         hardShutdownIface->setLowHysteresis(
670             threshold.value("HardShutdownLowHysteresis", defaultHysteresis));
671     }
672 
673     if (threshold.contains("SoftShutdownHigh") ||
674         threshold.contains("SoftShutdownLow"))
675     {
676         softShutdownIface = std::make_unique<Threshold<SoftShutdownObject>>(
677             bus, objPath.c_str());
678 
679         softShutdownIface->softShutdownHigh(threshold.value(
680             "SoftShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
681         softShutdownIface->softShutdownLow(threshold.value(
682             "SoftShutdownLow", std::numeric_limits<double>::quiet_NaN()));
683         softShutdownIface->setHighHysteresis(
684             threshold.value("SoftShutdownHighHysteresis", defaultHysteresis));
685         softShutdownIface->setLowHysteresis(
686             threshold.value("SoftShutdownLowHysteresis", defaultHysteresis));
687     }
688 
689     if (threshold.contains("PerformanceLossHigh") ||
690         threshold.contains("PerformanceLossLow"))
691     {
692         perfLossIface = std::make_unique<Threshold<PerformanceLossObject>>(
693             bus, objPath.c_str());
694 
695         perfLossIface->performanceLossHigh(threshold.value(
696             "PerformanceLossHigh", std::numeric_limits<double>::quiet_NaN()));
697         perfLossIface->performanceLossLow(threshold.value(
698             "PerformanceLossLow", std::numeric_limits<double>::quiet_NaN()));
699         perfLossIface->setHighHysteresis(threshold.value(
700             "PerformanceLossHighHysteresis", defaultHysteresis));
701         perfLossIface->setLowHysteresis(
702             threshold.value("PerformanceLossLowHysteresis", defaultHysteresis));
703     }
704 }
705 
706 ManagedObjectType VirtualSensors::getObjectsFromDBus()
707 {
708     ManagedObjectType objects;
709 
710     try
711     {
712         auto method = bus.new_method_call(
713             "xyz.openbmc_project.EntityManager",
714             "/xyz/openbmc_project/inventory",
715             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
716         auto reply = bus.call(method);
717         reply.read(objects);
718     }
719     catch (const sdbusplus::exception_t& ex)
720     {
721         // If entity manager isn't running yet, keep going.
722         if (std::string("org.freedesktop.DBus.Error.ServiceUnknown") !=
723             ex.name())
724         {
725             error("Could not reach entity-manager: {ERROR}", "ERROR", ex);
726             throw;
727         }
728     }
729 
730     return objects;
731 }
732 
733 void VirtualSensors::propertiesChanged(sdbusplus::message_t& msg)
734 {
735     std::string path;
736     PropertyMap properties;
737 
738     msg.read(path, properties);
739 
740     /* We get multiple callbacks for one sensor. 'Type' is a required field and
741      * is a unique label so use to to only proceed once per sensor */
742     if (properties.contains("Type"))
743     {
744         if (isCalculationType(path))
745         {
746             createVirtualSensorsFromDBus(path);
747         }
748     }
749 }
750 
751 /** @brief Parsing Virtual Sensor config JSON file  */
752 Json VirtualSensors::parseConfigFile()
753 {
754     using path = std::filesystem::path;
755     auto configFile = []() -> path {
756         static constexpr auto name = "virtual_sensor_config.json";
757 
758         for (auto pathSeg : {std::filesystem::current_path(),
759                              path{"/var/lib/phosphor-virtual-sensor"},
760                              path{"/usr/share/phosphor-virtual-sensor"}})
761         {
762             auto file = pathSeg / name;
763             if (std::filesystem::exists(file))
764             {
765                 return file;
766             }
767         }
768         return name;
769     }();
770 
771     std::ifstream jsonFile(configFile);
772     if (!jsonFile.is_open())
773     {
774         error("config JSON file {FILENAME} not found", "FILENAME", configFile);
775         return {};
776     }
777 
778     auto data = Json::parse(jsonFile, nullptr, false);
779     if (data.is_discarded())
780     {
781         error("config readings JSON parser failure with {FILENAME}", "FILENAME",
782               configFile);
783         throw std::exception{};
784     }
785 
786     return data;
787 }
788 
789 std::map<std::string, ValueIface::Unit> unitMap = {
790     {"temperature", ValueIface::Unit::DegreesC},
791     {"fan_tach", ValueIface::Unit::RPMS},
792     {"fan_pwm", ValueIface::Unit::Percent},
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