WS now sends color RGB data for color-type streams. Test modal renders a color history swatch strip below the chart, colors the chart line and fill area with the current output color, and shows rgb() in the stats. Works for static_color, animated_color, and adaptive_time_color sources.
This commit is contained in:
@@ -47,6 +47,7 @@ from wled_controller.storage.value_source import (
|
||||
from wled_controller.storage.value_source_store import ValueSourceStore
|
||||
from wled_controller.storage.output_target_store import OutputTargetStore
|
||||
from wled_controller.core.processing.processor_manager import ProcessorManager
|
||||
from wled_controller.core.processing.value_stream import ValueStream
|
||||
from wled_controller.utils import get_logger
|
||||
from wled_controller.storage.base_store import EntityNotFoundError
|
||||
|
||||
@@ -381,10 +382,21 @@ async def test_value_source_ws(
|
||||
await websocket.accept()
|
||||
logger.info(f"Value source test WebSocket connected for {source_id}")
|
||||
|
||||
# Detect if this stream produces colors
|
||||
_is_color_stream = (
|
||||
hasattr(stream, "get_color") and type(stream).get_color is not ValueStream.get_color
|
||||
)
|
||||
|
||||
try:
|
||||
while True:
|
||||
value = stream.get_value()
|
||||
msg: dict = {"value": round(value, 4)}
|
||||
if _is_color_stream:
|
||||
try:
|
||||
r, g, b = stream.get_color()
|
||||
msg["color"] = [int(r), int(g), int(b)]
|
||||
except NotImplementedError:
|
||||
pass
|
||||
if hasattr(stream, "get_raw_value"):
|
||||
raw = stream.get_raw_value()
|
||||
if raw is not None:
|
||||
|
||||
@@ -82,6 +82,17 @@
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.vs-test-color-swatch {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.vs-test-color-canvas {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.vs-test-stats {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
|
||||
@@ -760,6 +760,8 @@ let _testVsMinObserved = Infinity;
|
||||
let _testVsMaxObserved = -Infinity;
|
||||
let _testVsRawLatest: number | null = null;
|
||||
let _testVsRawRange: [number, number] | null = null;
|
||||
let _testVsColorLatest: [number, number, number] | null = null;
|
||||
let _testVsColorHistory: [number, number, number][] = [];
|
||||
|
||||
const testVsModal = new Modal('test-value-source-modal', { backdrop: true, lock: true });
|
||||
|
||||
@@ -777,11 +779,17 @@ export function testValueSource(sourceId: any) {
|
||||
_testVsMaxObserved = -Infinity;
|
||||
_testVsRawLatest = null;
|
||||
_testVsRawRange = null;
|
||||
_testVsColorLatest = null;
|
||||
_testVsColorHistory = [];
|
||||
|
||||
// Hide color swatch until color data arrives
|
||||
const swatchEl = document.getElementById('vs-test-color-swatch');
|
||||
if (swatchEl) swatchEl.style.display = 'none';
|
||||
|
||||
const currentEl = document.getElementById('vs-test-current');
|
||||
const minEl = document.getElementById('vs-test-min');
|
||||
const maxEl = document.getElementById('vs-test-max');
|
||||
if (currentEl) currentEl.textContent = '---';
|
||||
if (currentEl) { currentEl.textContent = '---'; currentEl.style.color = ''; }
|
||||
if (minEl) minEl.textContent = '---';
|
||||
if (maxEl) maxEl.textContent = '---';
|
||||
|
||||
@@ -816,6 +824,13 @@ export function testValueSource(sourceId: any) {
|
||||
_testVsRawLatest = data.raw_value;
|
||||
if (data.raw_range) _testVsRawRange = data.raw_range;
|
||||
}
|
||||
if (data.color) {
|
||||
_testVsColorLatest = data.color;
|
||||
_testVsColorHistory.push(data.color);
|
||||
if (_testVsColorHistory.length > VS_HISTORY_SIZE) {
|
||||
_testVsColorHistory.shift();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Value source test WS parse error:', e);
|
||||
return;
|
||||
@@ -869,6 +884,7 @@ function _sizeVsCanvas(canvas: HTMLCanvasElement) {
|
||||
|
||||
function _renderVsTestLoop() {
|
||||
_renderVsChart();
|
||||
_renderVsColorSwatch();
|
||||
if (testVsModal.isOpen) {
|
||||
_testVsAnimFrame = requestAnimationFrame(_renderVsTestLoop);
|
||||
}
|
||||
@@ -923,7 +939,9 @@ function _renderVsChart() {
|
||||
}
|
||||
ctx.lineTo((startOffset + history.length - 1) * stepX, h);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = 'rgba(76, 175, 80, 0.15)';
|
||||
ctx.fillStyle = _testVsColorLatest
|
||||
? `rgba(${_testVsColorLatest[0]},${_testVsColorLatest[1]},${_testVsColorLatest[2]},0.15)`
|
||||
: 'rgba(76, 175, 80, 0.15)';
|
||||
ctx.fill();
|
||||
|
||||
// Draw the line
|
||||
@@ -934,7 +952,10 @@ function _renderVsChart() {
|
||||
if (i === 0) ctx.moveTo(x, y);
|
||||
else ctx.lineTo(x, y);
|
||||
}
|
||||
ctx.strokeStyle = '#4caf50';
|
||||
const lineColor = _testVsColorLatest
|
||||
? `rgb(${_testVsColorLatest[0]},${_testVsColorLatest[1]},${_testVsColorLatest[2]})`
|
||||
: '#4caf50';
|
||||
ctx.strokeStyle = lineColor;
|
||||
ctx.lineWidth = 2;
|
||||
ctx.stroke();
|
||||
|
||||
@@ -993,6 +1014,51 @@ function _fmtRaw(v: number): string {
|
||||
return Number.isInteger(v) ? String(v) : v.toFixed(1);
|
||||
}
|
||||
|
||||
function _renderVsColorSwatch() {
|
||||
const swatchEl = document.getElementById('vs-test-color-swatch');
|
||||
const canvas = document.getElementById('vs-test-color-canvas') as HTMLCanvasElement | null;
|
||||
if (!canvas || !swatchEl) return;
|
||||
|
||||
if (_testVsColorHistory.length === 0) {
|
||||
swatchEl.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
swatchEl.style.display = '';
|
||||
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
const rect = canvas.parentElement!.getBoundingClientRect();
|
||||
const w = rect.width;
|
||||
const h = 32;
|
||||
canvas.width = w * dpr;
|
||||
canvas.height = h * dpr;
|
||||
canvas.style.height = h + 'px';
|
||||
|
||||
const ctx = canvas.getContext('2d')!;
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
|
||||
const colors = _testVsColorHistory;
|
||||
const stepX = w / VS_HISTORY_SIZE;
|
||||
const barW = Math.max(stepX, 1);
|
||||
const startOffset = VS_HISTORY_SIZE - colors.length;
|
||||
|
||||
for (let i = 0; i < colors.length; i++) {
|
||||
const [r, g, b] = colors[i];
|
||||
ctx.fillStyle = `rgb(${r},${g},${b})`;
|
||||
ctx.fillRect((startOffset + i) * stepX, 0, barW + 0.5, h);
|
||||
}
|
||||
|
||||
// Update current color stat display
|
||||
if (_testVsColorLatest) {
|
||||
const [r, g, b] = _testVsColorLatest;
|
||||
const curEl = document.getElementById('vs-test-current');
|
||||
if (curEl) {
|
||||
curEl.textContent = `rgb(${r}, ${g}, ${b})`;
|
||||
curEl.style.color = `rgb(${r},${g},${b})`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Card rendering (used by streams.js) ───────────────────────
|
||||
|
||||
export function createValueSourceCard(src: ValueSource) {
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<canvas id="vs-test-canvas" class="vs-test-canvas"></canvas>
|
||||
<div id="vs-test-color-swatch" class="vs-test-color-swatch" style="display:none">
|
||||
<canvas id="vs-test-color-canvas" class="vs-test-color-canvas"></canvas>
|
||||
</div>
|
||||
<div class="vs-test-stats">
|
||||
<span class="vs-test-stat vs-test-stat-current">
|
||||
<span class="vs-test-stat-label" data-i18n="value_source.test.current">Current</span>
|
||||
|
||||
Reference in New Issue
Block a user