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