GCC Code Coverage Report


Directory: ./
File: channeloutput/LOREnhanced.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 186 209 89.0%
Functions: 19 19 100.0%
Branches: 151 282 53.5%

Line Branch Exec Source
1 /*
2 * This file is part of the Falcon Player (FPP) and is Copyright (C)
3 * 2013-2022 by the Falcon Player Developers.
4 *
5 * The Falcon Player (FPP) is free software, and is covered under
6 * multiple Open Source licenses. Please see the included 'LICENSES'
7 * file for descriptions of what files are covered by each license.
8 *
9 * This source file is covered under the GPL v2 as described in the
10 * included LICENSE.GPL file.
11 */
12
13 #include "fpp-pch.h"
14
15 #include "fpp-json.h"
16
17 #include <cstdint>
18 #include <vector>
19
20 #include "../Warnings.h"
21 #include "../common.h"
22 #include "../log.h"
23
24 #include "LOREnhanced.h"
25 #include "LORSerial.h"
26 #include "ChannelOutputSetup.h"
27
28 // A Pixie port set for 340 pixels takes two unit IDs, so a unit ID never
29 // carries more than 170 pixels.
30 #define LORE_MAX_PIXELS_PER_UNIT 170
31 #define LORE_MAX_CHANNELS_PER_UNIT (LORE_MAX_PIXELS_PER_UNIT * 3)
32 #define LOR_ENHANCED_CMD 0x8E
33
34 /*
35 * Enhanced command: 00 <unit> 8E <ops...> 00
36 *
37 * The ops walk the unit's channels from channel 1. Each op is a command
38 * byte, [7:5] the op and [4:0] a count; a count of 11111 means the count is
39 * in the next byte instead (FPP only uses that for counts over 30).
40 *
41 * 000 literal: the next <count> bytes are values for the next channels
42 * 001 jump: skip <count> channels
43 * 010 repeat: the next 3 bytes are one pixel, written to <count> pixels
44 *
45 * Values are 1-255 (0 is the delimiter, so 0 is sent as 1). Checked on a
46 * Pixie: a jump leaves the channels it skips unchanged, and so do channels
47 * past the end of the packet, so a frame can carry only what changed. A
48 * unit blanks its outputs when the heartbeat stops.
49 */
50
51 /////////////////////////////////////////////////////////////////////////////
52
53 class LOREnhancedOutputUnit {
54 public:
55 int unitId = 1;
56 int numOfPixels = 0;
57 int lorStartPixel = 1;
58 int fppStartAddr = 0;
59
60 // What the unit was last sent for each of its channels, or LOR_UNKNOWN.
61 std::vector<uint16_t> lastSent;
62 };
63
64 class LOREnhancedOutputData : public LORSerialOutput {
65 public:
66 6 LOREnhancedOutputData() {
67 6 speed = 1000000;
68 6 }
69
70 std::vector<LOREnhancedOutputUnit> units;
71
72 // Unit the next frame starts with, so a frame whose changes do not all
73 // fit the budget does not starve the units after it.
74 int nextUnit = 0;
75 // Next pixel the background refresh marks for resending.
76 int refreshUnit = 0;
77 int refreshPixel = 0;
78
79 unsigned char batch[LOR_MAX_BATCH_SIZE];
80
81 // One per packet in the batch: the unit, the pixels its ops reach, and
82 // where the packet ends in the batch, so a short write can mark them
83 // unsent again.
84 struct SentPacket {
85 int unit;
86 int firstPixel;
87 int endPixel;
88 int batchEnd;
89 };
90 std::vector<SentPacket> sent;
91 };
92
93 /////////////////////////////////////////////////////////////////////////////
94
95 #include "Plugin.h"
96 class LOREnhancedPlugin : public FPPPlugins::Plugin, public FPPPlugins::ChannelOutputPlugin {
97 public:
98 6 LOREnhancedPlugin() :
99
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
18 FPPPlugins::Plugin("LOREnhanced") {
100 6 }
101 6 virtual ChannelOutput* createChannelOutput(unsigned int startChannel, unsigned int channelCount) override {
102
2/6
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
12 return new LOREnhancedOutput(startChannel, channelCount);
103 }
104 };
105
106 extern "C" {
107 6 FPPPlugins::Plugin* createPlugin() {
108
2/6
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
12 return new LOREnhancedPlugin();
109 }
110 }
111
112 6 LOREnhancedOutput::LOREnhancedOutput(unsigned int startChannel, unsigned int channelCount) :
113 ChannelOutput(startChannel, channelCount),
114 6 data(nullptr) {}
115 24 LOREnhancedOutput::~LOREnhancedOutput() {
116
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
12 if (data) {
117
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
12 delete data;
118 }
119 24 }
120
121 12 void LOREnhancedOutput::GetRequiredChannelRanges(const std::function<void(int, int)>& addRange) {
122 12 addRange(m_startChannel, m_startChannel + m_channelCount - 1);
123 12 }
124
125 6 int LOREnhancedOutput::Close(void) {
126 6 LogDebug(VB_CHANNELOUT, "LOREnhancedOutput::Close()\n");
127 6 data->StopHeartbeat();
128 6 data->closeSerialPort();
129 6 return ChannelOutput::Close();
130 }
131
132 6 void LOREnhancedOutput::DumpConfig(void) {
133 6 ChannelOutput::DumpConfig();
134
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (data) {
135 6 data->dumpSerialConfig();
136 6 LogDebug(VB_CHANNELOUT, " port speed : %d\n", data->speed);
137
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 6 times.
14 for (auto& unit : data->units) {
138
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 LogDebug(VB_CHANNELOUT, " Unit Id : %d\n", unit.unitId);
139
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 LogDebug(VB_CHANNELOUT, " Pixels : %d\n", unit.numOfPixels);
140
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 LogDebug(VB_CHANNELOUT, " Lor Start : %d\n", unit.lorStartPixel);
141
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 LogDebug(VB_CHANNELOUT, " FPP Start : %d\n", unit.fppStartAddr);
142 }
143 }
144 6 }
145
146 6 int LOREnhancedOutput::Init(Json::Value config) {
147 6 LogDebug(VB_CHANNELOUT, "LOREnhancedOutput::Init()\n");
148
2/6
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
6 data = new LOREnhancedOutputData();
149
150
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 times.
6 if (config.isMember("speed")) {
151 5 data->speed = config["speed"].asInt();
152 // Older versions of the UI offered a mistyped 115400.
153
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (data->speed == 115400) {
154 ✗ data->speed = 115200;
155 }
156 }
157
158
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (config.isMember("units")) {
159
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 auto root = config["units"];
160
6/10
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 16 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 10 times.
✓ Branch 9 taken 6 times.
16 for (Json::Value::iterator it = root.begin(); it != root.end(); ++it) {
161 10 LOREnhancedOutputUnit newUnit;
162
3/6
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
10 if ((*it).isMember("unitId")) {
163
3/6
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
10 newUnit.unitId = (*it)["unitId"].asInt();
164 }
165
3/6
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
10 if ((*it).isMember("numOfPixels")) {
166
3/6
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
10 newUnit.numOfPixels = (*it)["numOfPixels"].asInt();
167 }
168
3/6
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
10 if ((*it).isMember("lorStartPixel")) {
169
3/6
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
10 newUnit.lorStartPixel = (*it)["lorStartPixel"].asInt();
170 }
171 // 1-based. A blank field arrives as JSON null and so as 0, which
172 // sent the same packet as 1 (no leading jump) but made the
173 // 510-channel check below count one pixel short: 171 pixels then
174 // passed it and overran the 518 byte packet buffer.
175
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 8 times.
10 if (newUnit.lorStartPixel < 1) {
176 2 newUnit.lorStartPixel = 1;
177 }
178
3/6
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
10 if ((*it).isMember("fppStartAddr")) {
179
3/6
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
10 newUnit.fppStartAddr = (*it)["fppStartAddr"].asInt();
180 }
181 // SendUnitData indexes channelData[fppStartAddr - 1 + ...], so the
182 // channel is 1-based and there is no valid zero. The key is
183 // optional and a blank UI field arrives as JSON null, both of which
184 // asInt() turns into 0, so the unit has to be dropped here rather
185 // than reaching the output path.
186
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 8 times.
10 if (newUnit.fppStartAddr < 1) {
187
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 LogErr(VB_CHANNELOUT, "Invalid FPP Start Channel %d for LOR unit %d, ignoring the unit\n",
188 newUnit.fppStartAddr, newUnit.unitId);
189
5/10
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
✗ Branch 9 not taken.
2 AddOutputConfigWarning(39, "LOR unit " + std::to_string(newUnit.unitId) + ": invalid FPP start channel " + std::to_string(newUnit.fppStartAddr) + " - unit disabled");
190 2 continue;
191 }
192
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 newUnit.lastSent.assign(std::max(newUnit.numOfPixels, 0) * 3, LOR_UNKNOWN);
193
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 data->units.push_back(newUnit);
194
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
10 }
195 6 }
196
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (!data->setupSerialPort(config, data->speed, "8N1")) {
197 ✗ return 0;
198 }
199 6 data->StartHeartbeat();
200
201
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 return ChannelOutput::Init(config);
202 }
203
204 160 bool LOREnhancedOutput::UnitIsValid(const LOREnhancedOutputUnit& unit) {
205
2/2
✓ Branch 0 taken 45 times.
✓ Branch 1 taken 115 times.
160 if ((unit.lorStartPixel - 1) * 3 + unit.numOfPixels * 3 > LORE_MAX_CHANNELS_PER_UNIT) {
206 45 LogErr(VB_CHANNELOUT, "More than 510 Channels, ie 170 pixels, per unit. Unit %d, Total channels: %d\n",
207 unit.unitId, (unit.lorStartPixel - 1) * 3 + unit.numOfPixels * 3);
208
4/8
✓ Branch 0 taken 45 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 45 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 45 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 45 times.
✗ Branch 7 not taken.
45 WarningHolder::AddWarningTimeout(120, 39, "LOR unit " + std::to_string(unit.unitId) + ": more than 510 channels (170 pixels) per unit");
209 45 return false;
210 }
211 // The LOR start pixel places the unit's pixels on the controller; it
212 // does not move them in FPP's channel data, so it is not counted here.
213
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 115 times.
115 if (unit.fppStartAddr - 1 + unit.numOfPixels * 3 > (int)m_channelCount) {
214 ✗ LogErr(VB_CHANNELOUT, "Invalid FPP Channel Range. Unit %d\n",
215 unit.unitId);
216 ✗ WarningHolder::AddWarningTimeout(120, 39, "LOR unit " + std::to_string(unit.unitId) + ": channel range exceeds the configured channel count");
217 ✗ return false;
218 }
219
2/4
✓ Branch 0 taken 115 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 115 times.
115 if (unit.unitId < 1 || unit.unitId > 240) {
220 ✗ LogErr(VB_CHANNELOUT, "Invalid Unit ID. Unit %d\n",
221 unit.unitId);
222 ✗ WarningHolder::AddWarningTimeout(120, 39, "LOR: invalid unit ID " + std::to_string(unit.unitId) + " (must be 1-240)");
223 ✗ return false;
224 }
225 115 return unit.numOfPixels > 0;
226 }
227
228 18462 static inline uint16_t LORE_Value(unsigned char v) {
229
2/2
✓ Branch 0 taken 4536 times.
✓ Branch 1 taken 13926 times.
18462 return v ? v : 1;
230 }
231
232 // Writes an op's command byte(s) and returns how many it took.
233 180 static inline int LORE_PutCount(unsigned char* p, int op, int count) {
234
2/2
✓ Branch 0 taken 160 times.
✓ Branch 1 taken 20 times.
180 if (count <= 30) {
235 160 p[0] = op | count;
236 160 return 1;
237 }
238 20 p[0] = op | 0x1F;
239 20 p[1] = count;
240 20 return 2;
241 }
242 180 static inline int LORE_CountBytes(int count) {
243
2/2
✓ Branch 0 taken 160 times.
✓ Branch 1 taken 20 times.
180 return count <= 30 ? 1 : 2;
244 }
245 99 static int LORE_JumpBytes(int channels) {
246 99 int bytes = 0;
247
2/2
✓ Branch 0 taken 81 times.
✓ Branch 1 taken 99 times.
180 for (; channels > 0; channels -= 255) {
248 81 bytes += LORE_CountBytes(std::min(channels, 255));
249 }
250 99 return bytes;
251 }
252
253 /*
254 * Appends a packet bringing one unit up to date with cur, as far as room
255 * bytes allow. Unchanged pixels are jumped over, a run of two or more
256 * identical pixels is one repeat (4 bytes against 6 or more as literals),
257 * and the remaining changed pixels go as literals. Works in whole pixels,
258 * which is what was checked on hardware. Pixels it covers are recorded as
259 * sent. Returns the bytes appended, 0 if there was nothing to send or not
260 * even one op fit; complete says whether every changed pixel went.
261 */
262 115 int LOREnhancedOutput::EncodeUnit(int unitIdx, const unsigned char* cur, unsigned char* out, int room, bool& complete) {
263 115 LOREnhancedOutputUnit& unit = data->units[unitIdx];
264 115 const int n = unit.numOfPixels;
265 115 const int base = unit.lorStartPixel - 1;
266 889 auto changed = [&](int p) {
267 889 const uint16_t* last = &unit.lastSent[p * 3];
268
4/6
✓ Branch 0 taken 790 times.
✓ Branch 1 taken 99 times.
✓ Branch 2 taken 790 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 790 times.
889 return last[0] != LORE_Value(cur[p * 3]) || last[1] != LORE_Value(cur[p * 3 + 1]) || last[2] != LORE_Value(cur[p * 3 + 2]);
269 115 };
270 1711 auto same = [&](int a, int b) {
271
3/6
✓ Branch 0 taken 1711 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1711 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1711 times.
✗ Branch 5 not taken.
1711 return LORE_Value(cur[a * 3]) == LORE_Value(cur[b * 3]) && LORE_Value(cur[a * 3 + 1]) == LORE_Value(cur[b * 3 + 1]) && LORE_Value(cur[a * 3 + 2]) == LORE_Value(cur[b * 3 + 2]);
272 115 };
273
274 115 complete = true;
275 115 int len = 3;
276 115 int streamAt = 0; // pixels from LOR pixel 1 the ops have reached
277 115 int firstPixel = -1;
278 115 int p = 0;
279
2/2
✓ Branch 0 taken 889 times.
✓ Branch 1 taken 115 times.
1004 while (p < n) {
280
2/2
✓ Branch 0 taken 790 times.
✓ Branch 1 taken 99 times.
889 if (!changed(p)) {
281 790 p++;
282 790 continue;
283 }
284 99 int run = 1;
285
6/8
✓ Branch 0 taken 1711 times.
✓ Branch 1 taken 99 times.
✓ Branch 2 taken 1711 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1711 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1711 times.
✓ Branch 7 taken 99 times.
1810 while (p + run < n && run < 255 && same(p, p + run)) {
286 1711 run++;
287 }
288 99 bool repeat = run >= 2;
289 99 int count = run;
290
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 91 times.
99 if (!repeat) {
291 // Literal over the following changed pixels, stopping at an
292 // unchanged one (a jump is cheaper) or at the start of a run.
293 8 count = 1;
294
2/12
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ 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 8 times.
8 while (p + count < n && count < 85 && changed(p + count) && !(p + count + 1 < n && same(p + count, p + count + 1))) {
295 ✗ count++;
296 }
297 }
298 99 int jump = (base + p - streamAt) * 3;
299
1/2
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
99 int avail = room - len - 1 - LORE_JumpBytes(jump); // 1 for the closing 00
300
2/2
✓ Branch 0 taken 91 times.
✓ Branch 1 taken 8 times.
99 int opBytes = repeat ? LORE_CountBytes(count) + 3 : LORE_CountBytes(count * 3) + count * 3;
301
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 99 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
99 if (opBytes > avail && !repeat) {
302 ✗ while (count > 0 && LORE_CountBytes(count * 3) + count * 3 > avail) {
303 ✗ count--;
304 }
305 ✗ opBytes = LORE_CountBytes(count * 3) + count * 3;
306 }
307
2/4
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 99 times.
99 if (count == 0 || opBytes > avail) {
308 ✗ complete = false;
309 ✗ break;
310 }
311
2/2
✓ Branch 0 taken 81 times.
✓ Branch 1 taken 99 times.
180 for (; jump > 0; jump -= 255) {
312 81 len += LORE_PutCount(out + len, 0x20, std::min(jump, 255));
313 }
314
2/2
✓ Branch 0 taken 91 times.
✓ Branch 1 taken 8 times.
99 if (repeat) {
315 91 len += LORE_PutCount(out + len, 0x40, count);
316
2/2
✓ Branch 0 taken 273 times.
✓ Branch 1 taken 91 times.
364 for (int c = 0; c < 3; c++) {
317 273 out[len++] = LORE_Value(cur[p * 3 + c]);
318 }
319 } else {
320 8 len += LORE_PutCount(out + len, 0x00, count * 3);
321
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 8 times.
32 for (int c = 0; c < count * 3; c++) {
322 24 out[len++] = LORE_Value(cur[p * 3 + c]);
323 }
324 }
325
2/2
✓ Branch 0 taken 5430 times.
✓ Branch 1 taken 99 times.
5529 for (int c = p * 3; c < (p + count) * 3; c++) {
326 5430 unit.lastSent[c] = LORE_Value(cur[c]);
327 }
328
1/2
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
99 if (firstPixel < 0) {
329 99 firstPixel = p;
330 }
331 99 p += count;
332 99 streamAt = base + p;
333 }
334
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 99 times.
115 if (firstPixel < 0) {
335 16 return 0;
336 }
337 99 out[0] = 0x00;
338 99 out[1] = unit.unitId;
339 99 out[2] = LOR_ENHANCED_CMD;
340 99 out[len++] = 0x00;
341
1/2
✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
99 data->sent.push_back({ unitIdx, firstPixel, p, 0 });
342 99 return len;
343 }
344
345 150 int LOREnhancedOutput::SendData(unsigned char* channelData) {
346 150 LogDebug(VB_CHANNELDATA, "LOREnhancedOutput::SendData()\n");
347
348 150 const int count = data->units.size();
349
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 150 times.
150 if (!count) {
350 ✗ return m_channelCount;
351 }
352
353 // Changes that do not fit the frame's budget stay unsent and go out on
354 // a later frame; a unit can be brought up to date over several frames
355 // because a packet leaves everything it does not reach unchanged.
356 150 int budget = data->FrameBudget();
357 150 int len = 0;
358 150 bool allSent = true;
359 150 data->sent.clear();
360
2/2
✓ Branch 0 taken 160 times.
✓ Branch 1 taken 150 times.
310 for (int k = 0; k < count; k++) {
361 160 int u = (data->nextUnit + k) % count;
362 160 LOREnhancedOutputUnit& unit = data->units[u];
363
3/4
✓ Branch 0 taken 160 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 45 times.
✓ Branch 3 taken 115 times.
160 if (!UnitIsValid(unit)) {
364 45 continue;
365 }
366 115 bool complete = true;
367
1/2
✓ Branch 0 taken 115 times.
✗ Branch 1 not taken.
115 int added = EncodeUnit(u, channelData + unit.fppStartAddr - 1, data->batch + len, budget - len, complete);
368
2/2
✓ Branch 0 taken 99 times.
✓ Branch 1 taken 16 times.
115 if (added) {
369 99 len += added;
370 99 data->sent.back().batchEnd = len;
371 }
372
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 115 times.
115 if (!complete) {
373 ✗ data->nextUnit = u;
374 ✗ allSent = false;
375 ✗ break;
376 }
377 }
378
379
2/2
✓ Branch 0 taken 97 times.
✓ Branch 1 taken 53 times.
150 if (len) {
380 97 int written = data->WriteBatch(data->batch, len);
381
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 97 times.
97 if (written < len) {
382 // A cut-off packet may have been applied in part or not at all,
383 // so everything it and the packets after it reached is resent.
384 ✗ for (auto& s : data->sent) {
385 ✗ if (s.batchEnd > written) {
386 ✗ LOREnhancedOutputUnit& unit = data->units[s.unit];
387 ✗ std::fill(unit.lastSent.begin() + s.firstPixel * 3, unit.lastSent.begin() + s.endPixel * 3, LOR_UNKNOWN);
388 }
389 }
390 ✗ allSent = false;
391 }
392 }
393
394 // With everything up to date, mark a few pixels for resending. A unit
395 // that missed a packet (line noise, power cycled) would otherwise hold
396 // the wrong values until they next changed, which for a static scene is
397 // never. One pass over all the pixels per LOR_REFRESH_PERIOD at most.
398
1/2
✓ Branch 0 taken 150 times.
✗ Branch 1 not taken.
150 if (allSent) {
399 150 int total = 0;
400
2/2
✓ Branch 0 taken 160 times.
✓ Branch 1 taken 150 times.
310 for (auto& unit : data->units) {
401 160 total += std::max(unit.numOfPixels, 0);
402 }
403 150 int refresh = std::max(1, (int)((total * data->frameTime + LOR_REFRESH_PERIOD - 1) / LOR_REFRESH_PERIOD));
404
4/4
✓ Branch 0 taken 161 times.
✓ Branch 1 taken 140 times.
✓ Branch 2 taken 151 times.
✓ Branch 3 taken 10 times.
301 for (int checked = 0; refresh > 0 && checked < count; ) {
405 151 LOREnhancedOutputUnit& unit = data->units[data->refreshUnit % count];
406
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 141 times.
151 if (data->refreshPixel >= unit.numOfPixels) {
407 10 data->refreshUnit = (data->refreshUnit + 1) % count;
408 10 data->refreshPixel = 0;
409 10 checked++;
410 10 continue;
411 }
412 141 int take = std::min(refresh, unit.numOfPixels - data->refreshPixel);
413
1/2
✓ Branch 0 taken 141 times.
✗ Branch 1 not taken.
141 std::fill(unit.lastSent.begin() + data->refreshPixel * 3, unit.lastSent.begin() + (data->refreshPixel + take) * 3, LOR_UNKNOWN);
414 141 data->refreshPixel += take;
415 141 refresh -= take;
416 141 checked = 0;
417 }
418 }
419 150 return m_channelCount;
420 }
421