GCC Code Coverage Report


Directory: ./
File: sensors/MuxSensorSource.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 122 179 68.2%
Functions: 9 11 81.8%
Branches: 154 332 46.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 #include <cstdint>
17 #include <thread>
18
19 #include "../log.h"
20
21 #include "MuxSensorSource.h"
22
23 // Bounds for user-editable sensors.json. The largest realistic mux is a
24 // handful of select pins (2^N groups) over an 8/16-channel ADC; anything
25 // near these caps is corrupt or malicious. Capping keeps the per-update
26 // loops and the enable()/getValue() vectors bounded.
27 static constexpr int kMaxChannelsPerMux = 1024;
28 static constexpr int kMaxMuxCount = 1024;
29 static constexpr int kMaxTotalMuxChannels = 4096;
30 static constexpr int kMaxMuxSwitchDelayMs = 5000;
31 static constexpr int kMaxMuxSwitchReadCount = 100;
32
33 5 MuxSensorSource::MuxSensorSource(Json::Value& config) :
34 5 SensorSource(config) {
35
3/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 1 times.
5 std::string sourcename = config["source"].asString();
36
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 source = Sensors::INSTANCE.getSensorSource(sourcename);
37
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 channelsPerMux = config["channels"].asInt();
38
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 muxCount = config["muxCount"].asInt();
39
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
4 if (channelsPerMux <= 0 || muxCount <= 0 ||
40
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 channelsPerMux > kMaxChannelsPerMux || muxCount > kMaxMuxCount ||
41
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 (int64_t)channelsPerMux * (int64_t)muxCount > kMaxTotalMuxChannels) {
42 // User-editable sensors.json: a zero/negative value would divide by
43 // zero in enable()/getValue(), and a huge value would hang the
44 // per-update loops or OOM the vectors. Fail creation instead --
45 // addSensorSources warns and drops sources where isOK() is false.
46
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogErr(VB_GENERAL, "MuxSensorSource '%s' has invalid channels (%d) or muxCount (%d), ignoring\n",
47 getID().c_str(), (int)channelsPerMux, (int)muxCount);
48 1 source = nullptr;
49 1 return;
50 }
51
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 if (source == nullptr) {
52 // Unknown "source" name. The object would be dropped by isOK() below,
53 // but return before touching GPIO so a doomed object has no HW side
54 // effects.
55
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogErr(VB_GENERAL, "MuxSensorSource '%s' references unknown source '%s', ignoring\n",
56 getID().c_str(), sourcename.c_str());
57 1 return;
58 }
59
4/6
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 1 times.
4 for (int x = 0; x < config["muxPins"].size(); x++) {
60
4/8
✓ 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.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
3 const PinCapabilities& pc = PinCapabilities::getPinByName(config["muxPins"][x].asString());
61
3/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 2 times.
3 if (pc.ptr() == nullptr) {
62 // Unknown pin name yields the null pin (ptr() == nullptr); using it
63 // below would crash. Fail creation the same way as above.
64
4/8
✓ 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.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
1 LogErr(VB_GENERAL, "MuxSensorSource '%s' references unknown pin '%s', ignoring\n",
65 getID().c_str(), config["muxPins"][x].asString().c_str());
66 1 source = nullptr;
67 1 pins.clear();
68 1 return;
69 }
70
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 pins.push_back(pc.ptr());
71 }
72
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 for (auto p : pins) {
73
3/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
6 p->configPin("gpio", true, sourcename + "-Mux");
74
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 p->setValue(0);
75 }
76
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (config.isMember("muxSwitchDelay")) {
77 ✗ muxSwitchDelay = config["muxSwitchDelay"].asInt();
78 ✗ if (muxSwitchDelay < 0) {
79 ✗ muxSwitchDelay = 0;
80 ✗ } else if (muxSwitchDelay > kMaxMuxSwitchDelayMs) {
81 ✗ muxSwitchDelay = kMaxMuxSwitchDelayMs;
82 }
83 }
84
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (config.isMember("muxSwitchReadCount")) {
85 ✗ muxSwitchReadCount = config["muxSwitchReadCount"].asInt();
86 ✗ if (muxSwitchReadCount < 0) {
87 ✗ muxSwitchReadCount = 0;
88 ✗ } else if (muxSwitchReadCount > kMaxMuxSwitchReadCount) {
89 ✗ muxSwitchReadCount = kMaxMuxSwitchReadCount;
90 }
91 }
92
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
9 }
93 12 MuxSensorSource::~MuxSensorSource() {
94
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
6 for (auto p : pins) {
95 ✗ p->releasePin();
96 }
97 12 }
98
99 1 void MuxSensorSource::Init(std::map<int, std::function<bool(int)>>& callbacks) {
100 1 std::map<int, std::function<bool(int)>> cb;
101
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!source) {
102 ✗ return;
103 }
104
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 source->Init(cb);
105 1 updatingByCallback = false;
106
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!cb.empty()) {
107 ✗ updatingByCallback = true;
108 ✗ for (auto& c : cb) {
109 ✗ auto call = c.second;
110 ✗ callbacks[c.first] = [call, this](int i) {
111 ✗ std::lock_guard<std::recursive_mutex> lk(muxLock);
112 ✗ call(i);
113 ✗ if (updateCount > 0) {
114 ✗ getValues();
115 }
116 ✗ updateCount = 1 + updateCount;
117 ✗ if (updateCount == 4) {
118 ✗ nextMux();
119 }
120 ✗ return false;
121 ✗ };
122 ✗ }
123 }
124
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 }
125 75 void MuxSensorSource::nextMux() {
126
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 75 times.
75 if (lockedToGroup) {
127 ✗ return;
128 }
129
3/6
✓ Branch 0 taken 75 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 75 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 75 times.
75 if (!source || channelsPerMux <= 0 || muxCount <= 0) {
130 ✗ return;
131 }
132
3/6
✓ Branch 0 taken 75 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 75 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 75 times.
75 if (curMux < 0 || curMux >= muxCount) {
133 ✗ curMux = 0;
134 }
135 75 int cm = curMux;
136 75 int64_t start64 = (int64_t)cm * (int64_t)channelsPerMux;
137
2/2
✓ Branch 0 taken 150 times.
✓ Branch 1 taken 75 times.
225 for (int x = 0; x < channelsPerMux; x++) {
138 150 int64_t idx = start64 + x;
139
4/8
✓ Branch 0 taken 150 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 150 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 150 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 150 times.
150 if (idx < 0 || (uint64_t)idx >= enabled.size() || (uint64_t)idx >= current.size()) {
140 ✗ continue;
141 }
142
7/10
✓ Branch 0 taken 150 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 150 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 150 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 9 times.
✓ Branch 7 taken 141 times.
✓ Branch 8 taken 9 times.
✓ Branch 9 taken 141 times.
150 if (enabled[(size_t)idx] && !current[(size_t)idx]) {
143 9 getValues();
144 }
145 }
146
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 57 times.
75 if ((curMux + 1) >= muxCount) {
147 18 curMux = 0;
148 } else {
149 57 curMux = 1 + curMux;
150 }
151 75 updateCount = 0;
152 75 cm = curMux;
153 75 start64 = (int64_t)cm * (int64_t)channelsPerMux;
154
2/2
✓ Branch 0 taken 150 times.
✓ Branch 1 taken 75 times.
225 for (int x = 0; x < channelsPerMux; x++) {
155 150 int64_t idx = start64 + x;
156
3/6
✓ Branch 0 taken 150 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 150 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 150 times.
150 if (idx < 0 || (uint64_t)idx >= current.size()) {
157 ✗ continue;
158 }
159
1/2
✓ Branch 0 taken 150 times.
✗ Branch 1 not taken.
150 current[(size_t)idx] = false;
160 }
161 75 setGroupPins();
162 }
163 75 void MuxSensorSource::setGroupPins() {
164
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 75 times.
75 if (!source) {
165 ✗ return;
166 }
167 75 int tmp = curMux;
168
2/2
✓ Branch 0 taken 150 times.
✓ Branch 1 taken 75 times.
225 for (auto& a : pins) {
169
1/2
✓ Branch 0 taken 150 times.
✗ Branch 1 not taken.
150 a->setValue(tmp & 0x1 ? 1 : 0);
170 150 tmp >>= 1;
171 }
172
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 75 times.
75 if (muxSwitchDelay > 0) {
173 ✗ std::this_thread::sleep_for(std::chrono::milliseconds(muxSwitchDelay));
174 }
175
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 75 times.
75 for (int x = 0; x < muxSwitchReadCount; x++) {
176 ✗ source->update(true);
177 }
178 }
179 ✗ void MuxSensorSource::lockToGroup(int i) {
180 ✗ std::lock_guard<std::recursive_mutex> lk(muxLock);
181 ✗ if (!source || channelsPerMux <= 0 || muxCount <= 0) {
182 ✗ return;
183 }
184 ✗ if (i >= 0 && i < muxCount) {
185 ✗ lockedToGroup = true;
186 ✗ if (curMux != i) {
187 ✗ curMux = i;
188 ✗ updateCount = 0;
189 }
190 ✗ setGroupPins();
191 ✗ } else if (lockedToGroup) {
192 ✗ lockedToGroup = false;
193 ✗ nextMux();
194 }
195 ✗ }
196
197 263 void MuxSensorSource::getValues() {
198
3/6
✓ Branch 0 taken 263 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 263 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 263 times.
263 if (!source || channelsPerMux <= 0 || muxCount <= 0) {
199 ✗ return;
200 }
201
3/6
✓ Branch 0 taken 263 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 263 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 263 times.
263 if (curMux < 0 || curMux >= muxCount) {
202 ✗ return;
203 }
204 263 int cm = curMux;
205 263 int64_t start = (int64_t)cm * (int64_t)channelsPerMux;
206
2/2
✓ Branch 0 taken 526 times.
✓ Branch 1 taken 263 times.
789 for (int x = 0; x < channelsPerMux; x++) {
207 526 int64_t idx = start + x;
208
1/2
✓ Branch 0 taken 526 times.
✗ Branch 1 not taken.
526 if (idx < 0 || (uint64_t)idx >= enabled.size() ||
209
4/8
✓ Branch 0 taken 526 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 526 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 526 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 526 times.
1052 (uint64_t)idx >= values.size() || (uint64_t)idx >= current.size()) {
210 ✗ continue;
211 }
212
2/4
✓ Branch 0 taken 526 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 526 times.
✗ Branch 3 not taken.
526 if (enabled[(size_t)idx]) {
213 526 values[(size_t)idx] = source->getValue(x);
214
1/2
✓ Branch 0 taken 526 times.
✗ Branch 1 not taken.
526 current[(size_t)idx] = true;
215 }
216 }
217 }
218
219 302 void MuxSensorSource::update(bool forceInstant) {
220
1/2
✓ Branch 0 taken 302 times.
✗ Branch 1 not taken.
302 std::lock_guard<std::recursive_mutex> lk(muxLock);
221
3/6
✓ Branch 0 taken 302 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 302 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 302 times.
302 if (!source || channelsPerMux <= 0 || muxCount <= 0) {
222 ✗ return;
223 }
224
1/2
✓ Branch 0 taken 302 times.
✗ Branch 1 not taken.
302 if (!updatingByCallback) {
225
4/6
✓ Branch 0 taken 302 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 76 times.
✓ Branch 3 taken 226 times.
✓ Branch 4 taken 302 times.
✗ Branch 5 not taken.
302 source->update(forceInstant || (updateCount == 0));
226 302 updateCount = 1 + updateCount;
227
2/2
✓ Branch 0 taken 75 times.
✓ Branch 1 taken 227 times.
302 if (updateCount == 4) {
228
1/2
✓ Branch 0 taken 75 times.
✗ Branch 1 not taken.
75 nextMux();
229 } else {
230 227 int cm = curMux;
231 227 int64_t start = (int64_t)cm * (int64_t)channelsPerMux;
232
2/2
✓ Branch 0 taken 454 times.
✓ Branch 1 taken 227 times.
681 for (int x = 0; x < channelsPerMux; x++) {
233 454 int64_t idx = start + x;
234
3/6
✓ Branch 0 taken 454 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 454 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 454 times.
454 if (idx < 0 || (uint64_t)idx >= current.size()) {
235 ✗ continue;
236 }
237
1/2
✓ Branch 0 taken 454 times.
✗ Branch 1 not taken.
454 current[(size_t)idx] = false;
238 }
239 }
240 }
241
1/2
✓ Branch 0 taken 302 times.
✗ Branch 1 not taken.
302 }
242 8 void MuxSensorSource::enable(int id) {
243
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 std::lock_guard<std::recursive_mutex> lk(muxLock);
244
4/8
✓ 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.
✗ Branch 6 not taken.
✓ Branch 7 taken 8 times.
8 if (!source || channelsPerMux <= 0 || muxCount <= 0 || id < 0) {
245 ✗ return;
246 }
247 // Valid mux channels are 0 .. channelsPerMux*muxCount-1. Anything else is
248 // a corrupt sensors.json entry: ignore it instead of growing the vectors
249 // without bound (OOM) via resize(id+1).
250 8 int64_t total = (int64_t)channelsPerMux * (int64_t)muxCount;
251
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if ((int64_t)id >= total) {
252 ✗ return;
253 }
254
255 8 int i = id % channelsPerMux;
256
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 source->enable(i);
257
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 if ((size_t)id >= values.size()) {
258 8 size_t need = (size_t)id + 1;
259
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 values.resize(need);
260
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 enabled.resize(need);
261
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 current.resize(need);
262 }
263
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 enabled[(size_t)id] = true;
264
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 current[(size_t)id] = false;
265 8 values[(size_t)id] = 0;
266
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 }
267 2416 int32_t MuxSensorSource::getValue(int id) {
268
1/2
✓ Branch 0 taken 2416 times.
✗ Branch 1 not taken.
2416 std::lock_guard<std::recursive_mutex> lk(muxLock);
269
5/10
✓ Branch 0 taken 2416 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2416 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2416 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 2416 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 2416 times.
2416 if (!source || channelsPerMux <= 0 || id < 0 || (size_t)id >= values.size()) {
270 ✗ return 0;
271 }
272
3/6
✓ Branch 0 taken 2416 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2416 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 2416 times.
2416 if ((size_t)id >= enabled.size() || (size_t)id >= current.size()) {
273 ✗ return values[(size_t)id];
274 }
275
7/10
✓ Branch 0 taken 2416 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2416 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2416 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 292 times.
✓ Branch 7 taken 2124 times.
✓ Branch 8 taken 292 times.
✓ Branch 9 taken 2124 times.
2416 if (enabled[(size_t)id] && !current[(size_t)id]) {
276 292 int i = id / channelsPerMux;
277
2/2
✓ Branch 0 taken 254 times.
✓ Branch 1 taken 38 times.
292 if (i == curMux) {
278
1/2
✓ Branch 0 taken 254 times.
✗ Branch 1 not taken.
254 getValues();
279 }
280 }
281 2416 return values[(size_t)id];
282 2416 }
283