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