GCC Code Coverage Report


Directory: ./
File: channeloutput/ColorLight-5a-75.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 485 573 84.6%
Functions: 21 21 100.0%
Branches: 344 687 50.1%

Line Branch Exec Source
1 /*
2 * This file is part of the Falcon Player (FPP) and is Copyright (C)
3 * 2013-2025 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 /*
14 * Packet Format:
15 *
16 * All sent packets have the same source and destination MAC addresses:
17 * - Destination MAC: 11:22:33:44:55:66
18 * - Source MAC: 22:22:33:44:55:66 (doesn't seem necessary to harcoded this)
19 *
20 * Received packets are broadcast with a source MAC of 22:22:33:44:55:66, dest of FF:FF:FF:FF:FF:FF
21 *
22 * Packet Type is a single byte. First byte of data is stored with the packet
23 * type in the 'ether_type' field in the ethernet header. Data Byte count
24 * includes first byte of data stored in the ether_type field.
25 *
26 * Packet types:
27 * - 0x01 - Display/Sync Frame (sent twice for v13+ FW) (CL_SYNC_PACKET_TYPE)
28 * Older FW accepts this twice from LEDVision but is timing sensitive.
29 * FPP only sends once for older firmware.
30 * - Data Bytes: 99 (CL_SYNC_PACKET_SIZE - 13)
31 * - Data[0]: 0x00 - Sent from S2 Sender
32 * 0x07 - Sent from PC/Netcard
33 * - Data[22]: 0xff - Brightness
34 * - Data[23]: 0x00 - Value in 0x01 packet from S2 Sender
35 * 0x05 - Value in 0x01 packet from PC/Netcard
36 * - Data[25]: 0xff - Also brightness related
37 * - Data[26]: 0xff - Also brightness related
38 * - Data[27]: 0xff - Also brightness related
39 *
40 * 0x02 - Write receiver layout (dual 32x16 receivers, #0 @ 0,0, #1 @ 32,0
41 * - Data[0]: 0x00 - Receiver #
42 * - Data[1]: 0x00 - Receiver #
43 * - Data[2]: 0x00 - Receiver #
44 *
45 * Receiver #0 (of 64, #0-63)
46 * - Data[3]: 0x00 - Receiver #
47 * - Data[4]: 0x00 - Receiver #
48 * - Data[5]: 0x00 - Receiver #
49 * - Data[6]: 0x00 - Receiver #
50 * - Data[7]: 0x00 - Receiver # receiver width MSB
51 * - Data[8]: 0x20 - Receiver # receiver width LSB
52 * - Data[9]: 0x00 - Receiver # receiver height MSB
53 * - Data[10]: 0x10 - Receiver # receiver height LSB
54 * - Data[11]: 0x00 - Receiver #
55 * - Data[12]: 0x00 - Receiver #
56 * - Data[13]: 0x00 - Receiver # next receiver xOffset MSB
57 * - Data[14]: 0x20 - Receiver # next receiver xOffset LSB
58 * - Data[15]: 0x00 - Receiver # next receiver yOffset MSB
59 * - Data[16]: 0x00 - Receiver # next receiver yOffset LSB
60 * - Data[17]: 0x00 - Receiver # total display width MSB
61 * - Data[18]: 0x40 - Receiver # total display width LSB
62 * - Data[19]: 0x00 - Receiver # total display height MSB
63 * - Data[20]: 0x10 - Receiver # total display height LSB
64 * - Data[21]: 0x00 - Receiver #
65 * - Data[22]: 0x00 - Receiver #
66 *
67 * Receiver #1-63)
68 * - repeating 20 bytes of data in same format as receiver #0
69 *
70 * 0x07 - Discover (CL_DISC_PACKET_TYPE)
71 * - Data Bytes: 271 (CL_DISC_PACKET_SIZE - 13)
72 * - Data[3]: 0x00 - first receiver
73 * 0x01 - second receiver, etc.
74 *
75 * 0x08 - Discover Reply (from receiver to sender) (CL_RESP_PACKET_TYPE)
76 * - Data Bytes: 1057 (CL_RESP_PACKET_SIZE - 13)
77 * - Data[0]: 0x05 - 5A card
78 * - Data[2]: Firmware Version MSB
79 * - Data[3]: Firmware Version LSB
80 * - Data[38]: Received Packet Count? MSB
81 * - Data[39]: Received Packet Count?
82 * - Data[40]: Received Packet Count?
83 * - Data[41]: Received Packet Count? LSB
84 * - Data[46]: Uptime in ms MSB
85 * - Data[47]: Uptime in ms
86 * - Data[48]: Uptime in ms
87 * - Data[49]: Uptime in ms LSB
88 * - Data[85]: Receiver Card # (0x00, 0x01, etc.)
89 *
90 * These don't seem totally correct, offsetting a receiver card doesn't result in values that
91 * match the cabinet width/height fields, so there seems to be some other math going on.
92 * - Data[21]: Cabinet Width? MSB
93 * - Data[22]: Cabinet Width? LSB
94 * - Data[23]: Cabinet Height? MSB
95 * - Data[24]: Cabinet Height? LSB
96 * - Data[??]: Cabinet X-Offset MSB
97 * - Data[??]: Cabinet X-Offset LSB
98 * - Data[??]: Cabinet Y-Offset MSB
99 * - Data[??]: Cabinet Y-Offset LSB
100 *
101 * Data * * * * * * <-- Fields of interest
102 * Offset 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 - Offsets in packet data
103 * 00 00 00 20 00 10 00 00 00 00 00 00 01 02 03 ff - 32x16 size, r0: 32x16 cabinet, offset 0,0
104 * 00 10 00 20 00 20 00 00 00 00 00 00 01 02 03 ff - 32x32 size, r1: 32x16 cabinet, offset 0,16
105 *
106 * 00 00 00 20 00 10 00 00 00 00 00 00 01 02 03 ff - 32x16 size, r0: 32x16 cabinet, offset 0,0
107 * 00 10 00 40 00 20 00 00 00 00 00 00 01 02 03 ff - 64x32 size, r1: 32x16 cabinet, offset 32,16
108 *
109 * 00 00 00 20 00 10 00 00 00 00 00 00 01 02 03 ff - 32x16 size, r0: 32x16 cabinet, offset 0,0
110 * 00 10 00 20 00 20 00 00 00 00 00 00 01 02 03 ff - 64x16 size, r1: 32x16 cabinet, offset 32,0
111 *
112 * 0x10 - ???? - sent when sending config to receiver
113 * - Data Length: 1026
114 *
115 * 0x11 - Save Config??
116 * - Data Bytes: 1283
117 * - Data[3]: 0x00 - first receiver, 0x01 2nd, etc.
118 *
119 * 0x18 - ???? - sent when sending config to receiver
120 *
121 * 0x1F - ???? - sent when sending config to receiver
122 * - Data Length: 1034
123 *
124 * 0x26 - ???? - sent when sending config to receiver
125 * - Data Length: 264
126 *
127 * 0x31 - ???? - sent when sending config to receiver. Some kind of table, 2 bytes per entry?
128 *
129 * 0x32 - ???? - sent when sending config to receiver
130 *
131 * 0x76 - ???? - sent when sending config to receiver. Some kind of table, 3 bytes per entry?
132 *
133 * 0x0A - Panel Brightness (sent twice for v13+ FW, but possbily uncessary) (CL_BRIG_PACKET_TYPE)
134 * - Data Length: 64 (CL_BRIG_PACKET_SIZE - 13)
135 * - Data[0]: 0xFF - Brightness
136 * - Data[1]: 0xFF - Brightness
137 * - Data[2]: 0xFF - Brightness
138 * - Data[3]: 0xFF
139 *
140 * 0x55 - Pixel Data (sent twice per row for v13+ FW, but seems uncessary to do so) (CL_PIXL_PACKET_TYPE)
141 * - Data Length: CL_PIXL_HEADER_SIZE + (Row_Width * 3)
142 * - Data[0]: Row Number MSB
143 * - Data[1]: Row Number LSB
144 * - Data[2]: MSB of pixel offset for this packet
145 * - Data[3]: LSB of pixel offset for this packet
146 * - Data[4]: MSB of pixel count in packet
147 * - Data[5]: LSB of pixel count in packet
148 * - Data[6]: 0x08 - ?? unsure what this is
149 * - Data[7]: 0x88 - ?? unsure what this is
150 * - Data[8-end]: RGB order pixel data
151 *
152 * Notes:
153 * - Brightness may only work on newer firmware. Doesn't seem to work on v2.x and v3.x ColorLight FW.
154 *
155 * Other sources of info:
156 * https://www.mylifesucks.de/oss/mplayer-colorlight/
157 * https://hkubota.wordpress.com/2022/01/31/winter-project-colorlight-5a-75b-protocol/
158 */
159 #include "fpp-pch.h"
160
161 #include "fpp-json.h"
162
163 #ifndef PLATFORM_OSX
164 #include <linux/if_packet.h>
165 #include <linux/sockios.h>
166 #include <netinet/ether.h>
167 #else
168 #include <net/bpf.h>
169 #endif
170
171 #include "../SysSocket.h"
172 #include <arpa/inet.h>
173 #include <unistd.h>
174 #include <net/if.h>
175 #include <sys/ioctl.h>
176 #include <sys/socket.h>
177 #include <cmath>
178 #include <errno.h>
179 #include <fstream>
180 #include <iostream>
181
182 #include "../Warnings.h"
183 #include "../common.h"
184 #include "../log.h"
185 #include "../settings.h"
186
187 #include "ColorLight-5a-75.h"
188 #include "GammaLUT.h"
189 #include "overlays/PixelOverlay.h"
190
191 #include "Plugin.h"
192
193 static constexpr const char* CL_DROPPED_FRAMES_WARNING = "ColorLight: The network is not keeping up, frames are being dropped";
194
195 #ifdef PLATFORM_OSX
196 int bindBPFSocket(const std::string iface) {
197 char buf[11] = { 0 };
198 int i = 0;
199 int m_fd = -1;
200 for (int i = 0; i < 255; i++) {
201 snprintf(buf, sizeof(buf), "/dev/bpf%i", i);
202 m_fd = open(buf, O_RDWR);
203 if (m_fd != -1) {
204 break;
205 }
206 }
207 if (m_fd == -1) {
208 LogErr(VB_CHANNELOUT, "Error opening bpf file: %s\n", FPPstrerror(errno));
209 return -1;
210 }
211
212 struct ifreq bound_if;
213 memset(&bound_if, 0, sizeof(bound_if));
214 // ifr_name is IFNAMSIZ bytes; callers pass the validated m_ifName, but
215 // guard the copy itself so a future caller cannot reintroduce the overflow.
216 if (iface.size() >= sizeof(bound_if.ifr_name)) {
217 LogErr(VB_CHANNELOUT, "Interface name too long for bind (max %d chars): %s\n",
218 IFNAMSIZ - 1, iface.c_str());
219 close(m_fd);
220 return -1;
221 }
222 strcpy(bound_if.ifr_name, iface.c_str());
223 if (ioctl(m_fd, BIOCSETIF, &bound_if) > 0) {
224 LogErr(VB_CHANNELOUT, "Cannot bind bpf device to physical device %s, exiting\n", iface.c_str());
225 }
226 int yes = 1;
227 ioctl(m_fd, BIOCSHDRCMPLT, &yes);
228 return m_fd;
229 }
230 #endif
231
232 class ColorLight5a75Plugin : public FPPPlugins::Plugin, public FPPPlugins::ChannelOutputPlugin {
233 public:
234 100 ColorLight5a75Plugin() :
235
2/4
✓ Branch 0 taken 100 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 100 times.
✗ Branch 3 not taken.
300 FPPPlugins::Plugin("ColorLight5a75") {
236 100 }
237 100 virtual ChannelOutput* createChannelOutput(unsigned int startChannel, unsigned int channelCount) override {
238
2/6
✓ Branch 0 taken 100 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 100 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
200 return new ColorLight5a75Output(startChannel, channelCount);
239 }
240 };
241
242 extern "C" {
243 100 FPPPlugins::Plugin* createPlugin() {
244
2/6
✓ Branch 0 taken 100 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 100 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
200 return new ColorLight5a75Plugin();
245 }
246 }
247
248 /*
249 *
250 */
251 100 ColorLight5a75Output::ColorLight5a75Output(unsigned int startChannel, unsigned int channelCount) :
252 ChannelOutput(startChannel, channelCount),
253 100 m_width(0),
254 100 m_height(0),
255 100 m_colorOrder(FPPColorOrder::kColorOrderRGB),
256 100 m_fd(-1),
257 100 m_rowSize(0),
258 100 m_panelWidth(0),
259 100 m_panelHeight(0),
260 100 m_panels(0),
261 100 m_rows(0),
262 100 m_outputs(0),
263 100 m_longestChain(0),
264 100 m_invertedData(0),
265 100 m_frameCharge(0),
266 100 m_sendBufferSize(0),
267 100 m_frameSent(false),
268 100 m_rowsSentTime(0),
269 100 m_rowsWireUS(0),
270 100 m_syncGapUS(500),
271 100 m_droppedFrames(0),
272 100 m_droppedLogTime(0),
273 100 m_flippedLayout(0),
274 200 m_highestFirmwareVersion(0) {
275
1/2
✓ Branch 0 taken 100 times.
✗ Branch 1 not taken.
100 LogDebug(VB_CHANNELOUT, "ColorLight5a75Output::ColorLight5a75Output(%u, %u)\n",
276 startChannel, channelCount);
277 100 }
278
279 /*
280 *
281 */
282 400 ColorLight5a75Output::~ColorLight5a75Output() {
283 200 LogDebug(VB_CHANNELOUT, "ColorLight5a75Output::~ColorLight5a75Output()\n");
284
285
2/2
✓ Branch 0 taken 99 times.
✓ Branch 1 taken 100 times.
398 for (int i = 0; i < m_synciovecs.size(); i++) {
286 198 free(m_synciovecs[i].iov_base);
287
288 // Higher firmware has duplicate sync packet which re-uses same buffer
289
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 98 times.
198 if (m_highestFirmwareVersion >= 13)
290 2 i++;
291 }
292
293
2/2
✓ Branch 0 taken 1763 times.
✓ Branch 1 taken 100 times.
3726 for (int i = 0; i < m_iovecs.size(); i++) {
294 3526 free(m_iovecs[i].iov_base);
295
296 // Higher firmware has duplicate brightness packet which re-uses same buffer
297
4/4
✓ Branch 0 taken 99 times.
✓ Branch 1 taken 1664 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 98 times.
3526 if ((i == 0) && (m_highestFirmwareVersion >= 13))
298 2 i += 3;
299 else
300 3524 i++; // Every second iovec is either part of the header malloc() or external data
301 }
302
303
2/2
✓ Branch 0 taken 99 times.
✓ Branch 1 taken 1 times.
200 if (m_fd >= 0)
304 198 close(m_fd);
305 400 }
306
307 /*
308 *
309 */
310 100 int ColorLight5a75Output::Init(Json::Value config) {
311
1/2
✓ Branch 0 taken 100 times.
✗ Branch 1 not taken.
100 LogDebug(VB_CHANNELOUT, "ColorLight5a75Output::Init(JSON)\n");
312
313
2/4
✓ Branch 0 taken 100 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 100 times.
✗ Branch 3 not taken.
100 m_panelWidth = config["panelWidth"].asInt();
314
2/4
✓ Branch 0 taken 100 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 100 times.
✗ Branch 3 not taken.
100 m_panelHeight = config["panelHeight"].asInt();
315
316
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 100 times.
100 if (!m_panelWidth)
317 ✗ m_panelWidth = 32;
318
319
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 100 times.
100 if (!m_panelHeight)
320 ✗ m_panelHeight = 16;
321
322
2/4
✓ Branch 0 taken 100 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 100 times.
✗ Branch 3 not taken.
100 m_invertedData = config["invertedData"].asInt();
323
324
3/6
✓ Branch 0 taken 100 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 100 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 100 times.
✗ Branch 5 not taken.
100 m_colorOrder = ColorOrderFromString(config["colorOrder"].asString());
325
326 m_panelMatrix =
327
1/2
✓ Branch 0 taken 100 times.
✗ Branch 1 not taken.
100 std::make_unique<PanelMatrix>(m_panelWidth, m_panelHeight, m_invertedData);
328
329
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 100 times.
100 if (!m_panelMatrix) {
330 ✗ LogErr(VB_CHANNELOUT, "Unable to create PanelMatrix\n");
331 ✗ return 0;
332 }
333
334
2/4
✓ Branch 0 taken 100 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 100 times.
✗ Branch 3 not taken.
100 if (config.isMember("cfgVersion")) {
335
2/4
✓ Branch 0 taken 100 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 100 times.
✗ Branch 3 not taken.
100 m_flippedLayout = config["cfgVersion"].asInt() >= 2 ? 0 : 1;
336 } else {
337 ✗ m_flippedLayout = 1;
338 }
339
340
4/6
✓ Branch 0 taken 228 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 228 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 128 times.
✓ Branch 5 taken 100 times.
228 for (int i = 0; i < config["panels"].size(); i++) {
341
3/6
✓ Branch 0 taken 128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 128 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 128 times.
✗ Branch 5 not taken.
128 Json::Value p = config["panels"][i];
342 128 char orientation = 'N';
343
2/4
✓ Branch 0 taken 128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 128 times.
✗ Branch 3 not taken.
128 std::string o = p["orientation"].asString();
344
2/4
✓ Branch 0 taken 128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 128 times.
✗ Branch 3 not taken.
128 if (o != "") {
345 128 orientation = o[0];
346 }
347
348
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 128 times.
128 if (m_flippedLayout) {
349 ✗ switch (orientation) {
350 ✗ case 'N':
351 ✗ orientation = 'U';
352 ✗ break;
353 ✗ case 'U':
354 ✗ orientation = 'N';
355 ✗ break;
356 ✗ case 'R':
357 ✗ orientation = 'L';
358 ✗ break;
359 ✗ case 'L':
360 ✗ orientation = 'R';
361 ✗ break;
362 }
363 }
364
365
4/8
✓ Branch 0 taken 128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 128 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 128 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 128 times.
128 if (p["colorOrder"].asString() == "")
366 ✗ p["colorOrder"] = ColorOrderToString(m_colorOrder);
367
368 // a refused panel (out of range) must not count toward the outputs or chains below
369
4/8
✓ Branch 0 taken 128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 128 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 128 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 128 times.
✗ Branch 7 not taken.
384 if (!m_panelMatrix->AddPanel(p["outputNumber"].asInt(),
370
2/4
✓ Branch 0 taken 128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 128 times.
✗ Branch 3 not taken.
128 p["panelNumber"].asInt(), orientation,
371
4/8
✓ Branch 0 taken 128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 128 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 128 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 128 times.
✗ Branch 7 not taken.
128 p["xOffset"].asInt(), p["yOffset"].asInt(),
372
4/6
✓ Branch 0 taken 128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 128 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 125 times.
256 ColorOrderFromString(p["colorOrder"].asString()))) {
373 3 continue;
374 }
375
376
4/6
✓ Branch 0 taken 125 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 125 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 123 times.
125 if (p["outputNumber"].asInt() > m_outputs)
377
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 m_outputs = p["outputNumber"].asInt();
378
379
4/6
✓ Branch 0 taken 125 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 125 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 19 times.
✓ Branch 5 taken 106 times.
125 if (p["panelNumber"].asInt() > m_longestChain)
380
2/4
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19 times.
✗ Branch 3 not taken.
19 m_longestChain = p["panelNumber"].asInt();
381
4/4
✓ Branch 0 taken 125 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 125 times.
✓ Branch 3 taken 3 times.
131 }
382
383 // Both of these are 0-based, so bump them up by 1 for comparisons
384 100 m_outputs++;
385 100 m_longestChain++;
386
387 100 m_panels = m_panelMatrix->PanelCount();
388
389 100 m_rows = m_outputs * m_panelHeight;
390
391 100 m_width = m_panelMatrix->Width();
392 100 m_height = m_panelMatrix->Height();
393
394 100 m_channelCount = m_width * m_height * 3;
395
396
1/2
✓ Branch 0 taken 100 times.
✗ Branch 1 not taken.
100 m_outputFrame = std::make_unique<char[]>(m_outputs * m_longestChain * m_panelHeight * m_panelWidth * 3);
397
1/2
✓ Branch 0 taken 100 times.
✗ Branch 1 not taken.
100 BuildRemap();
398
399
1/2
✓ Branch 0 taken 100 times.
✗ Branch 1 not taken.
100 m_matrix = std::make_unique<Matrix>(m_startChannel, m_width, m_height);
400
401
3/4
✓ Branch 0 taken 100 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 97 times.
100 if (config.isMember("subMatrices")) {
402
4/6
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 3 times.
6 for (int i = 0; i < config["subMatrices"].size(); i++) {
403
3/6
✓ 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.
3 Json::Value sm = config["subMatrices"][i];
404
405
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
6 m_matrix->AddSubMatrix(
406
1/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
3 sm["enabled"].asInt(),
407
3/6
✓ 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.
3 sm["startChannel"].asInt() - 1,
408
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 sm["width"].asInt(),
409
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 sm["height"].asInt(),
410
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 sm["xOffset"].asInt(),
411
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 sm["yOffset"].asInt());
412 3 }
413 }
414
415
1/2
✓ Branch 0 taken 100 times.
✗ Branch 1 not taken.
100 GammaLUT::Build(m_gammaCurve, GammaLUT::ParseConfig(config, 1.0f));
416
417
2/4
✓ Branch 0 taken 100 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 100 times.
✗ Branch 3 not taken.
100 if (config.isMember("interface"))
418
2/4
✓ Branch 0 taken 100 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 100 times.
✗ Branch 3 not taken.
100 m_ifName = config["interface"].asString();
419 else
420 ✗ m_ifName = "eth1";
421
422 // ifr_name is IFNAMSIZ (16) bytes at every use below; an overlong name
423 // would overflow the stack copies. It never worked (ioctl/sysfs lookups
424 // fail on it), so fail init with an error like other bad config here.
425
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 99 times.
100 if (m_ifName.size() >= IFNAMSIZ) {
426
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogErr(VB_CHANNELOUT, "Error ColorLight: Configured interface name too long (max %d chars): %s\n",
427 IFNAMSIZ - 1, m_ifName.c_str());
428 1 return 0;
429 }
430
431 99 m_rowSize = m_longestChain * m_panelWidth * 3;
432
433 #ifndef PLATFORM_OSX
434
435 // Check if interface is up
436
3/6
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 99 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 99 times.
✗ Branch 5 not taken.
198 std::ifstream ifstate_src("/sys/class/net/" + m_ifName + "/operstate");
437 198 std::string ifstate;
438
439
1/2
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
99 if (ifstate_src.is_open()) {
440
1/2
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
99 ifstate_src >> ifstate; // pipe file's content into stream
441
1/2
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
99 ifstate_src.close();
442 }
443
444
1/2
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
99 m_highestFirmwareVersion = getSettingInt("ColorlightFirmwareVersion");
445
2/4
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 99 times.
✗ Branch 3 not taken.
99 m_syncGapUS = std::clamp(getSettingInt("ColorlightSyncGapUS", 500), 0, 20000);
446 99 m_colorlightDisable = true;
447
1/2
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
99 m_colorlightDisable = getSettingInt("ColorlightLinkDownDisable") == 1;
448
449
2/4
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 99 times.
✗ Branch 3 not taken.
99 if (config.isMember("linkCheck")) {
450
2/4
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 99 times.
✗ Branch 3 not taken.
99 m_colorlightDisable = config["linkCheck"].asBool();
451 }
452
3/4
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
✓ Branch 3 taken 70 times.
99 if (config.isMember("firmwareVersion")) {
453
2/4
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
✗ Branch 3 not taken.
29 m_highestFirmwareVersion = config["firmwareVersion"].asInt();
454 }
455
456
3/4
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 70 times.
✓ Branch 3 taken 29 times.
99 if (ifstate != "up") {
457
2/4
✓ Branch 0 taken 70 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 70 times.
✗ Branch 3 not taken.
70 LogErr(VB_CHANNELOUT, "Error ColorLight: Configured interface %s does not have link %s\n", m_ifName.c_str(), FPPstrerror(errno));
458
3/6
✓ Branch 0 taken 70 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 70 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 70 times.
✗ Branch 5 not taken.
70 WarningHolder::AddWarning(7, "ColorLight: Configured interface " + m_ifName + " does not have link");
459
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 70 times.
70 if (m_colorlightDisable) {
460 ✗ return 0;
461 }
462 } else {
463
4/8
✓ 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.
✓ Branch 6 taken 29 times.
✗ Branch 7 not taken.
58 WarningHolder::RemoveWarning(7, "ColorLight: Configured interface " + m_ifName + " does not have link");
464 }
465
466 // Check interface is 1000Mbps capable and display error if not
467 99 ifspeed = 0;
468
3/6
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 99 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 99 times.
✗ Branch 5 not taken.
198 std::ifstream ifspeed_src("/sys/class/net/" + m_ifName + "/speed");
469
470
1/2
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
99 if (ifspeed_src.is_open()) { // always check whether the file is open
471
1/2
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
99 ifspeed_src >> ifspeed; // pipe file's content into stream
472
1/2
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
99 ifspeed_src.close();
473 }
474
475
3/4
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 70 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 29 times.
99 if (ifspeed > 0 && ifspeed < 1000) {
476 ✗ LogErr(VB_CHANNELOUT, "Error ColorLight: Configured interface %s is not 1000Mbps Capable: %s\n", m_ifName.c_str(), FPPstrerror(errno));
477 ✗ WarningHolder::AddWarning(8, "ColorLight: Configured interface " + m_ifName + " is not 1000Mbps Capable");
478 ✗ if (m_colorlightDisable) {
479 ✗ return 0;
480 }
481 } else {
482
4/8
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 99 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 99 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 99 times.
✗ Branch 7 not taken.
198 WarningHolder::RemoveWarning(8, "ColorLight: Configured interface " + m_ifName + " is not 1000Mbps Capable");
483 }
484
485 // Open our raw socket
486
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 99 times.
99 if ((m_fd = OpenSocket(AF_PACKET, SOCK_RAW, IPPROTO_RAW)) == -1) {
487 ✗ LogErr(VB_CHANNELOUT, "Error creating raw socket: %s\n", FPPstrerror(errno));
488 ✗ return 0;
489 }
490
491 // Get the output interface ID
492 99 memset(&m_if_idx, 0, sizeof(struct ifreq));
493 99 strcpy(m_if_idx.ifr_name, m_ifName.c_str());
494
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 99 times.
99 if (ioctl(m_fd, SIOCGIFINDEX, &m_if_idx) < 0) {
495 ✗ LogErr(VB_CHANNELOUT, "Error getting index of %s interface: %s\n",
496 m_ifName.c_str(), FPPstrerror(errno));
497 ✗ return 0;
498 }
499
500 99 m_sock_addr.sll_family = AF_PACKET;
501 99 m_sock_addr.sll_ifindex = m_if_idx.ifr_ifindex;
502 99 m_sock_addr.sll_halen = ETH_ALEN;
503
504 99 unsigned char dhost[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 };
505 99 memcpy(m_sock_addr.sll_addr, dhost, 6);
506
507 // Force packets out the desired interface
508
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 99 times.
99 if ((bind(m_fd, (struct sockaddr*)&m_sock_addr, sizeof(m_sock_addr))) == -1) {
509 ✗ LogErr(VB_CHANNELOUT, "bind() failed\n");
510 ✗ return 0;
511 }
512
513 // 0 is auto-detect, else use the value loaded from the ColorlightFirmwareVersion setting
514
2/2
✓ Branch 0 taken 70 times.
✓ Branch 1 taken 29 times.
99 if (!m_highestFirmwareVersion) {
515 // Query the receivers to find out what firmware vesion is installed.
516
1/2
✓ Branch 0 taken 70 times.
✗ Branch 1 not taken.
70 GetReceiverInfo();
517
518
3/6
✓ Branch 0 taken 70 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 70 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 70 times.
70 if (!m_receivers.size() && m_colorlightDisable) {
519 ✗ return 0;
520 }
521 }
522
523 #else
524 m_fd = bindBPFSocket(m_ifName);
525 #endif
526
527 99 unsigned int byteCount = 0;
528 99 unsigned int p = 0;
529 99 unsigned char* header = nullptr;
530 99 unsigned char* data = nullptr;
531 99 unsigned char brightness = 0xff;
532
533
3/4
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 98 times.
99 if (config.isMember("brightness")) {
534
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 brightness = config["brightness"].asInt(); // 0-100% scale
535
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!brightness)
536 ✗ brightness = 0xFF;
537 else
538 1 brightness = 2.55 * brightness; // Scale up to 0-255
539 }
540
541 /////////////////////////////////////////////////
542 // Prep the brightness and pixel data packets
543 99 int packetCount = 1 + (m_rows * (((int)(m_rowSize - 1) / CL_MAX_CHAN_PER_PACKET) + 1));
544
545
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 98 times.
99 if (m_highestFirmwareVersion >= 13) {
546 1 packetCount += 1; // 0x0A brightness sent twice
547 }
548
549
1/2
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
99 m_msgs.resize(packetCount);
550
1/2
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
99 m_iovecs.resize(packetCount * 2);
551
552 // Brightness packet
553 99 byteCount = CL_BRIG_PACKET_SIZE;
554 99 header = (unsigned char*)malloc(byteCount);
555 99 memset(header, 0, byteCount);
556
557
1/2
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
99 SetHostMACs(header);
558
559 // Packet Type follows Dest MAC, data follows type
560 99 header[CL_PACKET_TYPE_OFFSET] = CL_BRIG_PACKET_TYPE;
561 99 data = header + CL_PACKET_DATA_OFFSET;
562
563 99 data[0] = brightness;
564 99 data[1] = brightness;
565 99 data[2] = brightness;
566 99 data[3] = 0xff;
567
568 // Split this apart since it is grouped with pixel data packets which are split into two iovec entries
569 99 m_iovecs[p * 2].iov_base = header;
570 99 m_iovecs[p * 2].iov_len = CL_PACKET_DATA_OFFSET;
571 99 m_iovecs[p * 2 + 1].iov_base = header + CL_PACKET_DATA_OFFSET;
572 99 m_iovecs[p * 2 + 1].iov_len = byteCount - CL_PACKET_DATA_OFFSET;
573 99 p++;
574
575
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 98 times.
99 if (m_highestFirmwareVersion >= 13) {
576 // Send duplicate brightness packet
577 1 m_iovecs[p * 2].iov_base = m_iovecs[(p - 1) * 2].iov_base;
578 1 m_iovecs[p * 2].iov_len = m_iovecs[(p - 1) * 2].iov_len;
579 1 m_iovecs[p * 2 + 1].iov_base = m_iovecs[(p - 1) * 2 + 1].iov_base;
580 1 m_iovecs[p * 2 + 1].iov_len = m_iovecs[(p - 1) * 2 + 1].iov_len;
581 1 p++;
582 }
583
584 // Pixel Data row packets - Type 0x55
585 99 char* rowPtr = m_outputFrame.get();
586 99 unsigned int dSize = 0;
587 99 unsigned int part = 0;
588 99 unsigned int hSize = CL_PACKET_DATA_OFFSET + CL_PIXL_HEADER_SIZE;
589 99 unsigned int offset = 0;
590 99 unsigned int bytesInPacket = 0;
591 99 unsigned int pixelOffset = 0;
592 99 unsigned int pixelsInPacket = 0;
593
594
2/2
✓ Branch 0 taken 1648 times.
✓ Branch 1 taken 99 times.
1747 for (int row = 0; row < m_rows; row++) {
595 1648 part = 0;
596 1648 offset = 0;
597
598
2/2
✓ Branch 0 taken 1664 times.
✓ Branch 1 taken 1648 times.
3312 while (offset < m_rowSize) {
599 1664 header = (unsigned char*)malloc(hSize);
600 1664 memset(header, 0, hSize);
601
602
1/2
✓ Branch 0 taken 1664 times.
✗ Branch 1 not taken.
1664 SetHostMACs(header);
603
604 // Packet Type follows Dest MAC, data follows type
605 1664 header[CL_PACKET_TYPE_OFFSET] = CL_PIXL_PACKET_TYPE;
606 1664 data = header + CL_PACKET_DATA_OFFSET;
607
608
2/2
✓ Branch 0 taken 1648 times.
✓ Branch 1 taken 16 times.
1664 if ((offset + CL_MAX_CHAN_PER_PACKET) > m_rowSize)
609 1648 bytesInPacket = m_rowSize - offset;
610 else
611 16 bytesInPacket = CL_MAX_CHAN_PER_PACKET;
612
613 1664 pixelOffset = offset / 3;
614 1664 pixelsInPacket = bytesInPacket / 3;
615
616 1664 data[0] = row >> 8; // Pixel Row MSB
617 1664 data[1] = row & 0xFF; // Pixel Row LSB
618 1664 data[2] = pixelOffset >> 8; // Pixel Offset MSB
619 1664 data[3] = pixelOffset & 0xFF; // Pixel Offset LSB
620 1664 data[4] = pixelsInPacket >> 8; // Pixels In Packet MSB
621 1664 data[5] = pixelsInPacket & 0xFF; // Pixels In Packet LSB
622 1664 data[6] = 0x08; // ?? not sure what this value is
623 1664 data[7] = 0x88; // ?? not sure what this value is
624
625 1664 m_iovecs[p * 2].iov_base = header;
626 1664 m_iovecs[p * 2].iov_len = hSize;
627 1664 m_iovecs[p * 2 + 1].iov_base = rowPtr + offset;
628 1664 m_iovecs[p * 2 + 1].iov_len = bytesInPacket;
629 1664 p++;
630
631 #if 0
632 // LEDVision sends data rows duplicated as well, but doesn't seem necessary.
633 // If this is enabled, the packetCount needs to be adjusted when set above.
634 if (0 && m_highestFirmwareVersion >= 13) {
635 // Send duplicate row data packet
636 m_iovecs[p * 2].iov_base = m_iovecs[(p-1) * 2].iov_base;
637 m_iovecs[p * 2].iov_len = m_iovecs[(p-1) * 2].iov_len;
638 m_iovecs[p * 2 + 1].iov_base = m_iovecs[(p-1) * 2 + 1].iov_base;
639 m_iovecs[p * 2 + 1].iov_len = m_iovecs[(p-1) * 2 + 1].iov_len;
640 p++;
641 }
642 #endif
643
644 1664 offset += bytesInPacket;
645 1664 part++;
646 }
647
648 1648 rowPtr += m_rowSize;
649 }
650
651
2/2
✓ Branch 0 taken 1764 times.
✓ Branch 1 taken 99 times.
1863 for (int m = 0; m < packetCount; m++) {
652 struct mmsghdr msg;
653 1764 memset(&msg, 0, sizeof(msg));
654 1764 msg.msg_hdr.msg_iov = &m_iovecs[m * 2];
655 1764 msg.msg_hdr.msg_iovlen = 2;
656 1764 m_msgs[m] = msg;
657 }
658
659 ////////////////////////////////////////////////////////////////////
660 // Prep the Display Frame packet(s) - sent in SendData()
661
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 98 times.
99 if (m_highestFirmwareVersion >= 13) {
662
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 m_syncMsgs.resize(2);
663
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 m_synciovecs.resize(2);
664 } else {
665
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 m_syncMsgs.resize(1);
666
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 m_synciovecs.resize(1);
667 }
668
669 99 p = 0;
670 99 byteCount = CL_SYNC_PACKET_SIZE;
671 99 header = (unsigned char*)malloc(byteCount);
672 99 memset(header, 0, byteCount);
673
674
1/2
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
99 SetHostMACs(header);
675
676 // Packet Type follows Dest MAC, data follows type
677 99 header[CL_PACKET_TYPE_OFFSET] = CL_SYNC_PACKET_TYPE;
678 99 data = header + CL_PACKET_DATA_OFFSET;
679
680 99 data[0] = 0x07;
681
682 99 data[22] = brightness;
683 99 data[23] = 0x05; // ?? not sure what this value is
684
685 99 data[25] = brightness;
686 99 data[26] = brightness;
687 99 data[27] = brightness;
688
689 99 m_synciovecs[p].iov_base = header;
690 99 m_synciovecs[p].iov_len = byteCount;
691 99 p++;
692
693
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 98 times.
99 if (m_highestFirmwareVersion >= 13) {
694 // Send duplicate display frame packet
695 1 m_synciovecs[p].iov_base = m_synciovecs[p - 1].iov_base;
696 1 m_synciovecs[p].iov_len = m_synciovecs[p - 1].iov_len;
697 1 p++;
698 }
699
700
2/2
✓ Branch 0 taken 100 times.
✓ Branch 1 taken 99 times.
199 for (int m = 0; m < p; m++) {
701 struct mmsghdr msg;
702 100 memset(&msg, 0, sizeof(msg));
703 100 msg.msg_hdr.msg_iov = &m_synciovecs[m];
704 100 msg.msg_hdr.msg_iovlen = 1;
705 100 m_syncMsgs[m] = msg;
706 }
707
708 // Wire time of the rows: payload plus preamble, FCS and inter-frame gap
709 // (24 bytes) per packet, at the link speed (1000 if unknown).
710 99 long long rowBits = 0;
711
2/2
✓ Branch 0 taken 1764 times.
✓ Branch 1 taken 99 times.
1863 for (auto& msg : m_msgs) {
712
2/2
✓ Branch 0 taken 3528 times.
✓ Branch 1 taken 1764 times.
5292 for (size_t i = 0; i < msg.msg_hdr.msg_iovlen; i++)
713 3528 rowBits += msg.msg_hdr.msg_iov[i].iov_len * 8;
714 1764 rowBits += 24 * 8;
715 }
716
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 70 times.
99 m_rowsWireUS = rowBits / (ifspeed > 0 ? ifspeed : 1000);
717
718 #ifndef PLATFORM_OSX
719 // Size the send buffer for two whole frames, so a frame normally queues
720 // in one go and FrameFits() can let one frame wait behind another before
721 // dropping. The kernel charges each packet its skb overhead on top of
722 // the payload, measured at ~1.3KB on a 32 bit Beagle kernel; 2048 bytes
723 // leaves room for 64 bit kernels. An estimate that comes up short only
724 // means the odd frame is found not to fit while it is being queued,
725 // and is dropped then instead. The kernel doubles what is asked for,
726 // so asking for one frame gets two.
727 99 m_frameCharge = 0;
728
2/2
✓ Branch 0 taken 198 times.
✓ Branch 1 taken 99 times.
297 for (auto* msgs : { &m_msgs, &m_syncMsgs }) {
729
2/2
✓ Branch 0 taken 1864 times.
✓ Branch 1 taken 198 times.
2062 for (auto& msg : *msgs) {
730
2/2
✓ Branch 0 taken 3628 times.
✓ Branch 1 taken 1864 times.
5492 for (size_t i = 0; i < msg.msg_hdr.msg_iovlen; i++)
731 3628 m_frameCharge += msg.msg_hdr.msg_iov[i].iov_len;
732 1864 m_frameCharge += 2048;
733 }
734 }
735 99 int sendBuffer = m_frameCharge;
736
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 99 times.
99 if (setsockopt(m_fd, SOL_SOCKET, SO_SNDBUFFORCE, &sendBuffer, sizeof(sendBuffer)) == -1)
737 ✗ setsockopt(m_fd, SOL_SOCKET, SO_SNDBUF, &sendBuffer, sizeof(sendBuffer));
738 99 socklen_t len = sizeof(m_sendBufferSize);
739
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 99 times.
99 if (getsockopt(m_fd, SOL_SOCKET, SO_SNDBUF, &m_sendBufferSize, &len) == -1)
740 ✗ m_sendBufferSize = 0;
741
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 99 times.
99 if (m_sendBufferSize < m_frameCharge) {
742 // Without room for a whole frame, FrameFits() would drop them all
743 ✗ LogWarn(VB_CHANNELOUT, "ColorLight: Send buffer is %d bytes, a frame needs %d\n", m_sendBufferSize, m_frameCharge);
744 ✗ m_sendBufferSize = 0;
745 }
746 #endif
747
748 // Create Pixel Overlay Models if needed
749
1/2
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
99 if (PixelOverlayManager::INSTANCE.isAutoCreatePixelOverlayModels()) {
750
1/2
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
99 std::string dd = "LED Panels";
751
4/16
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 99 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 99 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 99 times.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
99 if (config.isMember("LEDPanelMatrixName") && !config["LEDPanelMatrixName"].asString().empty()) {
752 ✗ dd = config["LEDPanelMatrixName"].asString();
753 }
754
3/4
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 70 times.
✓ Branch 3 taken 29 times.
99 if (config.isMember("description")) {
755
2/4
✓ Branch 0 taken 70 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 70 times.
✗ Branch 3 not taken.
70 dd = config["description"].asString();
756 }
757
1/2
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
99 std::string desc = dd;
758 99 int count = 0;
759
2/4
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 99 times.
99 while (PixelOverlayManager::INSTANCE.getModel(desc) != nullptr) {
760 ✗ count++;
761 ✗ desc = dd + "-" + std::to_string(count);
762 }
763
4/8
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 99 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 99 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 99 times.
✗ Branch 7 not taken.
495 PixelOverlayManager::INSTANCE.addAutoOverlayModel(desc,
764 m_startChannel, m_channelCount, 3,
765
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 99 times.
99 "H", m_invertedData ? "BL" : "TL",
766 99 m_height, 1);
767
1/2
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
99 m_autoCreatedModelName = desc;
768 99 }
769
770
2/4
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 99 times.
✗ Branch 3 not taken.
99 return ChannelOutput::Init(config);
771 }
772
773 /*
774 *
775 */
776 99 int ColorLight5a75Output::Close(void) {
777 99 LogDebug(VB_CHANNELOUT, "ColorLight5a75Output::Close()\n");
778
1/2
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
99 if (!m_autoCreatedModelName.empty()) {
779 99 PixelOverlayManager::INSTANCE.removeAutoOverlayModel(m_autoCreatedModelName);
780 99 m_autoCreatedModelName.clear();
781 }
782
2/2
✓ Branch 0 taken 70 times.
✓ Branch 1 taken 99 times.
169 for (auto& m : m_warnings) {
783
2/4
✓ Branch 0 taken 70 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 70 times.
✗ Branch 3 not taken.
140 WarningHolder::RemoveWarning(9, m);
784 }
785 99 ClearSendErrorWarning();
786 99 return ChannelOutput::Close();
787 }
788
789 201 void ColorLight5a75Output::GetRequiredChannelRanges(const std::function<void(int, int)>& addRange) {
790 201 addRange(m_startChannel, m_startChannel + m_channelCount - 1);
791 201 }
792
793 560 void ColorLight5a75Output::OverlayTestData(unsigned char* channelData, int cycleNum, float percentOfCycle, int testType, const Json::Value& config) {
794
2/2
✓ Branch 0 taken 560 times.
✓ Branch 1 taken 560 times.
1120 for (int output = 0; output < m_outputs; output++) {
795 560 int panelsOnOutput = m_panelMatrix->m_outputPanels[output].size();
796
2/2
✓ Branch 0 taken 560 times.
✓ Branch 1 taken 560 times.
1120 for (int i = 0; i < panelsOnOutput; i++) {
797 560 int panel = m_panelMatrix->m_outputPanels[output][i];
798 560 int chain = m_panelMatrix->m_panels[panel].chain;
799
800
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 560 times.
560 if (m_flippedLayout)
801 ✗ chain = (m_longestChain - 1) - m_panelMatrix->m_panels[panel].chain - 1;
802
803 560 m_panelMatrix->m_panels[panel].drawTestPattern(channelData + m_startChannel, cycleNum, percentOfCycle, testType);
804 }
805 }
806 560 }
807
808 /*
809 * Queue packets without ever waiting for the NIC: this runs on the channel
810 * output thread, and every other output on the box waits behind it.
811 * PrepData() has already checked that the whole frame fits in the socket's
812 * send buffer, so a short send means the network fell behind after all, or
813 * the interface is gone. Returns whether every packet was queued.
814 */
815 5433 bool ColorLight5a75Output::SendMessages(std::vector<struct mmsghdr>& msgsToSend) {
816 5433 struct mmsghdr* msgs = msgsToSend.data();
817 5433 int msgCount = msgsToSend.size();
818 5433 int sent = 0;
819 5433 int oc = 0;
820
821
2/2
✓ Branch 0 taken 5433 times.
✓ Branch 1 taken 5291 times.
10724 while (sent < msgCount) {
822 5433 errno = 0;
823 5433 oc = SendMessagesHelper(&msgs[sent], msgCount - sent);
824
2/2
✓ Branch 0 taken 142 times.
✓ Branch 1 taken 5291 times.
5433 if (oc <= 0)
825 142 break;
826 5291 sent += oc;
827 }
828
829
2/2
✓ Branch 0 taken 5291 times.
✓ Branch 1 taken 142 times.
5433 if (sent == msgCount) {
830 5291 ClearSendErrorWarning();
831 5291 return true;
832 }
833
834 142 int err = errno;
835
4/8
✓ Branch 0 taken 142 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 142 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 142 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 142 times.
✗ Branch 7 not taken.
142 if (oc < 0 && err != EAGAIN && err != EWOULDBLOCK && err != ENOBUFS) {
836 142 RaiseSendErrorWarning(err);
837 } else {
838 ✗ NoteDroppedFrame();
839 }
840 142 return false;
841 }
842
843 // Raised when sends fail outright (interface down, USB NIC unplugged), and
844 // cleared by the first frame that is sent whole.
845 142 void ColorLight5a75Output::RaiseSendErrorWarning(int err) {
846
4/8
✓ Branch 0 taken 142 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 142 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 142 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 142 times.
✗ Branch 7 not taken.
142 std::string warning = "ColorLight: Unable to send on interface " + m_ifName + ": " + FPPstrerror(err);
847
2/2
✓ Branch 0 taken 141 times.
✓ Branch 1 taken 1 times.
142 if (warning == m_sendErrorWarning)
848 141 return;
849
850
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 ClearSendErrorWarning();
851
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogErr(VB_CHANNELOUT, "%s\n", warning.c_str());
852
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 WarningHolder::AddWarning(7, warning);
853
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 m_sendErrorWarning = warning;
854
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 141 times.
142 }
855
856 5391 void ColorLight5a75Output::ClearSendErrorWarning() {
857
2/2
✓ Branch 0 taken 5390 times.
✓ Branch 1 taken 1 times.
5391 if (m_sendErrorWarning.empty())
858 5390 return;
859
860 1 LogInfo(VB_CHANNELOUT, "ColorLight: Sending on interface %s again\n", m_ifName.c_str());
861
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 WarningHolder::RemoveWarning(7, m_sendErrorWarning);
862 1 m_sendErrorWarning.clear();
863 }
864
865 // A frame the network could not take is dropped whole. The warning times
866 // out on its own once frames stop being dropped; the log gets one summary
867 // per interval rather than a line per frame.
868 149 void ColorLight5a75Output::NoteDroppedFrame() {
869
3/6
✓ Branch 0 taken 149 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 149 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 149 times.
✗ Branch 5 not taken.
447 WarningHolder::AddWarningTimeout(30, 21, CL_DROPPED_FRAMES_WARNING);
870
871 149 m_droppedFrames++;
872
873 149 long long now = GetMonotonicTimeMS();
874
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 148 times.
149 if (now - m_droppedLogTime >= 10000) {
875 1 LogWarn(VB_CHANNELOUT, "ColorLight: Interface %s is not keeping up, %d frame(s) dropped since the last report\n",
876 m_ifName.c_str(), m_droppedFrames);
877 1 m_droppedFrames = 0;
878 1 m_droppedLogTime = now;
879 }
880 149 }
881
882 // Whether a whole frame fits in what is left of the socket's send buffer.
883 // If the previous frames have not drained by now, the network (or the USB
884 // NIC behind it) cannot keep up, and queueing more only adds latency.
885 3006 bool ColorLight5a75Output::FrameFits() {
886 #ifndef PLATFORM_OSX
887 3006 int queued = 0;
888
3/6
✓ Branch 0 taken 3006 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3006 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3006 times.
✗ Branch 5 not taken.
3006 if (m_sendBufferSize && ioctl(m_fd, SIOCOUTQ, &queued) == 0)
889 3006 return queued + m_frameCharge <= m_sendBufferSize;
890 #endif
891 ✗ return true;
892 }
893
894 /*
895 *
896 */
897 3006 void ColorLight5a75Output::PrepData(unsigned char* channelData) {
898 3006 m_matrix->OverlaySubMatrices(channelData);
899
900 3006 m_frameSent = false;
901
2/2
✓ Branch 0 taken 149 times.
✓ Branch 1 taken 2857 times.
3006 if (!FrameFits()) {
902 149 NoteDroppedFrame();
903 149 return;
904 }
905
906 // Every pointer is a local __restrict copy: the stores are through a
907 // char pointer, which may alias anything, so reading the maps through
908 // members would reload them for every byte.
909 2857 const unsigned char* __restrict src = channelData + m_startChannel; // FIXME, this function gets offset 0
910 2857 unsigned char* __restrict frame = (unsigned char*)m_outputFrame.get();
911 2857 const uint8_t* __restrict gamma = m_gammaCurve;
912 2857 const uint32_t* __restrict pixel = m_remapSrc.data();
913
914
2/2
✓ Branch 0 taken 65776 times.
✓ Branch 1 taken 2857 times.
68633 for (const RemapRun& run : m_remapRuns) {
915 65776 unsigned char* __restrict dst = frame + run.dst;
916 65776 const uint32_t o0 = run.order[0];
917 65776 const uint32_t o1 = run.order[1];
918 65776 const uint32_t o2 = run.order[2];
919
920
2/2
✓ Branch 0 taken 2661888 times.
✓ Branch 1 taken 65776 times.
2727664 for (uint32_t x = 0; x < run.count; x++, dst += 3) {
921 2661888 const unsigned char* s = src + *(pixel++);
922 2661888 dst[0] = gamma[s[o0]];
923 2661888 dst[1] = gamma[s[o1]];
924 2661888 dst[2] = gamma[s[o2]];
925 }
926 }
927
928 2857 m_frameSent = SendMessages(m_msgs);
929 2857 m_rowsSentTime = GetMonotonicTimeMicros();
930 }
931
932 /*
933 * Flatten the panel maps into the order PrepData() writes the output frame.
934 * A map with one int per byte is three times the reads of one per pixel, and
935 * on a single-core Beagle it does not fit in cache, so the color order is
936 * pulled out into per-run offsets. PanelMatrix gives every pixel a single
937 * color order, so a run is normally a whole panel row; a pixel whose offsets
938 * differ from its neighbor's just starts a new run.
939 */
940 100 void ColorLight5a75Output::BuildRemap(void) {
941 100 m_remapRuns.clear();
942 100 m_remapSrc.clear();
943
944
2/2
✓ Branch 0 taken 102 times.
✓ Branch 1 taken 100 times.
202 for (int output = 0; output < m_outputs; output++) {
945
2/2
✓ Branch 0 taken 125 times.
✓ Branch 1 taken 102 times.
227 for (int panel : m_panelMatrix->m_outputPanels[output]) {
946 125 const LEDPanel& p = m_panelMatrix->m_panels[panel];
947 125 int chain = (m_longestChain - 1) - p.chain;
948
949
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 125 times.
125 if (m_flippedLayout)
950 ✗ chain = p.chain;
951
952
2/2
✓ Branch 0 taken 2128 times.
✓ Branch 1 taken 125 times.
2253 for (int y = 0; y < m_panelHeight; y++) {
953 2128 uint32_t dst = (((((output * m_panelHeight) + y) * m_panelWidth * m_longestChain) + (chain * m_panelWidth)) * 3);
954 2128 const int* map = &p.pixelMap[y * m_panelWidth * 3];
955 2128 RemapRun* run = nullptr;
956
957
2/2
✓ Branch 0 taken 76288 times.
✓ Branch 1 taken 2128 times.
78416 for (int x = 0; x < m_panelWidth; x++, map += 3, dst += 3) {
958 76288 uint32_t base = std::min(map[0], std::min(map[1], map[2]));
959 76288 uint32_t order[3] = { map[0] - base, map[1] - base, map[2] - base };
960
961
3/4
✓ Branch 0 taken 74160 times.
✓ Branch 1 taken 2128 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 74160 times.
76288 if (!run || memcmp(run->order, order, sizeof(order))) {
962
1/2
✓ Branch 0 taken 2128 times.
✗ Branch 1 not taken.
2128 m_remapRuns.push_back({ dst, 0, { order[0], order[1], order[2] } });
963 2128 run = &m_remapRuns.back();
964 }
965 76288 run->count++;
966
1/2
✓ Branch 0 taken 76288 times.
✗ Branch 1 not taken.
76288 m_remapSrc.push_back(base);
967 }
968 }
969 }
970 }
971
972 100 LogDebug(VB_CHANNELOUT, "ColorLight remap: %d pixels in %d runs\n",
973 (int)m_remapSrc.size(), (int)m_remapRuns.size());
974 100 }
975
976 5433 int ColorLight5a75Output::SendMessagesHelper(struct mmsghdr* msgs, int msgCount) {
977 #ifdef PLATFORM_OSX
978 char buf[1500];
979 for (int m = 0; m < msgCount; m++) {
980 int cur = 0;
981 for (int io = 0; io < msgs[m].msg_hdr.msg_iovlen; io++) {
982 memcpy(&buf[cur], msgs[m].msg_hdr.msg_iov[io].iov_base, msgs[m].msg_hdr.msg_iov[io].iov_len);
983 cur += msgs[m].msg_hdr.msg_iov[io].iov_len;
984 }
985 int bytes_sent = write(m_fd, buf, cur);
986 if (bytes_sent != cur) {
987 return m;
988 }
989 }
990 return msgCount;
991 #else
992 5433 return sendmmsg(m_fd, msgs, msgCount, MSG_DONTWAIT);
993 #endif
994 }
995
996 /*
997 *
998 */
999 2924 int ColorLight5a75Output::SendData(unsigned char* channelData) {
1000 2924 LogExcess(VB_CHANNELOUT, "ColorLight5a75Output::SendData(%p)\n", channelData);
1001
1002 // Latching a frame whose rows did not all go out would show part of it
1003 // over the last one, so the receiver keeps the last whole frame instead.
1004
2/2
✓ Branch 0 taken 2576 times.
✓ Branch 1 taken 348 times.
2924 if (m_frameSent) {
1005 // The receiver latches on the sync, and one that follows the last
1006 // row too closely on the wire arrives before it has stored that row:
1007 // the row then lights only part way. Normally a whole frame has
1008 // passed since PrepData(), but while bridging the two run back to
1009 // back, so wait until the rows have left (the socket's queue is
1010 // empty) and then m_syncGapUS more. Bounded at 20ms after the rows,
1011 // and only while bridging, so the output thread does not otherwise
1012 // wait.
1013
1/2
✓ Branch 0 taken 2576 times.
✗ Branch 1 not taken.
2576 long long now = GetMonotonicTimeMicros();
1014 2576 const long long limit = m_rowsSentTime + 20000;
1015 2576 bool drainSeen = false;
1016 2576 int queued = 0;
1017 #ifndef PLATFORM_OSX
1018
5/8
✓ Branch 0 taken 140 times.
✓ Branch 1 taken 2436 times.
✓ Branch 2 taken 140 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 140 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 2576 times.
2576 while (now < limit && ioctl(m_fd, SIOCOUTQ, &queued) == 0 && queued > 0) {
1019 ✗ drainSeen = true;
1020 ✗ std::this_thread::sleep_for(std::chrono::microseconds(50));
1021 ✗ now = GetMonotonicTimeMicros();
1022 }
1023 #endif
1024 // Already empty: the rows left no later than now, nor than their
1025 // estimated wire time after they were queued.
1026
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2576 times.
2576 long long rowsOut = drainSeen ? now : std::min(now, m_rowsSentTime + m_rowsWireUS);
1027 2576 long long wait = std::min(rowsOut + m_syncGapUS, limit) - now;
1028
2/2
✓ Branch 0 taken 129 times.
✓ Branch 1 taken 2447 times.
2576 if (wait > 0)
1029
1/2
✓ Branch 0 taken 129 times.
✗ Branch 1 not taken.
129 std::this_thread::sleep_for(std::chrono::microseconds(wait));
1030
1/2
✓ Branch 0 taken 2576 times.
✗ Branch 1 not taken.
2576 SendMessages(m_syncMsgs);
1031 }
1032
1033 2924 return m_channelCount;
1034 }
1035
1036 /*
1037 *
1038 */
1039 99 void ColorLight5a75Output::DumpConfig(void) {
1040 99 LogDebug(VB_CHANNELOUT, "ColorLight5a75Output::DumpConfig()\n");
1041
1042 99 LogDebug(VB_CHANNELOUT, " Width : %d\n", m_width);
1043 99 LogDebug(VB_CHANNELOUT, " Height : %d\n", m_height);
1044 99 LogDebug(VB_CHANNELOUT, " Rows : %d\n", m_rows);
1045 99 LogDebug(VB_CHANNELOUT, " Row Size : %d\n", m_rowSize);
1046 99 LogDebug(VB_CHANNELOUT, " m_fd : %d\n", m_fd);
1047 99 LogDebug(VB_CHANNELOUT, " Outputs : %d\n", m_outputs);
1048 99 LogDebug(VB_CHANNELOUT, " Longest Chain : %d\n", m_longestChain);
1049 99 LogDebug(VB_CHANNELOUT, " Inverted Data : %d\n", m_invertedData);
1050 99 LogDebug(VB_CHANNELOUT, " Interface : %s\n", m_ifName.c_str());
1051
1052 99 ChannelOutput::DumpConfig();
1053 99 }
1054
1055 /*
1056 *
1057 */
1058 1932 void ColorLight5a75Output::SetHostMACs(void* ptr) {
1059 1932 struct ether_header* eh = (struct ether_header*)ptr;
1060
1061 // Set the source MAC address
1062 1932 eh->ether_shost[0] = 0x22;
1063 1932 eh->ether_shost[1] = 0x22;
1064 1932 eh->ether_shost[2] = 0x33;
1065 1932 eh->ether_shost[3] = 0x44;
1066 1932 eh->ether_shost[4] = 0x55;
1067 1932 eh->ether_shost[5] = 0x66;
1068
1069 // Set the dest MAC address
1070 1932 eh->ether_dhost[0] = 0x11;
1071 1932 eh->ether_dhost[1] = 0x22;
1072 1932 eh->ether_dhost[2] = 0x33;
1073 1932 eh->ether_dhost[3] = 0x44;
1074 1932 eh->ether_dhost[4] = 0x55;
1075 1932 eh->ether_dhost[5] = 0x66;
1076 1932 }
1077
1078 /*
1079 *
1080 */
1081 70 void ColorLight5a75Output::GetReceiverInfo() {
1082 int sockfd;
1083 int sockopt;
1084 struct ifreq ifopts;
1085 struct mmsghdr msgs[1];
1086 struct iovec iovecs[1];
1087
1088 unsigned char discoverBuffer[CL_DISC_PACKET_SIZE];
1089 70 memset(discoverBuffer, 0, CL_DISC_PACKET_SIZE);
1090
1091 // Setup Discover packet
1092
1/2
✓ Branch 0 taken 70 times.
✗ Branch 1 not taken.
70 SetHostMACs(discoverBuffer);
1093
1094 70 discoverBuffer[CL_PACKET_TYPE_OFFSET] = CL_DISC_PACKET_TYPE;
1095
1096 70 iovecs[0].iov_base = discoverBuffer;
1097 70 iovecs[0].iov_len = CL_DISC_PACKET_SIZE;
1098
1099 70 memset(msgs, 0, sizeof(struct mmsghdr));
1100 70 msgs[0].msg_hdr.msg_iov = &iovecs[0];
1101 70 msgs[0].msg_hdr.msg_iovlen = 1;
1102
1103 #ifndef PLATFORM_OSX
1104 // Setup socket to receive response(s)
1105
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 70 times.
70 if ((sockfd = OpenSocket(PF_PACKET, SOCK_RAW, htons(0x0805))) == -1) {
1106 ✗ LogErr(VB_CHANNELOUT, "Unable to open receive socket\n");
1107 ✗ return;
1108 }
1109
1110 70 memset(&ifopts, 0, sizeof(ifopts));
1111 70 strncpy(ifopts.ifr_name, m_ifName.c_str(), IFNAMSIZ - 1);
1112 70 ioctl(sockfd, SIOCGIFFLAGS, &ifopts);
1113 70 ifopts.ifr_flags |= IFF_PROMISC;
1114 70 ioctl(sockfd, SIOCSIFFLAGS, &ifopts);
1115
1116 // Wait up to 300ms for responses, shouldn't take anywhere near this long
1117 struct timeval tv;
1118 70 tv.tv_sec = 0;
1119 70 tv.tv_usec = 300000;
1120
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 70 times.
70 if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv) == -1) {
1121 ✗ LogErr(VB_CHANNELOUT, "Unable to set socket receive timeout\n");
1122 ✗ close(sockfd);
1123 ✗ return;
1124 }
1125
1126
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 70 times.
70 if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof sockopt) == -1) {
1127 ✗ LogErr(VB_CHANNELOUT, "Unable to set receive socket options\n");
1128 ✗ close(sockfd);
1129 ✗ return;
1130 }
1131
1132
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 70 times.
70 if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, m_ifName.c_str(), IFNAMSIZ - 1) == -1) {
1133 ✗ LogErr(VB_CHANNELOUT, "Unable to bind receive socket\n");
1134 ✗ close(sockfd);
1135 ✗ return;
1136 }
1137 #else
1138 sockfd = bindBPFSocket(m_ifName);
1139 #endif
1140
1141
1/2
✓ Branch 0 taken 70 times.
✗ Branch 1 not taken.
70 LogInfo(VB_CHANNELOUT, "Searching for ColorLight receivers\n");
1142
1143 unsigned char receiveBuffer[CL_RESP_PACKET_SIZE];
1144 70 unsigned char* data = receiveBuffer + CL_PACKET_DATA_OFFSET;
1145 70 int recvBytes = 0;
1146 70 int r = 0;
1147 70 bool tryNextReceiver = true;
1148 70 bool foundReceiver = false;
1149
1150 70 memset(receiveBuffer, 0, CL_RESP_PACKET_SIZE);
1151
1152 70 int retryCount = 2;
1153
3/4
✓ Branch 0 taken 210 times.
✓ Branch 1 taken 70 times.
✓ Branch 2 taken 210 times.
✗ Branch 3 not taken.
280 while (tryNextReceiver && r < 32) {
1154 210 foundReceiver = false;
1155 210 discoverBuffer[16] = r;
1156
1/2
✓ Branch 0 taken 210 times.
✗ Branch 1 not taken.
210 sendmmsg(m_fd, msgs, 1, MSG_DONTWAIT);
1157
2/2
✓ Branch 0 taken 70 times.
✓ Branch 1 taken 140 times.
210 if (retryCount == 0) {
1158
1/2
✓ Branch 0 taken 70 times.
✗ Branch 1 not taken.
70 sendmmsg(m_fd, msgs, 1, MSG_DONTWAIT);
1159
1/2
✓ Branch 0 taken 70 times.
✗ Branch 1 not taken.
70 sendmmsg(m_fd, msgs, 1, MSG_DONTWAIT);
1160 }
1161
1162 // Should only get one packet from a receiver, but consume all just in case
1163
2/4
✓ Branch 0 taken 210 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 210 times.
210 while ((recvBytes = recvfrom(sockfd, receiveBuffer, CL_RESP_PACKET_SIZE, 0, NULL, NULL)) > 0) {
1164 ✗ if (recvBytes > 1000) {
1165 // Shouldn't be receiving anything, but ignore anything other than receiver replies
1166 ✗ if ((receiveBuffer[6] != 0x11) ||
1167 ✗ (receiveBuffer[7] != 0x22) ||
1168 ✗ (receiveBuffer[8] != 0x33) ||
1169 ✗ (receiveBuffer[9] != 0x44) ||
1170 ✗ (receiveBuffer[10] != 0x55) ||
1171 ✗ (receiveBuffer[11] != 0x66) ||
1172 ✗ (receiveBuffer[CL_PACKET_TYPE_OFFSET] != CL_RESP_PACKET_TYPE)) {
1173 ✗ continue;
1174 }
1175
1176 ✗ if (WillLog(LOG_DEBUG, VB_CHANNELOUT)) {
1177 ✗ LogDebug(VB_CHANNELOUT, "Response received from receiver id %d\n", r);
1178 ✗ HexDump("DiscoverResponse", receiveBuffer, 256, VB_CHANNELOUT);
1179 }
1180
1181 ✗ foundReceiver = true;
1182 ColorLightReceiver receiver;
1183
1184 ✗ receiver.id = data[85];
1185 ✗ receiver.majorFirmwareVersion = data[2];
1186 ✗ receiver.minorFirmwareVersion = data[3];
1187
1188 ✗ receiver.width = (data[21] << 8) | data[22];
1189 ✗ receiver.height = (data[23] << 8) | data[24];
1190 ✗ receiver.packets = (data[38] << 24) | (data[39] << 16) | (data[40] << 8) | data[41];
1191 ✗ receiver.uptime = (data[46] << 24) | (data[47] << 16) | (data[48] << 8) | data[49];
1192
1193 ✗ LogInfo(VB_CHANNELOUT, "Found Receiver #%d:\n", receiver.id);
1194 ✗ LogInfo(VB_CHANNELOUT, "- Firmware Version : v%d.%d\n",
1195 receiver.majorFirmwareVersion, receiver.minorFirmwareVersion);
1196 ✗ LogInfo(VB_CHANNELOUT, "- Cabinet Size (WxH) : %dx%d\n", receiver.width, receiver.height);
1197 // LogInfo(VB_CHANNELOUT, "- Cabinet Offset (X,Y): %d,%d\n", receiver.xOffset, receiver.yOffset);
1198 ✗ LogInfo(VB_CHANNELOUT, "- Uptime : %ums\n", receiver.uptime);
1199 ✗ LogInfo(VB_CHANNELOUT, "- Packets : %u\n", receiver.packets);
1200
1201 ✗ if (receiver.majorFirmwareVersion > m_highestFirmwareVersion) {
1202 ✗ m_highestFirmwareVersion = receiver.majorFirmwareVersion;
1203 }
1204
1205 ✗ m_receivers.resize(r + 1);
1206 ✗ m_receivers[r] = receiver;
1207 }
1208 }
1209
1/2
✓ Branch 0 taken 210 times.
✗ Branch 1 not taken.
210 if (!foundReceiver) {
1210
3/4
✓ Branch 0 taken 210 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 140 times.
✓ Branch 3 taken 70 times.
210 if (r == 0 && retryCount > 0) {
1211 140 r--;
1212 140 retryCount--;
1213 } else {
1214 70 tryNextReceiver = false;
1215 }
1216 }
1217 210 r++;
1218 }
1219
1220
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 70 times.
70 if (m_receivers.size()) {
1221 ✗ LogInfo(VB_CHANNELOUT, "Found %d ColorLight receiver(s)\n", m_receivers.size());
1222 } else {
1223
1/2
✓ Branch 0 taken 70 times.
✗ Branch 1 not taken.
70 std::string warning = "ColorLight: No receiver cards were detected on interface " + m_ifName;
1224
1/2
✓ Branch 0 taken 70 times.
✗ Branch 1 not taken.
70 WarningHolder::AddWarning(9, warning);
1225
1/2
✓ Branch 0 taken 70 times.
✗ Branch 1 not taken.
70 LogWarn(VB_CHANNELOUT, "WARNING: %s\n", warning.c_str());
1226
1/2
✓ Branch 0 taken 70 times.
✗ Branch 1 not taken.
70 m_warnings.push_front(warning);
1227 70 }
1228
1229
1/2
✓ Branch 0 taken 70 times.
✗ Branch 1 not taken.
70 close(sockfd);
1230 }
1231