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