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