GCC Code Coverage Report


Directory: ./
File: channeloutput/stringtesters/PixelCountStringTester.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 262 300 87.3%
Functions: 12 13 92.3%
Branches: 187 291 64.3%

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 GPL v2 as described in the
10 * included LICENSE.GPL file.
11 */
12
13 #include "fpp-pch.h"
14
15 #include "PixelCountStringTester.h"
16 #include "../../OutputMonitor.h"
17 #include "../../Timers.h"
18 #include "../../log.h"
19 #include "../../channeltester/ChannelTester.h"
20 #include "../PixelString.h"
21 #include "../channeloutputthread.h"
22
23 #include <algorithm>
24 #include <cmath>
25 #include <inttypes.h>
26
27 // #define PRINT_DEBUG_INFO
28
29 1 uint8_t* PixelCountPixelStringTester::createTestData(PixelString* ps, int cycleCount, float percentOfCycle, uint8_t* inChannelData, uint32_t& newLen) {
30 1 newLen = ps->m_outputBytes;
31 1 uint8_t* data = ps->m_outputBuffer;
32 1 uint8_t* out = ps->pixelDataStart();
33 1 uint32_t inCh = 0;
34 unsigned char clr[6];
35 1 clr[0] = clr[2] = 0;
36 1 clr[1] = 255;
37 1 clr[3] = clr[5] = 0;
38 1 clr[4] = 255;
39 1 int clrOffset = 0;
40 1 int curPixel = 1;
41
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (auto& vs : ps->m_virtualStrings) {
42
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (m_byString) {
43 ✗ clrOffset = 0;
44 ✗ curPixel = 1;
45 }
46
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (vs.receiverNum == -1) {
47
3/4
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 1 times.
7 for (int x = 0; x < vs.startNulls * vs.channelsPerNode(); ++x) {
48 6 *out++ = 0;
49 }
50
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 1 times.
11 for (int x = 0; x < vs.pixelCount; ++x) {
51
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (curPixel == 50) {
52 ✗ uint8_t brightness = vs.brightnessMap[255];
53 ✗ for (int y = 0; y < vs.channelsPerNode(); ++y) {
54 ✗ *out = brightness;
55 ✗ ++out;
56 ✗ ++inCh;
57 }
58 ✗ if (++clrOffset == 3) {
59 ✗ clrOffset = 0;
60 }
61 ✗ curPixel = 1;
62 } else {
63
2/4
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
10 if (vs.colorOrder.whiteOffset() == 0) {
64 ✗ ++out;
65 }
66
3/4
✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
✓ Branch 3 taken 10 times.
40 for (int y = 0; y < std::min(vs.channelsPerNode(), 3); ++y) {
67 30 *out = vs.brightnessMap[clr[y + clrOffset]];
68 30 out++;
69 }
70
2/4
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
10 if (vs.colorOrder.whiteOffset() == 3) {
71 ✗ ++out;
72 }
73
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 inCh += vs.channelsPerNode();
74
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 9 times.
10 if (curPixel % 10 == 0) {
75
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (++clrOffset == 3) {
76 ✗ clrOffset = 0;
77 }
78 }
79 10 ++curPixel;
80 }
81 }
82
3/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 times.
4 for (int x = 0; x < vs.endNulls * vs.channelsPerNode(); ++x) {
83 3 *out++ = 0;
84 }
85 } else {
86 ✗ fillInSmartReceiver(ps, vs, inChannelData, inCh, out);
87 }
88 }
89 1 return data;
90 }
91
92 PixelCountPixelStringTester PixelCountPixelStringTester::INSTANCE_BYPORT(false);
93 PixelCountPixelStringTester PixelCountPixelStringTester::INSTANCE_BYSTRING(true);
94
95 // Pixels lit at once while searching. Sixteen seed pixels draw enough to
96 // read clearly above the sensor's noise; sixteen bullets at full white still
97 // draw well under the ~2A the sensors read up to.
98 constexpr int WINDOW = 16;
99 // The furthest pixel looked for on a port with nothing configured on it.
100 constexpr int MAX_PIXELS = 500;
101 constexpr int WARMUP_MS = 250;
102 // Frames a pattern is held before reading, then frames read. The settle is
103 // long because some sensors are slow to follow: a buffered IIO source (the
104 // BBB capes) delivers new values about once a second, so a shorter hold read
105 // the previous pattern. A measurement taken again reads for longer.
106 constexpr uint32_t SETTLE_FRAMES = 16;
107 constexpr uint32_t SAMPLE_FRAMES = 6;
108 constexpr uint32_t REPEAT_SAMPLE_FRAMES = 24;
109 // How many times an unclear measurement is taken before it is decided on what
110 // has been read. A seed pixel on a noisy sensor needs most of them.
111 constexpr int MAX_REPEATS = 10;
112 // The least a window holding any pixel draws: well under one seed pixel.
113 constexpr float ANY_PIXEL_MA = 3.0f;
114 // The least uncertainty a measurement is given. A sensor that reads in steps
115 // can return the same value every time, and a spread of zero is not
116 // certainty.
117 constexpr float MIN_ERROR_MA = 1.0f;
118
119 constexpr int STATE_WARMUP = 0;
120 constexpr int STATE_MEASURE = 1;
121 constexpr int STATE_DISPLAY_PIXEL = 999;
122
123 enum Phase {
124 PHASE_BASE,
125 PHASE_FIRST_WINDOW,
126 PHASE_DOUBLING,
127 PHASE_HALVING,
128 PHASE_END,
129 PHASE_DONE
130 };
131
132 154 static float median(std::vector<float> v) {
133
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 154 times.
154 if (v.empty()) {
134 ✗ return 0;
135 }
136 154 std::sort(v.begin(), v.end());
137 154 return v[v.size() / 2];
138 }
139
140 // The mean of the readings without the highest and lowest fifth: the sensors
141 // throw the odd reading a couple of hundred mA out. Also the spread of the
142 // readings, from their median absolute deviation, as a standard deviation.
143 108 static float trimmedMean(std::vector<float> v, float& spread) {
144 108 spread = 0;
145
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 108 times.
108 if (v.empty()) {
146 ✗ return 0;
147 }
148
1/2
✓ Branch 0 taken 108 times.
✗ Branch 1 not taken.
108 std::sort(v.begin(), v.end());
149 108 float mid = v[v.size() / 2];
150 108 std::vector<float> dev;
151
2/2
✓ Branch 0 taken 1188 times.
✓ Branch 1 taken 108 times.
1296 for (float f : v) {
152
1/2
✓ Branch 0 taken 1188 times.
✗ Branch 1 not taken.
1188 dev.push_back(std::fabs(f - mid));
153 }
154
2/4
✓ Branch 0 taken 108 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 108 times.
✗ Branch 3 not taken.
108 spread = 1.4826f * median(dev);
155 108 size_t trim = v.size() / 5;
156 108 float sum = 0;
157
2/2
✓ Branch 0 taken 792 times.
✓ Branch 1 taken 108 times.
900 for (size_t x = trim; x < v.size() - trim; x++) {
158 792 sum += v[x];
159 }
160 108 return sum / (v.size() - 2 * trim);
161 108 }
162
163 32 void CurrentBasedPixelCountPixelStringTester::measureWindow(PortSearch& p, int start, int len) {
164 32 p.litStart = start;
165 32 p.litLen = len;
166 32 }
167
168 3 void CurrentBasedPixelCountPixelStringTester::startGroup() {
169
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 for (int x = 0; x < (int)ports.size(); x++) {
170 3 PortSearch& p = ports[x];
171 3 p.testing = OutputMonitor::INSTANCE.isPortInGroup(curGroup, x);
172 3 p.phase = PHASE_BASE;
173 3 p.count = -1;
174 3 p.repeats = 0;
175 3 p.dSum = 0;
176 3 p.errSq = 0;
177 3 p.spreads.clear();
178 3 p.fullWindows.clear();
179 3 p.samples.clear();
180 3 p.darkSamples.clear();
181 3 p.darkAfterSamples.clear();
182 3 measureWindow(p, 0, 0);
183 }
184 3 state = STATE_MEASURE;
185 3 frameInMeasure = 0;
186 3 sampleFrames = SAMPLE_FRAMES;
187 3 part = 0;
188 3 }
189
190 // The pixel to read next while finding the last one: either side of the
191 // count the last window's current suggests, then halfway between the last
192 // pixel known lit and the first known not to be.
193 6 static int nextProbe(int lit, int unlit, int guess) {
194
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
6 if (guess - 1 > lit && guess - 1 < unlit) {
195 3 return guess - 1;
196 }
197
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 if (guess > lit && guess < unlit) {
198 3 return guess;
199 }
200 ✗ return (lit + unlit) / 2;
201 }
202
203 // Takes one measurement for a port, the lit current less the dark one and
204 // how uncertain that is, and picks what it shows next. Returns true once
205 // the port's count is known.
206 36 bool CurrentBasedPixelCountPixelStringTester::step(PortSearch& p, int port, float d, float err) {
207
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 33 times.
36 if (p.phase == PHASE_BASE) {
208 // three dark readings, for the spread of the port's sensor
209 3 p.phase = PHASE_FIRST_WINDOW;
210
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 measureWindow(p, 0, WINDOW);
211 3 return false;
212 }
213 // A window is lit if it draws more than half a pixel's current (or, for
214 // the first, any current at all). A measurement that does not clear
215 // that by twice its uncertainty is taken again, and the readings are
216 // decided together.
217
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 3 times.
33 float threshold = p.phase == PHASE_FIRST_WINDOW ? ANY_PIXEL_MA : 0.5f * p.perPixel;
218 33 p.dSum += d;
219 33 p.errSq += err * err;
220 33 ++p.repeats;
221 33 float mean = p.dSum / p.repeats;
222 33 float meanErr = std::sqrt(p.errSq) / p.repeats;
223
5/6
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 23 times.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
✓ Branch 5 taken 23 times.
33 if (std::fabs(mean - threshold) < 2 * meanErr && p.repeats < MAX_REPEATS) {
224
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 LogDebug(VB_CHANNELOUT, "Pixel count: port %d phase %d lit %d+%d: %+.1f mA (mean %+.1f +/- %.1f over %d, threshold %.1f) unclear, again\n",
225 port + 1, p.phase, p.litStart, p.litLen, d, mean, meanErr, p.repeats, threshold);
226 10 return false;
227 }
228 23 bool present = mean > threshold;
229
3/4
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
23 LogDebug(VB_CHANNELOUT, "Pixel count: port %d phase %d lit %d+%d: %+.1f mA (mean %+.1f +/- %.1f over %d, threshold %.1f, per pixel %.1f) %s\n",
230 port + 1, p.phase, p.litStart, p.litLen, d, mean, meanErr, p.repeats, threshold, p.perPixel,
231 present ? "lit" : "dark");
232 23 p.dSum = 0;
233 23 p.errSq = 0;
234 23 p.repeats = 0;
235 23 d = mean;
236
237 // The end is in the window starting at lo: find its last pixel, one at a
238 // time, starting either side of the count the window's current suggests.
239 9 auto probe = [&]() {
240
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 6 times.
9 if (p.unlitPixel - p.litPixel <= 1) {
241 3 p.count = p.unlitPixel;
242 3 return true;
243 }
244 6 measureWindow(p, nextProbe(p.litPixel, p.unlitPixel, p.guess), 1);
245 6 return false;
246 23 };
247 7 auto halve = [&]() {
248
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 4 times.
7 if (p.hi - p.lo <= WINDOW) {
249 3 p.phase = PHASE_END;
250 3 p.litPixel = p.lo; // a lit window's first pixel is there
251 3 p.unlitPixel = p.hi;
252
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 p.guess = p.lo + std::clamp((int)std::lround(p.loCurrent / p.perPixel), 1, p.hi - p.lo);
253 3 return probe();
254 }
255 4 int mid = p.lo + ((p.hi - p.lo) / WINDOW / 2) * WINDOW;
256
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (mid <= p.lo) {
257 ✗ mid = p.lo + WINDOW;
258 }
259 4 p.phase = PHASE_HALVING;
260 4 measureWindow(p, mid, WINDOW);
261 4 return false;
262 23 };
263 // A window found lit proves every window before it full: the per-pixel
264 // current is taken from those, not from the first window alone, which is
265 // read while the port's dark current is still climbing after power-on.
266 10 auto foundLit = [&]() {
267
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 if (p.litStart > p.lo) {
268 10 p.fullWindows.push_back(p.loCurrent);
269
2/4
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
10 p.perPixel = median(p.fullWindows) / WINDOW;
270 }
271 10 p.lo = p.litStart;
272 10 p.loCurrent = d;
273 10 };
274
275 23 bool done = false;
276
4/5
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
23 switch (p.phase) {
277 3 case PHASE_FIRST_WINDOW:
278
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (!present) {
279 ✗ p.count = 0;
280 ✗ done = true;
281 ✗ break;
282 }
283 3 p.perPixel = d / WINDOW;
284 3 p.lo = 0;
285 3 p.loCurrent = d;
286 3 p.hi = p.maxPixels;
287 3 p.phase = PHASE_DOUBLING;
288
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (2 * WINDOW > p.maxPixels) {
289 ✗ done = halve();
290 } else {
291
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 measureWindow(p, WINDOW, WINDOW);
292 }
293 3 break;
294 10 case PHASE_DOUBLING:
295
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
10 if (present) {
296
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 foundLit();
297 8 int next = p.lo * 2;
298
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7 times.
8 if (next + WINDOW > p.maxPixels) {
299
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 done = halve();
300 } else {
301
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 measureWindow(p, next, WINDOW);
302 }
303 } else {
304 2 p.hi = p.litStart;
305
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 done = halve();
306 }
307 10 break;
308 4 case PHASE_HALVING:
309
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if (present) {
310
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 foundLit();
311 } else {
312 2 p.hi = p.litStart;
313 }
314
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 done = halve();
315 4 break;
316 6 case PHASE_END:
317
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 if (present) {
318 3 p.litPixel = p.litStart;
319 } else {
320 3 p.unlitPixel = p.litStart;
321 }
322
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 done = probe();
323 6 break;
324 ✗ default:
325 ✗ done = true;
326 ✗ break;
327 }
328
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 20 times.
23 if (done) {
329 3 p.phase = PHASE_DONE;
330
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 measureWindow(p, 0, 0);
331
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 LogInfo(VB_CHANNELOUT, "Pixel count: port %d has %d pixels on the wire (%d configured, %d nulls)\n", port + 1, p.count, std::max(p.configured, 0), p.nulls);
332 }
333 23 return done;
334 }
335
336 2946 void CurrentBasedPixelCountPixelStringTester::prepareTestData(int cycleCount, float percentOfCycle) {
337
1/2
✓ Branch 0 taken 2946 times.
✗ Branch 1 not taken.
2946 uint64_t now = GetMonotonicTimeMS();
338 // A new test: the first call, or the cycle count starting again (each
339 // test counts from 0). Not "cycleCount == 0": the cycles can already
340 // have advanced by the first frame a string asks for.
341
4/4
✓ Branch 0 taken 2944 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 2943 times.
2946 bool newTest = (state == -1) || (cycleCount < lastCycleCount);
342 2946 lastCycleCount = cycleCount;
343
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2943 times.
2946 if (newTest) {
344 3 curGroup = 0;
345
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 OutputMonitor::INSTANCE.lockToGroup(curGroup);
346
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 SetChannelOutputRefreshRate(20);
347
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
4 for (int x = 0; x < (int)ports.size(); x++) {
348
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 OutputMonitor::INSTANCE.SetPixelCount(x, -1, -1);
349 // Start each port from its configuration as it is now: the
350 // configured count, nulls and search limit are worked out from
351 // the string on the port's next frame. Kept from the first run,
352 // a string fixed after a count was still searched (and reported)
353 // against the old configuration until fppd restarted.
354 1 ports[x] = PortSearch();
355 }
356 3 state = STATE_WARMUP;
357 3 stateStartMS = now;
358 }
359
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 2928 times.
2946 if (state == STATE_WARMUP) {
360
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 15 times.
18 if (now - stateStartMS >= WARMUP_MS) {
361
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 startGroup();
362 }
363 2943 return;
364 }
365
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 2916 times.
2928 if (state != STATE_MEASURE) {
366 12 return;
367 }
368
2/2
✓ Branch 0 taken 1728 times.
✓ Branch 1 taken 1188 times.
2916 if (++frameInMeasure <= SETTLE_FRAMES) {
369 1728 return;
370 }
371
1/2
✓ Branch 0 taken 1188 times.
✗ Branch 1 not taken.
1188 std::vector<float> values = OutputMonitor::INSTANCE.GetPortCurrentValues();
372
2/2
✓ Branch 0 taken 1188 times.
✓ Branch 1 taken 1188 times.
2376 for (int x = 0; x < (int)ports.size(); x++) {
373
4/8
✓ Branch 0 taken 1188 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1188 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1188 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1188 times.
✗ Branch 7 not taken.
1188 if (ports[x].testing && ports[x].phase != PHASE_DONE && x < (int)values.size()) {
374
5/6
✓ Branch 0 taken 396 times.
✓ Branch 1 taken 792 times.
✓ Branch 2 taken 396 times.
✓ Branch 3 taken 396 times.
✓ Branch 4 taken 1188 times.
✗ Branch 5 not taken.
1188 (part == 0 ? ports[x].darkSamples : part == 1 ? ports[x].samples : ports[x].darkAfterSamples).push_back(values[x]);
375 }
376 }
377
2/2
✓ Branch 0 taken 1080 times.
✓ Branch 1 taken 108 times.
1188 if (frameInMeasure < SETTLE_FRAMES + sampleFrames) {
378 1080 return;
379 }
380 108 frameInMeasure = 0;
381
2/2
✓ Branch 0 taken 72 times.
✓ Branch 1 taken 36 times.
108 if (part < 2) {
382 // dark, then the patterns lit, then dark again
383 72 ++part;
384 72 return;
385 }
386 36 part = 0;
387 36 bool allDone = true;
388 36 bool repeating = false;
389
2/2
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 36 times.
72 for (int x = 0; x < (int)ports.size(); x++) {
390 36 PortSearch& p = ports[x];
391
2/4
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 36 times.
36 if (!p.testing || p.phase == PHASE_DONE) {
392 ✗ continue;
393 }
394 // The lit reading less the mean of the dark ones either side of it:
395 // a port's dark current climbs for a while after it powers on, and a
396 // string powered at both ends does not return to the same dark
397 // current once it has been lit.
398 float s0, s1, s2;
399
2/4
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 36 times.
✗ Branch 3 not taken.
36 float before = trimmedMean(p.darkSamples, s0);
400
2/4
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 36 times.
✗ Branch 3 not taken.
36 float lit = trimmedMean(p.samples, s1);
401
2/4
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 36 times.
✗ Branch 3 not taken.
36 float after = trimmedMean(p.darkAfterSamples, s2);
402
1/2
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
36 p.spreads.push_back(s0);
403
1/2
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
36 p.spreads.push_back(s1);
404
1/2
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
36 p.spreads.push_back(s2);
405
2/4
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 36 times.
✗ Branch 3 not taken.
36 float spread = median(p.spreads);
406 36 float n0 = std::max<size_t>(p.darkSamples.size(), 1);
407 36 float n1 = std::max<size_t>(p.samples.size(), 1);
408 36 float n2 = std::max<size_t>(p.darkAfterSamples.size(), 1);
409 // the error of the difference from the sensor's spread, and from the
410 // dark current having moved while it was read
411 36 float moved = std::fabs(after - before) / 2;
412 36 float err = std::sqrt(spread * spread * (1 / n1 + 0.25f / n0 + 0.25f / n2) + moved * moved + MIN_ERROR_MA * MIN_ERROR_MA);
413 36 p.base = (before + after) / 2;
414 36 p.darkSamples.clear();
415 36 p.samples.clear();
416 36 p.darkAfterSamples.clear();
417
3/4
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 33 times.
✓ Branch 3 taken 3 times.
36 if (!step(p, x, lit - p.base, err)) {
418 33 allDone = false;
419 33 repeating |= p.repeats > 0;
420 }
421 }
422
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 26 times.
36 sampleFrames = repeating ? REPEAT_SAMPLE_FRAMES : SAMPLE_FRAMES;
423
2/2
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 3 times.
36 if (!allDone) {
424 33 return;
425 }
426
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 for (int x = 0; x < (int)ports.size(); x++) {
427
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (ports[x].testing) {
428 // null nodes are pixels on the wire, but not configured ones
429
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 OutputMonitor::INSTANCE.SetPixelCount(x, std::max(ports[x].count - ports[x].nulls, 0), ports[x].configured);
430 }
431 }
432
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (curGroup < (OutputMonitor::INSTANCE.getGroupCount() - 1)) {
433 ✗ ++curGroup;
434 ✗ OutputMonitor::INSTANCE.lockToGroup(curGroup);
435 ✗ state = STATE_WARMUP;
436 ✗ stateStartMS = now;
437 ✗ for (auto& p : ports) {
438 ✗ measureWindow(p, 0, 0);
439 }
440 } else {
441
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 OutputMonitor::INSTANCE.lockToGroup(-1);
442 3 state = STATE_DISPLAY_PIXEL;
443 // show each string's last pixel
444
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 for (auto& p : ports) {
445
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 measureWindow(p, p.count > 0 ? p.count - 1 : 0, p.count > 0 ? 1 : 0);
446 }
447 }
448
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1185 times.
1188 }
449
450 2946 uint8_t* CurrentBasedPixelCountPixelStringTester::createTestData(PixelString* ps, int cycleCount, float percentOfCycle, uint8_t* inChannelData, uint32_t& newLen) {
451 // PixelString allocates every string at least 2400 bytes for testers
452 // like this one, and drivers have always been handed 2000 of them; a
453 // string configured longer than that is searched over its own length.
454 2946 uint32_t pixelLen = std::max<int>(2000, ps->m_outputBytes - (int)ps->m_preamble.size());
455 // everything below indexes from pixel 0, so work past any protocol
456 // preamble and hand back the whole buffer at the end
457 2946 uint8_t* buffer = ps->pixelDataStart();
458 2946 newLen = pixelLen + ps->m_preamble.size();
459 2946 int currentPort = ps->m_portNumber;
460
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2946 times.
2948 while ((int)ports.size() <= currentPort) {
461
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 ports.emplace_back();
462 }
463 2946 PortSearch& p = ports[currentPort];
464
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2943 times.
2946 if (p.configured == -1) {
465 3 p.configured = 0;
466 3 p.nulls = 0;
467
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 for (auto& a : ps->m_virtualStrings) {
468 3 p.configured += a.pixelCount;
469 3 p.nulls += a.startNulls + a.endNulls;
470 }
471 // Null nodes are physical pixels, sent zeros: the search counts what
472 // is on the wire, and the nulls come off the count it reports. A
473 // configured port is searched a little past its configuration (enough
474 // to find pixels tucked away unconfigured), not to the end of the
475 // buffer: the test frame is cut there too.
476 3 int onWire = p.configured + p.nulls;
477
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 int cpn = ps->m_virtualStrings.empty() ? 3 : ps->m_virtualStrings[0].channelsPerNode();
478
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 p.maxPixels = std::min(onWire > 0 ? onWire + 64 : MAX_PIXELS, (int)(pixelLen / cpn));
479 }
480 2946 memset(buffer, 0, pixelLen);
481
1/2
✓ Branch 0 taken 2946 times.
✗ Branch 1 not taken.
2946 if (!ps->m_virtualStrings.empty()) {
482
1/2
✓ Branch 0 taken 2946 times.
✗ Branch 1 not taken.
2946 int cpn = ps->m_virtualStrings[0].channelsPerNode();
483 2946 newLen = std::min(pixelLen, (uint32_t)(p.maxPixels * cpn)) + ps->m_preamble.size();
484 }
485
3/4
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 2931 times.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
2946 if (state == STATE_WARMUP && curGroup == 0) {
486 // everything dimly on for a moment first, as the outputs come up
487 15 memset(buffer, 10, pixelLen);
488
9/10
✓ Branch 0 taken 2733 times.
✓ Branch 1 taken 198 times.
✓ Branch 2 taken 2718 times.
✓ Branch 3 taken 15 times.
✓ Branch 4 taken 906 times.
✓ Branch 5 taken 1812 times.
✓ Branch 6 taken 921 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 921 times.
✓ Branch 9 taken 2010 times.
2931 } else if (p.litLen > 0 && !(state == STATE_MEASURE && part != 1) && !ps->m_virtualStrings.empty()) {
489
1/2
✓ Branch 0 taken 921 times.
✗ Branch 1 not taken.
921 int cpn = ps->m_virtualStrings[0].channelsPerNode();
490 921 int start = std::min(p.litStart, p.maxPixels);
491 921 int len = std::min(p.litLen, p.maxPixels - start);
492
1/2
✓ Branch 0 taken 921 times.
✗ Branch 1 not taken.
921 if ((uint32_t)((start + len) * cpn) <= pixelLen) {
493 921 memset(&buffer[start * cpn], 0xFF, len * cpn);
494 }
495 }
496 2946 return ps->m_outputBuffer;
497 }
498 ✗ CurrentBasedPixelCountPixelStringTester::Status CurrentBasedPixelCountPixelStringTester::getCurrentStatus() const {
499 ✗ if (state == STATE_DISPLAY_PIXEL) {
500 ✗ return CurrentBasedPixelCountPixelStringTester::Status::Complete;
501 }
502 ✗ if (state == -1) {
503 ✗ return CurrentBasedPixelCountPixelStringTester::Status::NotRun;
504 }
505 ✗ return CurrentBasedPixelCountPixelStringTester::Status::Running;
506 }
507
508 CurrentBasedPixelCountPixelStringTester CurrentBasedPixelCountPixelStringTester::INSTANCE;
509