blob: f0a456c30b5e76d548c2aadc9708214e75f7d72a [file] [log] [blame]
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001#include "virtualSensor.hpp"
2
3#include "config.hpp"
4
Matt Spinlerddc6dcd2020-11-09 11:16:31 -06005#include <fmt/format.h>
6
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07007#include <phosphor-logging/log.hpp>
8#include <sdeventplus/event.hpp>
9
10#include <fstream>
11#include <iostream>
12
13static constexpr bool DEBUG = false;
14static constexpr auto busName = "xyz.openbmc_project.VirtualSensor";
15static constexpr auto sensorDbusPath = "/xyz/openbmc_project/sensors/";
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070016
17using namespace phosphor::logging;
18
Vijay Khemka51f898e2020-09-09 22:24:18 -070019int handleDbusSignal(sd_bus_message* msg, void* usrData, sd_bus_error*)
20{
21 if (usrData == nullptr)
22 {
23 throw std::runtime_error("Invalid match");
24 }
25
26 auto sdbpMsg = sdbusplus::message::message(msg);
27 std::string msgIfce;
28 std::map<std::string, std::variant<int64_t, double, bool>> msgData;
29
30 sdbpMsg.read(msgIfce, msgData);
31
32 if (msgData.find("Value") != msgData.end())
33 {
34 using namespace phosphor::virtualSensor;
35 VirtualSensor* obj = static_cast<VirtualSensor*>(usrData);
36 // TODO(openbmc/phosphor-virtual-sensor#1): updateVirtualSensor should
37 // be changed to take the information we got from the signal, to avoid
38 // having to do numerous dbus queries.
39 obj->updateVirtualSensor();
40 }
41 return 0;
42}
43
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070044namespace phosphor
45{
46namespace virtualSensor
47{
48
49void printParams(const VirtualSensor::ParamMap& paramMap)
50{
51 for (const auto& p : paramMap)
52 {
53 const auto& p1 = p.first;
54 const auto& p2 = p.second;
55 auto val = p2->getParamValue();
56 std::cout << p1 << " = " << val << "\n";
57 }
58}
59
60double SensorParam::getParamValue()
61{
62 switch (paramType)
63 {
64 case constParam:
65 return value;
66 break;
Vijay Khemka7452a862020-08-11 16:01:23 -070067 case dbusParam:
68 return dbusSensor->getSensorValue();
69 break;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070070 default:
71 throw std::invalid_argument("param type not supported");
72 }
73}
74
Lei YU0fcf0e12021-06-04 11:14:17 +080075using AssociationList =
76 std::vector<std::tuple<std::string, std::string, std::string>>;
77
78AssociationList getAssociationsFromJson(const Json& j)
79{
80 AssociationList assocs{};
81 try
82 {
83 j.get_to(assocs);
84 }
85 catch (const std::exception& ex)
86 {
87 log<level::ERR>("Failed to parse association",
88 entry("EX=%s", ex.what()));
89 }
90 return assocs;
91}
92
Matt Spinlerce675222021-01-14 16:38:09 -060093void VirtualSensor::initVirtualSensor(const Json& sensorConfig,
94 const std::string& objPath)
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070095{
96
97 static const Json empty{};
98
99 /* Get threshold values if defined in config */
100 auto threshold = sensorConfig.value("Threshold", empty);
Matt Spinlerf15189e2021-01-15 10:13:28 -0600101
Rashmica Gupta3e999192021-06-09 16:17:04 +1000102 createThresholds(threshold, objPath);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700103
Harvey Wuf6443742021-04-09 16:47:36 +0800104 /* Get MaxValue, MinValue setting if defined in config */
105 auto confDesc = sensorConfig.value("Desc", empty);
106 if (auto maxConf = confDesc.find("MaxValue");
107 maxConf != confDesc.end() && maxConf->is_number())
108 {
109 ValueIface::maxValue(maxConf->get<double>());
110 }
111 if (auto minConf = confDesc.find("MinValue");
112 minConf != confDesc.end() && minConf->is_number())
113 {
114 ValueIface::minValue(minConf->get<double>());
115 }
116
Lei YU0fcf0e12021-06-04 11:14:17 +0800117 /* Get optional association */
118 auto assocJson = sensorConfig.value("Associations", empty);
119 if (!assocJson.empty())
120 {
121 auto assocs = getAssociationsFromJson(assocJson);
122 if (!assocs.empty())
123 {
124 associationIface =
125 std::make_unique<AssociationObject>(bus, objPath.c_str());
126 associationIface->associations(assocs);
127 }
128 }
129
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700130 /* Get expression string */
131 exprStr = sensorConfig.value("Expression", "");
132
133 /* Get all the parameter listed in configuration */
134 auto params = sensorConfig.value("Params", empty);
135
136 /* Check for constant parameter */
137 const auto& consParams = params.value("ConstParam", empty);
138 if (!consParams.empty())
139 {
140 for (auto& j : consParams)
141 {
142 if (j.find("ParamName") != j.end())
143 {
144 auto paramPtr = std::make_unique<SensorParam>(j["Value"]);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700145 std::string name = j["ParamName"];
146 symbols.create_variable(name);
147 paramMap.emplace(std::move(name), std::move(paramPtr));
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700148 }
149 else
150 {
151 /* Invalid configuration */
152 throw std::invalid_argument(
153 "ParamName not found in configuration");
154 }
155 }
156 }
157
Vijay Khemka7452a862020-08-11 16:01:23 -0700158 /* Check for dbus parameter */
159 auto dbusParams = params.value("DbusParam", empty);
160 if (!dbusParams.empty())
161 {
162 for (auto& j : dbusParams)
163 {
164 /* Get parameter dbus sensor descriptor */
165 auto desc = j.value("Desc", empty);
166 if ((!desc.empty()) && (j.find("ParamName") != j.end()))
167 {
168 std::string sensorType = desc.value("SensorType", "");
169 std::string name = desc.value("Name", "");
170
171 if (!sensorType.empty() && !name.empty())
172 {
173 std::string objPath(sensorDbusPath);
174 objPath += sensorType + "/" + name;
175
Vijay Khemka51f898e2020-09-09 22:24:18 -0700176 auto paramPtr =
177 std::make_unique<SensorParam>(bus, objPath, this);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700178 std::string name = j["ParamName"];
179 symbols.create_variable(name);
180 paramMap.emplace(std::move(name), std::move(paramPtr));
Vijay Khemka7452a862020-08-11 16:01:23 -0700181 }
182 }
183 }
184 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700185
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700186 symbols.add_constants();
Matt Spinler9f1ef4f2020-11-09 15:59:11 -0600187 symbols.add_package(vecopsPackage);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700188 expression.register_symbol_table(symbols);
189
190 /* parser from exprtk */
191 exprtk::parser<double> parser{};
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600192 if (!parser.compile(exprStr, expression))
193 {
194 log<level::ERR>("Expression compilation failed");
195
196 for (std::size_t i = 0; i < parser.error_count(); ++i)
197 {
198 auto error = parser.get_error(i);
199
200 log<level::ERR>(
201 fmt::format(
202 "Position: {} Type: {} Message: {}", error.token.position,
203 exprtk::parser_error::to_str(error.mode), error.diagnostic)
204 .c_str());
205 }
206 throw std::runtime_error("Expression compilation failed");
207 }
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700208
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700209 /* Print all parameters for debug purpose only */
210 if (DEBUG)
211 printParams(paramMap);
212}
213
214void VirtualSensor::setSensorValue(double value)
215{
Patrick Williams543bf662021-04-29 09:03:53 -0500216 value = std::clamp(value, ValueIface::minValue(), ValueIface::maxValue());
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700217 ValueIface::value(value);
218}
219
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700220void VirtualSensor::updateVirtualSensor()
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700221{
222 for (auto& param : paramMap)
223 {
224 auto& name = param.first;
225 auto& data = param.second;
226 if (auto var = symbols.get_variable(name))
227 {
228 var->ref() = data->getParamValue();
229 }
230 else
231 {
232 /* Invalid parameter */
233 throw std::invalid_argument("ParamName not found in symbols");
234 }
235 }
236 double val = expression.value();
Vijay Khemka32a71562020-09-10 15:29:18 -0700237
238 /* Set sensor value to dbus interface */
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700239 setSensorValue(val);
Vijay Khemka32a71562020-09-10 15:29:18 -0700240
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700241 if (DEBUG)
242 std::cout << "Sensor value is " << val << "\n";
Vijay Khemka32a71562020-09-10 15:29:18 -0700243
Matt Spinler8f5e6112021-01-15 10:44:32 -0600244 /* Check sensor thresholds and log required message */
Matt Spinlerb306b032021-02-01 10:05:46 -0600245 checkThresholds(val, perfLossIface);
Patrick Williamsfdb826d2021-01-20 14:37:53 -0600246 checkThresholds(val, warningIface);
247 checkThresholds(val, criticalIface);
248 checkThresholds(val, softShutdownIface);
249 checkThresholds(val, hardShutdownIface);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700250}
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700251
Rashmica Gupta3e999192021-06-09 16:17:04 +1000252void VirtualSensor::createThresholds(const Json& threshold,
253 const std::string& objPath)
254{
255 if (threshold.empty())
256 {
257 return;
258 }
259 // Only create the threshold interfaces if
260 // at least one of their values is present.
261 if (threshold.contains("CriticalHigh") || threshold.contains("CriticalLow"))
262 {
263 criticalIface =
264 std::make_unique<Threshold<CriticalObject>>(bus, objPath.c_str());
265
266 criticalIface->criticalHigh(threshold.value(
267 "CriticalHigh", std::numeric_limits<double>::quiet_NaN()));
268 criticalIface->criticalLow(threshold.value(
269 "CriticalLow", std::numeric_limits<double>::quiet_NaN()));
270 }
271
272 if (threshold.contains("WarningHigh") || threshold.contains("WarningLow"))
273 {
274 warningIface =
275 std::make_unique<Threshold<WarningObject>>(bus, objPath.c_str());
276
277 warningIface->warningHigh(threshold.value(
278 "WarningHigh", std::numeric_limits<double>::quiet_NaN()));
279 warningIface->warningLow(threshold.value(
280 "WarningLow", std::numeric_limits<double>::quiet_NaN()));
281 }
282
283 if (threshold.contains("HardShutdownHigh") ||
284 threshold.contains("HardShutdownLow"))
285 {
286 hardShutdownIface = std::make_unique<Threshold<HardShutdownObject>>(
287 bus, objPath.c_str());
288
289 hardShutdownIface->hardShutdownHigh(threshold.value(
290 "HardShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
291 hardShutdownIface->hardShutdownLow(threshold.value(
292 "HardShutdownLow", std::numeric_limits<double>::quiet_NaN()));
293 }
294
295 if (threshold.contains("SoftShutdownHigh") ||
296 threshold.contains("SoftShutdownLow"))
297 {
298 softShutdownIface = std::make_unique<Threshold<SoftShutdownObject>>(
299 bus, objPath.c_str());
300
301 softShutdownIface->softShutdownHigh(threshold.value(
302 "SoftShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
303 softShutdownIface->softShutdownLow(threshold.value(
304 "SoftShutdownLow", std::numeric_limits<double>::quiet_NaN()));
305 }
306
307 if (threshold.contains("PerformanceLossHigh") ||
308 threshold.contains("PerformanceLossLow"))
309 {
310 perfLossIface = std::make_unique<Threshold<PerformanceLossObject>>(
311 bus, objPath.c_str());
312
313 perfLossIface->performanceLossHigh(threshold.value(
314 "PerformanceLossHigh", std::numeric_limits<double>::quiet_NaN()));
315 perfLossIface->performanceLossLow(threshold.value(
316 "PerformanceLossLow", std::numeric_limits<double>::quiet_NaN()));
317 }
318}
319
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700320/** @brief Parsing Virtual Sensor config JSON file */
321Json VirtualSensors::parseConfigFile(const std::string configFile)
322{
323 std::ifstream jsonFile(configFile);
324 if (!jsonFile.is_open())
325 {
326 log<level::ERR>("config JSON file not found",
Patrick Williams1846d822021-06-23 14:44:07 -0500327 entry("FILENAME=%s", configFile.c_str()));
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700328 throw std::exception{};
329 }
330
331 auto data = Json::parse(jsonFile, nullptr, false);
332 if (data.is_discarded())
333 {
334 log<level::ERR>("config readings JSON parser failure",
Patrick Williams1846d822021-06-23 14:44:07 -0500335 entry("FILENAME=%s", configFile.c_str()));
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700336 throw std::exception{};
337 }
338
339 return data;
340}
341
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700342std::map<std::string, ValueIface::Unit> unitMap = {
343 {"temperature", ValueIface::Unit::DegreesC},
344 {"fan_tach", ValueIface::Unit::RPMS},
345 {"voltage", ValueIface::Unit::Volts},
346 {"altitude", ValueIface::Unit::Meters},
347 {"current", ValueIface::Unit::Amperes},
348 {"power", ValueIface::Unit::Watts},
349 {"energy", ValueIface::Unit::Joules},
Kumar Thangavel2b56ddb2021-01-13 20:16:11 +0530350 {"utilization", ValueIface::Unit::Percent},
Rashmica Gupta4ac7a7f2021-07-08 12:30:50 +1000351 {"airflow", ValueIface::Unit::CFM},
352 {"pressure", ValueIface::Unit::Pascals}};
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700353
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700354void VirtualSensors::createVirtualSensors()
355{
356 static const Json empty{};
357
358 auto data = parseConfigFile(VIRTUAL_SENSOR_CONFIG_FILE);
359 // print values
360 if (DEBUG)
361 std::cout << "Config json data:\n" << data << "\n\n";
362
363 /* Get virtual sensors config data */
364 for (const auto& j : data)
365 {
366 auto desc = j.value("Desc", empty);
367 if (!desc.empty())
368 {
369 std::string sensorType = desc.value("SensorType", "");
370 std::string name = desc.value("Name", "");
Rashmica Gupta665a0a22021-06-30 11:35:28 +1000371 std::replace(name.begin(), name.end(), ' ', '_');
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700372
373 if (!name.empty() && !sensorType.empty())
374 {
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700375 if (unitMap.find(sensorType) == unitMap.end())
376 {
377 log<level::ERR>("Sensor type is not supported",
Patrick Williams1846d822021-06-23 14:44:07 -0500378 entry("TYPE=%s", sensorType.c_str()));
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700379 }
380 else
381 {
Rashmica Gupta67d8b9d2021-06-30 11:41:14 +1000382 if (virtualSensorsMap.find(name) != virtualSensorsMap.end())
383 {
384 log<level::ERR>(
385 "A virtual sensor with this name already exists",
386 entry("TYPE=%s", name.c_str()));
387 continue;
388 }
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700389 std::string objPath(sensorDbusPath);
390 objPath += sensorType + "/" + name;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700391
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700392 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
393 bus, objPath.c_str(), j, name);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700394
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700395 log<level::INFO>("Added a new virtual sensor",
Patrick Williams1846d822021-06-23 14:44:07 -0500396 entry("NAME=%s", name.c_str()));
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700397 virtualSensorPtr->updateVirtualSensor();
398
399 /* Initialize unit value for virtual sensor */
400 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
401
402 virtualSensorsMap.emplace(std::move(name),
403 std::move(virtualSensorPtr));
404 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700405 }
406 else
407 {
408 log<level::ERR>("Sensor type or name not found in config file");
409 }
410 }
411 else
412 {
413 log<level::ERR>(
414 "Descriptor for new virtual sensor not found in config file");
415 }
416 }
417}
418
419} // namespace virtualSensor
420} // namespace phosphor
421
422/**
423 * @brief Main
424 */
425int main()
426{
427
428 // Get a default event loop
429 auto event = sdeventplus::Event::get_default();
430
431 // Get a handle to system dbus
432 auto bus = sdbusplus::bus::new_default();
433
Matt Spinler6c19e7d2021-01-12 16:26:45 -0600434 // Add the ObjectManager interface
435 sdbusplus::server::manager::manager objManager(bus, "/");
436
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700437 // Create an virtual sensors object
438 phosphor::virtualSensor::VirtualSensors virtualSensors(bus);
439
440 // Request service bus name
441 bus.request_name(busName);
442
443 // Attach the bus to sd_event to service user requests
444 bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
445 event.loop();
446
447 return 0;
448}