blob: c6bc6da16f5fd69111979ce25f70d42bd6277463 [file] [log] [blame]
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001#include "virtualSensor.hpp"
2
George Liu7f41a0d2024-08-28 16:49:06 +08003#include "calculate.hpp"
4
Patrick Williams82b39c62021-07-28 16:22:27 -05005#include <phosphor-logging/lg2.hpp>
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07006
7#include <fstream>
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07008
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07009static constexpr auto sensorDbusPath = "/xyz/openbmc_project/sensors/";
Rashmica Guptae7efe132021-07-27 19:42:11 +100010static constexpr auto vsThresholdsIfaceSuffix = ".Thresholds";
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +100011static constexpr auto defaultHysteresis = 0;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070012
Patrick Williams82b39c62021-07-28 16:22:27 -050013PHOSPHOR_LOG2_USING_WITH_FLAGS;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070014
Tao Linf2e94222023-10-31 17:38:17 +080015namespace phosphor::virtual_sensor
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070016{
17
Lei YU0ab9d832022-07-19 07:12:50 +000018FuncMaxIgnoreNaN<double> VirtualSensor::funcMaxIgnoreNaN;
Lei YU87d35112022-10-24 05:54:25 +000019FuncSumIgnoreNaN<double> VirtualSensor::funcSumIgnoreNaN;
Lei YUc77b6b32023-06-08 12:02:05 +000020FuncIfNan<double> VirtualSensor::funcIfNan;
Lei YU0ab9d832022-07-19 07:12:50 +000021
Amithash Prasad7d2f3232025-06-02 20:31:48 -070022std::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
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070035void 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();
Patrick Williamsfbd71452021-08-30 06:59:46 -050042 debug("Parameter: {PARAM} = {VALUE}", "PARAM", p1, "VALUE", val);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070043 }
44}
45
46double SensorParam::getParamValue()
47{
48 switch (paramType)
49 {
50 case constParam:
51 return value;
52 break;
Vijay Khemka7452a862020-08-11 16:01:23 -070053 case dbusParam:
54 return dbusSensor->getSensorValue();
55 break;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070056 default:
57 throw std::invalid_argument("param type not supported");
58 }
59}
60
Lei YU0fcf0e12021-06-04 11:14:17 +080061using AssociationList =
62 std::vector<std::tuple<std::string, std::string, std::string>>;
63
64AssociationList getAssociationsFromJson(const Json& j)
65{
66 AssociationList assocs{};
67 try
68 {
69 j.get_to(assocs);
70 }
71 catch (const std::exception& ex)
72 {
Patrick Williams82b39c62021-07-28 16:22:27 -050073 error("Failed to parse association: {ERROR}", "ERROR", ex);
Lei YU0fcf0e12021-06-04 11:14:17 +080074 }
75 return assocs;
76}
77
Rashmica Guptae7efe132021-07-27 19:42:11 +100078template <typename U>
79struct VariantToNumber
80{
81 template <typename T>
82 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
92template <typename U>
93U getNumberFromConfig(const PropertyMap& map, const std::string& name,
Jiaqing Zhao190f6d02022-05-21 23:26:15 +080094 bool required,
95 U defaultValue = std::numeric_limits<U>::quiet_NaN())
Rashmica Guptae7efe132021-07-27 19:42:11 +100096{
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 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500103 error("Required field {NAME} missing in config", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000104 throw std::invalid_argument("Required field missing in config");
105 }
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800106 return defaultValue;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000107}
108
Rashmica Guptae7efe132021-07-27 19:42:11 +1000109const std::string getThresholdType(const std::string& direction,
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100110 const std::string& severity)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000111{
Rashmica Guptae7efe132021-07-27 19:42:11 +1000112 std::string suffix;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000113
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 }
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100127 return severity + suffix;
128}
129
130std::string getSeverityField(const PropertyMap& propertyMap)
131{
Patrick Williams150d5f62024-08-16 15:21:45 -0400132 static const std::array thresholdTypes{
133 "Warning", "Critical", "PerformanceLoss", "SoftShutdown",
134 "HardShutdown"};
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100135
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 {
Patrick Williams150d5f62024-08-16 15:21:45 -0400151 auto sev =
152 getNumberFromConfig<uint64_t>(propertyMap, "Severity", true);
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100153 /* 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;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000164}
165
Tao Lin91799db2022-07-27 21:02:20 +0800166void parseThresholds(Json& thresholds, const PropertyMap& propertyMap,
167 const std::string& entityInterface = "")
Rashmica Guptae7efe132021-07-27 19:42:11 +1000168{
169 std::string direction;
170
Rashmica Guptae7efe132021-07-27 19:42:11 +1000171 auto value = getNumberFromConfig<double>(propertyMap, "Value", true);
172
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100173 auto severity = getSeverityField(propertyMap);
174
175 if (auto itr = propertyMap.find("Direction"); itr != propertyMap.end())
Rashmica Guptae7efe132021-07-27 19:42:11 +1000176 {
177 direction = std::get<std::string>(itr->second);
178 }
179
180 auto threshold = getThresholdType(direction, severity);
181 thresholds[threshold] = value;
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000182
Patrick Williams150d5f62024-08-16 15:21:45 -0400183 auto hysteresis =
184 getNumberFromConfig<double>(propertyMap, "Hysteresis", false);
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000185 if (hysteresis != std::numeric_limits<double>::quiet_NaN())
186 {
187 thresholds[threshold + "Hysteresis"] = hysteresis;
188 }
Tao Lin91799db2022-07-27 21:02:20 +0800189
190 if (!entityInterface.empty())
191 {
192 thresholds[threshold + "Direction"] = entityInterface;
193 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000194}
195
196void 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
Patrick Williams150d5f62024-08-16 15:21:45 -0400209 auto paramPtr =
210 std::make_unique<SensorParam>(bus, sensorObjPath, *this);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000211 symbols.create_variable(sensor);
212 paramMap.emplace(std::move(sensor), std::move(paramPtr));
213 }
214 }
215 /* Get expression string */
George Liu7f41a0d2024-08-28 16:49:06 +0800216 if (!calculationIfaces.contains(interface))
Rashmica Guptae7efe132021-07-27 19:42:11 +1000217 {
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 =
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800228 getNumberFromConfig<double>(propertyMap, "MaxValidInput", false,
229 std::numeric_limits<double>::infinity());
Rashmica Guptae7efe132021-07-27 19:42:11 +1000230 minValidInput =
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800231 getNumberFromConfig<double>(propertyMap, "MinValidInput", false,
232 -std::numeric_limits<double>::infinity());
Rashmica Guptae7efe132021-07-27 19:42:11 +1000233}
234
Matt Spinlerce675222021-01-14 16:38:09 -0600235void VirtualSensor::initVirtualSensor(const Json& sensorConfig,
Amithash Prasad7d2f3232025-06-02 20:31:48 -0700236 const std::string& objPath,
237 const std::string& type)
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700238{
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700239 static const Json empty{};
240
Amithash Prasad7d2f3232025-06-02 20:31:48 -0700241 units = unitMap.at(type);
242
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700243 /* Get threshold values if defined in config */
244 auto threshold = sensorConfig.value("Threshold", empty);
Matt Spinlerf15189e2021-01-15 10:13:28 -0600245
Amithash Prasad7d2f3232025-06-02 20:31:48 -0700246 createThresholds(threshold, objPath, units);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700247
Harvey Wuf6443742021-04-09 16:47:36 +0800248 /* 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
Lei YU0fcf0e12021-06-04 11:14:17 +0800261 /* 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
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700274 /* Get expression string */
Patrick Williams03c4c8e2022-04-14 22:19:44 -0500275 static constexpr auto exprKey = "Expression";
276 if (sensorConfig.contains(exprKey))
277 {
Patrick Williamsa9596782022-04-15 10:20:07 -0500278 auto& ref = sensorConfig.at(exprKey);
Patrick Williams03c4c8e2022-04-14 22:19:44 -0500279 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 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700292
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"]);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700305 std::string name = j["ParamName"];
306 symbols.create_variable(name);
307 paramMap.emplace(std::move(name), std::move(paramPtr));
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700308 }
309 else
310 {
311 /* Invalid configuration */
312 throw std::invalid_argument(
313 "ParamName not found in configuration");
314 }
315 }
316 }
317
Vijay Khemka7452a862020-08-11 16:01:23 -0700318 /* 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 {
George Liu1204b432021-12-29 17:24:48 +0800333 auto path = sensorDbusPath + sensorType + "/" + name;
Patrick Williams150d5f62024-08-16 15:21:45 -0400334 auto paramPtr =
335 std::make_unique<SensorParam>(bus, path, *this);
George Liu1204b432021-12-29 17:24:48 +0800336 std::string paramName = j["ParamName"];
337 symbols.create_variable(paramName);
338 paramMap.emplace(std::move(paramName), std::move(paramPtr));
Vijay Khemka7452a862020-08-11 16:01:23 -0700339 }
340 }
341 }
342 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700343
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700344 symbols.add_constants();
Matt Spinler9f1ef4f2020-11-09 15:59:11 -0600345 symbols.add_package(vecopsPackage);
Lei YU0ab9d832022-07-19 07:12:50 +0000346 symbols.add_function("maxIgnoreNaN", funcMaxIgnoreNaN);
Lei YU87d35112022-10-24 05:54:25 +0000347 symbols.add_function("sumIgnoreNaN", funcSumIgnoreNaN);
Lei YUc77b6b32023-06-08 12:02:05 +0000348 symbols.add_function("ifNan", funcIfNan);
Lei YU0ab9d832022-07-19 07:12:50 +0000349
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700350 expression.register_symbol_table(symbols);
351
352 /* parser from exprtk */
353 exprtk::parser<double> parser{};
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600354 if (!parser.compile(exprStr, expression))
355 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500356 error("Expression compilation failed");
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600357
358 for (std::size_t i = 0; i < parser.error_count(); ++i)
359 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500360 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);
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600365 }
366 throw std::runtime_error("Expression compilation failed");
367 }
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700368
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700369 /* Print all parameters for debug purpose only */
George Liua630f082024-08-28 14:42:55 +0800370 printParams(paramMap);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700371}
372
Tao Lindc777012022-07-27 20:41:46 +0800373void 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()}};
Patrick Williams150d5f62024-08-16 15:21:45 -0400384 associationIface =
385 std::make_unique<AssociationObject>(bus, objPath.c_str());
Tao Lindc777012022-07-27 20:41:46 +0800386 associationIface->associations(assocsDbus);
387}
388
Patrick Williams150d5f62024-08-16 15:21:45 -0400389void VirtualSensor::initVirtualSensor(
390 const InterfaceMap& interfaceMap, const std::string& objPath,
391 const std::string& sensorType, const std::string& calculationIface)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000392{
393 Json thresholds;
Patrick Williams150d5f62024-08-16 15:21:45 -0400394 const std::string vsThresholdsIntf =
395 calculationIface + vsThresholdsIfaceSuffix;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000396
Amithash Prasad7d2f3232025-06-02 20:31:48 -0700397 units = unitMap.at(sensorType);
398
Rashmica Guptae7efe132021-07-27 19:42:11 +1000399 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 {
Tao Lin91799db2022-07-27 21:02:20 +0800405 parseThresholds(thresholds, propertyMap, interface);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000406 }
407 else if (interface == calculationIface)
408 {
409 parseConfigInterface(propertyMap, sensorType, interface);
410 }
411 }
412
Amithash Prasad7d2f3232025-06-02 20:31:48 -0700413 createThresholds(thresholds, objPath, units);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000414 symbols.add_constants();
415 symbols.add_package(vecopsPackage);
416 expression.register_symbol_table(symbols);
417
Tao Lindc777012022-07-27 20:41:46 +0800418 createAssociation(objPath, entityPath);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000419 /* Print all parameters for debug purpose only */
George Liua630f082024-08-28 14:42:55 +0800420 printParams(paramMap);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000421}
422
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700423void VirtualSensor::setSensorValue(double value)
424{
Patrick Williams543bf662021-04-29 09:03:53 -0500425 value = std::clamp(value, ValueIface::minValue(), ValueIface::maxValue());
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700426 ValueIface::value(value);
427}
428
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000429double VirtualSensor::calculateValue(const std::string& calculation,
430 const VirtualSensor::ParamMap& paramMap)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000431{
George Liu7f41a0d2024-08-28 16:49:06 +0800432 auto iter = calculationIfaces.find(calculation);
433 if (iter == calculationIfaces.end())
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000434 {
435 return std::numeric_limits<double>::quiet_NaN();
436 }
George Liu7f41a0d2024-08-28 16:49:06 +0800437
438 std::vector<double> values;
439 for (auto& param : paramMap)
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000440 {
George Liu7f41a0d2024-08-28 16:49:06 +0800441 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 }
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000450 }
George Liu7f41a0d2024-08-28 16:49:06 +0800451
452 return iter->second(values);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000453}
454
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000455bool VirtualSensor::sensorInRange(double value)
456{
457 if (value <= this->maxValidInput && value >= this->minValidInput)
458 {
459 return true;
460 }
461 return false;
462}
463
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700464void VirtualSensor::updateVirtualSensor()
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700465{
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 }
George Liu7f41a0d2024-08-28 16:49:06 +0800480 auto val = (!calculationIfaces.contains(exprStr))
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000481 ? expression.value()
482 : calculateValue(exprStr, paramMap);
Vijay Khemka32a71562020-09-10 15:29:18 -0700483
484 /* Set sensor value to dbus interface */
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700485 setSensorValue(val);
George Liua630f082024-08-28 14:42:55 +0800486 debug("Sensor {NAME} = {VALUE}", "NAME", this->name, "VALUE", val);
Vijay Khemka32a71562020-09-10 15:29:18 -0700487
Matt Spinler8f5e6112021-01-15 10:44:32 -0600488 /* Check sensor thresholds and log required message */
Amithash Prasadb3f59462025-06-02 21:22:13 -0700489 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 }
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700511}
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700512
Amithash Prasad7d2f3232025-06-02 20:31:48 -0700513void VirtualSensor::createThresholds(
514 const Json& threshold, const std::string& objPath, ValueIface::Unit units)
Rashmica Gupta3e999192021-06-09 16:17:04 +1000515{
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 {
Amithash Prasad7d2f3232025-06-02 20:31:48 -0700524 criticalIface = std::make_unique<Threshold<CriticalObject>>(
525 bus, objPath.c_str(), units);
Rashmica Gupta3e999192021-06-09 16:17:04 +1000526
Tao Lin91799db2022-07-27 21:02:20 +0800527 if (threshold.contains("CriticalHigh"))
528 {
529 criticalIface->setEntityInterfaceHigh(
530 threshold.value("CriticalHighDirection", ""));
George Liua630f082024-08-28 14:42:55 +0800531 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
532 "INTF", threshold.value("CriticalHighDirection", ""));
Tao Lin91799db2022-07-27 21:02:20 +0800533 }
534 if (threshold.contains("CriticalLow"))
535 {
536 criticalIface->setEntityInterfaceLow(
537 threshold.value("CriticalLowDirection", ""));
George Liua630f082024-08-28 14:42:55 +0800538 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
539 "INTF", threshold.value("CriticalLowDirection", ""));
Tao Lin91799db2022-07-27 21:02:20 +0800540 }
541
542 criticalIface->setEntityPath(entityPath);
George Liua630f082024-08-28 14:42:55 +0800543 debug("Sensor Threshold:{NAME} = path:{PATH}", "NAME", objPath, "PATH",
544 entityPath);
Matt Spinlera291ce12023-02-06 15:12:44 -0600545
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));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000554 }
555
556 if (threshold.contains("WarningHigh") || threshold.contains("WarningLow"))
557 {
Amithash Prasad7d2f3232025-06-02 20:31:48 -0700558 warningIface = std::make_unique<Threshold<WarningObject>>(
559 bus, objPath.c_str(), units);
Rashmica Gupta3e999192021-06-09 16:17:04 +1000560
Tao Lin91799db2022-07-27 21:02:20 +0800561 if (threshold.contains("WarningHigh"))
562 {
563 warningIface->setEntityInterfaceHigh(
564 threshold.value("WarningHighDirection", ""));
George Liua630f082024-08-28 14:42:55 +0800565 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
566 "INTF", threshold.value("WarningHighDirection", ""));
Tao Lin91799db2022-07-27 21:02:20 +0800567 }
568 if (threshold.contains("WarningLow"))
569 {
570 warningIface->setEntityInterfaceLow(
571 threshold.value("WarningLowDirection", ""));
George Liua630f082024-08-28 14:42:55 +0800572 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
573 "INTF", threshold.value("WarningLowDirection", ""));
Tao Lin91799db2022-07-27 21:02:20 +0800574 }
575
576 warningIface->setEntityPath(entityPath);
George Liua630f082024-08-28 14:42:55 +0800577 debug("Sensor Threshold:{NAME} = path:{PATH}", "NAME", objPath, "PATH",
578 entityPath);
Matt Spinlera291ce12023-02-06 15:12:44 -0600579
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));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000588 }
589
590 if (threshold.contains("HardShutdownHigh") ||
591 threshold.contains("HardShutdownLow"))
592 {
593 hardShutdownIface = std::make_unique<Threshold<HardShutdownObject>>(
Amithash Prasad7d2f3232025-06-02 20:31:48 -0700594 bus, objPath.c_str(), units);
Rashmica Gupta3e999192021-06-09 16:17:04 +1000595
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()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000600 hardShutdownIface->setHighHysteresis(
601 threshold.value("HardShutdownHighHysteresis", defaultHysteresis));
602 hardShutdownIface->setLowHysteresis(
603 threshold.value("HardShutdownLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000604 }
605
606 if (threshold.contains("SoftShutdownHigh") ||
607 threshold.contains("SoftShutdownLow"))
608 {
609 softShutdownIface = std::make_unique<Threshold<SoftShutdownObject>>(
Amithash Prasad7d2f3232025-06-02 20:31:48 -0700610 bus, objPath.c_str(), units);
Rashmica Gupta3e999192021-06-09 16:17:04 +1000611
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()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000616 softShutdownIface->setHighHysteresis(
617 threshold.value("SoftShutdownHighHysteresis", defaultHysteresis));
618 softShutdownIface->setLowHysteresis(
619 threshold.value("SoftShutdownLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000620 }
621
622 if (threshold.contains("PerformanceLossHigh") ||
623 threshold.contains("PerformanceLossLow"))
624 {
625 perfLossIface = std::make_unique<Threshold<PerformanceLossObject>>(
Amithash Prasad7d2f3232025-06-02 20:31:48 -0700626 bus, objPath.c_str(), units);
Rashmica Gupta3e999192021-06-09 16:17:04 +1000627
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()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000632 perfLossIface->setHighHysteresis(threshold.value(
633 "PerformanceLossHighHysteresis", defaultHysteresis));
634 perfLossIface->setLowHysteresis(
635 threshold.value("PerformanceLossLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000636 }
637}
638
Rashmica Guptae7efe132021-07-27 19:42:11 +1000639ManagedObjectType VirtualSensors::getObjectsFromDBus()
640{
641 ManagedObjectType objects;
642
643 try
644 {
Patrick Williams150d5f62024-08-16 15:21:45 -0400645 auto method = bus.new_method_call(
646 "xyz.openbmc_project.EntityManager",
647 "/xyz/openbmc_project/inventory",
648 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000649 auto reply = bus.call(method);
650 reply.read(objects);
651 }
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500652 catch (const sdbusplus::exception_t& ex)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000653 {
654 // If entity manager isn't running yet, keep going.
655 if (std::string("org.freedesktop.DBus.Error.ServiceUnknown") !=
656 ex.name())
657 {
Matt Spinler71b9c112022-10-18 09:14:45 -0500658 error("Could not reach entity-manager: {ERROR}", "ERROR", ex);
659 throw;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000660 }
661 }
662
663 return objects;
664}
665
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500666void VirtualSensors::propertiesChanged(sdbusplus::message_t& msg)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000667{
George Liu7f41a0d2024-08-28 16:49:06 +0800668 std::string interface;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000669 PropertyMap properties;
670
George Liu7f41a0d2024-08-28 16:49:06 +0800671 msg.read(interface, properties);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000672
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 {
George Liu7f41a0d2024-08-28 16:49:06 +0800677 if (calculationIfaces.contains(interface))
Rashmica Guptae7efe132021-07-27 19:42:11 +1000678 {
George Liu7f41a0d2024-08-28 16:49:06 +0800679 createVirtualSensorsFromDBus(interface);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000680 }
681 }
682}
683
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700684/** @brief Parsing Virtual Sensor config JSON file */
Patrick Williams32dff212023-02-09 13:54:18 -0600685Json VirtualSensors::parseConfigFile()
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700686{
Patrick Williams32dff212023-02-09 13:54:18 -0600687 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
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700704 std::ifstream jsonFile(configFile);
705 if (!jsonFile.is_open())
706 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500707 error("config JSON file {FILENAME} not found", "FILENAME", configFile);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000708 return {};
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700709 }
710
711 auto data = Json::parse(jsonFile, nullptr, false);
712 if (data.is_discarded())
713 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500714 error("config readings JSON parser failure with {FILENAME}", "FILENAME",
715 configFile);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700716 throw std::exception{};
717 }
718
719 return data;
720}
721
Rashmica Guptae7efe132021-07-27 19:42:11 +1000722const 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
736void VirtualSensors::setupMatches()
737{
738 /* Already setup */
739 if (!this->matches.empty())
740 {
741 return;
742 }
743
744 /* Setup matches */
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500745 auto eventHandler = [this](sdbusplus::message_t& message) {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000746 if (message.is_method_error())
747 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500748 error("Callback method error");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000749 return;
750 }
751 this->propertiesChanged(message);
752 };
753
George Liu7f41a0d2024-08-28 16:49:06 +0800754 for (const auto& [iface, _] : calculationIfaces)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000755 {
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500756 auto match = std::make_unique<sdbusplus::bus::match_t>(
Rashmica Guptae7efe132021-07-27 19:42:11 +1000757 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
765void VirtualSensors::createVirtualSensorsFromDBus(
766 const std::string& calculationIface)
767{
768 if (calculationIface.empty())
769 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500770 error("No calculation type supplied");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000771 return;
772 }
773 auto objects = getObjectsFromDBus();
774
775 /* Get virtual sensors config data */
776 for (const auto& [path, interfaceMap] : objects)
777 {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000778 /* Find Virtual Sensor interfaces */
George Liu2db8d412023-08-21 16:41:04 +0800779 auto intfIter = interfaceMap.find(calculationIface);
780 if (intfIter == interfaceMap.end())
Rashmica Guptae7efe132021-07-27 19:42:11 +1000781 {
782 continue;
783 }
George Liu2db8d412023-08-21 16:41:04 +0800784
785 std::string name = path.filename();
Rashmica Guptae7efe132021-07-27 19:42:11 +1000786 if (name.empty())
787 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500788 error("Virtual Sensor name not found in entity manager config");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000789 continue;
790 }
791 if (virtualSensorsMap.contains(name))
792 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500793 error("A virtual sensor named {NAME} already exists", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000794 continue;
795 }
796
797 /* Extract the virtual sensor type as we need this to initialize the
798 * sensor */
George Liu2db8d412023-08-21 16:41:04 +0800799 std::string sensorType, sensorUnit;
800 auto propertyMap = intfIter->second;
801 auto proIter = propertyMap.find("Units");
802 if (proIter != propertyMap.end())
Rashmica Guptae7efe132021-07-27 19:42:11 +1000803 {
George Liu2db8d412023-08-21 16:41:04 +0800804 sensorUnit = std::get<std::string>(proIter->second);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000805 }
806 sensorType = getSensorTypeFromUnit(sensorUnit);
807 if (sensorType.empty())
808 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500809 error("Sensor unit type {TYPE} is not supported", "TYPE",
810 sensorUnit);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000811 continue;
812 }
813
814 try
815 {
George Liu2db8d412023-08-21 16:41:04 +0800816 auto objpath = static_cast<std::string>(path);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000817 auto virtObjPath = sensorDbusPath + sensorType + "/" + name;
818
819 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
820 bus, virtObjPath.c_str(), interfaceMap, name, sensorType,
Tao Lindc777012022-07-27 20:41:46 +0800821 calculationIface, objpath);
Patrick Williams82b39c62021-07-28 16:22:27 -0500822 info("Added a new virtual sensor: {NAME} {TYPE}", "NAME", name,
823 "TYPE", sensorType);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000824 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 */
Patrick Williamsae10c522023-10-20 11:19:44 -0500833 auto intfRemoved = [this, objpath,
834 name](sdbusplus::message_t& message) {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000835 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 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500843 info("Removed a virtual sensor: {NAME}", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000844 virtualSensorsMap.erase(name);
845 }
846 };
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500847 auto matchOnRemove = std::make_unique<sdbusplus::bus::match_t>(
Rashmica Guptae7efe132021-07-27 19:42:11 +1000848 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 }
Patrick Williamsdac26632021-10-06 14:38:47 -0500856 catch (const std::invalid_argument& ia)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000857 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500858 error("Failed to set up virtual sensor: {ERROR}", "ERROR", ia);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000859 }
860 }
861}
862
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700863void VirtualSensors::createVirtualSensors()
864{
865 static const Json empty{};
866
Patrick Williams32dff212023-02-09 13:54:18 -0600867 auto data = parseConfigFile();
Rashmica Guptae7efe132021-07-27 19:42:11 +1000868
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700869 // print values
George Liua630f082024-08-28 14:42:55 +0800870 debug("JSON: {JSON}", "JSON", data.dump());
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700871
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 {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000878 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
George Liu06f88742024-10-24 18:12:29 +0800885 for (const auto& intf : std::views::keys(calculationIfaces))
Rashmica Guptae7efe132021-07-27 19:42:11 +1000886 {
George Liu7f41a0d2024-08-28 16:49:06 +0800887 createVirtualSensorsFromDBus(intf);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000888 }
889 continue;
890 }
891
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700892 std::string sensorType = desc.value("SensorType", "");
893 std::string name = desc.value("Name", "");
Rashmica Gupta665a0a22021-06-30 11:35:28 +1000894 std::replace(name.begin(), name.end(), ' ', '_');
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700895
896 if (!name.empty() && !sensorType.empty())
897 {
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700898 if (unitMap.find(sensorType) == unitMap.end())
899 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500900 error("Sensor type {TYPE} is not supported", "TYPE",
901 sensorType);
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700902 }
903 else
904 {
Rashmica Gupta67d8b9d2021-06-30 11:41:14 +1000905 if (virtualSensorsMap.find(name) != virtualSensorsMap.end())
906 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500907 error("A virtual sensor named {NAME} already exists",
908 "NAME", name);
Rashmica Gupta67d8b9d2021-06-30 11:41:14 +1000909 continue;
910 }
Rashmica Gupta862c3d12021-08-06 12:19:31 +1000911 auto objPath = sensorDbusPath + sensorType + "/" + name;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700912
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700913 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
Amithash Prasad7d2f3232025-06-02 20:31:48 -0700914 bus, objPath.c_str(), j, name, sensorType);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700915
Patrick Williams82b39c62021-07-28 16:22:27 -0500916 info("Added a new virtual sensor: {NAME}", "NAME", name);
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700917 virtualSensorPtr->updateVirtualSensor();
918
919 /* Initialize unit value for virtual sensor */
920 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
Rashmica Guptaa2fa63a2021-08-06 12:21:13 +1000921 virtualSensorPtr->emit_object_added();
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700922
923 virtualSensorsMap.emplace(std::move(name),
924 std::move(virtualSensorPtr));
925 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700926 }
927 else
928 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500929 error(
930 "Sensor type ({TYPE}) or name ({NAME}) not found in config file",
931 "NAME", name, "TYPE", sensorType);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700932 }
933 }
934 else
935 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500936 error("Descriptor for new virtual sensor not found in config file");
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700937 }
938 }
939}
940
Tao Linf2e94222023-10-31 17:38:17 +0800941} // namespace phosphor::virtual_sensor