blob: 3aeddf3f552ad91bdead0c48fd137f6980fb4ce2 [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
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070022void 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();
Patrick Williamsfbd71452021-08-30 06:59:46 -050029 debug("Parameter: {PARAM} = {VALUE}", "PARAM", p1, "VALUE", val);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070030 }
31}
32
33double SensorParam::getParamValue()
34{
35 switch (paramType)
36 {
37 case constParam:
38 return value;
39 break;
Vijay Khemka7452a862020-08-11 16:01:23 -070040 case dbusParam:
41 return dbusSensor->getSensorValue();
42 break;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070043 default:
44 throw std::invalid_argument("param type not supported");
45 }
46}
47
Lei YU0fcf0e12021-06-04 11:14:17 +080048using AssociationList =
49 std::vector<std::tuple<std::string, std::string, std::string>>;
50
51AssociationList getAssociationsFromJson(const Json& j)
52{
53 AssociationList assocs{};
54 try
55 {
56 j.get_to(assocs);
57 }
58 catch (const std::exception& ex)
59 {
Patrick Williams82b39c62021-07-28 16:22:27 -050060 error("Failed to parse association: {ERROR}", "ERROR", ex);
Lei YU0fcf0e12021-06-04 11:14:17 +080061 }
62 return assocs;
63}
64
Rashmica Guptae7efe132021-07-27 19:42:11 +100065template <typename U>
66struct VariantToNumber
67{
68 template <typename T>
69 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
79template <typename U>
80U getNumberFromConfig(const PropertyMap& map, const std::string& name,
Jiaqing Zhao190f6d02022-05-21 23:26:15 +080081 bool required,
82 U defaultValue = std::numeric_limits<U>::quiet_NaN())
Rashmica Guptae7efe132021-07-27 19:42:11 +100083{
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 {
Patrick Williams82b39c62021-07-28 16:22:27 -050090 error("Required field {NAME} missing in config", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +100091 throw std::invalid_argument("Required field missing in config");
92 }
Jiaqing Zhao190f6d02022-05-21 23:26:15 +080093 return defaultValue;
Rashmica Guptae7efe132021-07-27 19:42:11 +100094}
95
Rashmica Guptae7efe132021-07-27 19:42:11 +100096const std::string getThresholdType(const std::string& direction,
Rashmica Gupta05b1d412021-11-03 12:01:36 +110097 const std::string& severity)
Rashmica Guptae7efe132021-07-27 19:42:11 +100098{
Rashmica Guptae7efe132021-07-27 19:42:11 +100099 std::string suffix;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000100
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 }
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100114 return severity + suffix;
115}
116
117std::string getSeverityField(const PropertyMap& propertyMap)
118{
Patrick Williams150d5f62024-08-16 15:21:45 -0400119 static const std::array thresholdTypes{
120 "Warning", "Critical", "PerformanceLoss", "SoftShutdown",
121 "HardShutdown"};
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100122
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 {
Patrick Williams150d5f62024-08-16 15:21:45 -0400138 auto sev =
139 getNumberFromConfig<uint64_t>(propertyMap, "Severity", true);
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100140 /* 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;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000151}
152
Tao Lin91799db2022-07-27 21:02:20 +0800153void parseThresholds(Json& thresholds, const PropertyMap& propertyMap,
154 const std::string& entityInterface = "")
Rashmica Guptae7efe132021-07-27 19:42:11 +1000155{
156 std::string direction;
157
Rashmica Guptae7efe132021-07-27 19:42:11 +1000158 auto value = getNumberFromConfig<double>(propertyMap, "Value", true);
159
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100160 auto severity = getSeverityField(propertyMap);
161
162 if (auto itr = propertyMap.find("Direction"); itr != propertyMap.end())
Rashmica Guptae7efe132021-07-27 19:42:11 +1000163 {
164 direction = std::get<std::string>(itr->second);
165 }
166
167 auto threshold = getThresholdType(direction, severity);
168 thresholds[threshold] = value;
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000169
Patrick Williams150d5f62024-08-16 15:21:45 -0400170 auto hysteresis =
171 getNumberFromConfig<double>(propertyMap, "Hysteresis", false);
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000172 if (hysteresis != std::numeric_limits<double>::quiet_NaN())
173 {
174 thresholds[threshold + "Hysteresis"] = hysteresis;
175 }
Tao Lin91799db2022-07-27 21:02:20 +0800176
177 if (!entityInterface.empty())
178 {
179 thresholds[threshold + "Direction"] = entityInterface;
180 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000181}
182
183void 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
Patrick Williams150d5f62024-08-16 15:21:45 -0400196 auto paramPtr =
197 std::make_unique<SensorParam>(bus, sensorObjPath, *this);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000198 symbols.create_variable(sensor);
199 paramMap.emplace(std::move(sensor), std::move(paramPtr));
200 }
201 }
202 /* Get expression string */
George Liu7f41a0d2024-08-28 16:49:06 +0800203 if (!calculationIfaces.contains(interface))
Rashmica Guptae7efe132021-07-27 19:42:11 +1000204 {
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 =
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800215 getNumberFromConfig<double>(propertyMap, "MaxValidInput", false,
216 std::numeric_limits<double>::infinity());
Rashmica Guptae7efe132021-07-27 19:42:11 +1000217 minValidInput =
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800218 getNumberFromConfig<double>(propertyMap, "MinValidInput", false,
219 -std::numeric_limits<double>::infinity());
Rashmica Guptae7efe132021-07-27 19:42:11 +1000220}
221
Matt Spinlerce675222021-01-14 16:38:09 -0600222void VirtualSensor::initVirtualSensor(const Json& sensorConfig,
223 const std::string& objPath)
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700224{
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700225 static const Json empty{};
226
227 /* Get threshold values if defined in config */
228 auto threshold = sensorConfig.value("Threshold", empty);
Matt Spinlerf15189e2021-01-15 10:13:28 -0600229
Rashmica Gupta3e999192021-06-09 16:17:04 +1000230 createThresholds(threshold, objPath);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700231
Harvey Wuf6443742021-04-09 16:47:36 +0800232 /* 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
Lei YU0fcf0e12021-06-04 11:14:17 +0800245 /* 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
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700258 /* Get expression string */
Patrick Williams03c4c8e2022-04-14 22:19:44 -0500259 static constexpr auto exprKey = "Expression";
260 if (sensorConfig.contains(exprKey))
261 {
Patrick Williamsa9596782022-04-15 10:20:07 -0500262 auto& ref = sensorConfig.at(exprKey);
Patrick Williams03c4c8e2022-04-14 22:19:44 -0500263 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 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700276
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"]);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700289 std::string name = j["ParamName"];
290 symbols.create_variable(name);
291 paramMap.emplace(std::move(name), std::move(paramPtr));
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700292 }
293 else
294 {
295 /* Invalid configuration */
296 throw std::invalid_argument(
297 "ParamName not found in configuration");
298 }
299 }
300 }
301
Vijay Khemka7452a862020-08-11 16:01:23 -0700302 /* 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 {
George Liu1204b432021-12-29 17:24:48 +0800317 auto path = sensorDbusPath + sensorType + "/" + name;
Vijay Khemka7452a862020-08-11 16:01:23 -0700318
Patrick Williams150d5f62024-08-16 15:21:45 -0400319 auto paramPtr =
320 std::make_unique<SensorParam>(bus, path, *this);
George Liu1204b432021-12-29 17:24:48 +0800321 std::string paramName = j["ParamName"];
322 symbols.create_variable(paramName);
323 paramMap.emplace(std::move(paramName), std::move(paramPtr));
Vijay Khemka7452a862020-08-11 16:01:23 -0700324 }
325 }
326 }
327 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700328
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700329 symbols.add_constants();
Matt Spinler9f1ef4f2020-11-09 15:59:11 -0600330 symbols.add_package(vecopsPackage);
Lei YU0ab9d832022-07-19 07:12:50 +0000331 symbols.add_function("maxIgnoreNaN", funcMaxIgnoreNaN);
Lei YU87d35112022-10-24 05:54:25 +0000332 symbols.add_function("sumIgnoreNaN", funcSumIgnoreNaN);
Lei YUc77b6b32023-06-08 12:02:05 +0000333 symbols.add_function("ifNan", funcIfNan);
Lei YU0ab9d832022-07-19 07:12:50 +0000334
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700335 expression.register_symbol_table(symbols);
336
337 /* parser from exprtk */
338 exprtk::parser<double> parser{};
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600339 if (!parser.compile(exprStr, expression))
340 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500341 error("Expression compilation failed");
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600342
343 for (std::size_t i = 0; i < parser.error_count(); ++i)
344 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500345 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);
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600350 }
351 throw std::runtime_error("Expression compilation failed");
352 }
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700353
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700354 /* Print all parameters for debug purpose only */
George Liua630f082024-08-28 14:42:55 +0800355 printParams(paramMap);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700356}
357
Tao Lindc777012022-07-27 20:41:46 +0800358void 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()}};
Patrick Williams150d5f62024-08-16 15:21:45 -0400369 associationIface =
370 std::make_unique<AssociationObject>(bus, objPath.c_str());
Tao Lindc777012022-07-27 20:41:46 +0800371 associationIface->associations(assocsDbus);
372}
373
Patrick Williams150d5f62024-08-16 15:21:45 -0400374void VirtualSensor::initVirtualSensor(
375 const InterfaceMap& interfaceMap, const std::string& objPath,
376 const std::string& sensorType, const std::string& calculationIface)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000377{
378 Json thresholds;
Patrick Williams150d5f62024-08-16 15:21:45 -0400379 const std::string vsThresholdsIntf =
380 calculationIface + vsThresholdsIfaceSuffix;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000381
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 {
Tao Lin91799db2022-07-27 21:02:20 +0800388 parseThresholds(thresholds, propertyMap, interface);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000389 }
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
Tao Lindc777012022-07-27 20:41:46 +0800401 createAssociation(objPath, entityPath);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000402 /* Print all parameters for debug purpose only */
George Liua630f082024-08-28 14:42:55 +0800403 printParams(paramMap);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000404}
405
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700406void VirtualSensor::setSensorValue(double value)
407{
Patrick Williams543bf662021-04-29 09:03:53 -0500408 value = std::clamp(value, ValueIface::minValue(), ValueIface::maxValue());
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700409 ValueIface::value(value);
410}
411
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000412double VirtualSensor::calculateValue(const std::string& calculation,
413 const VirtualSensor::ParamMap& paramMap)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000414{
George Liu7f41a0d2024-08-28 16:49:06 +0800415 auto iter = calculationIfaces.find(calculation);
416 if (iter == calculationIfaces.end())
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000417 {
418 return std::numeric_limits<double>::quiet_NaN();
419 }
George Liu7f41a0d2024-08-28 16:49:06 +0800420
421 std::vector<double> values;
422 for (auto& param : paramMap)
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000423 {
George Liu7f41a0d2024-08-28 16:49:06 +0800424 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 }
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000433 }
George Liu7f41a0d2024-08-28 16:49:06 +0800434
435 return iter->second(values);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000436}
437
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000438bool VirtualSensor::sensorInRange(double value)
439{
440 if (value <= this->maxValidInput && value >= this->minValidInput)
441 {
442 return true;
443 }
444 return false;
445}
446
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700447void VirtualSensor::updateVirtualSensor()
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700448{
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 }
George Liu7f41a0d2024-08-28 16:49:06 +0800463 auto val = (!calculationIfaces.contains(exprStr))
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000464 ? expression.value()
465 : calculateValue(exprStr, paramMap);
Vijay Khemka32a71562020-09-10 15:29:18 -0700466
467 /* Set sensor value to dbus interface */
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700468 setSensorValue(val);
George Liua630f082024-08-28 14:42:55 +0800469 debug("Sensor {NAME} = {VALUE}", "NAME", this->name, "VALUE", val);
Vijay Khemka32a71562020-09-10 15:29:18 -0700470
Matt Spinler8f5e6112021-01-15 10:44:32 -0600471 /* Check sensor thresholds and log required message */
Matt Spinlerb306b032021-02-01 10:05:46 -0600472 checkThresholds(val, perfLossIface);
Patrick Williamsfdb826d2021-01-20 14:37:53 -0600473 checkThresholds(val, warningIface);
474 checkThresholds(val, criticalIface);
475 checkThresholds(val, softShutdownIface);
476 checkThresholds(val, hardShutdownIface);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700477}
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700478
Rashmica Gupta3e999192021-06-09 16:17:04 +1000479void 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
Tao Lin91799db2022-07-27 21:02:20 +0800493 if (threshold.contains("CriticalHigh"))
494 {
495 criticalIface->setEntityInterfaceHigh(
496 threshold.value("CriticalHighDirection", ""));
George Liua630f082024-08-28 14:42:55 +0800497 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
498 "INTF", threshold.value("CriticalHighDirection", ""));
Tao Lin91799db2022-07-27 21:02:20 +0800499 }
500 if (threshold.contains("CriticalLow"))
501 {
502 criticalIface->setEntityInterfaceLow(
503 threshold.value("CriticalLowDirection", ""));
George Liua630f082024-08-28 14:42:55 +0800504 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
505 "INTF", threshold.value("CriticalLowDirection", ""));
Tao Lin91799db2022-07-27 21:02:20 +0800506 }
507
508 criticalIface->setEntityPath(entityPath);
George Liua630f082024-08-28 14:42:55 +0800509 debug("Sensor Threshold:{NAME} = path:{PATH}", "NAME", objPath, "PATH",
510 entityPath);
Matt Spinlera291ce12023-02-06 15:12:44 -0600511
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));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000520 }
521
522 if (threshold.contains("WarningHigh") || threshold.contains("WarningLow"))
523 {
524 warningIface =
525 std::make_unique<Threshold<WarningObject>>(bus, objPath.c_str());
526
Tao Lin91799db2022-07-27 21:02:20 +0800527 if (threshold.contains("WarningHigh"))
528 {
529 warningIface->setEntityInterfaceHigh(
530 threshold.value("WarningHighDirection", ""));
George Liua630f082024-08-28 14:42:55 +0800531 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
532 "INTF", threshold.value("WarningHighDirection", ""));
Tao Lin91799db2022-07-27 21:02:20 +0800533 }
534 if (threshold.contains("WarningLow"))
535 {
536 warningIface->setEntityInterfaceLow(
537 threshold.value("WarningLowDirection", ""));
George Liua630f082024-08-28 14:42:55 +0800538 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
539 "INTF", threshold.value("WarningLowDirection", ""));
Tao Lin91799db2022-07-27 21:02:20 +0800540 }
541
542 warningIface->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 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));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000554 }
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()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000566 hardShutdownIface->setHighHysteresis(
567 threshold.value("HardShutdownHighHysteresis", defaultHysteresis));
568 hardShutdownIface->setLowHysteresis(
569 threshold.value("HardShutdownLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000570 }
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()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000582 softShutdownIface->setHighHysteresis(
583 threshold.value("SoftShutdownHighHysteresis", defaultHysteresis));
584 softShutdownIface->setLowHysteresis(
585 threshold.value("SoftShutdownLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000586 }
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()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000598 perfLossIface->setHighHysteresis(threshold.value(
599 "PerformanceLossHighHysteresis", defaultHysteresis));
600 perfLossIface->setLowHysteresis(
601 threshold.value("PerformanceLossLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000602 }
603}
604
Rashmica Guptae7efe132021-07-27 19:42:11 +1000605ManagedObjectType VirtualSensors::getObjectsFromDBus()
606{
607 ManagedObjectType objects;
608
609 try
610 {
Patrick Williams150d5f62024-08-16 15:21:45 -0400611 auto method = bus.new_method_call(
612 "xyz.openbmc_project.EntityManager",
613 "/xyz/openbmc_project/inventory",
614 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000615 auto reply = bus.call(method);
616 reply.read(objects);
617 }
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500618 catch (const sdbusplus::exception_t& ex)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000619 {
620 // If entity manager isn't running yet, keep going.
621 if (std::string("org.freedesktop.DBus.Error.ServiceUnknown") !=
622 ex.name())
623 {
Matt Spinler71b9c112022-10-18 09:14:45 -0500624 error("Could not reach entity-manager: {ERROR}", "ERROR", ex);
625 throw;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000626 }
627 }
628
629 return objects;
630}
631
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500632void VirtualSensors::propertiesChanged(sdbusplus::message_t& msg)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000633{
George Liu7f41a0d2024-08-28 16:49:06 +0800634 std::string interface;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000635 PropertyMap properties;
636
George Liu7f41a0d2024-08-28 16:49:06 +0800637 msg.read(interface, properties);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000638
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 {
George Liu7f41a0d2024-08-28 16:49:06 +0800643 if (calculationIfaces.contains(interface))
Rashmica Guptae7efe132021-07-27 19:42:11 +1000644 {
George Liu7f41a0d2024-08-28 16:49:06 +0800645 createVirtualSensorsFromDBus(interface);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000646 }
647 }
648}
649
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700650/** @brief Parsing Virtual Sensor config JSON file */
Patrick Williams32dff212023-02-09 13:54:18 -0600651Json VirtualSensors::parseConfigFile()
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700652{
Patrick Williams32dff212023-02-09 13:54:18 -0600653 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
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700670 std::ifstream jsonFile(configFile);
671 if (!jsonFile.is_open())
672 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500673 error("config JSON file {FILENAME} not found", "FILENAME", configFile);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000674 return {};
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700675 }
676
677 auto data = Json::parse(jsonFile, nullptr, false);
678 if (data.is_discarded())
679 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500680 error("config readings JSON parser failure with {FILENAME}", "FILENAME",
681 configFile);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700682 throw std::exception{};
683 }
684
685 return data;
686}
687
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700688std::map<std::string, ValueIface::Unit> unitMap = {
689 {"temperature", ValueIface::Unit::DegreesC},
690 {"fan_tach", ValueIface::Unit::RPMS},
Konstantin Aladyshev9358f6b2024-03-14 14:23:40 +0300691 {"fan_pwm", ValueIface::Unit::Percent},
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700692 {"voltage", ValueIface::Unit::Volts},
693 {"altitude", ValueIface::Unit::Meters},
694 {"current", ValueIface::Unit::Amperes},
695 {"power", ValueIface::Unit::Watts},
696 {"energy", ValueIface::Unit::Joules},
Kumar Thangavel2b56ddb2021-01-13 20:16:11 +0530697 {"utilization", ValueIface::Unit::Percent},
Rashmica Gupta4ac7a7f2021-07-08 12:30:50 +1000698 {"airflow", ValueIface::Unit::CFM},
699 {"pressure", ValueIface::Unit::Pascals}};
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700700
Rashmica Guptae7efe132021-07-27 19:42:11 +1000701const 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
715void VirtualSensors::setupMatches()
716{
717 /* Already setup */
718 if (!this->matches.empty())
719 {
720 return;
721 }
722
723 /* Setup matches */
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500724 auto eventHandler = [this](sdbusplus::message_t& message) {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000725 if (message.is_method_error())
726 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500727 error("Callback method error");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000728 return;
729 }
730 this->propertiesChanged(message);
731 };
732
George Liu7f41a0d2024-08-28 16:49:06 +0800733 for (const auto& [iface, _] : calculationIfaces)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000734 {
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500735 auto match = std::make_unique<sdbusplus::bus::match_t>(
Rashmica Guptae7efe132021-07-27 19:42:11 +1000736 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
744void VirtualSensors::createVirtualSensorsFromDBus(
745 const std::string& calculationIface)
746{
747 if (calculationIface.empty())
748 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500749 error("No calculation type supplied");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000750 return;
751 }
752 auto objects = getObjectsFromDBus();
753
754 /* Get virtual sensors config data */
755 for (const auto& [path, interfaceMap] : objects)
756 {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000757 /* Find Virtual Sensor interfaces */
George Liu2db8d412023-08-21 16:41:04 +0800758 auto intfIter = interfaceMap.find(calculationIface);
759 if (intfIter == interfaceMap.end())
Rashmica Guptae7efe132021-07-27 19:42:11 +1000760 {
761 continue;
762 }
George Liu2db8d412023-08-21 16:41:04 +0800763
764 std::string name = path.filename();
Rashmica Guptae7efe132021-07-27 19:42:11 +1000765 if (name.empty())
766 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500767 error("Virtual Sensor name not found in entity manager config");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000768 continue;
769 }
770 if (virtualSensorsMap.contains(name))
771 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500772 error("A virtual sensor named {NAME} already exists", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000773 continue;
774 }
775
776 /* Extract the virtual sensor type as we need this to initialize the
777 * sensor */
George Liu2db8d412023-08-21 16:41:04 +0800778 std::string sensorType, sensorUnit;
779 auto propertyMap = intfIter->second;
780 auto proIter = propertyMap.find("Units");
781 if (proIter != propertyMap.end())
Rashmica Guptae7efe132021-07-27 19:42:11 +1000782 {
George Liu2db8d412023-08-21 16:41:04 +0800783 sensorUnit = std::get<std::string>(proIter->second);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000784 }
785 sensorType = getSensorTypeFromUnit(sensorUnit);
786 if (sensorType.empty())
787 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500788 error("Sensor unit type {TYPE} is not supported", "TYPE",
789 sensorUnit);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000790 continue;
791 }
792
793 try
794 {
George Liu2db8d412023-08-21 16:41:04 +0800795 auto objpath = static_cast<std::string>(path);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000796 auto virtObjPath = sensorDbusPath + sensorType + "/" + name;
797
798 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
799 bus, virtObjPath.c_str(), interfaceMap, name, sensorType,
Tao Lindc777012022-07-27 20:41:46 +0800800 calculationIface, objpath);
Patrick Williams82b39c62021-07-28 16:22:27 -0500801 info("Added a new virtual sensor: {NAME} {TYPE}", "NAME", name,
802 "TYPE", sensorType);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000803 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 */
Patrick Williamsae10c522023-10-20 11:19:44 -0500812 auto intfRemoved = [this, objpath,
813 name](sdbusplus::message_t& message) {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000814 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 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500822 info("Removed a virtual sensor: {NAME}", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000823 virtualSensorsMap.erase(name);
824 }
825 };
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500826 auto matchOnRemove = std::make_unique<sdbusplus::bus::match_t>(
Rashmica Guptae7efe132021-07-27 19:42:11 +1000827 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 }
Patrick Williamsdac26632021-10-06 14:38:47 -0500835 catch (const std::invalid_argument& ia)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000836 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500837 error("Failed to set up virtual sensor: {ERROR}", "ERROR", ia);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000838 }
839 }
840}
841
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700842void VirtualSensors::createVirtualSensors()
843{
844 static const Json empty{};
845
Patrick Williams32dff212023-02-09 13:54:18 -0600846 auto data = parseConfigFile();
Rashmica Guptae7efe132021-07-27 19:42:11 +1000847
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700848 // print values
George Liua630f082024-08-28 14:42:55 +0800849 debug("JSON: {JSON}", "JSON", data.dump());
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700850
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 {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000857 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 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500866 auto type = desc.value("Type", "");
George Liu7f41a0d2024-08-28 16:49:06 +0800867 auto intf = "xyz.openbmc_project.Configuration." + type;
Patrick Williams82b39c62021-07-28 16:22:27 -0500868
George Liu7f41a0d2024-08-28 16:49:06 +0800869 if (!calculationIfaces.contains(intf))
Rashmica Guptae7efe132021-07-27 19:42:11 +1000870 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500871 error("Invalid calculation type {TYPE} supplied.",
872 "TYPE", type);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000873 continue;
874 }
George Liu7f41a0d2024-08-28 16:49:06 +0800875 createVirtualSensorsFromDBus(intf);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000876 }
877 continue;
878 }
879
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700880 std::string sensorType = desc.value("SensorType", "");
881 std::string name = desc.value("Name", "");
Rashmica Gupta665a0a22021-06-30 11:35:28 +1000882 std::replace(name.begin(), name.end(), ' ', '_');
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700883
884 if (!name.empty() && !sensorType.empty())
885 {
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700886 if (unitMap.find(sensorType) == unitMap.end())
887 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500888 error("Sensor type {TYPE} is not supported", "TYPE",
889 sensorType);
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700890 }
891 else
892 {
Rashmica Gupta67d8b9d2021-06-30 11:41:14 +1000893 if (virtualSensorsMap.find(name) != virtualSensorsMap.end())
894 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500895 error("A virtual sensor named {NAME} already exists",
896 "NAME", name);
Rashmica Gupta67d8b9d2021-06-30 11:41:14 +1000897 continue;
898 }
Rashmica Gupta862c3d12021-08-06 12:19:31 +1000899 auto objPath = sensorDbusPath + sensorType + "/" + name;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700900
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700901 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
902 bus, objPath.c_str(), j, name);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700903
Patrick Williams82b39c62021-07-28 16:22:27 -0500904 info("Added a new virtual sensor: {NAME}", "NAME", name);
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700905 virtualSensorPtr->updateVirtualSensor();
906
907 /* Initialize unit value for virtual sensor */
908 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
Rashmica Guptaa2fa63a2021-08-06 12:21:13 +1000909 virtualSensorPtr->emit_object_added();
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700910
911 virtualSensorsMap.emplace(std::move(name),
912 std::move(virtualSensorPtr));
913 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700914 }
915 else
916 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500917 error(
918 "Sensor type ({TYPE}) or name ({NAME}) not found in config file",
919 "NAME", name, "TYPE", sensorType);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700920 }
921 }
922 else
923 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500924 error("Descriptor for new virtual sensor not found in config file");
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700925 }
926 }
927}
928
Tao Linf2e94222023-10-31 17:38:17 +0800929} // namespace phosphor::virtual_sensor