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:
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user