From d7dda58461c92f208a33ac40e759ff5dffa21e11 Mon Sep 17 00:00:00 2001 From: Isacco Date: Tue, 10 Feb 2026 20:17:08 +0100 Subject: [PATCH 1/7] ignore realtime --- wled00/e131.cpp | 3 +++ wled00/set.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/wled00/e131.cpp b/wled00/e131.cpp index 357e7841fe..d6da0b4599 100644 --- a/wled00/e131.cpp +++ b/wled00/e131.cpp @@ -26,6 +26,7 @@ void handleDDPPacket(e131_packet_t* p) { } } + if (!receiveDirect) return; unsigned ddpChannelsPerLed = ((p->dataType & 0b00111000)>>3 == 0b011) ? 4 : 3; // data type 0x1B (formerly 0x1A) is RGBW (type 3, 8 bit/channel) uint32_t start = htonl(p->channelOffset) / ddpChannelsPerLed; @@ -68,6 +69,8 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ uint8_t* e131_data = nullptr; int seq = 0, mde = REALTIME_MODE_E131; + if (!receiveDirect) return; + if (protocol == P_ARTNET) { if (p->art_opcode == ARTNET_OPCODE_OPPOLL) { diff --git a/wled00/set.cpp b/wled00/set.cpp index ab3060d06d..b61abdc574 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -1155,7 +1155,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) //receive live data via UDP/Hyperion pos = req.indexOf(F("RD=")); - if (pos > 0) receiveDirect = (req.charAt(pos+3) != '0'); + if (pos > 0) { receiveDirect = (req.charAt(pos+3) != '0'); if (!receiveDirect) exitRealtime(); } //main toggle on/off (parse before nightlight, #1214) pos = req.indexOf(F("&T=")); From 656a74350398c1f9f257801a10134e4e5e9134ab Mon Sep 17 00:00:00 2001 From: Isacco Date: Wed, 11 Feb 2026 00:37:51 +0100 Subject: [PATCH 2/7] Fix tested --- commit_diff.txt | 40 ++++ e131_orig.txt | 542 ++++++++++++++++++++++++++++++++++++++++++++++++ platformio.ini | 2 +- wled00/json.cpp | 7 +- wled00/set.cpp | 6 +- wled00/udp.cpp | 13 +- wled00/wled.cpp | 23 +- 7 files changed, 617 insertions(+), 16 deletions(-) create mode 100644 commit_diff.txt create mode 100644 e131_orig.txt diff --git a/commit_diff.txt b/commit_diff.txt new file mode 100644 index 0000000000..7b454ea849 --- /dev/null +++ b/commit_diff.txt @@ -0,0 +1,40 @@ +commit d7dda58461c92f208a33ac40e759ff5dffa21e11 +Author: Isacco +Date: Tue Feb 10 20:17:08 2026 +0100 + + ignore realtime + +diff --git a/wled00/e131.cpp b/wled00/e131.cpp +index 357e7841f..d6da0b459 100644 +--- a/wled00/e131.cpp ++++ b/wled00/e131.cpp +@@ -26,6 +26,7 @@ void handleDDPPacket(e131_packet_t* p) { + } + } + ++ if (!receiveDirect) return; + unsigned ddpChannelsPerLed = ((p->dataType & 0b00111000)>>3 == 0b011) ? 4 : 3; // data type 0x1B (formerly 0x1A) is RGBW (type 3, 8 bit/channel) + + uint32_t start = htonl(p->channelOffset) / ddpChannelsPerLed; +@@ -68,6 +69,8 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ + uint8_t* e131_data = nullptr; + int seq = 0, mde = REALTIME_MODE_E131; + ++ if (!receiveDirect) return; ++ + if (protocol == P_ARTNET) + { + if (p->art_opcode == ARTNET_OPCODE_OPPOLL) { +diff --git a/wled00/set.cpp b/wled00/set.cpp +index ab3060d06..b61abdc57 100644 +--- a/wled00/set.cpp ++++ b/wled00/set.cpp +@@ -1155,7 +1155,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) + + //receive live data via UDP/Hyperion + pos = req.indexOf(F("RD=")); +- if (pos > 0) receiveDirect = (req.charAt(pos+3) != '0'); ++ if (pos > 0) { receiveDirect = (req.charAt(pos+3) != '0'); if (!receiveDirect) exitRealtime(); } + + //main toggle on/off (parse before nightlight, #1214) + pos = req.indexOf(F("&T=")); diff --git a/e131_orig.txt b/e131_orig.txt new file mode 100644 index 0000000000..357e7841fe --- /dev/null +++ b/e131_orig.txt @@ -0,0 +1,542 @@ +#include "wled.h" + +#define MAX_3_CH_LEDS_PER_UNIVERSE 170 +#define MAX_4_CH_LEDS_PER_UNIVERSE 128 +#define MAX_CHANNELS_PER_UNIVERSE 512 + +/* + * E1.31 handler + */ + +//DDP protocol support, called by handleE131Packet +//handles RGB data only +void handleDDPPacket(e131_packet_t* p) { + static bool ddpSeenPush = false; // have we seen a push yet? + int lastPushSeq = e131LastSequenceNumber[0]; + + //reject late packets belonging to previous frame (assuming 4 packets max. before push) + if (e131SkipOutOfSequence && lastPushSeq) { + int sn = p->sequenceNum & 0xF; + if (sn) { + if (lastPushSeq > 5) { + if (sn > (lastPushSeq -5) && sn < lastPushSeq) return; + } else { + if (sn > (10 + lastPushSeq) || sn < lastPushSeq) return; + } + } + } + + unsigned ddpChannelsPerLed = ((p->dataType & 0b00111000)>>3 == 0b011) ? 4 : 3; // data type 0x1B (formerly 0x1A) is RGBW (type 3, 8 bit/channel) + + uint32_t start = htonl(p->channelOffset) / ddpChannelsPerLed; + start += DMXAddress / ddpChannelsPerLed; + uint16_t dataLen = htons(p->dataLen); + unsigned stop = start + dataLen / ddpChannelsPerLed; + uint8_t* data = p->data; + unsigned c = 0; + if (p->flags & DDP_TIMECODE_FLAG) c = 4; //packet has timecode flag, we do not support it, but data starts 4 bytes later + + unsigned numLeds = stop - start; // stop >= start is guaranteed + unsigned maxDataIndex = c + numLeds * ddpChannelsPerLed; // validate bounds before accessing data array + if (maxDataIndex > dataLen) { + DEBUG_PRINTLN(F("DDP packet data bounds exceeded, rejecting.")); + return; + } + + if (realtimeMode != REALTIME_MODE_DDP) ddpSeenPush = false; // just starting, no push yet + realtimeLock(realtimeTimeoutMs, REALTIME_MODE_DDP); + + if (!realtimeOverride) { + for (unsigned i = start; i < stop; i++, c += ddpChannelsPerLed) { + setRealtimePixel(i, data[c], data[c+1], data[c+2], ddpChannelsPerLed >3 ? data[c+3] : 0); + } + } + + bool push = p->flags & DDP_PUSH_FLAG; + ddpSeenPush |= push; + if (!ddpSeenPush || push) { // if we've never seen a push, or this is one, render display + e131NewData = true; + int sn = p->sequenceNum & 0xF; + if (sn) e131LastSequenceNumber[0] = sn; + } +} + +//E1.31 and Art-Net protocol support +void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ + + int uni = 0, dmxChannels = 0; + uint8_t* e131_data = nullptr; + int seq = 0, mde = REALTIME_MODE_E131; + + if (protocol == P_ARTNET) + { + if (p->art_opcode == ARTNET_OPCODE_OPPOLL) { + handleArtnetPollReply(clientIP); + return; + } + uni = p->art_universe; + dmxChannels = htons(p->art_length); + e131_data = p->art_data; + seq = p->art_sequence_number; + mde = REALTIME_MODE_ARTNET; + } else if (protocol == P_E131) { + // Ignore PREVIEW data (E1.31: 6.2.6) + if ((p->options & 0x80) != 0) return; + dmxChannels = htons(p->property_value_count) - 1; + // DMX level data is zero start code. Ignore everything else. (E1.11: 8.5) + if (dmxChannels == 0 || p->property_values[0] != 0) return; + uni = htons(p->universe); + e131_data = p->property_values; + seq = p->sequence_number; + if (e131Priority != 0) { + if (p->priority < e131Priority ) return; + // track highest priority & skip all lower priorities + if (p->priority >= highPriority.get()) highPriority.set(p->priority); + if (p->priority < highPriority.get()) return; + } + } else { //DDP + realtimeIP = clientIP; + handleDDPPacket(p); + return; + } + + #ifdef WLED_ENABLE_DMX + // does not act on out-of-order packets yet + if (e131ProxyUniverse > 0 && uni == e131ProxyUniverse) { + for (uint16_t i = 1; i <= dmxChannels; i++) + dmx.write(i, e131_data[i]); + dmx.update(); + } + #endif + + // only listen for universes we're handling & allocated memory + if (uni < e131Universe || uni >= (e131Universe + E131_MAX_UNIVERSE_COUNT)) return; + + unsigned previousUniverses = uni - e131Universe; + + if (e131SkipOutOfSequence) + if (seq < e131LastSequenceNumber[previousUniverses] && seq > 20 && e131LastSequenceNumber[previousUniverses] < 250){ + DEBUG_PRINTF_P(PSTR("skipping E1.31 frame (last seq=%d, current seq=%d, universe=%d)\n"), e131LastSequenceNumber[previousUniverses], seq, uni); + return; + } + e131LastSequenceNumber[previousUniverses] = seq; + + // update status info + realtimeIP = clientIP; + + handleDMXData(uni, dmxChannels, e131_data, mde, previousUniverses); +} + +void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8_t mde, uint8_t previousUniverses) { + byte wChannel = 0; + unsigned totalLen = strip.getLengthTotal(); + unsigned availDMXLen = 0; + unsigned dataOffset = DMXAddress; + + // For legacy DMX start address 0 the available DMX length offset is 0 + const unsigned dmxLenOffset = (DMXAddress == 0) ? 0 : 1; + + // Check if DMX start address fits in available channels + if (dmxChannels >= DMXAddress) { + availDMXLen = (dmxChannels - DMXAddress) + dmxLenOffset; + } + + // DMX data in Art-Net packet starts at index 0, for E1.31 at index 1 + if (mde == REALTIME_MODE_ARTNET && dataOffset > 0) { + dataOffset--; + } + + switch (DMXMode) { + case DMX_MODE_DISABLED: + return; // nothing to do + break; + + case DMX_MODE_SINGLE_RGB: // 3 channel: [R,G,B] + if (uni != e131Universe) return; + if (availDMXLen < 3) return; + + realtimeLock(realtimeTimeoutMs, mde); + + if (realtimeOverride) return; + + wChannel = (availDMXLen > 3) ? e131_data[dataOffset+3] : 0; + for (unsigned i = 0; i < totalLen; i++) + setRealtimePixel(i, e131_data[dataOffset+0], e131_data[dataOffset+1], e131_data[dataOffset+2], wChannel); + break; + + case DMX_MODE_SINGLE_DRGB: // 4 channel: [Dimmer,R,G,B] + if (uni != e131Universe) return; + if (availDMXLen < 4) return; + + realtimeLock(realtimeTimeoutMs, mde); + if (realtimeOverride) return; + wChannel = (availDMXLen > 4) ? e131_data[dataOffset+4] : 0; + + if (bri != e131_data[dataOffset+0]) { + bri = e131_data[dataOffset+0]; + strip.setBrightness(bri, true); + } + + for (unsigned i = 0; i < totalLen; i++) + setRealtimePixel(i, e131_data[dataOffset+1], e131_data[dataOffset+2], e131_data[dataOffset+3], wChannel); + break; + + case DMX_MODE_PRESET: // 2 channel: [Dimmer,Preset] + { + if (uni != e131Universe || availDMXLen < 2) return; + + // limit max. selectable preset to 250, even though DMX max. val is 255 + int dmxValPreset = (e131_data[dataOffset+1] > 250 ? 250 : e131_data[dataOffset+1]); + + // only apply preset if value changed + if (dmxValPreset != 0 && dmxValPreset != currentPreset && + // only apply preset if not in playlist, or playlist changed + (currentPlaylist < 0 || dmxValPreset != currentPlaylist)) { + presetCycCurr = dmxValPreset; + applyPreset(dmxValPreset, CALL_MODE_NOTIFICATION); + } + + // only change brightness if value changed + if (bri != e131_data[dataOffset]) { + bri = e131_data[dataOffset]; + strip.setBrightness(bri, false); + stateUpdated(CALL_MODE_WS_SEND); + } + return; + break; + } + + case DMX_MODE_EFFECT: // 15 channels [bri,effectCurrent,effectSpeed,effectIntensity,effectPalette,effectOption,R,G,B,R2,G2,B2,R3,G3,B3] + case DMX_MODE_EFFECT_W: // 18 channels, same as above but with extra +3 white channels [..,W,W2,W3] + case DMX_MODE_EFFECT_SEGMENT: // 15 channels per segment; + case DMX_MODE_EFFECT_SEGMENT_W: // 18 Channels per segment; + { + if (uni != e131Universe) return; + bool isSegmentMode = DMXMode == DMX_MODE_EFFECT_SEGMENT || DMXMode == DMX_MODE_EFFECT_SEGMENT_W; + unsigned dmxEffectChannels = (DMXMode == DMX_MODE_EFFECT || DMXMode == DMX_MODE_EFFECT_SEGMENT) ? 15 : 18; + for (unsigned id = 0; id < strip.getSegmentsNum(); id++) { + Segment& seg = strip.getSegment(id); + if (isSegmentMode) + dataOffset = DMXAddress + id * (dmxEffectChannels + DMXSegmentSpacing); + else + dataOffset = DMXAddress; + // Modify address for Art-Net data + if (mde == REALTIME_MODE_ARTNET && dataOffset > 0) + dataOffset--; + // Skip out of universe addresses + if (dataOffset > dmxChannels - dmxEffectChannels + 1) + return; + + if (e131_data[dataOffset+1] < strip.getModeCount()) + if (e131_data[dataOffset+1] != seg.mode) seg.setMode( e131_data[dataOffset+1]); + if (e131_data[dataOffset+2] != seg.speed) seg.speed = e131_data[dataOffset+2]; + if (e131_data[dataOffset+3] != seg.intensity) seg.intensity = e131_data[dataOffset+3]; + if (e131_data[dataOffset+4] != seg.palette) seg.setPalette(e131_data[dataOffset+4]); + + if (bool(e131_data[dataOffset+5] & 0b00000010) != seg.reverse_y) { seg.reverse_y = bool(e131_data[dataOffset+5] & 0b00000010); } + if (bool(e131_data[dataOffset+5] & 0b00000100) != seg.mirror_y) { seg.mirror_y = bool(e131_data[dataOffset+5] & 0b00000100); } + if (bool(e131_data[dataOffset+5] & 0b00001000) != seg.transpose) { seg.transpose = bool(e131_data[dataOffset+5] & 0b00001000); } + if ((e131_data[dataOffset+5] & 0b00110000) >> 4 != seg.map1D2D) { + seg.map1D2D = (e131_data[dataOffset+5] & 0b00110000) >> 4; + } + // To maintain backwards compatibility with prior e1.31 values, reverse is fixed to mask 0x01000000 + if ((e131_data[dataOffset+5] & 0b01000000) != seg.reverse) { seg.reverse = bool(e131_data[dataOffset+5] & 0b01000000); } + // To maintain backwards compatibility with prior e1.31 values, mirror is fixed to mask 0x10000000 + if ((e131_data[dataOffset+5] & 0b10000000) != seg.mirror) { seg.mirror = bool(e131_data[dataOffset+5] & 0b10000000); } + + uint32_t colors[3]; + byte whites[3] = {0,0,0}; + if (dmxEffectChannels == 18) { + whites[0] = e131_data[dataOffset+15]; + whites[1] = e131_data[dataOffset+16]; + whites[2] = e131_data[dataOffset+17]; + } + colors[0] = RGBW32(e131_data[dataOffset+ 6], e131_data[dataOffset+ 7], e131_data[dataOffset+ 8], whites[0]); + colors[1] = RGBW32(e131_data[dataOffset+ 9], e131_data[dataOffset+10], e131_data[dataOffset+11], whites[1]); + colors[2] = RGBW32(e131_data[dataOffset+12], e131_data[dataOffset+13], e131_data[dataOffset+14], whites[2]); + if (colors[0] != seg.colors[0]) seg.setColor(0, colors[0]); + if (colors[1] != seg.colors[1]) seg.setColor(1, colors[1]); + if (colors[2] != seg.colors[2]) seg.setColor(2, colors[2]); + + // Set segment opacity or global brightness + if (isSegmentMode) { + if (e131_data[dataOffset] != seg.opacity) seg.setOpacity(e131_data[dataOffset]); + } else if ( id == strip.getSegmentsNum()-1U ) { + if (bri != e131_data[dataOffset]) { + bri = e131_data[dataOffset]; + strip.setBrightness(bri, true); + } + } + } + return; + break; + } + + case DMX_MODE_MULTIPLE_DRGB: + case DMX_MODE_MULTIPLE_RGB: + case DMX_MODE_MULTIPLE_RGBW: + { + const bool is4Chan = (DMXMode == DMX_MODE_MULTIPLE_RGBW); + const unsigned dmxChannelsPerLed = is4Chan ? 4 : 3; + const unsigned ledsPerUniverse = is4Chan ? MAX_4_CH_LEDS_PER_UNIVERSE : MAX_3_CH_LEDS_PER_UNIVERSE; + uint8_t stripBrightness = bri; + unsigned previousLeds, dmxOffset, ledsTotal; + + if (previousUniverses == 0) { + if (availDMXLen < 1) return; + dmxOffset = dataOffset; + previousLeds = 0; + // First DMX address is dimmer in DMX_MODE_MULTIPLE_DRGB mode. + if (DMXMode == DMX_MODE_MULTIPLE_DRGB) { + stripBrightness = e131_data[dmxOffset++]; + ledsTotal = (availDMXLen - 1) / dmxChannelsPerLed; + } else { + ledsTotal = availDMXLen / dmxChannelsPerLed; + } + } else { + // All subsequent universes start at the first channel. + dmxOffset = (mde == REALTIME_MODE_ARTNET) ? 0 : 1; + const unsigned dimmerOffset = (DMXMode == DMX_MODE_MULTIPLE_DRGB) ? 1 : 0; + unsigned ledsInFirstUniverse = (((MAX_CHANNELS_PER_UNIVERSE - DMXAddress) + dmxLenOffset) - dimmerOffset) / dmxChannelsPerLed; + previousLeds = ledsInFirstUniverse + (previousUniverses - 1) * ledsPerUniverse; + ledsTotal = previousLeds + (dmxChannels / dmxChannelsPerLed); + } + + // All LEDs already have values + if (previousLeds >= totalLen) { + return; + } + + realtimeLock(realtimeTimeoutMs, mde); + if (realtimeOverride) return; + + if (ledsTotal > totalLen) { + ledsTotal = totalLen; + } + + if (DMXMode == DMX_MODE_MULTIPLE_DRGB && previousUniverses == 0) { + if (bri != stripBrightness) { + bri = stripBrightness; + strip.setBrightness(bri, true); + } + } + + for (unsigned i = previousLeds; i < ledsTotal; i++) { + setRealtimePixel(i, e131_data[dmxOffset], e131_data[dmxOffset+1], e131_data[dmxOffset+2], is4Chan ? e131_data[dmxOffset+3] : 0); + dmxOffset += dmxChannelsPerLed; + } + break; + } + default: + DEBUG_PRINTLN(F("unknown E1.31 DMX mode")); + return; // nothing to do + break; + } + + e131NewData = true; +} + +void handleArtnetPollReply(IPAddress ipAddress) { + ArtPollReply artnetPollReply; + prepareArtnetPollReply(&artnetPollReply); + + unsigned startUniverse = e131Universe; + unsigned endUniverse = e131Universe; + + switch (DMXMode) { + case DMX_MODE_DISABLED: + break; + + case DMX_MODE_SINGLE_RGB: + case DMX_MODE_SINGLE_DRGB: + case DMX_MODE_PRESET: + case DMX_MODE_EFFECT: + case DMX_MODE_EFFECT_W: + case DMX_MODE_EFFECT_SEGMENT: + case DMX_MODE_EFFECT_SEGMENT_W: + break; // 1 universe is enough + + case DMX_MODE_MULTIPLE_DRGB: + case DMX_MODE_MULTIPLE_RGB: + case DMX_MODE_MULTIPLE_RGBW: + { + bool is4Chan = (DMXMode == DMX_MODE_MULTIPLE_RGBW); + const unsigned dmxChannelsPerLed = is4Chan ? 4 : 3; + const unsigned dimmerOffset = (DMXMode == DMX_MODE_MULTIPLE_DRGB) ? 1 : 0; + const unsigned dmxLenOffset = (DMXAddress == 0) ? 0 : 1; // For legacy DMX start address 0 + const unsigned ledsInFirstUniverse = (((MAX_CHANNELS_PER_UNIVERSE - DMXAddress) + dmxLenOffset) - dimmerOffset) / dmxChannelsPerLed; + const unsigned totalLen = strip.getLengthTotal(); + + if (totalLen > ledsInFirstUniverse) { + const unsigned ledsPerUniverse = is4Chan ? MAX_4_CH_LEDS_PER_UNIVERSE : MAX_3_CH_LEDS_PER_UNIVERSE; + const unsigned remainLED = totalLen - ledsInFirstUniverse; + + endUniverse += (remainLED / ledsPerUniverse); + + if ((remainLED % ledsPerUniverse) > 0) { + endUniverse++; + } + + if ((endUniverse - startUniverse) > E131_MAX_UNIVERSE_COUNT) { + endUniverse = startUniverse + E131_MAX_UNIVERSE_COUNT - 1; + } + } + break; + } + default: + DEBUG_PRINTLN(F("unknown E1.31 DMX mode")); + return; // nothing to do + break; + } + + if (DMXMode != DMX_MODE_DISABLED) { + for (unsigned i = startUniverse; i <= endUniverse; ++i) { + sendArtnetPollReply(&artnetPollReply, ipAddress, i); + } + } + + #ifdef WLED_ENABLE_DMX + if (e131ProxyUniverse > 0 && (DMXMode == DMX_MODE_DISABLED || (e131ProxyUniverse < startUniverse || e131ProxyUniverse > endUniverse))) { + sendArtnetPollReply(&artnetPollReply, ipAddress, e131ProxyUniverse); + } + #endif +} + +void prepareArtnetPollReply(ArtPollReply *reply) { + // Art-Net + reply->reply_id[0] = 0x41; + reply->reply_id[1] = 0x72; + reply->reply_id[2] = 0x74; + reply->reply_id[3] = 0x2d; + reply->reply_id[4] = 0x4e; + reply->reply_id[5] = 0x65; + reply->reply_id[6] = 0x74; + reply->reply_id[7] = 0x00; + + reply->reply_opcode = ARTNET_OPCODE_OPPOLLREPLY; + + IPAddress localIP = Network.localIP(); + for (unsigned i = 0; i < 4; i++) { + reply->reply_ip[i] = localIP[i]; + } + + reply->reply_port = ARTNET_DEFAULT_PORT; + + char * numberEnd = (char*) versionString; // strtol promises not to try to edit this. + reply->reply_version_h = (uint8_t)strtol(numberEnd, &numberEnd, 10); + numberEnd++; + reply->reply_version_l = (uint8_t)strtol(numberEnd, &numberEnd, 10); + + // Switch values depend on universe, set before sending + reply->reply_net_sw = 0x00; + reply->reply_sub_sw = 0x00; + + reply->reply_oem_h = 0x00; // TODO add assigned oem code + reply->reply_oem_l = 0x00; + + reply->reply_ubea_ver = 0x00; + + // Indicators in Normal Mode + // All or part of Port-Address programmed by network or Web browser + reply->reply_status_1 = 0xE0; + + reply->reply_esta_man = 0x0000; + + strlcpy((char *)(reply->reply_short_name), serverDescription, 18); + strlcpy((char *)(reply->reply_long_name), serverDescription, 64); + + reply->reply_node_report[0] = '\0'; + + reply->reply_num_ports_h = 0x00; + reply->reply_num_ports_l = 0x01; // One output port + + reply->reply_port_types[0] = 0x80; // Output DMX data + reply->reply_port_types[1] = 0x00; + reply->reply_port_types[2] = 0x00; + reply->reply_port_types[3] = 0x00; + + // No inputs + reply->reply_good_input[0] = 0x00; + reply->reply_good_input[1] = 0x00; + reply->reply_good_input[2] = 0x00; + reply->reply_good_input[3] = 0x00; + + // One output + reply->reply_good_output_a[0] = 0x80; // Data is being transmitted + reply->reply_good_output_a[1] = 0x00; + reply->reply_good_output_a[2] = 0x00; + reply->reply_good_output_a[3] = 0x00; + + // Values depend on universe, set before sending + reply->reply_sw_in[0] = 0x00; + reply->reply_sw_in[1] = 0x00; + reply->reply_sw_in[2] = 0x00; + reply->reply_sw_in[3] = 0x00; + + // Values depend on universe, set before sending + reply->reply_sw_out[0] = 0x00; + reply->reply_sw_out[1] = 0x00; + reply->reply_sw_out[2] = 0x00; + reply->reply_sw_out[3] = 0x00; + + reply->reply_sw_video = 0x00; + reply->reply_sw_macro = 0x00; + reply->reply_sw_remote = 0x00; + + reply->reply_spare[0] = 0x00; + reply->reply_spare[1] = 0x00; + reply->reply_spare[2] = 0x00; + + // A DMX to / from Art-Net device + reply->reply_style = 0x00; + + Network.localMAC(reply->reply_mac); + + for (unsigned i = 0; i < 4; i++) { + reply->reply_bind_ip[i] = localIP[i]; + } + + reply->reply_bind_index = 1; + + // Product supports web browser configuration + // Node’s IP is DHCP or manually configured + // Node is DHCP capable + // Node supports 15 bit Port-Address (Art-Net 3 or 4) + // Node is able to switch between ArtNet and sACN + reply->reply_status_2 = (multiWiFi[0].staticIP[0] == 0) ? 0x1F : 0x1D; + + // RDM is disabled + // Output style is continuous + reply->reply_good_output_b[0] = 0xC0; + reply->reply_good_output_b[1] = 0xC0; + reply->reply_good_output_b[2] = 0xC0; + reply->reply_good_output_b[3] = 0xC0; + + // Fail-over state: Hold last state + // Node does not support fail-over + reply->reply_status_3 = 0x00; + + for (unsigned i = 0; i < 21; i++) { + reply->reply_filler[i] = 0x00; + } +} + +void sendArtnetPollReply(ArtPollReply *reply, IPAddress ipAddress, uint16_t portAddress) { + reply->reply_net_sw = (uint8_t)((portAddress >> 8) & 0x007F); + reply->reply_sub_sw = (uint8_t)((portAddress >> 4) & 0x000F); + reply->reply_sw_out[0] = (uint8_t)(portAddress & 0x000F); + + snprintf_P((char *)reply->reply_node_report, sizeof(reply->reply_node_report)-1, PSTR("#0001 [%04u] OK - WLED v%s"), pollReplyCount, versionString); + + if (pollReplyCount < 9999) { + pollReplyCount++; + } else { + pollReplyCount = 0; + } + + notifierUdp.beginPacket(ipAddress, ARTNET_DEFAULT_PORT); + notifierUdp.write(reply->raw, sizeof(ArtPollReply)); + notifierUdp.endPacket(); + + reply->reply_bind_index++; +} \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 5de55062cc..9dccb94afd 100644 --- a/platformio.ini +++ b/platformio.ini @@ -373,7 +373,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266\" #-DWLED_DISABLE_2D +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266\" -D WLED_DEBUG #-DWLED_DISABLE_2D -D WLED_DISABLE_PARTICLESYSTEM2D lib_deps = ${esp8266.lib_deps} monitor_filters = esp8266_exception_decoder diff --git a/wled00/json.cpp b/wled00/json.cpp index 3e053708c0..62a7d2702c 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -428,6 +428,11 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) sendNotificationsRT = getBoolVal(udpn[F("send")], sendNotificationsRT); syncGroups = udpn[F("sgrp")] | syncGroups; receiveGroups = udpn[F("rgrp")] | receiveGroups; + receiveDirect = getBoolVal(udpn[F("rd")], receiveDirect); + if (udpn.containsKey(F("rd"))) { + + if (!receiveDirect) exitRealtime(); + } if ((bool)udpn[F("nn")]) callMode = CALL_MODE_NO_NOTIFY; //send no notification just for this request unsigned long timein = root["time"] | UINT32_MAX; //backup time source if NTP not synced @@ -445,7 +450,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) if (realtimeOverride > 2) realtimeOverride = REALTIME_OVERRIDE_ALWAYS; if (realtimeMode && useMainSegmentOnly) { strip.getMainSegment().freeze = !realtimeOverride; - realtimeOverride = REALTIME_OVERRIDE_NONE; // ignore request for override if using main segment only + // realtimeOverride = REALTIME_OVERRIDE_NONE; // Fix: do not clear override even if using main segment only } if (root.containsKey("live")) { diff --git a/wled00/set.cpp b/wled00/set.cpp index b61abdc574..60fd27d5e0 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -1155,7 +1155,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) //receive live data via UDP/Hyperion pos = req.indexOf(F("RD=")); - if (pos > 0) { receiveDirect = (req.charAt(pos+3) != '0'); if (!receiveDirect) exitRealtime(); } + if (pos >= 0) { receiveDirect = (req.charAt(pos+3) != '0'); if (!receiveDirect) exitRealtime(); } //main toggle on/off (parse before nightlight, #1214) pos = req.indexOf(F("&T=")); @@ -1228,10 +1228,12 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) pos = req.indexOf(F("LO=")); if (pos > 0) { realtimeOverride = getNumVal(req, pos); +DEBUG_PRINT(F("HTTP LO:")); DEBUG_PRINTLN(realtimeOverride); if (realtimeOverride > 2) realtimeOverride = REALTIME_OVERRIDE_ALWAYS; if (realtimeMode && useMainSegmentOnly) { +DEBUG_PRINTLN(F("LO cleared due to useMainSegmentOnly")); strip.getMainSegment().freeze = !realtimeOverride; - realtimeOverride = REALTIME_OVERRIDE_NONE; // ignore request for override if using main segment only +// realtimeOverride = REALTIME_OVERRIDE_NONE; // Fix: do not clear override even if using main segment only } } diff --git a/wled00/udp.cpp b/wled00/udp.cpp index f0e0ea7ea0..7013f6cc9d 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -431,9 +431,11 @@ void realtimeLock(uint32_t timeoutMs, byte md) if (realtimeTimeout != UINT32_MAX) { realtimeTimeout = (timeoutMs == 255001 || timeoutMs == 65000) ? UINT32_MAX : millis() + timeoutMs; } + if (realtimeMode != md) { DEBUG_PRINT(F("RT md change:")); DEBUG_PRINTLN(md); } realtimeMode = md; + - if (realtimeOverride) return; + if (realtimeOverride) { DEBUG_PRINT(F("RT Lock blocked by Override:")); DEBUG_PRINTLN(realtimeOverride); return; } if (arlsForceMaxBri) strip.setBrightness(255, true); if (briT > 0 && md == REALTIME_MODE_GENERIC) strip.show(); } @@ -444,6 +446,7 @@ void exitRealtime() { strip.setBrightness(bri, true); realtimeTimeout = 0; // cancel realtime mode immediately realtimeMode = REALTIME_MODE_INACTIVE; // inform UI immediately + DEBUG_PRINTLN(F("RT Mode -> INACTIVE")); realtimeIP[0] = 0; if (useMainSegmentOnly) { // unfreeze live segment again strip.getMainSegment().freeze = false; @@ -501,11 +504,11 @@ void handleNotifications() if (!receiveDirect) return; if (packetSize > UDP_IN_MAXSIZE || packetSize < 3) return; realtimeIP = rgbUdp.remoteIP(); - DEBUG_PRINTLN(rgbUdp.remoteIP()); + // DEBUG_PRINTLN(rgbUdp.remoteIP()); // Silenced for noise uint8_t lbuf[packetSize]; rgbUdp.read(lbuf, packetSize); realtimeLock(realtimeTimeoutMs, REALTIME_MODE_HYPERION); - if (realtimeOverride) return; + if (realtimeOverride) { DEBUG_PRINT(F("RT Lock blocked by Override:")); DEBUG_PRINTLN(realtimeOverride); return; } unsigned totalLen = strip.getLengthTotal(); for (size_t i = 0, id = 0; i < packetSize -2 && id < totalLen; i += 3, id++) { setRealtimePixel(id, lbuf[i], lbuf[i+1], lbuf[i+2], 0); @@ -578,7 +581,7 @@ void handleNotifications() realtimeIP = (isSupp) ? notifier2Udp.remoteIP() : notifierUdp.remoteIP(); realtimeLock(realtimeTimeoutMs, REALTIME_MODE_TPM2NET); - if (realtimeOverride) return; + if (realtimeOverride) { DEBUG_PRINT(F("RT Lock blocked by Override:")); DEBUG_PRINTLN(realtimeOverride); return; } tpmPacketCount++; //increment the packet count if (tpmPacketCount == 1) tpmPayloadFrameSize = (udpIn[2] << 8) + udpIn[3]; //save frame size for the whole payload if this is the first packet @@ -610,7 +613,7 @@ void handleNotifications() } else { realtimeLock(udpIn[1]*1000 +1, REALTIME_MODE_UDP); } - if (realtimeOverride) return; + if (realtimeOverride) { DEBUG_PRINT(F("RT Lock blocked by Override:")); DEBUG_PRINTLN(realtimeOverride); return; } unsigned totalLen = strip.getLengthTotal(); if (udpIn[0] == 1 && packetSize > 5) { //warls diff --git a/wled00/wled.cpp b/wled00/wled.cpp index ee205feaea..53278cbd89 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -106,7 +106,21 @@ void WLED::loop() #ifdef WLED_DEBUG stripMillis = millis(); #endif - if (!realtimeMode || realtimeOverride || (realtimeMode && useMainSegmentOnly)) // block stuff if WARLS/Adalight is enabled + bool allowNormal = !realtimeMode || realtimeOverride || (realtimeMode && useMainSegmentOnly); +#ifdef WLED_DEBUG + +#endif + #ifdef WLED_DEBUG + + #endif + + if (!presetNeedsSaving()) { + handlePlaylist(); + yield(); + } + handlePresets(); + yield(); + if (allowNormal) // block stuff if WARLS/Adalight is enabled { if (apActive) dnsServer.processNextRequest(); #ifdef WLED_ENABLE_AOTA @@ -120,12 +134,7 @@ void WLED::loop() yield(); #endif - if (!presetNeedsSaving()) { - handlePlaylist(); - yield(); - } - handlePresets(); - yield(); + if (!offMode || strip.isOffRefreshRequired() || strip.needsUpdate()) strip.service(); From 8f77932c44797ce0ed43630f95fd3422f53705fb Mon Sep 17 00:00:00 2001 From: Isacco Date: Wed, 11 Feb 2026 23:53:04 +0100 Subject: [PATCH 3/7] Fix: Realtime override deadlock and RD API support --- wled00/json.cpp | 5 +---- wled00/set.cpp | 4 ++-- wled00/udp.cpp | 14 +++++++------- wled00/wled.cpp | 7 +------ 4 files changed, 11 insertions(+), 19 deletions(-) diff --git a/wled00/json.cpp b/wled00/json.cpp index 62a7d2702c..d80b21fa8c 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -429,10 +429,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) syncGroups = udpn[F("sgrp")] | syncGroups; receiveGroups = udpn[F("rgrp")] | receiveGroups; receiveDirect = getBoolVal(udpn[F("rd")], receiveDirect); - if (udpn.containsKey(F("rd"))) { - - if (!receiveDirect) exitRealtime(); - } + if (udpn.containsKey(F("rd")) && !receiveDirect) exitRealtime(); if ((bool)udpn[F("nn")]) callMode = CALL_MODE_NO_NOTIFY; //send no notification just for this request unsigned long timein = root["time"] | UINT32_MAX; //backup time source if NTP not synced diff --git a/wled00/set.cpp b/wled00/set.cpp index 60fd27d5e0..a875d17384 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -1228,10 +1228,10 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) pos = req.indexOf(F("LO=")); if (pos > 0) { realtimeOverride = getNumVal(req, pos); -DEBUG_PRINT(F("HTTP LO:")); DEBUG_PRINTLN(realtimeOverride); + if (realtimeOverride > 2) realtimeOverride = REALTIME_OVERRIDE_ALWAYS; if (realtimeMode && useMainSegmentOnly) { -DEBUG_PRINTLN(F("LO cleared due to useMainSegmentOnly")); + strip.getMainSegment().freeze = !realtimeOverride; // realtimeOverride = REALTIME_OVERRIDE_NONE; // Fix: do not clear override even if using main segment only } diff --git a/wled00/udp.cpp b/wled00/udp.cpp index 7013f6cc9d..8934f9786f 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -431,11 +431,11 @@ void realtimeLock(uint32_t timeoutMs, byte md) if (realtimeTimeout != UINT32_MAX) { realtimeTimeout = (timeoutMs == 255001 || timeoutMs == 65000) ? UINT32_MAX : millis() + timeoutMs; } - if (realtimeMode != md) { DEBUG_PRINT(F("RT md change:")); DEBUG_PRINTLN(md); } + realtimeMode = md; - if (realtimeOverride) { DEBUG_PRINT(F("RT Lock blocked by Override:")); DEBUG_PRINTLN(realtimeOverride); return; } + if (realtimeOverride) return; if (arlsForceMaxBri) strip.setBrightness(255, true); if (briT > 0 && md == REALTIME_MODE_GENERIC) strip.show(); } @@ -446,7 +446,7 @@ void exitRealtime() { strip.setBrightness(bri, true); realtimeTimeout = 0; // cancel realtime mode immediately realtimeMode = REALTIME_MODE_INACTIVE; // inform UI immediately - DEBUG_PRINTLN(F("RT Mode -> INACTIVE")); + realtimeIP[0] = 0; if (useMainSegmentOnly) { // unfreeze live segment again strip.getMainSegment().freeze = false; @@ -504,11 +504,11 @@ void handleNotifications() if (!receiveDirect) return; if (packetSize > UDP_IN_MAXSIZE || packetSize < 3) return; realtimeIP = rgbUdp.remoteIP(); - // DEBUG_PRINTLN(rgbUdp.remoteIP()); // Silenced for noise + uint8_t lbuf[packetSize]; rgbUdp.read(lbuf, packetSize); realtimeLock(realtimeTimeoutMs, REALTIME_MODE_HYPERION); - if (realtimeOverride) { DEBUG_PRINT(F("RT Lock blocked by Override:")); DEBUG_PRINTLN(realtimeOverride); return; } + if (realtimeOverride) return; unsigned totalLen = strip.getLengthTotal(); for (size_t i = 0, id = 0; i < packetSize -2 && id < totalLen; i += 3, id++) { setRealtimePixel(id, lbuf[i], lbuf[i+1], lbuf[i+2], 0); @@ -581,7 +581,7 @@ void handleNotifications() realtimeIP = (isSupp) ? notifier2Udp.remoteIP() : notifierUdp.remoteIP(); realtimeLock(realtimeTimeoutMs, REALTIME_MODE_TPM2NET); - if (realtimeOverride) { DEBUG_PRINT(F("RT Lock blocked by Override:")); DEBUG_PRINTLN(realtimeOverride); return; } + if (realtimeOverride) return; tpmPacketCount++; //increment the packet count if (tpmPacketCount == 1) tpmPayloadFrameSize = (udpIn[2] << 8) + udpIn[3]; //save frame size for the whole payload if this is the first packet @@ -613,7 +613,7 @@ void handleNotifications() } else { realtimeLock(udpIn[1]*1000 +1, REALTIME_MODE_UDP); } - if (realtimeOverride) { DEBUG_PRINT(F("RT Lock blocked by Override:")); DEBUG_PRINTLN(realtimeOverride); return; } + if (realtimeOverride) return; unsigned totalLen = strip.getLengthTotal(); if (udpIn[0] == 1 && packetSize > 5) { //warls diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 53278cbd89..c23784abc5 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -107,12 +107,7 @@ void WLED::loop() stripMillis = millis(); #endif bool allowNormal = !realtimeMode || realtimeOverride || (realtimeMode && useMainSegmentOnly); -#ifdef WLED_DEBUG - -#endif - #ifdef WLED_DEBUG - - #endif + if (!presetNeedsSaving()) { handlePlaylist(); From 817ce1cf6d7c2fab098ef053f3f5668d452d5763 Mon Sep 17 00:00:00 2001 From: Isacco Date: Wed, 11 Feb 2026 23:54:47 +0100 Subject: [PATCH 4/7] build flags --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 9dccb94afd..5de55062cc 100644 --- a/platformio.ini +++ b/platformio.ini @@ -373,7 +373,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266\" -D WLED_DEBUG #-DWLED_DISABLE_2D +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266\" #-DWLED_DISABLE_2D -D WLED_DISABLE_PARTICLESYSTEM2D lib_deps = ${esp8266.lib_deps} monitor_filters = esp8266_exception_decoder From cbddd381c0c8b9a0a0bf3c56d43e613870dc32ef Mon Sep 17 00:00:00 2001 From: Isacco Date: Thu, 12 Feb 2026 00:05:51 +0100 Subject: [PATCH 5/7] removing debug files --- commit_diff.txt | 40 ---- e131_orig.txt | 542 ------------------------------------------------ 2 files changed, 582 deletions(-) delete mode 100644 commit_diff.txt delete mode 100644 e131_orig.txt diff --git a/commit_diff.txt b/commit_diff.txt deleted file mode 100644 index 7b454ea849..0000000000 --- a/commit_diff.txt +++ /dev/null @@ -1,40 +0,0 @@ -commit d7dda58461c92f208a33ac40e759ff5dffa21e11 -Author: Isacco -Date: Tue Feb 10 20:17:08 2026 +0100 - - ignore realtime - -diff --git a/wled00/e131.cpp b/wled00/e131.cpp -index 357e7841f..d6da0b459 100644 ---- a/wled00/e131.cpp -+++ b/wled00/e131.cpp -@@ -26,6 +26,7 @@ void handleDDPPacket(e131_packet_t* p) { - } - } - -+ if (!receiveDirect) return; - unsigned ddpChannelsPerLed = ((p->dataType & 0b00111000)>>3 == 0b011) ? 4 : 3; // data type 0x1B (formerly 0x1A) is RGBW (type 3, 8 bit/channel) - - uint32_t start = htonl(p->channelOffset) / ddpChannelsPerLed; -@@ -68,6 +69,8 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ - uint8_t* e131_data = nullptr; - int seq = 0, mde = REALTIME_MODE_E131; - -+ if (!receiveDirect) return; -+ - if (protocol == P_ARTNET) - { - if (p->art_opcode == ARTNET_OPCODE_OPPOLL) { -diff --git a/wled00/set.cpp b/wled00/set.cpp -index ab3060d06..b61abdc57 100644 ---- a/wled00/set.cpp -+++ b/wled00/set.cpp -@@ -1155,7 +1155,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) - - //receive live data via UDP/Hyperion - pos = req.indexOf(F("RD=")); -- if (pos > 0) receiveDirect = (req.charAt(pos+3) != '0'); -+ if (pos > 0) { receiveDirect = (req.charAt(pos+3) != '0'); if (!receiveDirect) exitRealtime(); } - - //main toggle on/off (parse before nightlight, #1214) - pos = req.indexOf(F("&T=")); diff --git a/e131_orig.txt b/e131_orig.txt deleted file mode 100644 index 357e7841fe..0000000000 --- a/e131_orig.txt +++ /dev/null @@ -1,542 +0,0 @@ -#include "wled.h" - -#define MAX_3_CH_LEDS_PER_UNIVERSE 170 -#define MAX_4_CH_LEDS_PER_UNIVERSE 128 -#define MAX_CHANNELS_PER_UNIVERSE 512 - -/* - * E1.31 handler - */ - -//DDP protocol support, called by handleE131Packet -//handles RGB data only -void handleDDPPacket(e131_packet_t* p) { - static bool ddpSeenPush = false; // have we seen a push yet? - int lastPushSeq = e131LastSequenceNumber[0]; - - //reject late packets belonging to previous frame (assuming 4 packets max. before push) - if (e131SkipOutOfSequence && lastPushSeq) { - int sn = p->sequenceNum & 0xF; - if (sn) { - if (lastPushSeq > 5) { - if (sn > (lastPushSeq -5) && sn < lastPushSeq) return; - } else { - if (sn > (10 + lastPushSeq) || sn < lastPushSeq) return; - } - } - } - - unsigned ddpChannelsPerLed = ((p->dataType & 0b00111000)>>3 == 0b011) ? 4 : 3; // data type 0x1B (formerly 0x1A) is RGBW (type 3, 8 bit/channel) - - uint32_t start = htonl(p->channelOffset) / ddpChannelsPerLed; - start += DMXAddress / ddpChannelsPerLed; - uint16_t dataLen = htons(p->dataLen); - unsigned stop = start + dataLen / ddpChannelsPerLed; - uint8_t* data = p->data; - unsigned c = 0; - if (p->flags & DDP_TIMECODE_FLAG) c = 4; //packet has timecode flag, we do not support it, but data starts 4 bytes later - - unsigned numLeds = stop - start; // stop >= start is guaranteed - unsigned maxDataIndex = c + numLeds * ddpChannelsPerLed; // validate bounds before accessing data array - if (maxDataIndex > dataLen) { - DEBUG_PRINTLN(F("DDP packet data bounds exceeded, rejecting.")); - return; - } - - if (realtimeMode != REALTIME_MODE_DDP) ddpSeenPush = false; // just starting, no push yet - realtimeLock(realtimeTimeoutMs, REALTIME_MODE_DDP); - - if (!realtimeOverride) { - for (unsigned i = start; i < stop; i++, c += ddpChannelsPerLed) { - setRealtimePixel(i, data[c], data[c+1], data[c+2], ddpChannelsPerLed >3 ? data[c+3] : 0); - } - } - - bool push = p->flags & DDP_PUSH_FLAG; - ddpSeenPush |= push; - if (!ddpSeenPush || push) { // if we've never seen a push, or this is one, render display - e131NewData = true; - int sn = p->sequenceNum & 0xF; - if (sn) e131LastSequenceNumber[0] = sn; - } -} - -//E1.31 and Art-Net protocol support -void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ - - int uni = 0, dmxChannels = 0; - uint8_t* e131_data = nullptr; - int seq = 0, mde = REALTIME_MODE_E131; - - if (protocol == P_ARTNET) - { - if (p->art_opcode == ARTNET_OPCODE_OPPOLL) { - handleArtnetPollReply(clientIP); - return; - } - uni = p->art_universe; - dmxChannels = htons(p->art_length); - e131_data = p->art_data; - seq = p->art_sequence_number; - mde = REALTIME_MODE_ARTNET; - } else if (protocol == P_E131) { - // Ignore PREVIEW data (E1.31: 6.2.6) - if ((p->options & 0x80) != 0) return; - dmxChannels = htons(p->property_value_count) - 1; - // DMX level data is zero start code. Ignore everything else. (E1.11: 8.5) - if (dmxChannels == 0 || p->property_values[0] != 0) return; - uni = htons(p->universe); - e131_data = p->property_values; - seq = p->sequence_number; - if (e131Priority != 0) { - if (p->priority < e131Priority ) return; - // track highest priority & skip all lower priorities - if (p->priority >= highPriority.get()) highPriority.set(p->priority); - if (p->priority < highPriority.get()) return; - } - } else { //DDP - realtimeIP = clientIP; - handleDDPPacket(p); - return; - } - - #ifdef WLED_ENABLE_DMX - // does not act on out-of-order packets yet - if (e131ProxyUniverse > 0 && uni == e131ProxyUniverse) { - for (uint16_t i = 1; i <= dmxChannels; i++) - dmx.write(i, e131_data[i]); - dmx.update(); - } - #endif - - // only listen for universes we're handling & allocated memory - if (uni < e131Universe || uni >= (e131Universe + E131_MAX_UNIVERSE_COUNT)) return; - - unsigned previousUniverses = uni - e131Universe; - - if (e131SkipOutOfSequence) - if (seq < e131LastSequenceNumber[previousUniverses] && seq > 20 && e131LastSequenceNumber[previousUniverses] < 250){ - DEBUG_PRINTF_P(PSTR("skipping E1.31 frame (last seq=%d, current seq=%d, universe=%d)\n"), e131LastSequenceNumber[previousUniverses], seq, uni); - return; - } - e131LastSequenceNumber[previousUniverses] = seq; - - // update status info - realtimeIP = clientIP; - - handleDMXData(uni, dmxChannels, e131_data, mde, previousUniverses); -} - -void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8_t mde, uint8_t previousUniverses) { - byte wChannel = 0; - unsigned totalLen = strip.getLengthTotal(); - unsigned availDMXLen = 0; - unsigned dataOffset = DMXAddress; - - // For legacy DMX start address 0 the available DMX length offset is 0 - const unsigned dmxLenOffset = (DMXAddress == 0) ? 0 : 1; - - // Check if DMX start address fits in available channels - if (dmxChannels >= DMXAddress) { - availDMXLen = (dmxChannels - DMXAddress) + dmxLenOffset; - } - - // DMX data in Art-Net packet starts at index 0, for E1.31 at index 1 - if (mde == REALTIME_MODE_ARTNET && dataOffset > 0) { - dataOffset--; - } - - switch (DMXMode) { - case DMX_MODE_DISABLED: - return; // nothing to do - break; - - case DMX_MODE_SINGLE_RGB: // 3 channel: [R,G,B] - if (uni != e131Universe) return; - if (availDMXLen < 3) return; - - realtimeLock(realtimeTimeoutMs, mde); - - if (realtimeOverride) return; - - wChannel = (availDMXLen > 3) ? e131_data[dataOffset+3] : 0; - for (unsigned i = 0; i < totalLen; i++) - setRealtimePixel(i, e131_data[dataOffset+0], e131_data[dataOffset+1], e131_data[dataOffset+2], wChannel); - break; - - case DMX_MODE_SINGLE_DRGB: // 4 channel: [Dimmer,R,G,B] - if (uni != e131Universe) return; - if (availDMXLen < 4) return; - - realtimeLock(realtimeTimeoutMs, mde); - if (realtimeOverride) return; - wChannel = (availDMXLen > 4) ? e131_data[dataOffset+4] : 0; - - if (bri != e131_data[dataOffset+0]) { - bri = e131_data[dataOffset+0]; - strip.setBrightness(bri, true); - } - - for (unsigned i = 0; i < totalLen; i++) - setRealtimePixel(i, e131_data[dataOffset+1], e131_data[dataOffset+2], e131_data[dataOffset+3], wChannel); - break; - - case DMX_MODE_PRESET: // 2 channel: [Dimmer,Preset] - { - if (uni != e131Universe || availDMXLen < 2) return; - - // limit max. selectable preset to 250, even though DMX max. val is 255 - int dmxValPreset = (e131_data[dataOffset+1] > 250 ? 250 : e131_data[dataOffset+1]); - - // only apply preset if value changed - if (dmxValPreset != 0 && dmxValPreset != currentPreset && - // only apply preset if not in playlist, or playlist changed - (currentPlaylist < 0 || dmxValPreset != currentPlaylist)) { - presetCycCurr = dmxValPreset; - applyPreset(dmxValPreset, CALL_MODE_NOTIFICATION); - } - - // only change brightness if value changed - if (bri != e131_data[dataOffset]) { - bri = e131_data[dataOffset]; - strip.setBrightness(bri, false); - stateUpdated(CALL_MODE_WS_SEND); - } - return; - break; - } - - case DMX_MODE_EFFECT: // 15 channels [bri,effectCurrent,effectSpeed,effectIntensity,effectPalette,effectOption,R,G,B,R2,G2,B2,R3,G3,B3] - case DMX_MODE_EFFECT_W: // 18 channels, same as above but with extra +3 white channels [..,W,W2,W3] - case DMX_MODE_EFFECT_SEGMENT: // 15 channels per segment; - case DMX_MODE_EFFECT_SEGMENT_W: // 18 Channels per segment; - { - if (uni != e131Universe) return; - bool isSegmentMode = DMXMode == DMX_MODE_EFFECT_SEGMENT || DMXMode == DMX_MODE_EFFECT_SEGMENT_W; - unsigned dmxEffectChannels = (DMXMode == DMX_MODE_EFFECT || DMXMode == DMX_MODE_EFFECT_SEGMENT) ? 15 : 18; - for (unsigned id = 0; id < strip.getSegmentsNum(); id++) { - Segment& seg = strip.getSegment(id); - if (isSegmentMode) - dataOffset = DMXAddress + id * (dmxEffectChannels + DMXSegmentSpacing); - else - dataOffset = DMXAddress; - // Modify address for Art-Net data - if (mde == REALTIME_MODE_ARTNET && dataOffset > 0) - dataOffset--; - // Skip out of universe addresses - if (dataOffset > dmxChannels - dmxEffectChannels + 1) - return; - - if (e131_data[dataOffset+1] < strip.getModeCount()) - if (e131_data[dataOffset+1] != seg.mode) seg.setMode( e131_data[dataOffset+1]); - if (e131_data[dataOffset+2] != seg.speed) seg.speed = e131_data[dataOffset+2]; - if (e131_data[dataOffset+3] != seg.intensity) seg.intensity = e131_data[dataOffset+3]; - if (e131_data[dataOffset+4] != seg.palette) seg.setPalette(e131_data[dataOffset+4]); - - if (bool(e131_data[dataOffset+5] & 0b00000010) != seg.reverse_y) { seg.reverse_y = bool(e131_data[dataOffset+5] & 0b00000010); } - if (bool(e131_data[dataOffset+5] & 0b00000100) != seg.mirror_y) { seg.mirror_y = bool(e131_data[dataOffset+5] & 0b00000100); } - if (bool(e131_data[dataOffset+5] & 0b00001000) != seg.transpose) { seg.transpose = bool(e131_data[dataOffset+5] & 0b00001000); } - if ((e131_data[dataOffset+5] & 0b00110000) >> 4 != seg.map1D2D) { - seg.map1D2D = (e131_data[dataOffset+5] & 0b00110000) >> 4; - } - // To maintain backwards compatibility with prior e1.31 values, reverse is fixed to mask 0x01000000 - if ((e131_data[dataOffset+5] & 0b01000000) != seg.reverse) { seg.reverse = bool(e131_data[dataOffset+5] & 0b01000000); } - // To maintain backwards compatibility with prior e1.31 values, mirror is fixed to mask 0x10000000 - if ((e131_data[dataOffset+5] & 0b10000000) != seg.mirror) { seg.mirror = bool(e131_data[dataOffset+5] & 0b10000000); } - - uint32_t colors[3]; - byte whites[3] = {0,0,0}; - if (dmxEffectChannels == 18) { - whites[0] = e131_data[dataOffset+15]; - whites[1] = e131_data[dataOffset+16]; - whites[2] = e131_data[dataOffset+17]; - } - colors[0] = RGBW32(e131_data[dataOffset+ 6], e131_data[dataOffset+ 7], e131_data[dataOffset+ 8], whites[0]); - colors[1] = RGBW32(e131_data[dataOffset+ 9], e131_data[dataOffset+10], e131_data[dataOffset+11], whites[1]); - colors[2] = RGBW32(e131_data[dataOffset+12], e131_data[dataOffset+13], e131_data[dataOffset+14], whites[2]); - if (colors[0] != seg.colors[0]) seg.setColor(0, colors[0]); - if (colors[1] != seg.colors[1]) seg.setColor(1, colors[1]); - if (colors[2] != seg.colors[2]) seg.setColor(2, colors[2]); - - // Set segment opacity or global brightness - if (isSegmentMode) { - if (e131_data[dataOffset] != seg.opacity) seg.setOpacity(e131_data[dataOffset]); - } else if ( id == strip.getSegmentsNum()-1U ) { - if (bri != e131_data[dataOffset]) { - bri = e131_data[dataOffset]; - strip.setBrightness(bri, true); - } - } - } - return; - break; - } - - case DMX_MODE_MULTIPLE_DRGB: - case DMX_MODE_MULTIPLE_RGB: - case DMX_MODE_MULTIPLE_RGBW: - { - const bool is4Chan = (DMXMode == DMX_MODE_MULTIPLE_RGBW); - const unsigned dmxChannelsPerLed = is4Chan ? 4 : 3; - const unsigned ledsPerUniverse = is4Chan ? MAX_4_CH_LEDS_PER_UNIVERSE : MAX_3_CH_LEDS_PER_UNIVERSE; - uint8_t stripBrightness = bri; - unsigned previousLeds, dmxOffset, ledsTotal; - - if (previousUniverses == 0) { - if (availDMXLen < 1) return; - dmxOffset = dataOffset; - previousLeds = 0; - // First DMX address is dimmer in DMX_MODE_MULTIPLE_DRGB mode. - if (DMXMode == DMX_MODE_MULTIPLE_DRGB) { - stripBrightness = e131_data[dmxOffset++]; - ledsTotal = (availDMXLen - 1) / dmxChannelsPerLed; - } else { - ledsTotal = availDMXLen / dmxChannelsPerLed; - } - } else { - // All subsequent universes start at the first channel. - dmxOffset = (mde == REALTIME_MODE_ARTNET) ? 0 : 1; - const unsigned dimmerOffset = (DMXMode == DMX_MODE_MULTIPLE_DRGB) ? 1 : 0; - unsigned ledsInFirstUniverse = (((MAX_CHANNELS_PER_UNIVERSE - DMXAddress) + dmxLenOffset) - dimmerOffset) / dmxChannelsPerLed; - previousLeds = ledsInFirstUniverse + (previousUniverses - 1) * ledsPerUniverse; - ledsTotal = previousLeds + (dmxChannels / dmxChannelsPerLed); - } - - // All LEDs already have values - if (previousLeds >= totalLen) { - return; - } - - realtimeLock(realtimeTimeoutMs, mde); - if (realtimeOverride) return; - - if (ledsTotal > totalLen) { - ledsTotal = totalLen; - } - - if (DMXMode == DMX_MODE_MULTIPLE_DRGB && previousUniverses == 0) { - if (bri != stripBrightness) { - bri = stripBrightness; - strip.setBrightness(bri, true); - } - } - - for (unsigned i = previousLeds; i < ledsTotal; i++) { - setRealtimePixel(i, e131_data[dmxOffset], e131_data[dmxOffset+1], e131_data[dmxOffset+2], is4Chan ? e131_data[dmxOffset+3] : 0); - dmxOffset += dmxChannelsPerLed; - } - break; - } - default: - DEBUG_PRINTLN(F("unknown E1.31 DMX mode")); - return; // nothing to do - break; - } - - e131NewData = true; -} - -void handleArtnetPollReply(IPAddress ipAddress) { - ArtPollReply artnetPollReply; - prepareArtnetPollReply(&artnetPollReply); - - unsigned startUniverse = e131Universe; - unsigned endUniverse = e131Universe; - - switch (DMXMode) { - case DMX_MODE_DISABLED: - break; - - case DMX_MODE_SINGLE_RGB: - case DMX_MODE_SINGLE_DRGB: - case DMX_MODE_PRESET: - case DMX_MODE_EFFECT: - case DMX_MODE_EFFECT_W: - case DMX_MODE_EFFECT_SEGMENT: - case DMX_MODE_EFFECT_SEGMENT_W: - break; // 1 universe is enough - - case DMX_MODE_MULTIPLE_DRGB: - case DMX_MODE_MULTIPLE_RGB: - case DMX_MODE_MULTIPLE_RGBW: - { - bool is4Chan = (DMXMode == DMX_MODE_MULTIPLE_RGBW); - const unsigned dmxChannelsPerLed = is4Chan ? 4 : 3; - const unsigned dimmerOffset = (DMXMode == DMX_MODE_MULTIPLE_DRGB) ? 1 : 0; - const unsigned dmxLenOffset = (DMXAddress == 0) ? 0 : 1; // For legacy DMX start address 0 - const unsigned ledsInFirstUniverse = (((MAX_CHANNELS_PER_UNIVERSE - DMXAddress) + dmxLenOffset) - dimmerOffset) / dmxChannelsPerLed; - const unsigned totalLen = strip.getLengthTotal(); - - if (totalLen > ledsInFirstUniverse) { - const unsigned ledsPerUniverse = is4Chan ? MAX_4_CH_LEDS_PER_UNIVERSE : MAX_3_CH_LEDS_PER_UNIVERSE; - const unsigned remainLED = totalLen - ledsInFirstUniverse; - - endUniverse += (remainLED / ledsPerUniverse); - - if ((remainLED % ledsPerUniverse) > 0) { - endUniverse++; - } - - if ((endUniverse - startUniverse) > E131_MAX_UNIVERSE_COUNT) { - endUniverse = startUniverse + E131_MAX_UNIVERSE_COUNT - 1; - } - } - break; - } - default: - DEBUG_PRINTLN(F("unknown E1.31 DMX mode")); - return; // nothing to do - break; - } - - if (DMXMode != DMX_MODE_DISABLED) { - for (unsigned i = startUniverse; i <= endUniverse; ++i) { - sendArtnetPollReply(&artnetPollReply, ipAddress, i); - } - } - - #ifdef WLED_ENABLE_DMX - if (e131ProxyUniverse > 0 && (DMXMode == DMX_MODE_DISABLED || (e131ProxyUniverse < startUniverse || e131ProxyUniverse > endUniverse))) { - sendArtnetPollReply(&artnetPollReply, ipAddress, e131ProxyUniverse); - } - #endif -} - -void prepareArtnetPollReply(ArtPollReply *reply) { - // Art-Net - reply->reply_id[0] = 0x41; - reply->reply_id[1] = 0x72; - reply->reply_id[2] = 0x74; - reply->reply_id[3] = 0x2d; - reply->reply_id[4] = 0x4e; - reply->reply_id[5] = 0x65; - reply->reply_id[6] = 0x74; - reply->reply_id[7] = 0x00; - - reply->reply_opcode = ARTNET_OPCODE_OPPOLLREPLY; - - IPAddress localIP = Network.localIP(); - for (unsigned i = 0; i < 4; i++) { - reply->reply_ip[i] = localIP[i]; - } - - reply->reply_port = ARTNET_DEFAULT_PORT; - - char * numberEnd = (char*) versionString; // strtol promises not to try to edit this. - reply->reply_version_h = (uint8_t)strtol(numberEnd, &numberEnd, 10); - numberEnd++; - reply->reply_version_l = (uint8_t)strtol(numberEnd, &numberEnd, 10); - - // Switch values depend on universe, set before sending - reply->reply_net_sw = 0x00; - reply->reply_sub_sw = 0x00; - - reply->reply_oem_h = 0x00; // TODO add assigned oem code - reply->reply_oem_l = 0x00; - - reply->reply_ubea_ver = 0x00; - - // Indicators in Normal Mode - // All or part of Port-Address programmed by network or Web browser - reply->reply_status_1 = 0xE0; - - reply->reply_esta_man = 0x0000; - - strlcpy((char *)(reply->reply_short_name), serverDescription, 18); - strlcpy((char *)(reply->reply_long_name), serverDescription, 64); - - reply->reply_node_report[0] = '\0'; - - reply->reply_num_ports_h = 0x00; - reply->reply_num_ports_l = 0x01; // One output port - - reply->reply_port_types[0] = 0x80; // Output DMX data - reply->reply_port_types[1] = 0x00; - reply->reply_port_types[2] = 0x00; - reply->reply_port_types[3] = 0x00; - - // No inputs - reply->reply_good_input[0] = 0x00; - reply->reply_good_input[1] = 0x00; - reply->reply_good_input[2] = 0x00; - reply->reply_good_input[3] = 0x00; - - // One output - reply->reply_good_output_a[0] = 0x80; // Data is being transmitted - reply->reply_good_output_a[1] = 0x00; - reply->reply_good_output_a[2] = 0x00; - reply->reply_good_output_a[3] = 0x00; - - // Values depend on universe, set before sending - reply->reply_sw_in[0] = 0x00; - reply->reply_sw_in[1] = 0x00; - reply->reply_sw_in[2] = 0x00; - reply->reply_sw_in[3] = 0x00; - - // Values depend on universe, set before sending - reply->reply_sw_out[0] = 0x00; - reply->reply_sw_out[1] = 0x00; - reply->reply_sw_out[2] = 0x00; - reply->reply_sw_out[3] = 0x00; - - reply->reply_sw_video = 0x00; - reply->reply_sw_macro = 0x00; - reply->reply_sw_remote = 0x00; - - reply->reply_spare[0] = 0x00; - reply->reply_spare[1] = 0x00; - reply->reply_spare[2] = 0x00; - - // A DMX to / from Art-Net device - reply->reply_style = 0x00; - - Network.localMAC(reply->reply_mac); - - for (unsigned i = 0; i < 4; i++) { - reply->reply_bind_ip[i] = localIP[i]; - } - - reply->reply_bind_index = 1; - - // Product supports web browser configuration - // Node’s IP is DHCP or manually configured - // Node is DHCP capable - // Node supports 15 bit Port-Address (Art-Net 3 or 4) - // Node is able to switch between ArtNet and sACN - reply->reply_status_2 = (multiWiFi[0].staticIP[0] == 0) ? 0x1F : 0x1D; - - // RDM is disabled - // Output style is continuous - reply->reply_good_output_b[0] = 0xC0; - reply->reply_good_output_b[1] = 0xC0; - reply->reply_good_output_b[2] = 0xC0; - reply->reply_good_output_b[3] = 0xC0; - - // Fail-over state: Hold last state - // Node does not support fail-over - reply->reply_status_3 = 0x00; - - for (unsigned i = 0; i < 21; i++) { - reply->reply_filler[i] = 0x00; - } -} - -void sendArtnetPollReply(ArtPollReply *reply, IPAddress ipAddress, uint16_t portAddress) { - reply->reply_net_sw = (uint8_t)((portAddress >> 8) & 0x007F); - reply->reply_sub_sw = (uint8_t)((portAddress >> 4) & 0x000F); - reply->reply_sw_out[0] = (uint8_t)(portAddress & 0x000F); - - snprintf_P((char *)reply->reply_node_report, sizeof(reply->reply_node_report)-1, PSTR("#0001 [%04u] OK - WLED v%s"), pollReplyCount, versionString); - - if (pollReplyCount < 9999) { - pollReplyCount++; - } else { - pollReplyCount = 0; - } - - notifierUdp.beginPacket(ipAddress, ARTNET_DEFAULT_PORT); - notifierUdp.write(reply->raw, sizeof(ArtPollReply)); - notifierUdp.endPacket(); - - reply->reply_bind_index++; -} \ No newline at end of file From af31689fe969db92426319a515deb3a071dcb2c4 Mon Sep 17 00:00:00 2001 From: Isacco Date: Fri, 13 Feb 2026 00:23:08 +0100 Subject: [PATCH 6/7] Clean up --- wled00/e131.cpp | 3 --- wled00/json.cpp | 4 +--- wled00/set.cpp | 6 ++---- wled00/udp.cpp | 5 +---- wled00/wled.cpp | 19 ++++++++----------- 5 files changed, 12 insertions(+), 25 deletions(-) diff --git a/wled00/e131.cpp b/wled00/e131.cpp index bf6d516500..8029feec3e 100644 --- a/wled00/e131.cpp +++ b/wled00/e131.cpp @@ -33,7 +33,6 @@ static void handleDDPPacket(e131_packet_t* p) { } } - if (!receiveDirect) return; unsigned ddpChannelsPerLed = ((p->dataType & 0b00111000)>>3 == 0b011) ? 4 : 3; // data type 0x1B (formerly 0x1A) is RGBW (type 3, 8 bit/channel) uint32_t start = htonl(p->channelOffset) / ddpChannelsPerLed; @@ -76,8 +75,6 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ uint8_t* e131_data = nullptr; int seq = 0, mde = REALTIME_MODE_E131; - if (!receiveDirect) return; - if (protocol == P_ARTNET) { if (p->art_opcode == ARTNET_OPCODE_OPPOLL) { diff --git a/wled00/json.cpp b/wled00/json.cpp index 8e3a529d44..15b7f75922 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -428,8 +428,6 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) sendNotificationsRT = getBoolVal(udpn[F("send")], sendNotificationsRT); syncGroups = udpn[F("sgrp")] | syncGroups; receiveGroups = udpn[F("rgrp")] | receiveGroups; - receiveDirect = getBoolVal(udpn[F("rd")], receiveDirect); - if (udpn.containsKey(F("rd")) && !receiveDirect) exitRealtime(); if ((bool)udpn[F("nn")]) callMode = CALL_MODE_NO_NOTIFY; //send no notification just for this request unsigned long timein = root["time"] | UINT32_MAX; //backup time source if NTP not synced @@ -447,7 +445,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) if (realtimeOverride > 2) realtimeOverride = REALTIME_OVERRIDE_ALWAYS; if (realtimeMode && useMainSegmentOnly) { strip.getMainSegment().freeze = !realtimeOverride; - // realtimeOverride = REALTIME_OVERRIDE_NONE; // Fix: do not clear override even if using main segment only + realtimeOverride = REALTIME_OVERRIDE_NONE; // ignore request for override if using main segment only } if (root.containsKey("live")) { diff --git a/wled00/set.cpp b/wled00/set.cpp index c7862c82d2..d69bda905e 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -1155,7 +1155,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) //receive live data via UDP/Hyperion pos = req.indexOf(F("RD=")); - if (pos >= 0) { receiveDirect = (req.charAt(pos+3) != '0'); if (!receiveDirect) exitRealtime(); } + if (pos > 0) receiveDirect = (req.charAt(pos+3) != '0'); //main toggle on/off (parse before nightlight, #1214) pos = req.indexOf(F("&T=")); @@ -1228,12 +1228,10 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) pos = req.indexOf(F("LO=")); if (pos > 0) { realtimeOverride = getNumVal(req, pos); - if (realtimeOverride > 2) realtimeOverride = REALTIME_OVERRIDE_ALWAYS; if (realtimeMode && useMainSegmentOnly) { - strip.getMainSegment().freeze = !realtimeOverride; -// realtimeOverride = REALTIME_OVERRIDE_NONE; // Fix: do not clear override even if using main segment only + realtimeOverride = REALTIME_OVERRIDE_NONE; // ignore request for override if using main segment only } } diff --git a/wled00/udp.cpp b/wled00/udp.cpp index 76d2cbd4e8..fab67bc600 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -431,9 +431,7 @@ void realtimeLock(uint32_t timeoutMs, byte md) if (realtimeTimeout != UINT32_MAX) { realtimeTimeout = (timeoutMs == 255001 || timeoutMs == 65000) ? UINT32_MAX : millis() + timeoutMs; } - realtimeMode = md; - if (realtimeOverride) return; if (arlsForceMaxBri) strip.setBrightness(255, true); @@ -446,7 +444,6 @@ void exitRealtime() { strip.setBrightness(bri, true); realtimeTimeout = 0; // cancel realtime mode immediately realtimeMode = REALTIME_MODE_INACTIVE; // inform UI immediately - realtimeIP[0] = 0; if (useMainSegmentOnly) { // unfreeze live segment again strip.getMainSegment().freeze = false; @@ -504,7 +501,7 @@ void handleNotifications() if (!receiveDirect) return; if (packetSize > UDP_IN_MAXSIZE || packetSize < 3) return; realtimeIP = rgbUdp.remoteIP(); - + DEBUG_PRINTLN(rgbUdp.remoteIP()); uint8_t lbuf[packetSize]; rgbUdp.read(lbuf, packetSize); realtimeLock(realtimeTimeoutMs, REALTIME_MODE_HYPERION); diff --git a/wled00/wled.cpp b/wled00/wled.cpp index c23784abc5..2136b9cd01 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -106,16 +106,15 @@ void WLED::loop() #ifdef WLED_DEBUG stripMillis = millis(); #endif - bool allowNormal = !realtimeMode || realtimeOverride || (realtimeMode && useMainSegmentOnly); - - if (!presetNeedsSaving()) { - handlePlaylist(); - yield(); - } - handlePresets(); + if (!presetNeedsSaving()) { + handlePlaylist(); yield(); - if (allowNormal) // block stuff if WARLS/Adalight is enabled + } + handlePresets(); + yield(); + + if (!realtimeMode || realtimeOverride || (realtimeMode && useMainSegmentOnly)) // block stuff if WARLS/Adalight is enabled { if (apActive) dnsServer.processNextRequest(); #ifdef WLED_ENABLE_AOTA @@ -128,9 +127,7 @@ void WLED::loop() handleHue(); yield(); #endif - - - + if (!offMode || strip.isOffRefreshRequired() || strip.needsUpdate()) strip.service(); #ifdef ESP8266 From 009dca704f5d03a6237f9e083bc609451b083b3b Mon Sep 17 00:00:00 2001 From: Isacco Date: Fri, 20 Feb 2026 20:36:30 +0100 Subject: [PATCH 7/7] Adding checklist and avoiding changing action order in main loop --- wled00/cfg.cpp | 2 ++ wled00/data/settings_sync.htm | 3 ++- wled00/set.cpp | 1 + wled00/wled.cpp | 25 ++++++++++++++++--------- wled00/wled.h | 1 + wled00/xml.cpp | 1 + 6 files changed, 23 insertions(+), 10 deletions(-) diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 37028130fe..dcdea0597e 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -590,6 +590,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(receiveDirect, if_live["en"]); // UDP/Hyperion realtime CJSON(useMainSegmentOnly, if_live[F("mso")]); CJSON(realtimeRespectLedMaps, if_live[F("rlm")]); + CJSON(realtimeAllowPresets, if_live[F("rop")]); CJSON(e131Port, if_live["port"]); // 5568 if (e131Port == DDP_DEFAULT_PORT) e131Port = E131_DEFAULT_PORT; // prevent double DDP port allocation CJSON(e131Multicast, if_live[F("mc")]); @@ -1126,6 +1127,7 @@ void serializeConfig(JsonObject root) { if_live["en"] = receiveDirect; // UDP/Hyperion realtime if_live[F("mso")] = useMainSegmentOnly; if_live[F("rlm")] = realtimeRespectLedMaps; + if_live[F("rop")] = realtimeAllowPresets; if_live["port"] = e131Port; if_live[F("mc")] = e131Multicast; diff --git a/wled00/data/settings_sync.htm b/wled00/data/settings_sync.htm index 1d026946f0..2d08d05cbd 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -126,7 +126,8 @@

Instance List

Realtime

Receive UDP realtime:
Use main segment only:
-Respect LED Maps:

+Respect LED Maps:
+Immediately force presets switching during realtime mode:

Network DMX input
Type: