GCC Code Coverage Report


Directory: ./
File: sensors/Sensors.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 334 457 73.1%
Functions: 37 40 92.5%
Branches: 449 1020 44.0%

Line Branch Exec Source
1 /*
2 * This file is part of the Falcon Player (FPP) and is Copyright (C)
3 * 2013-2022 by the Falcon Player Developers.
4 *
5 * The Falcon Player (FPP) is free software, and is covered under
6 * multiple Open Source licenses. Please see the included 'LICENSES'
7 * file for descriptions of what files are covered by each license.
8 *
9 * This source file is covered under the LGPL v2.1 as described in the
10 * included LICENSE.LGPL file.
11 */
12
13 #include "fpp-pch.h"
14
15 #include "fpp-json.h"
16
17 #include <sys/ioctl.h>
18 #include <sys/stat.h>
19 #include <fcntl.h>
20 #include <fstream>
21 #include <iomanip>
22 #include <set>
23 #include <thread>
24 #include <unistd.h>
25
26 #include "../Warnings.h"
27 #include "../common.h"
28 #include "../settings.h"
29
30 #include "ADS7828.h"
31 #include "IIOSensorSource.h"
32 #include "MultiSensorSource.h"
33 #include "MuxSensorSource.h"
34 #include "util/I2CUtils.h"
35
36 #include "Sensors.h"
37
38 #if defined(PLATFORM_BBB) || defined(PLATFORM_BB64)
39 #define I2C_DEV 2
40 #else
41 #define I2C_DEV 1
42 #endif
43
44 2 static std::string exec(const std::string& cmd) {
45 std::array<char, 128> buffer;
46 2 std::string result;
47
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 struct PipeCloser { void operator()(FILE* f) const { if (f) pclose(f); } };
48
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 std::unique_ptr<FILE, PipeCloser> pipe(popen(cmd.c_str(), "r"));
49
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!pipe) {
50 ✗ throw std::runtime_error("popen() failed!");
51 }
52
3/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
12 while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
53
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 result += buffer.data();
54 }
55 2 return result;
56 2 }
57
58 15 SensorSource::SensorSource(Json::Value& s) {
59
2/4
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
15 if (s.isMember("id")) {
60
2/4
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
15 _id = s["id"].asString();
61 ✗ } else if (s.isMember("type")) {
62 ✗ _id = s["type"].asString();
63 }
64 15 }
65
66 class Sensor {
67 public:
68 364 explicit Sensor(Json::Value& s) :
69 364 label(s["label"].asString()) {
70
2/4
✓ Branch 0 taken 363 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 363 times.
363 if (s.isMember("prefix")) {
71 ✗ prefix = s["prefix"].asString();
72 }
73
3/4
✓ Branch 0 taken 363 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 62 times.
✓ Branch 3 taken 301 times.
363 if (s.isMember("postfix")) {
74
2/4
✓ Branch 0 taken 62 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 62 times.
✗ Branch 3 not taken.
62 postfix = s["postfix"].asString();
75 }
76
2/4
✓ Branch 0 taken 363 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 363 times.
363 if (s.isMember("precision")) {
77 ✗ precision = s["precision"].asInt();
78 }
79
3/4
✓ Branch 0 taken 363 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 361 times.
✓ Branch 3 taken 2 times.
363 if (s.isMember("valueType")) {
80
2/4
✓ Branch 0 taken 361 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 361 times.
✗ Branch 3 not taken.
361 valueType = s["valueType"].asString();
81 }
82 363 }
83 726 virtual ~Sensor() {}
84
85 Sensor(Sensor const&) = delete;
86 void operator=(Sensor const& x) = delete;
87
88 virtual double getValue() = 0;
89 4189 virtual SensorSource* getSensorSource() {
90 4189 return nullptr;
91 }
92
93 6644 void report(Json::Value& s) {
94
1/2
✓ Branch 0 taken 6644 times.
✗ Branch 1 not taken.
6644 std::unique_lock<std::mutex> lock(sensorLock);
95
1/2
✓ Branch 0 taken 6644 times.
✗ Branch 1 not taken.
6644 Json::Value v;
96
2/4
✓ Branch 0 taken 6644 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6644 times.
✗ Branch 3 not taken.
6644 v["label"] = label;
97
2/4
✓ Branch 0 taken 6644 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6644 times.
✗ Branch 3 not taken.
6644 v["prefix"] = prefix;
98
2/4
✓ Branch 0 taken 6644 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6644 times.
✗ Branch 3 not taken.
6644 v["postfix"] = postfix;
99
1/2
✓ Branch 0 taken 6644 times.
✗ Branch 1 not taken.
6644 double d = getValue();
100
2/4
✓ Branch 0 taken 6644 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6644 times.
✗ Branch 3 not taken.
6644 v["value"] = d;
101
2/4
✓ Branch 0 taken 6644 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6644 times.
✗ Branch 3 not taken.
6644 v["valueType"] = valueType;
102
103
1/2
✓ Branch 0 taken 6644 times.
✗ Branch 1 not taken.
6644 std::ostringstream stringStream;
104
1/2
✓ Branch 0 taken 6644 times.
✗ Branch 1 not taken.
6644 stringStream << prefix
105
1/2
✓ Branch 0 taken 6644 times.
✗ Branch 1 not taken.
6644 << std::fixed << std::setprecision(precision)
106
1/2
✓ Branch 0 taken 6644 times.
✗ Branch 1 not taken.
6644 << d
107
1/2
✓ Branch 0 taken 6644 times.
✗ Branch 1 not taken.
6644 << postfix;
108
3/6
✓ Branch 0 taken 6644 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6644 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6644 times.
✗ Branch 5 not taken.
6644 v["formatted"] = stringStream.str();
109
1/2
✓ Branch 0 taken 6644 times.
✗ Branch 1 not taken.
6644 s.append(v);
110 6644 }
111 std::string label;
112 std::string prefix;
113 std::string postfix;
114 std::string valueType;
115 int precision = 1;
116 std::mutex sensorLock;
117 };
118
119 // I2C sensor addresses are hex with an optional 0x prefix (e.g. "0x48").
120 // Manual checks (no new headers): anything else cannot be an address and must
121 // never reach the shell command built in HasI2CDevice() below.
122 4 static bool isValidI2CAddress(const std::string& a) {
123 4 size_t i = 0;
124
4/10
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 4 times.
✗ Branch 9 not taken.
4 if (a.size() > 2 && a[0] == '0' && (a[1] == 'x' || a[1] == 'X')) {
125 4 i = 2;
126 }
127
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (i == a.size()) {
128 ✗ return false;
129 }
130
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 2 times.
12 for (; i < a.size(); i++) {
131 10 char c = a[i];
132
5/12
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 8 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 2 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
10 if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
133 2 return false;
134 }
135 }
136 2 return true;
137 }
138
139 class I2CSensor : public Sensor {
140 public:
141 2 explicit I2CSensor(Json::Value& s) :
142 Sensor(s),
143
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 address(s["address"].asString()),
144
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 path(s["path"].asString()),
145
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 driver(s["driver"].asString()),
146
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 file(-1) {
147
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (s.isMember("multiplier")) {
148 ✗ multiplier = s["multiplier"].asDouble();
149 }
150 2 int bus = I2C_DEV;
151
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 if (!CheckForFile(path)) {
152 // path doesn't exist, need to enable
153
2/8
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 2 times.
2 if (bus == 2 && !HasI2CDevice(bus)) {
154 // wasn't found on the default bus of the BBB, we can
155 // try the other i2c bus
156 ✗ bus = 1;
157 ✗ size_t start_pos = path.find("/i2c-2");
158 ✗ if (start_pos != std::string::npos) {
159 ✗ path.replace(start_pos, 6, "/i2c-1");
160 }
161 }
162
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (HasI2CDevice(bus)) {
163 ✗ std::ofstream out("/sys/bus/i2c/devices/i2c-" + std::to_string(bus) + "/new_device");
164 ✗ out << driver << " " << address;
165 ✗ out.close();
166 ✗ }
167 }
168 2 int count = 0;
169
6/8
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 10 times.
✓ Branch 7 taken 2 times.
12 while (count < 5 && !CheckForFile(path)) {
170
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 std::this_thread::sleep_for(std::chrono::milliseconds(100));
171 10 count++;
172 }
173 // after 0.5 seconds, still doesn't exist
174
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (CheckForFile(path)) {
175 ✗ file = open(path.c_str(), O_RDONLY);
176
3/10
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 2 times.
2 } else if (HasI2CDevice(bus) && driver == "lm75") {
177 // Kernel 4.19 on Pi seems to not like the lm75 chips, we'll need to read the values directly
178 // switch to rawIO
179 ✗ int add = strtol(address.c_str(), nullptr, 16);
180 ✗ i2cUtils = new I2CUtils(bus, add);
181 }
182 2 }
183 14 bool CheckForFile(std::string& path) {
184
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 if (path.find("hwmon0") == std::string::npos) {
185
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 return FileExists(path);
186 }
187 // if it's a hwmon path, it MAY be on a different hwmon depending on the order that hwmon
188 // loads the drivers
189 ✗ std::string base = path.substr(0, path.find("hwmon0"));
190 ✗ std::string next = path.substr(path.find("hwmon0") + 6);
191 ✗ if (!DirectoryExists(base.c_str())) {
192 ✗ return false;
193 }
194 ✗ for (int x = 0; x < 10; x++) {
195 ✗ std::string npath = base + "hwmon" + std::to_string(x) + next;
196 ✗ if (FileExists(npath)) {
197 ✗ path = npath;
198 ✗ return true;
199 }
200 ✗ }
201 ✗ return false;
202 ✗ }
203
204 8 virtual ~I2CSensor() {
205
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
4 if (file != -1) {
206 ✗ close(file);
207 }
208
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
4 if (i2cUtils) {
209 ✗ delete i2cUtils;
210 }
211 8 }
212 4 bool HasI2CDevice(int i2cBus) {
213 // address comes from operator-editable sensors.json and is
214 // interpolated unquoted into a shell below. I2C addresses are hex
215 // (e.g. "0x48"), so anything else is rejected here; a rejection reads
216 // as "no device", the same outcome as a genuinely absent chip, and
217 // the bus-fallback/rawIO branches keep working off that signal.
218
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if (!isValidI2CAddress(address)) {
219 2 return false;
220 }
221 char buf[256];
222 2 snprintf(buf, sizeof(buf), "i2cdetect -y -r %d %s %s", i2cBus, address.c_str(), address.c_str());
223
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 std::string result = exec(buf);
224 2 return result.find("--") == std::string::npos;
225 2 }
226
227 3 virtual double getValue() override {
228
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (i2cUtils) {
229 ✗ int ret = i2cUtils->readWordData(0);
230 ✗ if (ret >= 0) {
231 ✗ int t = (ret >> 8) & 0xFF;
232 ✗ t |= (ret << 8) & 0xFF00;
233 ✗ if (t & 0x8000) {
234 // negative temperature
235 ✗ t |= 0xFFFF0000;
236 }
237 ✗ t = t >> 5;
238 ✗ double d = t;
239 ✗ d *= 0.125;
240 ✗ return d;
241 }
242
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 } else if (file != -1) {
243 ✗ lseek(file, 0, SEEK_SET);
244 char buffer[20];
245 // Leave room for the terminator; a failed read must not index at
246 // [-1] and returns the same 0.0 as a missing file below.
247 ✗ int i = read(file, buffer, sizeof(buffer) - 1);
248 ✗ if (i <= 0) {
249 ✗ return 0.0;
250 }
251 ✗ buffer[i] = 0;
252 ✗ double d = atof(buffer);
253 ✗ d *= multiplier;
254 ✗ return d;
255 }
256 3 return 0.0;
257 }
258
259 std::string address;
260 std::string path;
261 std::string driver;
262 double multiplier = 1.0;
263
264 I2CUtils* i2cUtils = nullptr;
265 volatile int file;
266 };
267
268 class SensorSourceSensor : public Sensor {
269 public:
270 22 explicit SensorSourceSensor(Json::Value& s) :
271 22 Sensor(s) {
272
2/4
✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 21 times.
21 if (s.isMember("scale")) {
273 ✗ scale = s["scale"].asDouble();
274 }
275
2/4
✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 21 times.
21 if (s.isMember("offset")) {
276 ✗ offset = s["offset"].asDouble();
277 }
278
2/4
✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 21 times.
21 if (s.isMember("min")) {
279 ✗ min = s["min"].asDouble();
280 ✗ isMinMax = true;
281 }
282
2/4
✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 21 times.
21 if (s.isMember("max")) {
283 ✗ max = s["max"].asDouble();
284 ✗ isMinMax = true;
285 }
286
2/4
✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 21 times.
✗ Branch 3 not taken.
21 if (s.isMember("channel")) {
287
2/4
✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 21 times.
✗ Branch 3 not taken.
21 channel = s["channel"].asInt();
288 }
289
3/6
✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 21 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 21 times.
✗ Branch 5 not taken.
21 source = Sensors::INSTANCE.getSensorSource(s["sensor"].asString());
290
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 3 times.
21 if (source) {
291
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 source->enable(channel);
292 }
293 21 }
294 84 virtual ~SensorSourceSensor() {
295 84 }
296 2455 virtual SensorSource* getSensorSource() override {
297 2455 return source;
298 }
299 2455 virtual double getValue() override {
300
2/2
✓ Branch 0 taken 2449 times.
✓ Branch 1 taken 6 times.
2455 if (source) {
301 2449 double d = source->getValue(channel);
302 2449 d -= offset;
303 2449 d *= scale;
304
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2449 times.
2449 if (isMinMax) {
305 ✗ d *= (max - min + 1);
306 ✗ d += min;
307 }
308 2449 return d;
309 }
310 6 return 0.0f;
311 }
312 SensorSource* source = nullptr;
313 double scale = 1.0;
314 double offset = 0.0;
315 double min = 0.0;
316 double max = 0.0;
317 bool isMinMax = false;
318 int channel = 0;
319 };
320
321 class AINSensor : public Sensor {
322 public:
323 ✗ explicit AINSensor(Json::Value& s) :
324 Sensor(s),
325 ✗ errcount(0),
326 ✗ address(s["address"].asString()),
327 ✗ path(s["path"].asString()),
328 ✗ file(-1) {
329 ✗ if (s.isMember("max")) {
330 ✗ max = s["max"].asDouble();
331 }
332 ✗ if (s.isMember("min")) {
333 ✗ min = s["min"].asDouble();
334 }
335 ✗ if (s.isMember("vScale")) {
336 ✗ vScale = s["vScale"].asFloat();
337 } else {
338 #ifdef PLATFORM_BB64
339 ✗ std::string vs = GetFileContents("/sys/bus/iio/devices/iio:device0/in_voltage_scale");
340 ✗ vScale = std::atof(vs.c_str());
341 // original reference is 1.8V which for 12bit would be 0.439453125.
342 // If the cape doesn't specify a reference scale, we'll adjust and assume
343 // the params are based on 1.8 ref, but we're reading to 3.3V
344 ✗ vScale = vScale / 0.439453125;
345 #else
346 ✗ vScale = 1.0f;
347 #endif
348 ✗ }
349 ✗ }
350 ✗ virtual ~AINSensor() {
351 ✗ close(file);
352 ✗ }
353
354 ✗ virtual double getValue() override {
355 ✗ if (file == -1 && errcount < 10) {
356 char path[256];
357 ✗ snprintf(path, sizeof(path), "/sys/bus/iio/devices/iio:device0/in_voltage%s_raw", address.c_str());
358 // need to use low level calls for reading from sysfs
359 // to avoid any buffering that ifstream does
360 ✗ if (FileExists(path)) {
361 ✗ file = open(path, O_RDONLY);
362 ✗ errcount = errcount + 1;
363 }
364 }
365
366 ✗ if (file != -1) {
367 ✗ lseek(file, 0, SEEK_SET);
368 char buffer[20];
369 // Leave room for the terminator: a full-size read leaves no room
370 // for buffer[i] = 0, and a failed read must not index at [-1].
371 ✗ int i = read(file, buffer, sizeof(buffer) - 1);
372 ✗ if (i <= 0) {
373 ✗ return 0.0;
374 }
375 ✗ buffer[i] = 0;
376 ✗ double d = atof(buffer);
377 ✗ d *= vScale;
378
379 ✗ d /= 4096; // 12 bit a2d
380 ✗ d *= (max - min + 1);
381 ✗ d += min;
382 ✗ return d;
383 }
384
385 ✗ return 0.0;
386 }
387
388 std::string address;
389 std::string path;
390 std::string driver;
391 double min = 0.0;
392 double max = 100.0;
393 double vScale = 1.0;
394
395 volatile int file;
396 volatile int errcount;
397 };
398
399 class ThermalSensor : public Sensor {
400 public:
401 278 explicit ThermalSensor(Json::Value& s) :
402 Sensor(s),
403
2/4
✓ Branch 0 taken 278 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 278 times.
✗ Branch 3 not taken.
278 path(s["path"].asString()) {
404
1/2
✓ Branch 0 taken 278 times.
✗ Branch 1 not taken.
278 file = open(path.c_str(), O_RDONLY);
405 278 }
406 1112 virtual ~ThermalSensor() {
407 556 close(file);
408 1112 }
409
410 3314 virtual double getValue() override {
411
1/2
✓ Branch 0 taken 3314 times.
✗ Branch 1 not taken.
3314 if (file != -1) {
412 3314 lseek(file, 0, SEEK_SET);
413 char buffer[20];
414
1/2
✓ Branch 0 taken 3314 times.
✗ Branch 1 not taken.
3314 int i = read(file, buffer, sizeof(buffer) - 1);
415 3314 double d = 0;
416
1/2
✓ Branch 0 taken 3314 times.
✗ Branch 1 not taken.
3314 if (i > 0) {
417 3314 buffer[i] = 0;
418 3314 d = std::atof(buffer);
419 3314 d /= 1000;
420 }
421 3314 return d;
422 }
423
424 ✗ return 0.0;
425 }
426
427 volatile int file;
428 std::string path;
429 };
430
431 class FanSensor : public Sensor {
432 public:
433 62 explicit FanSensor(Json::Value& s) :
434 Sensor(s),
435
2/4
✓ Branch 0 taken 62 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 62 times.
✗ Branch 3 not taken.
62 path(s["path"].asString()) {
436
1/2
✓ Branch 0 taken 62 times.
✗ Branch 1 not taken.
62 file = open(path.c_str(), O_RDONLY);
437 62 }
438 248 virtual ~FanSensor() {
439
1/2
✓ Branch 0 taken 62 times.
✗ Branch 1 not taken.
124 if (file != -1) close(file);
440 248 }
441
442 872 virtual double getValue() override {
443
1/2
✓ Branch 0 taken 872 times.
✗ Branch 1 not taken.
872 if (file != -1) {
444 872 lseek(file, 0, SEEK_SET);
445 char buffer[20];
446
1/2
✓ Branch 0 taken 872 times.
✗ Branch 1 not taken.
872 int i = read(file, buffer, sizeof(buffer) - 1);
447 872 double d = 0;
448
1/2
✓ Branch 0 taken 872 times.
✗ Branch 1 not taken.
872 if (i > 0) {
449 872 buffer[i] = 0;
450 872 d = std::atof(buffer);
451 }
452 872 return d;
453 }
454
455 ✗ return 0.0;
456 }
457
458 volatile int file;
459 std::string path;
460 };
461
462 Sensors Sensors::INSTANCE;
463
464 2346 Sensors::Sensors() {
465 2346 }
466 // sensorSources is walked on the OutputMonitor and string tester threads and
467 // appended to by getSensorSource() during an output reload (the default
468 // "iio" source, created on first use). Sources are never removed while
469 // fppd runs. Recursive: a source's constructor looks up the one it reads.
470 static std::recursive_mutex sensorSourcesLock;
471
472 458 Sensors::~Sensors() {
473
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 458 times.
458 for (auto x : sensorSources) {
474 ✗ delete x;
475 }
476 458 sensorSources.clear();
477 458 }
478 1885 void Sensors::Init(std::map<int, std::function<bool(int)>>& callbacks) {
479
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1885 times.
1892 for (auto it = sensorSources.rbegin(); it != sensorSources.rend(); ++it) {
480
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 (*it)->Init(callbacks);
481 }
482 1885 }
483
484 7 void Sensors::addSensorSources(Json::Value& config) {
485
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 std::lock_guard<std::recursive_mutex> lk(sensorSourcesLock);
486
2/4
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
7 if (!config.isArray()) {
487 ✗ return;
488 }
489
3/4
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✓ Branch 3 taken 7 times.
25 for (int x = 0; x < config.size(); x++) {
490 // Read from the cape's config at startup: a source with a value of the
491 // wrong JSON type is skipped, as addSensors() does, not thrown on.
492 18 SensorSource* ss = nullptr;
493 try {
494
2/4
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
18 Json::Value v = config[x];
495
4/4
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 15 times.
✓ Branch 3 taken 1 times.
18 std::string type = v["type"].asString();
496
7/10
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 12 times.
✓ Branch 8 taken 3 times.
✓ Branch 9 taken 12 times.
15 if (type == "ads7828" || type == "ads7830") {
497
5/8
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
3 ss = new ADS7828Sensor(v);
498
3/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 6 times.
12 } else if (type == "iio") {
499
5/8
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 5 times.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
6 ss = new IIOSensorSource(v);
500
3/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 1 times.
6 } else if (type == "mux") {
501
5/8
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
5 ss = new MuxSensorSource(v);
502
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 } else if (type == "multi") {
503
3/8
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
1 ss = new MultiSensorSource(v);
504 }
505
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
31 } catch (const std::exception& e) {
506
3/6
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
24 WarningHolder::InvalidConfigEntry("sensorSources", e.what());
507 8 continue;
508 8 }
509
6/8
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 7 times.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 7 times.
✓ Branch 7 taken 3 times.
10 if (ss && ss->isOK()) {
510
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 sensorSources.push_back(ss);
511
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 } else if (ss) {
512
3/6
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
3 WarningHolder::AddWarning(53, "Could not create SensorSource: " + ss->getID());
513
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 delete ss;
514 }
515 }
516
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 }
517 1574 void Sensors::updateSensorSources(bool forceInstant) {
518
1/2
✓ Branch 0 taken 1574 times.
✗ Branch 1 not taken.
1574 std::lock_guard<std::recursive_mutex> lk(sensorSourcesLock);
519
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1574 times.
1574 for (auto& ss : sensorSources) {
520 ✗ ss->update(forceInstant);
521 }
522 1574 }
523 25 SensorSource* Sensors::getSensorSource(const std::string& name) {
524
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 std::lock_guard<std::recursive_mutex> lk(sensorSourcesLock);
525
2/2
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 4 times.
37 for (auto ss : sensorSources) {
526
3/4
✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 21 times.
✓ Branch 3 taken 12 times.
33 if (ss->getID() == name) {
527 21 return ss;
528 }
529 }
530
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
4 if (name == "iio") {
531 // default the iio name if requested
532 ✗ Json::Value v;
533 ✗ v["type"] = "iio";
534 ✗ v["id"] = "iio";
535 ✗ SensorSource* ss = new IIOSensorSource(v);
536 ✗ if (ss->isOK()) {
537 ✗ sensorSources.push_back(ss);
538 } else {
539 ✗ WarningHolder::AddWarning(53, "Could not create SensorSource: " + ss->getID());
540 ✗ delete ss;
541 ✗ ss = nullptr;
542 }
543 ✗ return ss;
544 ✗ }
545 4 return nullptr;
546 25 }
547 6 void Sensors::lockToGroup(int i) {
548
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 std::lock_guard<std::recursive_mutex> lk(sensorSourcesLock);
549
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 for (auto ss : sensorSources) {
550 ✗ ss->lockToGroup(i);
551 }
552 6 }
553
554 1887 void Sensors::DetectHWSensors() {
555 1887 int i = 0;
556 1887 char path[256] = { 0 };
557 1887 snprintf(path, sizeof(path), "/sys/class/thermal/thermal_zone%d/temp", i);
558
3/4
✓ Branch 0 taken 2165 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 278 times.
✓ Branch 3 taken 1887 times.
2165 while (FileExists(path)) {
559
1/2
✓ Branch 0 taken 278 times.
✗ Branch 1 not taken.
278 Json::Value v;
560
2/4
✓ Branch 0 taken 278 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 278 times.
✗ Branch 3 not taken.
278 v["path"] = path;
561
2/4
✓ Branch 0 taken 278 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 278 times.
✗ Branch 3 not taken.
278 v["valueType"] = "Temperature";
562
563 278 snprintf(path, sizeof(path), "/sys/class/thermal/thermal_zone%d/type", i);
564
2/4
✓ Branch 0 taken 278 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 278 times.
✗ Branch 3 not taken.
278 if (FileExists(path)) {
565
1/2
✓ Branch 0 taken 278 times.
✗ Branch 1 not taken.
278 int file = open(path, O_RDONLY);
566
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 278 times.
278 if (file == -1) {
567 ✗ v["label"] = "CPU: ";
568 } else {
569 // Cap the read below the buffer so there is always room for
570 // the terminator; short/failed reads fall back like a missing
571 // type file instead of indexing out of bounds.
572
1/2
✓ Branch 0 taken 278 times.
✗ Branch 1 not taken.
278 int r = read(file, path, sizeof(path) - 1);
573
1/2
✓ Branch 0 taken 278 times.
✗ Branch 1 not taken.
278 close(file);
574
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 278 times.
278 if (r <= 0) {
575 ✗ v["label"] = "CPU: ";
576 } else {
577 278 path[r] = 0;
578
1/2
✓ Branch 0 taken 278 times.
✗ Branch 1 not taken.
278 if (path[r - 1] == '\n') {
579 278 path[r - 1] = 0; // remove trailing newline
580 278 --r;
581 }
582
1/2
✓ Branch 0 taken 278 times.
✗ Branch 1 not taken.
278 std::string label = path;
583 // Each strip removes the suffix plus its separator, so only
584 // strip when a non-empty prefix is left: an exact-length
585 // match would otherwise index before the buffer.
586
18/32
✓ Branch 0 taken 278 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 278 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 277 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 277 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 277 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 1 times.
✓ Branch 11 taken 276 times.
✓ Branch 12 taken 1 times.
✓ Branch 13 taken 1 times.
✓ Branch 14 taken 277 times.
✓ Branch 15 taken 1 times.
✓ Branch 16 taken 277 times.
✓ Branch 17 taken 1 times.
✓ Branch 18 taken 278 times.
✗ Branch 19 not taken.
✓ Branch 20 taken 278 times.
✗ Branch 21 not taken.
✓ Branch 22 taken 1 times.
✓ Branch 23 taken 277 times.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
✗ Branch 30 not taken.
✗ Branch 31 not taken.
1388 if ((endsWith(label, "thermal_zone") || endsWith(label, "thermal-zone")) && (int)label.size() > 13) {
587 1 path[r - 13] = 0; // remove the "thermal_zone" suffix
588
9/18
✓ Branch 0 taken 277 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 277 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 276 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 276 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 277 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 277 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 276 times.
✓ Branch 13 taken 1 times.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
831 } else if (endsWith(label, "thermal") && (int)label.size() > 8) {
589 276 path[r - 8] = 0; // remove the "thermal" suffix
590 }
591 278 bool hasUpper = false;
592
2/2
✓ Branch 0 taken 1361 times.
✓ Branch 1 taken 278 times.
1639 for (int x = 0; path[x]; x++) {
593
2/2
✓ Branch 0 taken 148 times.
✓ Branch 1 taken 1213 times.
1361 if (isupper(path[x])) {
594 148 hasUpper = true;
595 }
596 }
597
2/2
✓ Branch 0 taken 204 times.
✓ Branch 1 taken 74 times.
278 if (!hasUpper) {
598 // convert to lower case
599
2/2
✓ Branch 0 taken 917 times.
✓ Branch 1 taken 204 times.
1121 for (int x = 0; path[x]; x++) {
600 917 path[x] = toupper(path[x]);
601 }
602 }
603
1/2
✓ Branch 0 taken 278 times.
✗ Branch 1 not taken.
278 label = path;
604
3/6
✓ Branch 0 taken 278 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 278 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 278 times.
556 if (endsWith(label, "_THERMAL_ZONE")) {
605 ✗ label = label.substr(0, label.size() - 13);
606
3/6
✓ Branch 0 taken 278 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 278 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 278 times.
556 } else if (endsWith(label, "_THERMAL")) {
607 ✗ label = label.substr(0, label.size() - 8);
608 }
609
1/2
✓ Branch 0 taken 278 times.
✗ Branch 1 not taken.
278 label += ": ";
610
2/4
✓ Branch 0 taken 278 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 278 times.
✗ Branch 3 not taken.
278 v["label"] = label;
611 278 }
612 }
613 } else {
614 ✗ v["label"] = "CPU: ";
615 }
616
4/10
✓ Branch 0 taken 278 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 278 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 278 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 278 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
278 sensors.push_back(new ThermalSensor(v));
617 278 i++;
618 278 snprintf(path, sizeof(path), "/sys/class/thermal/thermal_zone%d/temp", i);
619 278 }
620
1/2
✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
1887 DetectFanSensors();
621 1887 }
622 1887 void Sensors::DetectFanSensors() {
623 // A pwm-fan hwmon node exposes fanN_input even when no working fan is wired
624 // to it -- it just reads 0, which looks like a stalled fan. fppinit's
625 // boot-time probe (probeFanPresence in FPPINIT_Config.cpp) forces each fan on
626 // and records whether its tachometer actually reported RPM into
627 // media/tmp/fan_probe.json, keyed by "<hwmon name>:<fan index>". Skip the RPM
628 // sensor for any fan that probed as absent -- there is nothing useful to
629 // show. A missing file or entry means "unknown", so we register as before.
630 // fppinit only writes the probe file when it finds a fanN_input to probe, so
631 // a board with no tachometer-capable fan node -- the overwhelming majority --
632 // never has one. That is the "unknown" case handled above, not a problem, so
633 // skip the load entirely rather than have it report the file as missing.
634
1/2
✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
1887 Json::Value fanProbe;
635
2/4
✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1887 times.
✗ Branch 3 not taken.
1887 std::string fanProbeFile = FPP_DIR_MEDIA("/tmp/fan_probe.json");
636
3/4
✓ Branch 0 taken 1887 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1886 times.
1887 if (FileExists(fanProbeFile)) {
637
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LoadJsonFromFile(fanProbeFile, fanProbe, JsonRoot::Object);
638 }
639
640
1/2
✓ Branch 0 taken 3809 times.
✗ Branch 1 not taken.
3809 for (int h = 0; h < 32; h++) {
641 char hwmonPath[256];
642 3809 snprintf(hwmonPath, sizeof(hwmonPath), "/sys/class/hwmon/hwmon%d", h);
643
3/4
✓ Branch 0 taken 3809 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1887 times.
✓ Branch 3 taken 1922 times.
3809 if (!DirectoryExists(hwmonPath)) {
644 1887 break;
645 }
646
647 char namePath[256];
648 1922 snprintf(namePath, sizeof(namePath), "%s/name", hwmonPath);
649
1/2
✓ Branch 0 taken 1922 times.
✗ Branch 1 not taken.
1922 std::string deviceName = "hwmon" + std::to_string(h);
650
2/4
✓ Branch 0 taken 1922 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1922 times.
✗ Branch 3 not taken.
1922 if (FileExists(namePath)) {
651
1/2
✓ Branch 0 taken 1922 times.
✗ Branch 1 not taken.
1922 int f = open(namePath, O_RDONLY);
652
1/2
✓ Branch 0 taken 1922 times.
✗ Branch 1 not taken.
1922 if (f != -1) {
653 1922 char buf[64] = {0};
654
1/2
✓ Branch 0 taken 1922 times.
✗ Branch 1 not taken.
1922 int r = read(f, buf, 63);
655
1/2
✓ Branch 0 taken 1922 times.
✗ Branch 1 not taken.
1922 if (r > 0) {
656
1/2
✓ Branch 0 taken 1922 times.
✗ Branch 1 not taken.
1922 if (buf[r - 1] == '\n') buf[r - 1] = 0;
657
1/2
✓ Branch 0 taken 1922 times.
✗ Branch 1 not taken.
1922 deviceName = buf;
658 }
659
1/2
✓ Branch 0 taken 1922 times.
✗ Branch 1 not taken.
1922 close(f);
660 }
661 }
662
663 // Pick the nicest available base name for the label:
664 // 1. a cape-supplied DT override: of_node/fpp,fan-label (e.g. "K16-Pro Fan")
665 // 2. the DT node name: of_node/name (e.g. Pi5 "cooling_fan")
666 // 3. the hwmon device name: e.g. "pwmfan"
667 3843 auto readDTString = [](const std::string& path) {
668
6/10
✓ Branch 0 taken 313 times.
✓ Branch 1 taken 3530 times.
✓ Branch 2 taken 313 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3530 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3530 times.
✓ Branch 7 taken 313 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
7373 std::string s = FileExists(path) ? GetFileContents(path) : "";
669 // device-tree strings are NUL-terminated; trim NUL/whitespace
670
5/8
✓ Branch 0 taken 313 times.
✓ Branch 1 taken 3530 times.
✓ Branch 2 taken 313 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 313 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 3843 times.
3843 while (!s.empty() && (s.back() == '\0' || isspace((unsigned char)s.back())))
671 ✗ s.pop_back();
672 3843 return s;
673 };
674
3/6
✓ Branch 0 taken 1922 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1922 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1922 times.
✗ Branch 5 not taken.
1922 std::string baseName = readDTString(std::string(hwmonPath) + "/of_node/fpp,fan-label");
675
2/2
✓ Branch 0 taken 1921 times.
✓ Branch 1 taken 1 times.
1922 if (baseName.empty()) {
676
3/6
✓ Branch 0 taken 1921 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1921 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1921 times.
✗ Branch 5 not taken.
1921 std::string nodeName = readDTString(std::string(hwmonPath) + "/of_node/name");
677
9/14
✓ Branch 0 taken 1921 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1846 times.
✓ Branch 3 taken 75 times.
✓ Branch 4 taken 1846 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1846 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1846 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 1846 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 1846 times.
✓ Branch 13 taken 75 times.
1921 if (nodeName != "pwm-fan" && nodeName != "pwm_fan" && nodeName != "fan") {
678 // prettify, e.g. "cooling_fan" -> "Cooling Fan"
679 1846 bool up = true;
680
2/2
✓ Branch 0 taken 1717 times.
✓ Branch 1 taken 1846 times.
3563 for (auto& c : nodeName) {
681
3/4
✓ Branch 0 taken 1662 times.
✓ Branch 1 taken 55 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1662 times.
1717 if (c == '_' || c == '-') { c = ' '; up = true; }
682
3/4
✓ Branch 0 taken 292 times.
✓ Branch 1 taken 1370 times.
✓ Branch 2 taken 292 times.
✗ Branch 3 not taken.
1662 else if (up && islower((unsigned char)c)) { c = toupper((unsigned char)c); up = false; }
683 1370 else up = false;
684 }
685
1/2
✓ Branch 0 taken 1846 times.
✗ Branch 1 not taken.
1846 baseName = nodeName;
686 }
687 1921 }
688
2/2
✓ Branch 0 taken 1684 times.
✓ Branch 1 taken 238 times.
1922 if (baseName.empty())
689
1/2
✓ Branch 0 taken 1684 times.
✗ Branch 1 not taken.
1684 baseName = deviceName;
690
691 // count fans on this device so we only append an index when there's more than one
692 1922 int fanCount = 0;
693
2/2
✓ Branch 0 taken 15376 times.
✓ Branch 1 taken 1922 times.
17298 for (int f = 1; f <= 8; f++) {
694 char fp[256];
695 15376 snprintf(fp, sizeof(fp), "%s/fan%d_input", hwmonPath, f);
696
3/4
✓ Branch 0 taken 15376 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✓ Branch 3 taken 15313 times.
15376 if (FileExists(fp)) fanCount++;
697 }
698
699
2/2
✓ Branch 0 taken 15376 times.
✓ Branch 1 taken 1922 times.
17298 for (int fan = 1; fan <= 8; fan++) {
700 char fanPath[256];
701 15376 snprintf(fanPath, sizeof(fanPath), "%s/fan%d_input", hwmonPath, fan);
702
3/4
✓ Branch 0 taken 15376 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✓ Branch 3 taken 15313 times.
15376 if (FileExists(fanPath)) {
703
1/2
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
63 Json::Value v;
704
2/4
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
63 v["path"] = fanPath;
705
2/4
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
63 v["valueType"] = "FanSpeed";
706 // "<base> Fan", skipping a redundant trailing "Fan", and adding the
707 // index only when the device actually has more than one fan
708
1/2
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
63 std::string label = baseName;
709
16/44
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 56 times.
✓ Branch 5 taken 7 times.
✓ Branch 6 taken 56 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 56 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 56 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✓ Branch 19 taken 63 times.
✗ Branch 20 not taken.
✓ Branch 21 taken 63 times.
✓ Branch 22 taken 56 times.
✓ Branch 23 taken 7 times.
✓ Branch 24 taken 56 times.
✓ Branch 25 taken 7 times.
✓ Branch 26 taken 63 times.
✗ Branch 27 not taken.
✓ Branch 28 taken 63 times.
✗ Branch 29 not taken.
✗ Branch 30 not taken.
✓ Branch 31 taken 63 times.
✗ Branch 32 not taken.
✗ Branch 33 not taken.
✗ Branch 34 not taken.
✗ Branch 35 not taken.
✗ Branch 36 not taken.
✗ Branch 37 not taken.
✗ Branch 38 not taken.
✗ Branch 39 not taken.
✗ Branch 40 not taken.
✗ Branch 41 not taken.
✗ Branch 42 not taken.
✗ Branch 43 not taken.
301 if (!(endsWith(label, "fan") || endsWith(label, "Fan") || endsWith(label, "FAN")))
710 ✗ label += " Fan";
711
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 61 times.
63 if (fanCount > 1)
712
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 label += " " + std::to_string(fan);
713
3/6
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
63 v["label"] = label + ": ";
714
2/4
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
63 v["postfix"] = " RPM";
715
716
2/4
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
63 std::string probeKey = deviceName + ":" + std::to_string(fan);
717
10/14
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 61 times.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 1 times.
✓ Branch 11 taken 1 times.
✓ Branch 12 taken 1 times.
✓ Branch 13 taken 62 times.
63 if (fanProbe.isMember(probeKey) && !fanProbe[probeKey]["present"].asBool()) {
718
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogInfo(VB_GENERAL, "Fan '%s' (%s) reported no tachometer signal when forced on at boot; suppressing RPM display\n",
719 label.c_str(), probeKey.c_str());
720 1 continue;
721 }
722
4/10
✓ Branch 0 taken 62 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 62 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 62 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 62 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
62 sensors.push_back(new FanSensor(v));
723
6/6
✓ Branch 0 taken 62 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 62 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 62 times.
✓ Branch 5 taken 1 times.
65 }
724 }
725 1922 }
726 1887 }
727 1867 void Sensors::Close() {
728
2/2
✓ Branch 0 taken 363 times.
✓ Branch 1 taken 1867 times.
2230 for (auto x : sensors) {
729
1/2
✓ Branch 0 taken 363 times.
✗ Branch 1 not taken.
363 delete x;
730 }
731 1867 sensors.clear();
732 1867 }
733
734 24 Sensor* Sensors::createSensor(Json::Value& s) {
735
5/8
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 22 times.
24 if (s["type"].asString() == "i2c") {
736
2/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
2 return new I2CSensor(s);
737
4/8
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 22 times.
22 } else if (s["type"].asString() == "ain") {
738 ✗ return new AINSensor(s);
739
4/8
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 22 times.
✗ Branch 7 not taken.
22 } else if (s["type"].asString() == "sensor") {
740
4/6
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 21 times.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
22 return new SensorSourceSensor(s);
741 }
742 ✗ return nullptr;
743 }
744
745 11 void Sensors::addSensors(Json::Value& config) {
746
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 10 times.
11 if (!config.isArray()) {
747 1 return;
748 }
749
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 10 times.
34 for (int x = 0; x < config.size(); x++) {
750 // A sensor with a value of the wrong JSON type threw out of its
751 // constructor at startup, to std::terminate; skip that one. The
752 // constructors read all their fields before doing anything.
753 try {
754
2/4
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
24 Json::Value v = config[x];
755
2/2
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 1 times.
24 Sensor* sensor = createSensor(v);
756
1/2
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
23 if (sensor) {
757
1/2
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
23 sensors.push_back(sensor);
758 }
759
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
25 } catch (const std::exception& e) {
760
3/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
3 WarningHolder::InvalidConfigEntry("sensors", e.what());
761 1 }
762 }
763 }
764
765 137639 void Sensors::reportSensors(Json::Value& root) {
766
2/2
✓ Branch 0 taken 2010 times.
✓ Branch 1 taken 135629 times.
137639 if (!sensors.empty()) {
767 2010 std::set<SensorSource*> sensorSources;
768
1/2
✓ Branch 0 taken 2010 times.
✗ Branch 1 not taken.
2010 Json::Value& s = root["sensors"];
769
2/2
✓ Branch 0 taken 6644 times.
✓ Branch 1 taken 2010 times.
8654 for (auto a : sensors) {
770
1/2
✓ Branch 0 taken 6644 times.
✗ Branch 1 not taken.
6644 SensorSource* ss = a->getSensorSource();
771
2/2
✓ Branch 0 taken 2449 times.
✓ Branch 1 taken 4195 times.
6644 if (ss) {
772 // only trigger updates on the sensor sources that
773 // actually report via the normal reporting and not
774 // all the sensor sources that may be used for
775 // current monitoring or other things
776
3/4
✓ Branch 0 taken 2449 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 321 times.
✓ Branch 3 taken 2128 times.
2449 if (!sensorSources.contains(ss)) {
777
1/2
✓ Branch 0 taken 321 times.
✗ Branch 1 not taken.
321 ss->update(false);
778
1/2
✓ Branch 0 taken 321 times.
✗ Branch 1 not taken.
321 sensorSources.emplace(ss);
779 }
780 }
781
1/2
✓ Branch 0 taken 6644 times.
✗ Branch 1 not taken.
6644 a->report(s);
782 }
783 2010 }
784 137639 }
785