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