| 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 |