blob: 1e1d5bb7be9600016d63eaf7fb76c8d46c817914 [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);
101 if (!threshold.empty())
102 {
Matt Spinlerf15189e2021-01-15 10:13:28 -0600103 // Only create the threshold interfaces if
Matt Spinlerce675222021-01-14 16:38:09 -0600104 // at least one of their values is present.
Matt Spinlerf15189e2021-01-15 10:13:28 -0600105
106 if (threshold.contains("CriticalHigh") ||
107 threshold.contains("CriticalLow"))
108 {
Patrick Williamsfdb826d2021-01-20 14:37:53 -0600109 criticalIface = std::make_unique<Threshold<CriticalObject>>(
110 bus, objPath.c_str());
Matt Spinlerf15189e2021-01-15 10:13:28 -0600111
112 criticalIface->criticalHigh(threshold.value(
113 "CriticalHigh", std::numeric_limits<double>::quiet_NaN()));
114 criticalIface->criticalLow(threshold.value(
115 "CriticalLow", std::numeric_limits<double>::quiet_NaN()));
116 }
117
118 if (threshold.contains("WarningHigh") ||
119 threshold.contains("WarningLow"))
120 {
Patrick Williamsfdb826d2021-01-20 14:37:53 -0600121 warningIface = std::make_unique<Threshold<WarningObject>>(
122 bus, objPath.c_str());
Matt Spinlerf15189e2021-01-15 10:13:28 -0600123
124 warningIface->warningHigh(threshold.value(
125 "WarningHigh", std::numeric_limits<double>::quiet_NaN()));
126 warningIface->warningLow(threshold.value(
127 "WarningLow", std::numeric_limits<double>::quiet_NaN()));
128 }
129
Matt Spinlerce675222021-01-14 16:38:09 -0600130 if (threshold.contains("HardShutdownHigh") ||
131 threshold.contains("HardShutdownLow"))
132 {
Patrick Williamsfdb826d2021-01-20 14:37:53 -0600133 hardShutdownIface = std::make_unique<Threshold<HardShutdownObject>>(
134 bus, objPath.c_str());
Matt Spinlerce675222021-01-14 16:38:09 -0600135
136 hardShutdownIface->hardShutdownHigh(threshold.value(
137 "HardShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
138 hardShutdownIface->hardShutdownLow(threshold.value(
139 "HardShutdownLow", std::numeric_limits<double>::quiet_NaN()));
140 }
141
142 if (threshold.contains("SoftShutdownHigh") ||
143 threshold.contains("SoftShutdownLow"))
144 {
Patrick Williamsfdb826d2021-01-20 14:37:53 -0600145 softShutdownIface = std::make_unique<Threshold<SoftShutdownObject>>(
146 bus, objPath.c_str());
Matt Spinlerce675222021-01-14 16:38:09 -0600147
148 softShutdownIface->softShutdownHigh(threshold.value(
149 "SoftShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
150 softShutdownIface->softShutdownLow(threshold.value(
151 "SoftShutdownLow", std::numeric_limits<double>::quiet_NaN()));
152 }
Matt Spinlerb306b032021-02-01 10:05:46 -0600153
154 if (threshold.contains("PerformanceLossHigh") ||
155 threshold.contains("PerformanceLossLow"))
156 {
157 perfLossIface = std::make_unique<Threshold<PerformanceLossObject>>(
158 bus, objPath.c_str());
159
160 perfLossIface->performanceLossHigh(
161 threshold.value("PerformanceLossHigh",
162 std::numeric_limits<double>::quiet_NaN()));
163 perfLossIface->performanceLossLow(
164 threshold.value("PerformanceLossLow",
165 std::numeric_limits<double>::quiet_NaN()));
166 }
Vijay Khemkac62a5542020-09-10 14:45:49 -0700167 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700168
Harvey Wuf6443742021-04-09 16:47:36 +0800169 /* Get MaxValue, MinValue setting if defined in config */
170 auto confDesc = sensorConfig.value("Desc", empty);
171 if (auto maxConf = confDesc.find("MaxValue");
172 maxConf != confDesc.end() && maxConf->is_number())
173 {
174 ValueIface::maxValue(maxConf->get<double>());
175 }
176 if (auto minConf = confDesc.find("MinValue");
177 minConf != confDesc.end() && minConf->is_number())
178 {
179 ValueIface::minValue(minConf->get<double>());
180 }
181
Lei YU0fcf0e12021-06-04 11:14:17 +0800182 /* Get optional association */
183 auto assocJson = sensorConfig.value("Associations", empty);
184 if (!assocJson.empty())
185 {
186 auto assocs = getAssociationsFromJson(assocJson);
187 if (!assocs.empty())
188 {
189 associationIface =
190 std::make_unique<AssociationObject>(bus, objPath.c_str());
191 associationIface->associations(assocs);
192 }
193 }
194
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700195 /* Get expression string */
196 exprStr = sensorConfig.value("Expression", "");
197
198 /* Get all the parameter listed in configuration */
199 auto params = sensorConfig.value("Params", empty);
200
201 /* Check for constant parameter */
202 const auto& consParams = params.value("ConstParam", empty);
203 if (!consParams.empty())
204 {
205 for (auto& j : consParams)
206 {
207 if (j.find("ParamName") != j.end())
208 {
209 auto paramPtr = std::make_unique<SensorParam>(j["Value"]);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700210 std::string name = j["ParamName"];
211 symbols.create_variable(name);
212 paramMap.emplace(std::move(name), std::move(paramPtr));
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700213 }
214 else
215 {
216 /* Invalid configuration */
217 throw std::invalid_argument(
218 "ParamName not found in configuration");
219 }
220 }
221 }
222
Vijay Khemka7452a862020-08-11 16:01:23 -0700223 /* Check for dbus parameter */
224 auto dbusParams = params.value("DbusParam", empty);
225 if (!dbusParams.empty())
226 {
227 for (auto& j : dbusParams)
228 {
229 /* Get parameter dbus sensor descriptor */
230 auto desc = j.value("Desc", empty);
231 if ((!desc.empty()) && (j.find("ParamName") != j.end()))
232 {
233 std::string sensorType = desc.value("SensorType", "");
234 std::string name = desc.value("Name", "");
235
236 if (!sensorType.empty() && !name.empty())
237 {
238 std::string objPath(sensorDbusPath);
239 objPath += sensorType + "/" + name;
240
Vijay Khemka51f898e2020-09-09 22:24:18 -0700241 auto paramPtr =
242 std::make_unique<SensorParam>(bus, objPath, this);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700243 std::string name = j["ParamName"];
244 symbols.create_variable(name);
245 paramMap.emplace(std::move(name), std::move(paramPtr));
Vijay Khemka7452a862020-08-11 16:01:23 -0700246 }
247 }
248 }
249 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700250
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700251 symbols.add_constants();
Matt Spinler9f1ef4f2020-11-09 15:59:11 -0600252 symbols.add_package(vecopsPackage);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700253 expression.register_symbol_table(symbols);
254
255 /* parser from exprtk */
256 exprtk::parser<double> parser{};
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600257 if (!parser.compile(exprStr, expression))
258 {
259 log<level::ERR>("Expression compilation failed");
260
261 for (std::size_t i = 0; i < parser.error_count(); ++i)
262 {
263 auto error = parser.get_error(i);
264
265 log<level::ERR>(
266 fmt::format(
267 "Position: {} Type: {} Message: {}", error.token.position,
268 exprtk::parser_error::to_str(error.mode), error.diagnostic)
269 .c_str());
270 }
271 throw std::runtime_error("Expression compilation failed");
272 }
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700273
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700274 /* Print all parameters for debug purpose only */
275 if (DEBUG)
276 printParams(paramMap);
277}
278
279void VirtualSensor::setSensorValue(double value)
280{
Patrick Williams543bf662021-04-29 09:03:53 -0500281 value = std::clamp(value, ValueIface::minValue(), ValueIface::maxValue());
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700282 ValueIface::value(value);
283}
284
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700285void VirtualSensor::updateVirtualSensor()
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700286{
287 for (auto& param : paramMap)
288 {
289 auto& name = param.first;
290 auto& data = param.second;
291 if (auto var = symbols.get_variable(name))
292 {
293 var->ref() = data->getParamValue();
294 }
295 else
296 {
297 /* Invalid parameter */
298 throw std::invalid_argument("ParamName not found in symbols");
299 }
300 }
301 double val = expression.value();
Vijay Khemka32a71562020-09-10 15:29:18 -0700302
303 /* Set sensor value to dbus interface */
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700304 setSensorValue(val);
Vijay Khemka32a71562020-09-10 15:29:18 -0700305
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700306 if (DEBUG)
307 std::cout << "Sensor value is " << val << "\n";
Vijay Khemka32a71562020-09-10 15:29:18 -0700308
Matt Spinler8f5e6112021-01-15 10:44:32 -0600309 /* Check sensor thresholds and log required message */
Matt Spinlerb306b032021-02-01 10:05:46 -0600310 checkThresholds(val, perfLossIface);
Patrick Williamsfdb826d2021-01-20 14:37:53 -0600311 checkThresholds(val, warningIface);
312 checkThresholds(val, criticalIface);
313 checkThresholds(val, softShutdownIface);
314 checkThresholds(val, hardShutdownIface);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700315}
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700316
317/** @brief Parsing Virtual Sensor config JSON file */
318Json VirtualSensors::parseConfigFile(const std::string configFile)
319{
320 std::ifstream jsonFile(configFile);
321 if (!jsonFile.is_open())
322 {
323 log<level::ERR>("config JSON file not found",
324 entry("FILENAME = %s", configFile.c_str()));
325 throw std::exception{};
326 }
327
328 auto data = Json::parse(jsonFile, nullptr, false);
329 if (data.is_discarded())
330 {
331 log<level::ERR>("config readings JSON parser failure",
332 entry("FILENAME = %s", configFile.c_str()));
333 throw std::exception{};
334 }
335
336 return data;
337}
338
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700339std::map<std::string, ValueIface::Unit> unitMap = {
340 {"temperature", ValueIface::Unit::DegreesC},
341 {"fan_tach", ValueIface::Unit::RPMS},
342 {"voltage", ValueIface::Unit::Volts},
343 {"altitude", ValueIface::Unit::Meters},
344 {"current", ValueIface::Unit::Amperes},
345 {"power", ValueIface::Unit::Watts},
346 {"energy", ValueIface::Unit::Joules},
Kumar Thangavel2b56ddb2021-01-13 20:16:11 +0530347 {"utilization", ValueIface::Unit::Percent},
348 {"airflow", ValueIface::Unit::CFM}};
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700349
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700350void VirtualSensors::createVirtualSensors()
351{
352 static const Json empty{};
353
354 auto data = parseConfigFile(VIRTUAL_SENSOR_CONFIG_FILE);
355 // print values
356 if (DEBUG)
357 std::cout << "Config json data:\n" << data << "\n\n";
358
359 /* Get virtual sensors config data */
360 for (const auto& j : data)
361 {
362 auto desc = j.value("Desc", empty);
363 if (!desc.empty())
364 {
365 std::string sensorType = desc.value("SensorType", "");
366 std::string name = desc.value("Name", "");
367
368 if (!name.empty() && !sensorType.empty())
369 {
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700370 if (unitMap.find(sensorType) == unitMap.end())
371 {
372 log<level::ERR>("Sensor type is not supported",
373 entry("TYPE = %s", sensorType.c_str()));
374 }
375 else
376 {
377 std::string objPath(sensorDbusPath);
378 objPath += sensorType + "/" + name;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700379
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700380 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
381 bus, objPath.c_str(), j, name);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700382
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700383 log<level::INFO>("Added a new virtual sensor",
384 entry("NAME = %s", name.c_str()));
385 virtualSensorPtr->updateVirtualSensor();
386
387 /* Initialize unit value for virtual sensor */
388 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
389
390 virtualSensorsMap.emplace(std::move(name),
391 std::move(virtualSensorPtr));
392 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700393 }
394 else
395 {
396 log<level::ERR>("Sensor type or name not found in config file");
397 }
398 }
399 else
400 {
401 log<level::ERR>(
402 "Descriptor for new virtual sensor not found in config file");
403 }
404 }
405}
406
407} // namespace virtualSensor
408} // namespace phosphor
409
410/**
411 * @brief Main
412 */
413int main()
414{
415
416 // Get a default event loop
417 auto event = sdeventplus::Event::get_default();
418
419 // Get a handle to system dbus
420 auto bus = sdbusplus::bus::new_default();
421
Matt Spinler6c19e7d2021-01-12 16:26:45 -0600422 // Add the ObjectManager interface
423 sdbusplus::server::manager::manager objManager(bus, "/");
424
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700425 // Create an virtual sensors object
426 phosphor::virtualSensor::VirtualSensors virtualSensors(bus);
427
428 // Request service bus name
429 bus.request_name(busName);
430
431 // Attach the bus to sd_event to service user requests
432 bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
433 event.loop();
434
435 return 0;
436}