GCC Code Coverage Report


Directory: ./
File: non-gpl/FalconV5Support/FalconV5Support.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 199 370 53.8%
Functions: 20 31 64.5%
Branches: 170 542 31.4%

Line Branch Exec Source
1 /*
2 * This file is part of the Falcon Player (FPP) and is Copyright (C)
3 * 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 CC-BY-ND as described in the
10 * included LICENSE.CC-BY-ND file. This file may be modified for
11 * personal use, but modified copies MAY NOT be redistributed in any form.
12 */
13 #include "fpp-pch.h"
14
15 #include "fpp-json.h"
16
17 #include <unistd.h>
18
19 #include "FalconV5Support.h"
20
21 #include <stdexcept>
22 #include "OutputMonitor.h"
23 #include "Warnings.h"
24 #include "common.h"
25 #include "channeloutput/PixelString.h"
26 #include "channeloutput/serialutil.h"
27 #include "util/GPIOUtils.h"
28
29 #include "util/BBBPruUtils.h"
30 #include "pru/SMEMRing.hp"
31 #include "util/BBBUtils.h"
32 #include <sys/wait.h>
33 #include <arm_neon.h>
34
35 extern "C" {
36 bool encodeFalconV5Packet(const Json::Value& description, uint8_t* packet);
37 bool decodeFalconV5Packet(uint8_t* packet, Json::Value& result);
38 }
39
40 class FalconV5Listener {
41 public:
42 17 FalconV5Listener(const Json::Value& config) {
43
2/4
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
17 pin = config["pin"].asString();
44
1/2
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
17 const PinCapabilities& p = PinCapabilities::getPinByName(pin);
45 // the cape config comes from the cape EEPROM; a pin this board does
46 // not have fails the output (Init() catches this) instead of being
47 // dereferenced as null below
48
3/4
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 16 times.
17 if (p.ptr() == nullptr) {
49
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.
1 throw std::runtime_error("FalconV5 listener pin '" + pin + "' does not exist on this board");
50 }
51
3/6
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
64 p.configPin("pru0in", false, "FalconV5 Listener");
52
53
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 const BBBPinCapabilities* pc = (const BBBPinCapabilities*)p.ptr();
54 16 offset = pc->pruPin(0);
55 17 }
56 16 ~FalconV5Listener() {
57 16 const PinCapabilities& p = PinCapabilities::getPinByName(pin);
58 16 p.releasePin();
59 16 }
60 std::string pin;
61 int offset = 0;
62 };
63
64 // structure of the data at the start of the PRU ram
65 // that the pru program expects to see
66 typedef struct {
67 // 0xFFFF to abort
68 volatile uint32_t command;
69 // bytes captured in the last completed listen window. Written by the
70 // listener only when the string PRU closes the window, never while a
71 // capture is in progress; 'captures' is bumped right after it, so a
72 // changed count means a new, complete length is there to read.
73 volatile uint32_t length;
74 volatile uint32_t captures;
75 // 1 from the moment a listen window opens until the capture is published.
76 // The ARM reads it before and after copying the buffer: a window can open
77 // while the ARM is still reading the previous capture (the frame tick
78 // lands inside the next window whenever a frame overran its period), and
79 // the listener then overwrites the buffer from the start, splicing the
80 // new reply's head onto the old reply's tail. Two replies scrambled with
81 // different random keys decode into garbage that still parses.
82 volatile uint32_t busy;
83 } __attribute__((__packed__)) FalconV5PRUData;
84
85 class PRUControl {
86 public:
87 16 PRUControl() {
88
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 const std::string filename = "/opt/fpp/src/non-gpl/FalconV5Support/FalconV5PRUListener_pru0.out";
89
90
3/8
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 16 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
16 pru = new BBBPru(0, true, false);
91 16 pruData = (FalconV5PRUData*)pru->data_ram;
92 16 data = pru->shared_ram;
93 // never clear the shared RAM here: on the AM62x the string PRU's
94 // data ring lives there and is already attached and live by the
95 // time the listener starts - zeroing its counters desyncs the ring
96 // and wedges the string PRU in its ring wait. The listener's own
97 // state lives in PRU0's data RAM (still cleared above) and the
98 // capture bytes are rewritten before each capture completes.
99
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 pru->run(filename, false);
100 16 }
101 16 ~PRUControl() {
102 16 pruData->command = 0xFFFF;
103 16 __asm__ __volatile__("" ::
104 : "memory");
105 16 pru->stop();
106
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 delete pru;
107 16 }
108
109 BBBPru* pru = nullptr;
110 FalconV5PRUData* pruData = nullptr;
111 uint8_t* data = nullptr;
112 };
113
114 20 FalconV5Support::FalconV5Support() {
115
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 OutputMonitor::INSTANCE.setSmartReceiverEventCallback([this](int port, int index, const std::string& cmd) {
116 279 PendingToggle pt;
117 279 pt.port = (int16_t)port;
118 279 pt.index = (uint8_t)index;
119
2/4
✓ Branch 0 taken 279 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 279 times.
✗ Branch 3 not taken.
279 if (cmd == "ToggleOutput") {
120 279 pt.cmd = ToggleCommand::Toggle;
121 ✗ } else if (cmd == "ResetOutput") {
122 ✗ pt.cmd = ToggleCommand::Reset;
123 } else {
124 ✗ return;
125 }
126 279 pendingToggle.store(pt);
127 });
128 20 }
129 20 FalconV5Support::~FalconV5Support() {
130 20 OutputMonitor::INSTANCE.setSmartReceiverEventCallback(nullptr);
131
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 20 times.
40 for (auto rc : receiverChains) {
132
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 delete rc;
133 }
134
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
20 for (auto& p : muxPins) {
135 ✗ p->releasePin();
136 }
137
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 20 times.
36 for (auto l : listeners) {
138
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 delete l;
139 }
140
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 4 times.
20 if (pru) {
141
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 delete pru;
142 }
143 20 listeners.clear();
144 20 receiverChains.clear();
145 20 }
146 17 void FalconV5Support::addListeners(const Json::Value& config) {
147
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 16 times.
33 for (int x = 0; x < config["listenerPins"].size(); x++) {
148
8/14
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 17 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 16 times.
✓ Branch 7 taken 1 times.
✓ Branch 8 taken 16 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 16 times.
✓ Branch 12 taken 1 times.
✗ Branch 13 not taken.
18 listeners.push_back(new FalconV5Listener(config["listenerPins"][x]));
149 }
150
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 if (!listeners.empty()) {
151
2/6
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 16 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
16 pru = new PRUControl();
152 }
153
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 for (int x = 0; x < config["muxPins"].size(); x++) {
154 ✗ std::string p = config["muxPins"][x].asString();
155 ✗ if (PinCapabilities::getPinByName(p).ptr() == nullptr) {
156 ✗ throw std::runtime_error("FalconV5 mux pin '" + p + "' does not exist on this board");
157 }
158 ✗ muxPins.push_back(PinCapabilities::getPinByName(p).ptr());
159 ✗ muxPins.back()->configPin("gpio", true, "FalconV5 Mux Pin");
160 ✗ muxPins.back()->setValue(0);
161 ✗ }
162 16 }
163
164 ✗ static inline void maskBit(int len, int bit, uint8_t* s, uint8_t* d) {
165 ✗ uint8_t m1 = 1 << bit;
166 ✗ const uint8x8_t bitmask = { m1, m1, m1, m1, m1, m1, m1, m1 };
167 ✗ int idx = 0;
168 ✗ while (idx < len) {
169 ✗ uint8x8_t work = vld1_u8(s); // load 8 bytes
170 ✗ uint8x8_t msk = vand_u8(work, bitmask);
171 vst1_u8(d, msk);
172 ✗ s += 8;
173 ✗ d += 8;
174 ✗ idx += 8;
175 }
176 ✗ }
177 ✗ static void printDataBuf(int len, uint8_t* data) {
178 ✗ printf("Len: %d \n", len);
179 ✗ for (int x = 0; x < 40; x++)
180 ✗ printf("%02X %s", data[x], (x % 5 != 4 ? "" : " "));
181 ✗ printf("\n");
182 ✗ for (int x = 0; x < 40; x++)
183 ✗ printf("%02X %s", data[x + 40], (x % 5 != 4 ? "" : " "));
184 ✗ printf("\n");
185 ✗ for (int x = 0; x < 40; x++)
186 ✗ printf("%02X %s", data[x + 80], (x % 5 != 4 ? "" : " "));
187 ✗ printf("\n\n");
188 ✗ }
189 ✗ static uint8_t readByte(uint8_t* data, int& pos) {
190 ✗ constexpr int superSample = 5;
191 ✗ constexpr int sampleOffset = 2;
192 // find start bit
193 ✗ uint8_t ret = 0;
194 ✗ while (data[pos] != 0) {
195 ✗ ++pos;
196 }
197 ✗ pos += sampleOffset + superSample;
198 ✗ if (data[pos])
199 ✗ ret |= 0x1;
200 ✗ pos += superSample;
201 ✗ if (data[pos])
202 ✗ ret |= 0x2;
203 ✗ pos += superSample;
204 ✗ if (data[pos])
205 ✗ ret |= 0x4;
206 ✗ pos += superSample;
207 ✗ if (data[pos])
208 ✗ ret |= 0x8;
209 ✗ pos += superSample;
210 ✗ if (data[pos])
211 ✗ ret |= 0x10;
212 ✗ pos += superSample;
213 ✗ if (data[pos])
214 ✗ ret |= 0x20;
215 ✗ pos += superSample;
216 ✗ if (data[pos])
217 ✗ ret |= 0x40;
218 ✗ pos += superSample;
219 ✗ if (data[pos])
220 ✗ ret |= 0x80;
221 ✗ pos += superSample; // should be in the stop bit now
222 ✗ return ret;
223 }
224
225 17 float FalconV5Support::queryFrameShare() const {
226
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 16 times.
17 if (!pru) {
227 1 return 0.0f;
228 }
229 16 int receivers = 0;
230
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 16 times.
32 for (auto c : maxCount) {
231 16 receivers += c;
232 }
233
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 if (receivers == 0) {
234 ✗ return 0.0f;
235 }
236 16 return (float)receivers / (float)(receivers + maxCount.size());
237 }
238
239 88450 void FalconV5Support::stageListenerData() {
240
2/2
✓ Branch 0 taken 15168 times.
✓ Branch 1 taken 73282 times.
88450 if (!pru) {
241 88450 return;
242 }
243 // The listener publishes the length and bumps the capture count only
244 // when the string PRU closes the window, and holds 'busy' from the
245 // moment a window opens until then. A window can open while we are
246 // still copying the previous capture (the frame tick lands inside the
247 // next window whenever a frame overran its period), and the listener
248 // then overwrites the buffer from the start, splicing the new reply's
249 // head onto the old reply's tail - two replies scrambled with different
250 // random keys, which decode into garbage that still parses. So: count
251 // first, copy, then busy and count again; anything that moved means the
252 // copy is a splice and is dropped. The next query repeats the question.
253 // both the pump thread and the output thread may call this
254
1/2
✓ Branch 0 taken 73282 times.
✗ Branch 1 not taken.
73282 std::unique_lock<std::mutex> lock(stageLock);
255 73282 uint32_t busy0 = pru->pruData->busy;
256 73282 uint32_t captures = pru->pruData->captures;
257
2/2
✓ Branch 0 taken 73042 times.
✓ Branch 1 taken 240 times.
73282 if (captures == lastCaptures) {
258 73042 return;
259 }
260 240 lastCaptures = captures;
261 240 uint32_t len = pru->pruData->length;
262 // the listener stops storing at its shared RAM reservation; on the
263 // AM62x the string PRU's ring starts immediately above it
264
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 240 times.
240 if (len > SMEM_RING_LISTENER_BYTES) {
265 ✗ len = SMEM_RING_LISTENER_BYTES;
266 }
267
1/2
✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
240 if (!len) {
268 240 return;
269 }
270 ✗ if (stageBuf.size() < SMEM_RING_LISTENER_BYTES + 8) {
271 ✗ stageBuf.resize(SMEM_RING_LISTENER_BYTES + 8);
272 ✗ workBuf.resize(SMEM_RING_LISTENER_BYTES + 8);
273 }
274 // copy exactly len: the listener publishes whole 32 byte stores, so len
275 // is a multiple of the 8 bytes maskBit() reads at a time, and the clamp
276 // above keeps it inside the capture reservation. Reading past it ran off
277 // the end of the AM335x's 12KB shared RAM.
278 ✗ pru->pru->memcpyToPRU(&stageBuf[0], pru->data, len);
279 ✗ uint32_t busy1 = pru->pruData->busy;
280 ✗ uint32_t captures2 = pru->pruData->captures;
281 ✗ if (busy0 || busy1 || captures2 != captures) {
282 ✗ lastCaptures = captures2;
283 ✗ ++tornCaptures;
284 ✗ LogExcess(VB_CHANNELOUT, "FalconV5: listener capture %u overwritten while being read, dropped (%u so far)\n", captures, tornCaptures);
285 ✗ return;
286 }
287 ✗ stageLen = len;
288 ✗ stageValid = true;
289
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 73282 times.
73282 }
290
291 568 void FalconV5Support::processListenerData() {
292
2/2
✓ Branch 0 taken 148 times.
✓ Branch 1 taken 420 times.
568 if (!pru) {
293 148 return;
294 }
295 // no pump thread polling for us (AM335x), or it has not looked yet
296 420 stageListenerData();
297 420 uint32_t len = 0;
298 {
299
1/2
✓ Branch 0 taken 420 times.
✗ Branch 1 not taken.
420 std::unique_lock<std::mutex> lock(stageLock);
300
1/2
✓ Branch 0 taken 420 times.
✗ Branch 1 not taken.
420 if (!stageValid) {
301 420 return;
302 }
303 ✗ std::swap(stageBuf, workBuf);
304 ✗ len = stageLen;
305 ✗ stageValid = false;
306
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 420 times.
420 }
307 ✗ uint8_t* buf = &workBuf[0];
308 ✗ for (auto& l : listeners) {
309 // readByte() walks up to a byte cell past 'len' while finishing the
310 // last byte, so the sample buffer carries a zeroed tail for it
311 uint8_t data[SMEM_RING_LISTENER_BYTES + 64];
312 uint8_t packet[1024];
313 ✗ maskBit(len, l->offset, buf, data);
314 ✗ memset(data + len, 0, 64);
315 ✗ int pidx = 0;
316 ✗ int pos = 0;
317 ✗ while (pos < len) {
318 ✗ packet[pidx++] = readByte(data, pos);
319 }
320 ✗ if (pidx > 1) {
321 // printf("L: %d\n", l->offset);
322 // printDataBuf(len, data);
323 // printDataBuf(pidx, packet);
324 ✗ Json::Value json;
325 ✗ if (pidx != 0 && decodeFalconV5Packet(packet, json)) {
326 ✗ int port = json["port"].asInt();
327 // printf("Port: %d Index: %d\n", port, json["index"].asInt());
328 // printf("%s\n", SaveJsonToString(json, " ").c_str());
329 ✗ for (auto& rc : receiverChains) {
330 ✗ if (rc->getPixelStrings()[0]->m_portNumber == port) {
331 ✗ rc->handleQueryResponse(json);
332 }
333 }
334 }
335 ✗ }
336 }
337 }
338
339 ✗ void FalconV5Support::setCurrentMux(int i) {
340 ✗ curMux = i;
341 ✗ for (auto& p : muxPins) {
342 ✗ int d = i & 0x1;
343 ✗ p->setValue(d);
344 ✗ i >>= 1;
345 }
346 ✗ }
347 20 FalconV5Support::ReceiverChain::ReceiverChain(PixelString* p1, PixelString* p2, PixelString* p3, PixelString* p4, int grp, int m, bool so) :
348 20 group(grp), mux(m), numReceivers(1), sendOnly(so) {
349 20 strings[0] = p1;
350 20 strings[1] = p2;
351 20 strings[2] = p3;
352 20 strings[3] = p4;
353
2/2
✓ Branch 0 taken 80 times.
✓ Branch 1 taken 20 times.
100 for (auto& s : strings) {
354 // a chain head in the last three strings has fewer than four
355
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 80 times.
80 if (s == nullptr) {
356 ✗ continue;
357 }
358 80 uint32_t srCount = 1;
359
2/2
✓ Branch 0 taken 80 times.
✓ Branch 1 taken 80 times.
160 for (auto& vs : s->m_virtualStrings) {
360
2/4
✓ Branch 0 taken 80 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 80 times.
80 if (vs.isFalconV5SmartReceiver()) {
361 ✗ srCount++;
362 }
363 }
364 80 numReceivers = std::max(numReceivers, srCount);
365 }
366 20 }
367 20 FalconV5Support::ReceiverChain* FalconV5Support::addReceiverChain(PixelString* p1, PixelString* p2, PixelString* p3, PixelString* p4, int group, int m, bool sendOnly) {
368 // the mux comes from the cape config (falconV5ListenerMux); a negative
369 // one compares as huge against the sizes below and grew maxCount until
370 // memory ran out
371
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 19 times.
20 if (m < 0) {
372 1 m = 0;
373 }
374
3/8
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 20 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
20 FalconV5Support::ReceiverChain* rc = new FalconV5Support::ReceiverChain(p1, p2, p3, p4, group, m, sendOnly);
375
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 receiverChains.push_back(rc);
376
377
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 17 times.
20 if (sendOnly) {
378 // V4 chains only get the config packet: keep them out of the query
379 // scheduling entirely so no query/listen frames are generated
380 3 return rc;
381 }
382
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 1 times.
17 if (m >= queryData.size()) {
383
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 queryData.resize(m + 1);
384
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 16 times.
32 while (m >= maxCount.size()) {
385
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 maxCount.push_back(0);
386 }
387 }
388
2/4
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
17 queryData[m][group].push_back(rc);
389
390 17 int newM = 0;
391
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 17 times.
34 for (auto& g : queryData[m]) {
392
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 17 times.
35 for (auto& rc : g.second) {
393 18 newM += rc->getReceiverCount();
394 }
395 }
396 17 maxCount[m] = std::max(maxCount[m], newM);
397 17 return rc;
398 }
399
400 80 static bool generateConfigJSONForPixelString(const PixelString* p, Json::Value& config) {
401
5/6
✓ Branch 0 taken 80 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 60 times.
✓ Branch 3 taken 20 times.
✓ Branch 4 taken 9 times.
✓ Branch 5 taken 51 times.
80 if (p && (p->smartReceiverType == PixelString::ReceiverType::FalconV5 || p->smartReceiverType == PixelString::ReceiverType::FalconV4)) {
402
1/2
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
29 Json::Value v;
403
3/6
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 29 times.
✗ Branch 5 not taken.
29 v["startChannels"].append(0);
404
405 29 int srCount = 0;
406 29 int curChanCount = 0;
407
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 29 times.
58 for (auto& vs : p->m_virtualStrings) {
408 29 curChanCount += vs.chMapCount;
409
2/4
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 29 times.
29 if (vs.isFalconV5SmartReceiver()) {
410 ✗ srCount++;
411 ✗ v["startChannels"].append(curChanCount);
412 }
413 }
414 29 srCount++;
415
3/6
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 29 times.
✗ Branch 5 not taken.
29 v["startChannels"].append(curChanCount);
416
2/4
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
✗ Branch 3 not taken.
29 config["ports"].append(v);
417 29 return true;
418 29 }
419
1/2
✓ Branch 0 taken 51 times.
✗ Branch 1 not taken.
51 Json::Value v;
420
3/6
✓ Branch 0 taken 51 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 51 times.
✗ Branch 5 not taken.
51 v["startChannels"].append(0);
421
2/4
✓ Branch 0 taken 51 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
51 config["ports"].append(v);
422 51 return false;
423 51 }
424 20 bool FalconV5Support::ReceiverChain::generateConfigPacket(uint8_t* packet) const {
425
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 Json::Value config;
426
2/4
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
20 config["type"] = "config";
427
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 bool hasSR = generateConfigJSONForPixelString(strings[0], config);
428
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 hasSR |= generateConfigJSONForPixelString(strings[1], config);
429
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 hasSR |= generateConfigJSONForPixelString(strings[2], config);
430
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 hasSR |= generateConfigJSONForPixelString(strings[3], config);
431
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 if (hasSR) {
432
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 return encodeFalconV5Packet(config, packet);
433 }
434 ✗ return false;
435 20 }
436 240 bool FalconV5Support::ReceiverChain::generateQueryPacket(uint8_t* packet, int receiver) const {
437
1/2
✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
240 Json::Value config;
438
2/4
✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 240 times.
✗ Branch 3 not taken.
240 config["type"] = "query";
439
2/4
✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 240 times.
✗ Branch 3 not taken.
240 config["receiver"] = receiver;
440
1/2
✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
480 return encodeFalconV5Packet(config, packet);
441 240 }
442 ✗ bool FalconV5Support::ReceiverChain::generateToggleEFusePacket(uint8_t* packet, int receiver, int port) const {
443 ✗ Json::Value config;
444 ✗ config["type"] = "toggleFuse";
445 ✗ config["receiver"] = receiver;
446 ✗ config["port"] = port;
447 ✗ return encodeFalconV5Packet(config, packet);
448 ✗ }
449
450 17 bool FalconV5Support::ReceiverChain::generateNumberPackets(uint8_t* packet, uint8_t* packet2) const {
451
1/2
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
17 Json::Value config;
452
2/4
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
17 config["type"] = "number";
453
2/4
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
17 config["port"] = strings[0]->m_portNumber;
454
2/4
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
17 config["phase"] = 1;
455
2/4
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
17 if (encodeFalconV5Packet(config, packet)) {
456
2/4
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
17 config["phase"] = 2;
457
1/2
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
17 return encodeFalconV5Packet(config, packet2);
458 }
459 ✗ return false;
460 17 }
461 ✗ bool FalconV5Support::ReceiverChain::generateResetEFusePacket(uint8_t* packet, int receiver, int port) const {
462 ✗ Json::Value config;
463 ✗ config["type"] = "resetFuses";
464 ✗ config["receiver"] = receiver;
465 ✗ config["port"] = port;
466 ✗ return encodeFalconV5Packet(config, packet);
467 ✗ }
468
469 17 bool FalconV5Support::ReceiverChain::generateSetFusesPacket(uint8_t* packet, bool on) const {
470
1/2
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
17 Json::Value config;
471
2/4
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
17 config["type"] = "setFuses";
472
3/6
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 17 times.
✗ Branch 5 not taken.
17 config["state"] = on ? 1 : 0;
473
1/2
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
34 return encodeFalconV5Packet(config, packet);
474 17 }
475
476 ✗ bool FalconV5Support::ReceiverChain::generateResetFusesPacket(uint8_t* packet) const {
477 ✗ Json::Value config;
478 ✗ config["type"] = "resetFuses";
479 ✗ return encodeFalconV5Packet(config, packet);
480 ✗ }
481 240 bool FalconV5Support::ReceiverChain::generateQueryPacket(uint8_t* packet) {
482
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 240 times.
240 if (curReceiverQuery == numReceivers) {
483 ✗ curReceiverQuery = 0;
484 }
485 240 return generateQueryPacket(packet, curReceiverQuery++);
486 }
487 ✗ bool FalconV5Support::ReceiverChain::generatePixelCountPacket(uint8_t* packet) const {
488 ✗ Json::Value config;
489 ✗ config["type"] = "pixelCount";
490 ✗ return encodeFalconV5Packet(config, packet);
491 ✗ }
492
493 ✗ void FalconV5Support::ReceiverChain::handleQueryResponse(Json::Value& json) {
494 try {
495 ✗ int index = json["index"].asInt();
496 ✗ int port = json["port"].asInt();
497 ✗ int dial = json["dial"].asInt();
498 // decoded from a reply on the wire; a receiver has at most 4 ports
499 ✗ int numPorts = std::clamp(json["numPorts"].asInt(), 0, 4);
500 ✗ int id = json["id"].asInt();
501 // printf("Resp: Idx: %d Port: %d dial: %d NP: %d ID: %d\n", index, port, dial, numPorts, id);
502 ✗ for (int x = 0; x < numPorts; x++) {
503 ✗ OutputMonitor::INSTANCE.setSmartReceiverInfo(port + x % 4, index,
504 ✗ json["ports"][x]["fuseOn"].asBool(),
505 ✗ json["ports"][x]["fuseBlown"].asBool(),
506 ✗ json["ports"][x]["current"].asInt(),
507 ✗ json["ports"][x]["pixelCount"].asInt());
508 }
509 ✗ } catch (std::exception& e) {
510 ✗ LogWarn(VB_CHANNELOUT, "Error (%s) handling response JSON: %s\n", e.what(), SaveJsonToString(json, " ").c_str());
511 ✗ }
512 ✗ }
513
514 489 bool FalconV5Support::generateDynamicPacket(std::vector<std::array<uint8_t, 64>>& packets, bool& listen) {
515
2/2
✓ Branch 0 taken 129 times.
✓ Branch 1 taken 360 times.
489 if (maxCount.empty()) {
516 129 return false;
517 }
518
2/2
✓ Branch 0 taken 120 times.
✓ Branch 1 taken 240 times.
360 if (curCount == maxCount[curMux]) {
519 // finished this mux, but we need to wait a frame before changing muxes
520 120 ++curCount;
521 120 return false;
522
2/2
✓ Branch 0 taken 119 times.
✓ Branch 1 taken 121 times.
240 } else if (curCount > maxCount[curMux]) {
523 119 curCount = 0;
524 119 int newMux = curMux + 1;
525
1/2
✓ Branch 0 taken 119 times.
✗ Branch 1 not taken.
119 if (newMux >= maxCount.size()) {
526 119 newMux = 0;
527 119 triggerPixelCount = false;
528 }
529
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 119 times.
119 if (newMux != curMux) {
530 ✗ setCurrentMux(newMux);
531 }
532
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 119 times.
119 if (maxCount[newMux] == 0) {
533 ✗ curCount++;
534 ✗ return false;
535 }
536 }
537 240 curCount++;
538 // Snapshot the pending toggle once for this pass so every chain sees the
539 // same port/index/command triple.
540 240 PendingToggle toggle = pendingToggle.load();
541
2/2
✓ Branch 0 taken 240 times.
✓ Branch 1 taken 240 times.
480 for (auto& g : queryData[curMux]) {
542 240 auto rc = g.second.front();
543
2/2
✓ Branch 0 taken 239 times.
✓ Branch 1 taken 1 times.
240 if (!rc->hasMoreQueries()) {
544 239 rc->resetQueryCount();
545 239 g.second.pop_front();
546
1/2
✓ Branch 0 taken 239 times.
✗ Branch 1 not taken.
239 g.second.push_back(rc);
547 239 rc = g.second.front();
548 }
549 240 int rcP = rc->getPixelStrings().front()->m_portNumber;
550
3/6
✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 240 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 240 times.
240 if (rcP < 0 || rcP >= (int)packets.size()) {
551 ✗ continue; // a port past the strings would write past the packets
552 }
553
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
240 if (toggle.cmd != ToggleCommand::None && rcP <= toggle.port && (rcP + 4) > toggle.port) {
554 ✗ if (toggle.cmd == ToggleCommand::Toggle) {
555 ✗ rc->generateToggleEFusePacket(&packets[rcP][0], toggle.index, toggle.port % 4);
556 } else {
557 ✗ rc->generateResetEFusePacket(&packets[rcP][0], toggle.index, toggle.port % 4);
558 }
559 // Clear only the toggle we just acted on: a compare_exchange keeps
560 // a request that arrived since the snapshot from being dropped.
561 // 'expected' is a copy because a failed exchange writes through it.
562 ✗ PendingToggle expected = toggle;
563 ✗ pendingToggle.compare_exchange_strong(expected, PendingToggle());
564 ✗ toggle = PendingToggle();
565
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 240 times.
240 } else if (triggerPixelCount) {
566 ✗ rc->generatePixelCountPacket(&packets[rcP][0]);
567 ✗ listen = false;
568 } else {
569
1/2
✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
240 rc->generateQueryPacket(&packets[rcP][0]);
570 240 listen = true;
571 }
572 }
573 240 return true;
574 }
575
576 ✗ void FalconV5Support::sendCountPixelPackets() {
577 ✗ setCurrentMux(0);
578 ✗ curCount = 0;
579 ✗ triggerPixelCount = true;
580 ✗ }
581