1 #include "virtualSensor.hpp"
2 
3 #include "config.hpp"
4 
5 #include <fmt/format.h>
6 
7 #include <phosphor-logging/log.hpp>
8 #include <sdeventplus/event.hpp>
9 
10 #include <fstream>
11 #include <iostream>
12 
13 static constexpr bool DEBUG = false;
14 static constexpr auto busName = "xyz.openbmc_project.VirtualSensor";
15 static constexpr auto sensorDbusPath = "/xyz/openbmc_project/sensors/";
16 
17 using namespace phosphor::logging;
18 
19 int 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 
44 namespace phosphor
45 {
46 namespace virtualSensor
47 {
48 
49 void 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 
60 double SensorParam::getParamValue()
61 {
62     switch (paramType)
63     {
64         case constParam:
65             return value;
66             break;
67         case dbusParam:
68             return dbusSensor->getSensorValue();
69             break;
70         default:
71             throw std::invalid_argument("param type not supported");
72     }
73 }
74 
75 void VirtualSensor::initVirtualSensor(const Json& sensorConfig,
76                                       const std::string& objPath)
77 {
78 
79     static const Json empty{};
80 
81     /* Get threshold values if defined in config */
82     auto threshold = sensorConfig.value("Threshold", empty);
83     if (!threshold.empty())
84     {
85         // Only create the threshold interfaces if
86         // at least one of their values is present.
87 
88         if (threshold.contains("CriticalHigh") ||
89             threshold.contains("CriticalLow"))
90         {
91             criticalIface = std::make_unique<Threshold<CriticalObject>>(
92                 bus, objPath.c_str());
93 
94             criticalIface->criticalHigh(threshold.value(
95                 "CriticalHigh", std::numeric_limits<double>::quiet_NaN()));
96             criticalIface->criticalLow(threshold.value(
97                 "CriticalLow", std::numeric_limits<double>::quiet_NaN()));
98         }
99 
100         if (threshold.contains("WarningHigh") ||
101             threshold.contains("WarningLow"))
102         {
103             warningIface = std::make_unique<Threshold<WarningObject>>(
104                 bus, objPath.c_str());
105 
106             warningIface->warningHigh(threshold.value(
107                 "WarningHigh", std::numeric_limits<double>::quiet_NaN()));
108             warningIface->warningLow(threshold.value(
109                 "WarningLow", std::numeric_limits<double>::quiet_NaN()));
110         }
111 
112         if (threshold.contains("HardShutdownHigh") ||
113             threshold.contains("HardShutdownLow"))
114         {
115             hardShutdownIface = std::make_unique<Threshold<HardShutdownObject>>(
116                 bus, objPath.c_str());
117 
118             hardShutdownIface->hardShutdownHigh(threshold.value(
119                 "HardShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
120             hardShutdownIface->hardShutdownLow(threshold.value(
121                 "HardShutdownLow", std::numeric_limits<double>::quiet_NaN()));
122         }
123 
124         if (threshold.contains("SoftShutdownHigh") ||
125             threshold.contains("SoftShutdownLow"))
126         {
127             softShutdownIface = std::make_unique<Threshold<SoftShutdownObject>>(
128                 bus, objPath.c_str());
129 
130             softShutdownIface->softShutdownHigh(threshold.value(
131                 "SoftShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
132             softShutdownIface->softShutdownLow(threshold.value(
133                 "SoftShutdownLow", std::numeric_limits<double>::quiet_NaN()));
134         }
135 
136         if (threshold.contains("PerformanceLossHigh") ||
137             threshold.contains("PerformanceLossLow"))
138         {
139             perfLossIface = std::make_unique<Threshold<PerformanceLossObject>>(
140                 bus, objPath.c_str());
141 
142             perfLossIface->performanceLossHigh(
143                 threshold.value("PerformanceLossHigh",
144                                 std::numeric_limits<double>::quiet_NaN()));
145             perfLossIface->performanceLossLow(
146                 threshold.value("PerformanceLossLow",
147                                 std::numeric_limits<double>::quiet_NaN()));
148         }
149     }
150 
151     /* Get expression string */
152     exprStr = sensorConfig.value("Expression", "");
153 
154     /* Get all the parameter listed in configuration */
155     auto params = sensorConfig.value("Params", empty);
156 
157     /* Check for constant parameter */
158     const auto& consParams = params.value("ConstParam", empty);
159     if (!consParams.empty())
160     {
161         for (auto& j : consParams)
162         {
163             if (j.find("ParamName") != j.end())
164             {
165                 auto paramPtr = std::make_unique<SensorParam>(j["Value"]);
166                 std::string name = j["ParamName"];
167                 symbols.create_variable(name);
168                 paramMap.emplace(std::move(name), std::move(paramPtr));
169             }
170             else
171             {
172                 /* Invalid configuration */
173                 throw std::invalid_argument(
174                     "ParamName not found in configuration");
175             }
176         }
177     }
178 
179     /* Check for dbus parameter */
180     auto dbusParams = params.value("DbusParam", empty);
181     if (!dbusParams.empty())
182     {
183         for (auto& j : dbusParams)
184         {
185             /* Get parameter dbus sensor descriptor */
186             auto desc = j.value("Desc", empty);
187             if ((!desc.empty()) && (j.find("ParamName") != j.end()))
188             {
189                 std::string sensorType = desc.value("SensorType", "");
190                 std::string name = desc.value("Name", "");
191 
192                 if (!sensorType.empty() && !name.empty())
193                 {
194                     std::string objPath(sensorDbusPath);
195                     objPath += sensorType + "/" + name;
196 
197                     auto paramPtr =
198                         std::make_unique<SensorParam>(bus, objPath, this);
199                     std::string name = j["ParamName"];
200                     symbols.create_variable(name);
201                     paramMap.emplace(std::move(name), std::move(paramPtr));
202                 }
203             }
204         }
205     }
206 
207     symbols.add_constants();
208     symbols.add_package(vecopsPackage);
209     expression.register_symbol_table(symbols);
210 
211     /* parser from exprtk */
212     exprtk::parser<double> parser{};
213     if (!parser.compile(exprStr, expression))
214     {
215         log<level::ERR>("Expression compilation failed");
216 
217         for (std::size_t i = 0; i < parser.error_count(); ++i)
218         {
219             auto error = parser.get_error(i);
220 
221             log<level::ERR>(
222                 fmt::format(
223                     "Position: {} Type: {} Message: {}", error.token.position,
224                     exprtk::parser_error::to_str(error.mode), error.diagnostic)
225                     .c_str());
226         }
227         throw std::runtime_error("Expression compilation failed");
228     }
229 
230     /* Print all parameters for debug purpose only */
231     if (DEBUG)
232         printParams(paramMap);
233 }
234 
235 void VirtualSensor::setSensorValue(double value)
236 {
237     ValueIface::value(value);
238 }
239 
240 void VirtualSensor::updateVirtualSensor()
241 {
242     for (auto& param : paramMap)
243     {
244         auto& name = param.first;
245         auto& data = param.second;
246         if (auto var = symbols.get_variable(name))
247         {
248             var->ref() = data->getParamValue();
249         }
250         else
251         {
252             /* Invalid parameter */
253             throw std::invalid_argument("ParamName not found in symbols");
254         }
255     }
256     double val = expression.value();
257 
258     /* Set sensor value to dbus interface */
259     setSensorValue(val);
260 
261     if (DEBUG)
262         std::cout << "Sensor value is " << val << "\n";
263 
264     /* Check sensor thresholds and log required message */
265     checkThresholds(val, perfLossIface);
266     checkThresholds(val, warningIface);
267     checkThresholds(val, criticalIface);
268     checkThresholds(val, softShutdownIface);
269     checkThresholds(val, hardShutdownIface);
270 }
271 
272 /** @brief Parsing Virtual Sensor config JSON file  */
273 Json VirtualSensors::parseConfigFile(const std::string configFile)
274 {
275     std::ifstream jsonFile(configFile);
276     if (!jsonFile.is_open())
277     {
278         log<level::ERR>("config JSON file not found",
279                         entry("FILENAME = %s", configFile.c_str()));
280         throw std::exception{};
281     }
282 
283     auto data = Json::parse(jsonFile, nullptr, false);
284     if (data.is_discarded())
285     {
286         log<level::ERR>("config readings JSON parser failure",
287                         entry("FILENAME = %s", configFile.c_str()));
288         throw std::exception{};
289     }
290 
291     return data;
292 }
293 
294 std::map<std::string, ValueIface::Unit> unitMap = {
295     {"temperature", ValueIface::Unit::DegreesC},
296     {"fan_tach", ValueIface::Unit::RPMS},
297     {"voltage", ValueIface::Unit::Volts},
298     {"altitude", ValueIface::Unit::Meters},
299     {"current", ValueIface::Unit::Amperes},
300     {"power", ValueIface::Unit::Watts},
301     {"energy", ValueIface::Unit::Joules},
302     {"utilization", ValueIface::Unit::Percent},
303     {"airflow", ValueIface::Unit::CFM}};
304 
305 void VirtualSensors::createVirtualSensors()
306 {
307     static const Json empty{};
308 
309     auto data = parseConfigFile(VIRTUAL_SENSOR_CONFIG_FILE);
310     // print values
311     if (DEBUG)
312         std::cout << "Config json data:\n" << data << "\n\n";
313 
314     /* Get virtual sensors  config data */
315     for (const auto& j : data)
316     {
317         auto desc = j.value("Desc", empty);
318         if (!desc.empty())
319         {
320             std::string sensorType = desc.value("SensorType", "");
321             std::string name = desc.value("Name", "");
322 
323             if (!name.empty() && !sensorType.empty())
324             {
325                 if (unitMap.find(sensorType) == unitMap.end())
326                 {
327                     log<level::ERR>("Sensor type is not supported",
328                                     entry("TYPE = %s", sensorType.c_str()));
329                 }
330                 else
331                 {
332                     std::string objPath(sensorDbusPath);
333                     objPath += sensorType + "/" + name;
334 
335                     auto virtualSensorPtr = std::make_unique<VirtualSensor>(
336                         bus, objPath.c_str(), j, name);
337 
338                     log<level::INFO>("Added a new virtual sensor",
339                                      entry("NAME = %s", name.c_str()));
340                     virtualSensorPtr->updateVirtualSensor();
341 
342                     /* Initialize unit value for virtual sensor */
343                     virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
344 
345                     virtualSensorsMap.emplace(std::move(name),
346                                               std::move(virtualSensorPtr));
347                 }
348             }
349             else
350             {
351                 log<level::ERR>("Sensor type or name not found in config file");
352             }
353         }
354         else
355         {
356             log<level::ERR>(
357                 "Descriptor for new virtual sensor not found in config file");
358         }
359     }
360 }
361 
362 } // namespace virtualSensor
363 } // namespace phosphor
364 
365 /**
366  * @brief Main
367  */
368 int main()
369 {
370 
371     // Get a default event loop
372     auto event = sdeventplus::Event::get_default();
373 
374     // Get a handle to system dbus
375     auto bus = sdbusplus::bus::new_default();
376 
377     // Add the ObjectManager interface
378     sdbusplus::server::manager::manager objManager(bus, "/");
379 
380     // Create an virtual sensors object
381     phosphor::virtualSensor::VirtualSensors virtualSensors(bus);
382 
383     // Request service bus name
384     bus.request_name(busName);
385 
386     // Attach the bus to sd_event to service user requests
387     bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
388     event.loop();
389 
390     return 0;
391 }
392