From c2deef214ef3a95b9b67f8f2de8a0be5fdf361e4 Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Thu, 26 Feb 2026 14:44:03 +0300 Subject: [PATCH] Add brightness overlay and enlarge LED preview on target cards Show effective brightness percentage on the LED preview when a value source dims below 100%. Prepend a brightness byte to the preview WebSocket wire format. Increase preview height to 32px. Co-Authored-By: Claude Opus 4.6 --- .../core/processing/wled_target_processor.py | 14 +++++++++----- server/src/wled_controller/static/css/cards.css | 16 +++++++++++++++- .../static/js/features/targets.js | 17 ++++++++++++++++- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/server/src/wled_controller/core/processing/wled_target_processor.py b/server/src/wled_controller/core/processing/wled_target_processor.py index 5c733e3..70d3db8 100644 --- a/server/src/wled_controller/core/processing/wled_target_processor.py +++ b/server/src/wled_controller/core/processing/wled_target_processor.py @@ -433,12 +433,16 @@ class WledTargetProcessor(TargetProcessor): if ws in self._preview_clients: self._preview_clients.remove(ws) - async def _broadcast_led_preview(self, colors: np.ndarray) -> None: - """Broadcast LED colors as binary RGB bytes to preview WebSocket clients.""" + async def _broadcast_led_preview(self, colors: np.ndarray, brightness: int = 255) -> None: + """Broadcast LED colors as binary RGB bytes to preview WebSocket clients. + + Wire format: [brightness_byte] [R G B R G B ...] + First byte is the effective brightness (0-255), rest is RGB pixel data. + """ if not self._preview_clients: return - data = colors.astype(np.uint8).tobytes() + data = bytes([brightness]) + colors.astype(np.uint8).tobytes() async def _send_safe(ws): try: @@ -605,7 +609,7 @@ class WledTargetProcessor(TargetProcessor): send_timestamps.append(now) self._metrics.frames_keepalive += 1 if self._preview_clients and (now - _last_preview_broadcast) >= 0.066: - await self._broadcast_led_preview(send_colors) + await self._broadcast_led_preview(send_colors, cur_brightness) _last_preview_broadcast = now self._metrics.frames_skipped += 1 while send_timestamps and send_timestamps[0] < now - 1.0: @@ -640,7 +644,7 @@ class WledTargetProcessor(TargetProcessor): # Broadcast to LED preview WebSocket clients (throttled to ~15 fps) if self._preview_clients and (now - _last_preview_broadcast) >= 0.066: - await self._broadcast_led_preview(send_colors) + await self._broadcast_led_preview(send_colors, cur_brightness) _last_preview_broadcast = now self._metrics.timing_send_ms = send_ms diff --git a/server/src/wled_controller/static/css/cards.css b/server/src/wled_controller/static/css/cards.css index f2d06b1..9822cd0 100644 --- a/server/src/wled_controller/static/css/cards.css +++ b/server/src/wled_controller/static/css/cards.css @@ -631,13 +631,27 @@ ul.section-tip li { .led-preview-panel { padding: 4px 0 0; + position: relative; } .led-preview-canvas { display: block; width: 100%; - height: 16px; + height: 32px; border-radius: 3px; image-rendering: pixelated; background: #111; } + +.led-preview-brightness { + position: absolute; + right: 4px; + top: 50%; + transform: translateY(-50%); + font-size: 0.8rem; + font-family: var(--font-mono, monospace); + color: #fff; + text-shadow: 0 0 3px rgba(0,0,0,0.8); + pointer-events: none; + opacity: 0.8; +} diff --git a/server/src/wled_controller/static/js/features/targets.js b/server/src/wled_controller/static/js/features/targets.js index 3c809e1..b0085f4 100644 --- a/server/src/wled_controller/static/js/features/targets.js +++ b/server/src/wled_controller/static/js/features/targets.js @@ -846,6 +846,7 @@ export function createTargetCard(target, deviceMap, colorStripSourceMap, valueSo
+
${isProcessing ? ` @@ -1041,10 +1042,24 @@ function connectLedPreviewWS(targetId) { ws.onmessage = (event) => { if (event.data instanceof ArrayBuffer) { - const frame = new Uint8Array(event.data); + const raw = new Uint8Array(event.data); + // Wire format: [brightness_byte] [R G B R G B ...] + const brightness = raw[0]; + const frame = raw.subarray(1); _ledPreviewLastFrame[targetId] = frame; const canvas = document.getElementById(`led-preview-canvas-${targetId}`); if (canvas) _renderLedStrip(canvas, frame); + // Show brightness label when below 100% + const bLabel = document.getElementById(`led-preview-brightness-${targetId}`); + if (bLabel) { + const pct = Math.round(brightness / 255 * 100); + if (pct < 100) { + bLabel.textContent = `☀ ${pct}%`; + bLabel.style.display = ''; + } else { + bLabel.style.display = 'none'; + } + } } };