blob: d1fb22e8b99ba6d5e03e2831ac687ee876bbb0c3 [file] [log] [blame]
James Feist8fd8a582018-11-16 11:10:46 -08001#pragma once
2
Ed Tanous6cb732a2021-02-18 15:33:51 -08003#include <SensorPaths.hpp>
Ed Tanous8a57ec02020-10-09 12:46:52 -07004#include <Thresholds.hpp>
5#include <Utils.hpp>
James Feist38fb5982020-05-28 10:09:54 -07006#include <sdbusplus/asio/object_server.hpp>
7
Patrick Venturefd6ba732019-10-31 14:27:39 -07008#include <limits>
9#include <memory>
Patrick Venturefd6ba732019-10-31 14:27:39 -070010#include <string>
11#include <vector>
James Feist8fd8a582018-11-16 11:10:46 -080012
James Feist1169eb42018-10-31 10:08:47 -070013constexpr size_t sensorFailedPollTimeMs = 5000;
James Feista5e58722019-04-22 14:43:11 -070014
Josh Lehan3bcd8232020-10-29 00:22:12 -070015// Enable useful logging with sensor instrumentation
16// This is intentionally not DEBUG, avoid clash with usage in .cpp files
17constexpr bool enableInstrumentation = false;
18
James Feista5e58722019-04-22 14:43:11 -070019constexpr const char* sensorValueInterface = "xyz.openbmc_project.Sensor.Value";
James Feist67601bd2020-06-16 17:14:44 -070020constexpr const char* availableInterfaceName =
21 "xyz.openbmc_project.State.Decorator.Availability";
James Feist961bf092020-07-01 16:38:12 -070022constexpr const char* operationalInterfaceName =
23 "xyz.openbmc_project.State.Decorator.OperationalStatus";
24constexpr const size_t errorThreshold = 5;
25
Josh Lehan3bcd8232020-10-29 00:22:12 -070026struct SensorInstrumentation
27{
28 // These are for instrumentation for debugging
29 int numCollectsGood = 0;
30 int numCollectsMiss = 0;
31 int numStreakGreats = 0;
32 int numStreakMisses = 0;
33 double minCollected = 0.0;
34 double maxCollected = 0.0;
35};
36
James Feist8fd8a582018-11-16 11:10:46 -080037struct Sensor
38{
James Feist930fcde2019-05-28 12:58:43 -070039 Sensor(const std::string& name,
James Feistd8705872019-02-08 13:26:09 -080040 std::vector<thresholds::Threshold>&& thresholdData,
41 const std::string& configurationPath, const std::string& objectType,
James Feist961bf092020-07-01 16:38:12 -070042 const double max, const double min,
James Feiste3338522020-09-15 15:40:30 -070043 std::shared_ptr<sdbusplus::asio::connection>& conn,
James Feist961bf092020-07-01 16:38:12 -070044 PowerState readState = PowerState::always) :
Ed Tanous6cb732a2021-02-18 15:33:51 -080045 name(sensor_paths::escapePathForDbus(name)),
James Feist930fcde2019-05-28 12:58:43 -070046 configurationPath(configurationPath), objectType(objectType),
Brad Bishopfbb44ad2019-11-08 09:42:37 -050047 maxValue(max), minValue(min), thresholds(std::move(thresholdData)),
Josh Lehan883fb3a2020-02-27 14:41:39 -080048 hysteresisTrigger((max - min) * 0.01),
James Feiste3338522020-09-15 15:40:30 -070049 hysteresisPublish((max - min) * 0.0001), dbusConnection(conn),
Josh Lehan3bcd8232020-10-29 00:22:12 -070050 readState(readState), errCount(0),
51 instrumentation(enableInstrumentation
52 ? std::make_unique<SensorInstrumentation>()
53 : nullptr)
James Feist38fb5982020-05-28 10:09:54 -070054 {}
James Feist8fd8a582018-11-16 11:10:46 -080055 virtual ~Sensor() = default;
James Feistce3fca42018-11-21 12:58:24 -080056 virtual void checkThresholds(void) = 0;
James Feistdc6c55f2018-10-31 12:53:20 -070057 std::string name;
James Feistce3fca42018-11-21 12:58:24 -080058 std::string configurationPath;
59 std::string objectType;
60 double maxValue;
61 double minValue;
James Feist8fd8a582018-11-16 11:10:46 -080062 std::vector<thresholds::Threshold> thresholds;
63 std::shared_ptr<sdbusplus::asio::dbus_interface> sensorInterface;
64 std::shared_ptr<sdbusplus::asio::dbus_interface> thresholdInterfaceWarning;
65 std::shared_ptr<sdbusplus::asio::dbus_interface> thresholdInterfaceCritical;
James Feist078f2322019-03-08 11:09:05 -080066 std::shared_ptr<sdbusplus::asio::dbus_interface> association;
James Feist67601bd2020-06-16 17:14:44 -070067 std::shared_ptr<sdbusplus::asio::dbus_interface> availableInterface;
James Feist961bf092020-07-01 16:38:12 -070068 std::shared_ptr<sdbusplus::asio::dbus_interface> operationalInterface;
James Feist8fd8a582018-11-16 11:10:46 -080069 double value = std::numeric_limits<double>::quiet_NaN();
Zhikui Rend3da1282020-09-11 17:02:01 -070070 double rawValue = std::numeric_limits<double>::quiet_NaN();
Richard Marian Thomaiyarc0ca7ee2019-04-24 21:22:52 +053071 bool overriddenState = false;
Richard Marian Thomaiyar87219222018-11-06 20:25:38 +053072 bool internalSet = false;
Josh Lehan883fb3a2020-02-27 14:41:39 -080073 double hysteresisTrigger;
74 double hysteresisPublish;
James Feiste3338522020-09-15 15:40:30 -070075 std::shared_ptr<sdbusplus::asio::connection> dbusConnection;
James Feist961bf092020-07-01 16:38:12 -070076 PowerState readState;
77 size_t errCount;
Josh Lehan3bcd8232020-10-29 00:22:12 -070078 std::unique_ptr<SensorInstrumentation> instrumentation;
79
Josh Lehanffe18342021-03-17 13:29:51 -070080 // This member variable provides a hook that can be used to receive
81 // notification whenever this Sensor's value is externally set via D-Bus.
82 // If interested, assign your own lambda to this variable, during
83 // construction of your Sensor subclass. See ExternalSensor for example.
84 std::function<void()> externalSetHook;
85
Josh Lehan3bcd8232020-10-29 00:22:12 -070086 void updateInstrumentation(double readValue)
87 {
88 // Do nothing if this feature is not enabled
89 if constexpr (!enableInstrumentation)
90 {
91 return;
92 }
93 if (!instrumentation)
94 {
95 return;
96 }
97
98 // Save some typing
99 auto& inst = *instrumentation;
100
101 // Show constants if first reading (even if unsuccessful)
102 if ((inst.numCollectsGood == 0) && (inst.numCollectsMiss == 0))
103 {
104 std::cerr << "Sensor " << name << ": Configuration min=" << minValue
105 << ", max=" << maxValue << ", type=" << objectType
106 << ", path=" << configurationPath << "\n";
107 }
108
109 // Sensors can use "nan" to indicate unavailable reading
110 if (!std::isfinite(readValue))
111 {
112 // Only show this if beginning a new streak
113 if (inst.numStreakMisses == 0)
114 {
115 std::cerr << "Sensor " << name
116 << ": Missing reading, Reading counts good="
117 << inst.numCollectsGood
118 << ", miss=" << inst.numCollectsMiss
119 << ", Prior good streak=" << inst.numStreakGreats
120 << "\n";
121 }
122
123 inst.numStreakGreats = 0;
124 ++(inst.numCollectsMiss);
125 ++(inst.numStreakMisses);
126
127 return;
128 }
129
130 // Only show this if beginning a new streak and not the first time
131 if ((inst.numStreakGreats == 0) && (inst.numCollectsGood != 0))
132 {
133 std::cerr << "Sensor " << name
134 << ": Recovered reading, Reading counts good="
135 << inst.numCollectsGood
136 << ", miss=" << inst.numCollectsMiss
137 << ", Prior miss streak=" << inst.numStreakMisses << "\n";
138 }
139
140 // Initialize min/max if the first successful reading
141 if (inst.numCollectsGood == 0)
142 {
143 std::cerr << "Sensor " << name << ": First reading=" << readValue
144 << "\n";
145
146 inst.minCollected = readValue;
147 inst.maxCollected = readValue;
148 }
149
150 inst.numStreakMisses = 0;
151 ++(inst.numCollectsGood);
152 ++(inst.numStreakGreats);
153
154 // Only provide subsequent output if new min/max established
155 if (readValue < inst.minCollected)
156 {
157 std::cerr << "Sensor " << name << ": Lowest reading=" << readValue
158 << "\n";
159
160 inst.minCollected = readValue;
161 }
162
163 if (readValue > inst.maxCollected)
164 {
165 std::cerr << "Sensor " << name << ": Highest reading=" << readValue
166 << "\n";
167
168 inst.maxCollected = readValue;
169 }
170 }
James Feistce3fca42018-11-21 12:58:24 -0800171
James Feistd8705872019-02-08 13:26:09 -0800172 int setSensorValue(const double& newValue, double& oldValue)
Richard Marian Thomaiyar87219222018-11-06 20:25:38 +0530173 {
Richard Marian Thomaiyaraf6b87c2019-04-03 23:54:28 +0530174 if (!internalSet)
Richard Marian Thomaiyar87219222018-11-06 20:25:38 +0530175 {
Richard Marian Thomaiyar87219222018-11-06 20:25:38 +0530176 oldValue = newValue;
Richard Marian Thomaiyarc0ca7ee2019-04-24 21:22:52 +0530177 overriddenState = true;
178 // check thresholds for external set
179 value = newValue;
180 checkThresholds();
Josh Lehanffe18342021-03-17 13:29:51 -0700181
182 // Trigger the hook, as an external set has just happened
183 if (externalSetHook)
184 {
185 externalSetHook();
186 }
Richard Marian Thomaiyar87219222018-11-06 20:25:38 +0530187 }
Richard Marian Thomaiyarc0ca7ee2019-04-24 21:22:52 +0530188 else if (!overriddenState)
Richard Marian Thomaiyaraf6b87c2019-04-03 23:54:28 +0530189 {
190 oldValue = newValue;
191 }
Richard Marian Thomaiyar87219222018-11-06 20:25:38 +0530192 return 1;
193 }
James Feistce3fca42018-11-21 12:58:24 -0800194
195 void
Cheng C Yang6b1247a2020-03-09 23:48:39 +0800196 setInitialProperties(std::shared_ptr<sdbusplus::asio::connection>& conn,
Zev Weiss6b6891c2021-04-22 02:46:21 -0500197 const std::string& unit,
Ed Tanous8a57ec02020-10-09 12:46:52 -0700198 const std::string& label = std::string(),
Cheng C Yang6b1247a2020-03-09 23:48:39 +0800199 size_t thresholdSize = 0)
James Feistce3fca42018-11-21 12:58:24 -0800200 {
James Feist961bf092020-07-01 16:38:12 -0700201 if (readState == PowerState::on || readState == PowerState::biosPost)
202 {
203 setupPowerMatch(conn);
204 }
205
James Feist82bac4c2019-03-11 11:16:53 -0700206 createAssociation(association, configurationPath);
AppaRao Pulic82213c2020-02-27 01:24:58 +0530207
Zev Weiss6b6891c2021-04-22 02:46:21 -0500208 sensorInterface->register_property("Unit", unit);
James Feistce3fca42018-11-21 12:58:24 -0800209 sensorInterface->register_property("MaxValue", maxValue);
210 sensorInterface->register_property("MinValue", minValue);
211 sensorInterface->register_property(
James Feistd8705872019-02-08 13:26:09 -0800212 "Value", value, [&](const double& newValue, double& oldValue) {
James Feistce3fca42018-11-21 12:58:24 -0800213 return setSensorValue(newValue, oldValue);
214 });
James Feistd8705872019-02-08 13:26:09 -0800215 for (auto& threshold : thresholds)
James Feistce3fca42018-11-21 12:58:24 -0800216 {
217 std::shared_ptr<sdbusplus::asio::dbus_interface> iface;
218 std::string level;
219 std::string alarm;
220 if (threshold.level == thresholds::Level::CRITICAL)
221 {
222 iface = thresholdInterfaceCritical;
223 if (threshold.direction == thresholds::Direction::HIGH)
224 {
225 level = "CriticalHigh";
226 alarm = "CriticalAlarmHigh";
227 }
228 else
229 {
230 level = "CriticalLow";
231 alarm = "CriticalAlarmLow";
232 }
233 }
234 else if (threshold.level == thresholds::Level::WARNING)
235 {
236 iface = thresholdInterfaceWarning;
237 if (threshold.direction == thresholds::Direction::HIGH)
238 {
239 level = "WarningHigh";
240 alarm = "WarningAlarmHigh";
241 }
242 else
243 {
244 level = "WarningLow";
245 alarm = "WarningAlarmLow";
246 }
247 }
248 else
249 {
250 std::cerr << "Unknown threshold level" << threshold.level
251 << "\n";
252 continue;
253 }
254 if (!iface)
255 {
256 std::cout << "trying to set uninitialized interface\n";
257 continue;
258 }
Cheng C Yang6b1247a2020-03-09 23:48:39 +0800259
260 size_t thresSize =
261 label.empty() ? thresholds.size() : thresholdSize;
James Feistce3fca42018-11-21 12:58:24 -0800262 iface->register_property(
263 level, threshold.value,
Cheng C Yang6b1247a2020-03-09 23:48:39 +0800264 [&, label, thresSize](const double& request, double& oldValue) {
James Feistce3fca42018-11-21 12:58:24 -0800265 oldValue = request; // todo, just let the config do this?
266 threshold.value = request;
267 thresholds::persistThreshold(configurationPath, objectType,
Cheng C Yang6b1247a2020-03-09 23:48:39 +0800268 threshold, conn, thresSize,
269 label);
Josh Lehan883fb3a2020-02-27 14:41:39 -0800270 // Invalidate previously remembered value,
271 // so new thresholds will be checked during next update,
272 // even if sensor reading remains unchanged.
273 value = std::numeric_limits<double>::quiet_NaN();
274
275 // Although tempting, don't call checkThresholds() from here
276 // directly. Let the regular sensor monitor call the same
277 // using updateValue(), which can check conditions like
278 // poweron, etc., before raising any event.
James Feistce3fca42018-11-21 12:58:24 -0800279 return 1;
280 });
281 iface->register_property(alarm, false);
282 }
283 if (!sensorInterface->initialize())
284 {
285 std::cerr << "error initializing value interface\n";
286 }
287 if (thresholdInterfaceWarning &&
Yong Lif902c052020-05-07 17:13:53 +0800288 !thresholdInterfaceWarning->initialize(true))
James Feistce3fca42018-11-21 12:58:24 -0800289 {
290 std::cerr << "error initializing warning threshold interface\n";
291 }
292
293 if (thresholdInterfaceCritical &&
Yong Lif902c052020-05-07 17:13:53 +0800294 !thresholdInterfaceCritical->initialize(true))
James Feistce3fca42018-11-21 12:58:24 -0800295 {
296 std::cerr << "error initializing critical threshold interface\n";
297 }
James Feist67601bd2020-06-16 17:14:44 -0700298
299 if (!availableInterface)
300 {
301 availableInterface =
302 std::make_shared<sdbusplus::asio::dbus_interface>(
303 conn, sensorInterface->get_object_path(),
304 availableInterfaceName);
305 availableInterface->register_property(
306 "Available", true, [this](const bool propIn, bool& old) {
307 if (propIn == old)
308 {
309 return 1;
310 }
James Feist961bf092020-07-01 16:38:12 -0700311 old = propIn;
James Feist67601bd2020-06-16 17:14:44 -0700312 if (!propIn)
313 {
314 updateValue(std::numeric_limits<double>::quiet_NaN());
315 }
James Feist67601bd2020-06-16 17:14:44 -0700316 return 1;
317 });
318 availableInterface->initialize();
319 }
James Feist961bf092020-07-01 16:38:12 -0700320 if (!operationalInterface)
321 {
322 operationalInterface =
323 std::make_shared<sdbusplus::asio::dbus_interface>(
324 conn, sensorInterface->get_object_path(),
325 operationalInterfaceName);
326 operationalInterface->register_property("Functional", true);
327 operationalInterface->initialize();
328 }
329 }
330
331 bool readingStateGood()
332 {
333 if (readState == PowerState::on && !isPowerOn())
334 {
335 return false;
336 }
337 if (readState == PowerState::biosPost &&
338 (!hasBiosPost() || !isPowerOn()))
339 {
340 return false;
341 }
342
343 return true;
344 }
345
346 void markFunctional(bool isFunctional)
347 {
348 if (operationalInterface)
349 {
350 operationalInterface->set_property("Functional", isFunctional);
351 }
352 if (isFunctional)
353 {
354 errCount = 0;
355 }
356 else
357 {
358 updateValue(std::numeric_limits<double>::quiet_NaN());
359 }
360 }
361
362 void markAvailable(bool isAvailable)
363 {
364 if (availableInterface)
365 {
366 availableInterface->set_property("Available", isAvailable);
367 errCount = 0;
368 }
369 }
370
371 void incrementError()
372 {
373 if (!readingStateGood())
374 {
375 markAvailable(false);
376 return;
377 }
378
379 if (errCount >= errorThreshold)
380 {
381 return;
382 }
383
384 errCount++;
385 if (errCount == errorThreshold)
386 {
387 std::cerr << "Sensor " << name << " reading error!\n";
388 markFunctional(false);
389 }
James Feistce3fca42018-11-21 12:58:24 -0800390 }
391
James Feistd8705872019-02-08 13:26:09 -0800392 void updateValue(const double& newValue)
James Feistce3fca42018-11-21 12:58:24 -0800393 {
Richard Marian Thomaiyarc0ca7ee2019-04-24 21:22:52 +0530394 // Ignore if overriding is enabled
James Feist961bf092020-07-01 16:38:12 -0700395 if (overriddenState)
Richard Marian Thomaiyarc0ca7ee2019-04-24 21:22:52 +0530396 {
Josh Lehan883fb3a2020-02-27 14:41:39 -0800397 return;
Richard Marian Thomaiyarc0ca7ee2019-04-24 21:22:52 +0530398 }
Josh Lehan883fb3a2020-02-27 14:41:39 -0800399
James Feist961bf092020-07-01 16:38:12 -0700400 if (!readingStateGood())
401 {
402 markAvailable(false);
Adrian Ambrożewicz623723b2020-07-29 12:53:54 +0200403 updateValueProperty(std::numeric_limits<double>::quiet_NaN());
James Feist961bf092020-07-01 16:38:12 -0700404 return;
405 }
406
Adrian Ambrożewicz623723b2020-07-29 12:53:54 +0200407 updateValueProperty(newValue);
Josh Lehan3bcd8232020-10-29 00:22:12 -0700408 updateInstrumentation(newValue);
Josh Lehan883fb3a2020-02-27 14:41:39 -0800409
410 // Always check thresholds after changing the value,
411 // as the test against hysteresisTrigger now takes place in
412 // the thresholds::checkThresholds() method,
413 // which is called by checkThresholds() below,
414 // in all current implementations of sensors that have thresholds.
415 checkThresholds();
James Feist961bf092020-07-01 16:38:12 -0700416 if (!std::isnan(newValue))
417 {
418 markFunctional(true);
419 markAvailable(true);
420 }
James Feistce3fca42018-11-21 12:58:24 -0800421 }
Zbigniew Kurzynski4f45e422020-06-09 12:42:15 +0200422
423 void updateProperty(
424 std::shared_ptr<sdbusplus::asio::dbus_interface>& interface,
425 double& oldValue, const double& newValue, const char* dbusPropertyName)
426 {
427 if (requiresUpdate(oldValue, newValue))
428 {
429 oldValue = newValue;
Jae Hyun Yoo1a540b82020-07-30 23:33:18 -0700430 if (interface &&
431 !(interface->set_property(dbusPropertyName, newValue)))
Zbigniew Kurzynski4f45e422020-06-09 12:42:15 +0200432 {
433 std::cerr << "error setting property " << dbusPropertyName
434 << " to " << newValue << "\n";
435 }
436 }
437 }
438
439 bool requiresUpdate(const double& lVal, const double& rVal)
440 {
441 if (std::isnan(lVal) || std::isnan(rVal))
442 {
443 return true;
444 }
445 double diff = std::abs(lVal - rVal);
446 if (diff > hysteresisPublish)
447 {
448 return true;
449 }
450 return false;
451 }
Adrian Ambrożewicz623723b2020-07-29 12:53:54 +0200452
453 private:
454 void updateValueProperty(const double& newValue)
455 {
456 // Indicate that it is internal set call, not an external overwrite
457 internalSet = true;
458 updateProperty(sensorInterface, value, newValue, "Value");
459 internalSet = false;
460 }
Richard Marian Thomaiyarc0ca7ee2019-04-24 21:22:52 +0530461};