GCC Code Coverage Report


Directory: ./
File: sensors/IIOSensorSource.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 57 157 36.3%
Functions: 7 9 77.8%
Branches: 57 244 23.4%

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 <cmath>
18 #include <fcntl.h>
19 #include <unistd.h>
20
21 #include "../Warnings.h"
22 #include "../common.h"
23
24 #include "IIOSensorSource.h"
25
26 6 IIOSensorSource::IIOSensorSource(Json::Value& config) :
27 6 SensorSource(config) {
28
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 if (config.isMember("devId")) {
29
3/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 1 times.
6 iioDevNumber = config["devId"].asInt();
30 } else {
31 ✗ int i2cBus = config["i2cBus"].asInt();
32 ✗ int i2cDev = config["i2cDev"].asInt();
33 uint8_t buf[8];
34 ✗ snprintf((char*)buf, 8, "%04x", i2cDev);
35 ✗ std::string d = (char*)buf;
36 ✗ std::string basePath = "/sys/class/i2c-dev/i2c-" + std::to_string(i2cBus) + "/device/" + std::to_string(i2cBus) + "-" + d + "/iio:device";
37 ✗ for (int x = 0; x < 16; x++) {
38 ✗ if (FileExists(basePath + std::to_string(x))) {
39 ✗ iioDevNumber = x;
40 ✗ break;
41 }
42 }
43 ✗ }
44
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 if (config.isMember("useBuffers")) {
45
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 usingBuffers = config["useBuffers"].asBool();
46 } else {
47 ✗ usingBuffers = FileExists("/sys/bus/iio/devices/iio:device" + std::to_string(iioDevNumber) + "/buffer/enable") && FileExists("/dev/iio:device" + std::to_string(iioDevNumber));
48 }
49
50
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 if (config.isMember("vScale")) {
51
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 vScale = config["vScale"].asFloat();
52 } else {
53 #ifdef PLATFORM_BB64
54 ✗ std::string vs = GetFileContents("/sys/bus/iio/devices/iio:device" + std::to_string(iioDevNumber) + "/in_voltage_scale");
55 ✗ vScale = std::atof(vs.c_str());
56 // original reference is 1.8V which for 12bit would be 0.439453125.
57 // If the cape doesn't specify a reference scale, we'll adjust and assume
58 // the params are based on 1.8 ref, but we're reading to 3.3V
59 ✗ vScale = vScale / 0.439453125;
60 #else
61 ✗ vScale = 1.0f;
62 #endif
63 ✗ }
64
3/6
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
5 readIterations = config.get("readIterations", 3).asInt();
65 // update() averages over readIterations reads: 0 divided by zero
66
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
5 if (readIterations < 1) {
67 1 readIterations = 1;
68
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 } else if (readIterations > 100) {
69 ✗ readIterations = 100;
70 }
71
72 // usingBuffers = false;
73
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 std::string base = "/sys/bus/iio/devices/iio:device" + std::to_string(iioDevNumber) + "/in_voltage";
74 5 int max = -1;
75
2/2
✓ Branch 0 taken 80 times.
✓ Branch 1 taken 5 times.
85 for (int x = 0; x < 16; x++) {
76
5/8
✓ Branch 0 taken 80 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 80 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 80 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 12 times.
✓ Branch 7 taken 68 times.
80 if (FileExists(base + std::to_string(x) + "_raw")) {
77 12 max = x;
78 }
79 }
80
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (max >= 0) {
81 5 ++max;
82
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 channelMapping.resize(max);
83
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 values.resize(max);
84
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 5 times.
17 for (int x = 0; x < max; x++) {
85 12 channelMapping[x] = -1;
86 12 values[x] = 0;
87 }
88 } else {
89 ✗ WarningHolder::AddWarning(53, "Could not enable IIOSensorSource");
90 }
91 8 }
92 ✗ IIOSensorSource::~IIOSensorSource() {
93 ✗ if (usingBuffers) {
94 char buf[256];
95 ✗ snprintf(buf, 256, "/sys/bus/iio/devices/iio:device%d/buffer/enable", iioDevNumber);
96 ✗ int f = open(buf, O_WRONLY);
97 ✗ write(f, "0\n", 2);
98 ✗ close(f);
99
100 ✗ close(iioDevFile);
101 } else {
102 ✗ for (int x = 0; x < channelMapping.size(); x++) {
103 ✗ if (channelMapping[x] != -1) {
104 ✗ close(channelMapping[x]);
105 }
106 }
107 }
108 ✗ if (readBuffer) {
109 ✗ delete[] readBuffer;
110 }
111 ✗ }
112 5 bool IIOSensorSource::isOK() const {
113
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 return FileExists("/dev/iio:device" + std::to_string(iioDevNumber));
114 }
115
116 6 void IIOSensorSource::Init(std::map<int, std::function<bool(int)>>& callbacks) {
117
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (usingBuffers) {
118 ✗ if (iioDevFile >= 0) {
119 ✗ return;
120 }
121 char buf[256];
122 ✗ snprintf(buf, 256, "/sys/bus/iio/devices/iio:device%d/buffer/length", iioDevNumber);
123 ✗ int f = open(buf, O_WRONLY);
124 ✗ write(f, "8\n", 2);
125 ✗ close(f);
126
127 ✗ snprintf(buf, 256, "/sys/bus/iio/devices/iio:device%d/buffer/enable", iioDevNumber);
128 ✗ f = open(buf, O_WRONLY);
129 ✗ write(f, "1\n", 2);
130 ✗ close(f);
131
132 ✗ snprintf(buf, 256, "/dev/iio:device%d", iioDevNumber);
133 ✗ iioDevFile = open(buf, O_RDONLY | O_NONBLOCK);
134
135 ✗ int curIdx = 0;
136 ✗ for (int x = 0; x < channelMapping.size(); x++) {
137 ✗ snprintf(buf, 256, "/sys/bus/iio/devices/iio:device%d/scan_elements/in_voltage%d_en", iioDevNumber, x);
138 ✗ int f = open(buf, O_RDONLY);
139 ✗ read(f, buf, 240);
140 ✗ if (buf[0] == '1') {
141 ✗ channelMapping[curIdx] = x;
142 ✗ curIdx++;
143 }
144 ✗ close(f);
145 }
146 ✗ channelMapping.resize(curIdx);
147 ✗ callbacks[iioDevFile] = [this](int) {
148 ✗ update(false, true);
149 ✗ return false;
150 ✗ };
151 ✗ readBufferSize = channelMapping.size() * 64 * 2;
152 ✗ readBuffer = new uint16_t[readBufferSize];
153 }
154 }
155 314 void IIOSensorSource::update(bool forceInstant) {
156 314 update(forceInstant, false);
157 314 }
158
159 314 void IIOSensorSource::update(bool forceInstant, bool fromSelect) {
160
1/2
✓ Branch 0 taken 314 times.
✗ Branch 1 not taken.
314 std::unique_lock<std::mutex> lock(updateMutex);
161
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 314 times.
314 if (usingBuffers) {
162 ✗ int i = read(iioDevFile, readBuffer, readBufferSize * sizeof(uint16_t));
163 ✗ if (i > 1) {
164 ✗ int countRead = i / sizeof(uint16_t);
165 ✗ std::vector<uint32_t> sums(channelMapping.size());
166 ✗ std::vector<uint16_t> mins(channelMapping.size());
167 ✗ std::vector<uint16_t> maxes(channelMapping.size());
168 ✗ int samples = 0;
169 ✗ for (int x = 0; x < channelMapping.size(); x++) {
170 ✗ maxes[x] = 0;
171 ✗ mins[x] = 0xFFFF;
172 ✗ sums[x] = 0;
173 }
174
175 ✗ for (int y = 0; y < countRead; y += channelMapping.size()) {
176 ✗ samples++;
177 ✗ for (int x = 0; x < channelMapping.size(); x++) {
178 ✗ uint16_t v = readBuffer[y + x];
179 ✗ sums[x] += v;
180 ✗ maxes[x] = std::max(maxes[x], v);
181 ✗ mins[x] = std::min(mins[x], v);
182 }
183 }
184 ✗ if (samples > 3) {
185 ✗ for (int x = 0; x < channelMapping.size(); x++) {
186 ✗ sums[x] -= mins[x];
187 ✗ sums[x] -= maxes[x];
188 ✗ float f = sums[x];
189 ✗ f /= (float)(samples - 2);
190 ✗ f *= vScale;
191
192 ✗ int idx = channelMapping[x];
193 ✗ values[idx] = f;
194 }
195 } else {
196 ✗ for (int x = 0; x < channelMapping.size(); x++) {
197 ✗ float f = sums[x];
198 ✗ f /= (float)(samples);
199 ✗ f *= vScale;
200 ✗ int idx = channelMapping[x];
201 ✗ values[idx] = f;
202 }
203 }
204 ✗ }
205 } else {
206 char buf[24];
207
2/2
✓ Branch 0 taken 632 times.
✓ Branch 1 taken 314 times.
946 for (int x = 0; x < channelMapping.size(); x++) {
208
2/2
✓ Branch 0 taken 622 times.
✓ Branch 1 taken 10 times.
632 if (channelMapping[x] >= 0) {
209 622 float s = 0;
210
2/2
✓ Branch 0 taken 1862 times.
✓ Branch 1 taken 622 times.
2484 for (int m = 0; m < readIterations; m++) {
211 1862 lseek(channelMapping[x], 0, SEEK_SET);
212
1/2
✓ Branch 0 taken 1862 times.
✗ Branch 1 not taken.
1862 int r = read(channelMapping[x], buf, 23);
213 // a failed read (-1) wrote buf[-1]
214 1862 buf[r > 0 ? r : 0] = 0;
215 1862 s += std::atoi(buf);
216 }
217 622 s /= ((float)readIterations);
218 622 s *= vScale;
219 622 values[x] = std::round(s);
220 }
221 }
222 }
223 314 }
224 17 void IIOSensorSource::enable(int id) {
225
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
17 if (id < 0) {
226 ✗ return;
227 }
228
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
17 if (usingBuffers) {
229 char buf[256];
230 ✗ snprintf(buf, 256, "/sys/bus/iio/devices/iio:device%d/scan_elements/in_voltage%d_en", iioDevNumber, id);
231 ✗ if (FileExists(buf)) {
232 ✗ int f = open(buf, O_WRONLY);
233 ✗ write(f, "1\n", 2);
234 ✗ close(f);
235 }
236 } else {
237 // enable() is called once per consumer, not once per channel: every
238 // port's current monitor enables its channel again on each channel
239 // output reload, and a MuxSensorSource enables the same underlying
240 // channel for every port it multiplexes. Opening unconditionally
241 // overwrote the previous descriptor without closing it, leaking one
242 // fd per call until fppd hit EMFILE.
243
6/8
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
✓ Branch 5 taken 7 times.
✓ Branch 6 taken 10 times.
✓ Branch 7 taken 7 times.
17 if (id < 0 || id >= (int)channelMapping.size() || channelMapping[id] >= 0) {
244 10 return;
245 }
246 char buf[256];
247 7 snprintf(buf, 256, "/sys/bus/iio/devices/iio:device%d/in_voltage%d_raw", iioDevNumber, id);
248
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 channelMapping[id] = open(buf, O_RDONLY | O_CLOEXEC);
249 }
250 }
251
252 552 int32_t IIOSensorSource::getValue(int id) {
253
3/6
✓ Branch 0 taken 552 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 552 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 552 times.
552 if (id < 0 || (size_t)id >= values.size()) {
254 ✗ return 0;
255 }
256 552 return values[(size_t)id];
257 };
258