From 350dafb1e85f93ceb2c78393145a87b9c8a268e5 Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Mon, 16 Feb 2026 16:48:08 +0300 Subject: [PATCH] Fix WLED LED stutters: restore DDP PUSH flag, skip HTTP during streaming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three changes to eliminate periodic LED stutters on high-LED-count WLED devices: 1. DDP PUSH flag: re-enable on the last packet of each frame so WLED waits for the complete frame before rendering (prevents tearing from partial multi-packet frames). 2. Health check: skip HTTP probe while a target is actively streaming to the device — the device is clearly online and the HTTP request to the ESP causes LED output to stutter. 3. Brightness polling: cache the value after first fetch and reuse it on subsequent 2-second UI refreshes instead of hitting the ESP every cycle. Co-Authored-By: Claude Opus 4.6 --- server/src/wled_controller/core/ddp_client.py | 8 ++++---- .../wled_controller/core/processor_manager.py | 16 +++++++++++++++- server/src/wled_controller/static/app.js | 16 +++++++++++++++- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/server/src/wled_controller/core/ddp_client.py b/server/src/wled_controller/core/ddp_client.py index 94297b0..535bc7c 100644 --- a/server/src/wled_controller/core/ddp_client.py +++ b/server/src/wled_controller/core/ddp_client.py @@ -223,11 +223,10 @@ class DDPClient: # Increment sequence number self._sequence = (self._sequence + 1) % 256 - # Build and send packet (no PUSH flag — WLED 0.15.x - # handles DDP without it; adding PUSH broke rendering) + # Set PUSH flag on the last packet to signal frame completion packet = self._build_ddp_packet( chunk, offset=start, - sequence=self._sequence, push=False, + sequence=self._sequence, push=is_last, ) self._transport.sendto(packet) @@ -273,10 +272,11 @@ class DDPClient: start = i * bytes_per_packet end = min(start + bytes_per_packet, total_bytes) chunk = pixel_bytes[start:end] + is_last = (i == num_packets - 1) self._sequence = (self._sequence + 1) % 256 packet = self._build_ddp_packet( chunk, offset=start, - sequence=self._sequence, push=False, + sequence=self._sequence, push=is_last, ) self._transport.sendto(packet) diff --git a/server/src/wled_controller/core/processor_manager.py b/server/src/wled_controller/core/processor_manager.py index 114a8b8..cfd1f39 100644 --- a/server/src/wled_controller/core/processor_manager.py +++ b/server/src/wled_controller/core/processor_manager.py @@ -1002,6 +1002,13 @@ class ProcessorManager: state.health_task.cancel() state.health_task = None + def _device_is_processing(self, device_id: str) -> bool: + """Check if any target is actively streaming to this device.""" + return any( + t.is_running for t in self._targets.values() + if t.device_id == device_id + ) + async def _health_check_loop(self, device_id: str): """Background loop that periodically checks a WLED device via GET /json/info.""" state = self._devices.get(device_id) @@ -1012,7 +1019,14 @@ class ProcessorManager: try: while self._health_monitoring_active: - await self._check_device_health(device_id) + # Skip health check while actively streaming — the device is + # clearly online and the HTTP request causes LED stutters + if not self._device_is_processing(device_id): + await self._check_device_health(device_id) + else: + # Mark as online since we're successfully sending frames + if state.health: + state.health.online = True await asyncio.sleep(check_interval) except asyncio.CancelledError: pass diff --git a/server/src/wled_controller/static/app.js b/server/src/wled_controller/static/app.js index 7b2175e..5824208 100644 --- a/server/src/wled_controller/static/app.js +++ b/server/src/wled_controller/static/app.js @@ -4562,7 +4562,21 @@ async function loadTargetsTab() { devicesWithState.forEach(device => { attachDeviceListeners(device.id); if ((device.capabilities || []).includes('brightness_control')) { - fetchDeviceBrightness(device.id); + // Only fetch from device if we don't have a cached value yet — + // avoids HTTP requests to the ESP every 2s which cause LED stutters + if (device.id in _deviceBrightnessCache) { + const bri = _deviceBrightnessCache[device.id]; + const slider = document.querySelector(`[data-device-brightness="${device.id}"]`); + if (slider) { + slider.value = bri; + slider.title = Math.round(bri / 255 * 100) + '%'; + slider.disabled = false; + } + const wrap = document.querySelector(`[data-brightness-wrap="${device.id}"]`); + if (wrap) wrap.classList.remove('brightness-loading'); + } else { + fetchDeviceBrightness(device.id); + } } });