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