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 <noreply@anthropic.com>
This commit is contained in:
2026-02-26 14:44:03 +03:00
parent a0c9cb0039
commit c2deef214e
3 changed files with 40 additions and 7 deletions

View File

@@ -433,12 +433,16 @@ class WledTargetProcessor(TargetProcessor):
if ws in self._preview_clients: if ws in self._preview_clients:
self._preview_clients.remove(ws) self._preview_clients.remove(ws)
async def _broadcast_led_preview(self, colors: np.ndarray) -> None: async def _broadcast_led_preview(self, colors: np.ndarray, brightness: int = 255) -> None:
"""Broadcast LED colors as binary RGB bytes to preview WebSocket clients.""" """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: if not self._preview_clients:
return return
data = colors.astype(np.uint8).tobytes() data = bytes([brightness]) + colors.astype(np.uint8).tobytes()
async def _send_safe(ws): async def _send_safe(ws):
try: try:
@@ -605,7 +609,7 @@ class WledTargetProcessor(TargetProcessor):
send_timestamps.append(now) send_timestamps.append(now)
self._metrics.frames_keepalive += 1 self._metrics.frames_keepalive += 1
if self._preview_clients and (now - _last_preview_broadcast) >= 0.066: 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 _last_preview_broadcast = now
self._metrics.frames_skipped += 1 self._metrics.frames_skipped += 1
while send_timestamps and send_timestamps[0] < now - 1.0: 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) # Broadcast to LED preview WebSocket clients (throttled to ~15 fps)
if self._preview_clients and (now - _last_preview_broadcast) >= 0.066: 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 _last_preview_broadcast = now
self._metrics.timing_send_ms = send_ms self._metrics.timing_send_ms = send_ms

View File

@@ -631,13 +631,27 @@ ul.section-tip li {
.led-preview-panel { .led-preview-panel {
padding: 4px 0 0; padding: 4px 0 0;
position: relative;
} }
.led-preview-canvas { .led-preview-canvas {
display: block; display: block;
width: 100%; width: 100%;
height: 16px; height: 32px;
border-radius: 3px; border-radius: 3px;
image-rendering: pixelated; image-rendering: pixelated;
background: #111; 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;
}

View File

@@ -846,6 +846,7 @@ export function createTargetCard(target, deviceMap, colorStripSourceMap, valueSo
</div> </div>
<div id="led-preview-panel-${target.id}" class="led-preview-panel" style="display:${ledPreviewWebSockets[target.id] ? '' : 'none'}"> <div id="led-preview-panel-${target.id}" class="led-preview-panel" style="display:${ledPreviewWebSockets[target.id] ? '' : 'none'}">
<canvas id="led-preview-canvas-${target.id}" class="led-preview-canvas"></canvas> <canvas id="led-preview-canvas-${target.id}" class="led-preview-canvas"></canvas>
<span id="led-preview-brightness-${target.id}" class="led-preview-brightness" style="display:none"></span>
</div> </div>
<div class="card-actions"> <div class="card-actions">
${isProcessing ? ` ${isProcessing ? `
@@ -1041,10 +1042,24 @@ function connectLedPreviewWS(targetId) {
ws.onmessage = (event) => { ws.onmessage = (event) => {
if (event.data instanceof ArrayBuffer) { 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; _ledPreviewLastFrame[targetId] = frame;
const canvas = document.getElementById(`led-preview-canvas-${targetId}`); const canvas = document.getElementById(`led-preview-canvas-${targetId}`);
if (canvas) _renderLedStrip(canvas, frame); 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';
}
}
} }
}; };