diff --git a/server/src/wled_controller/api/routes/value_sources.py b/server/src/wled_controller/api/routes/value_sources.py index 5fd2579..285a037 100644 --- a/server/src/wled_controller/api/routes/value_sources.py +++ b/server/src/wled_controller/api/routes/value_sources.py @@ -397,6 +397,8 @@ async def test_value_source_ws( msg["color"] = [int(r), int(g), int(b)] except NotImplementedError: pass + if hasattr(stream, "get_input_value"): + msg["input_value"] = round(stream.get_input_value(), 4) if hasattr(stream, "get_raw_value"): raw = stream.get_raw_value() if raw is not None: diff --git a/server/src/wled_controller/core/processing/value_stream.py b/server/src/wled_controller/core/processing/value_stream.py index 54b8710..38b8a72 100644 --- a/server/src/wled_controller/core/processing/value_stream.py +++ b/server/src/wled_controller/core/processing/value_stream.py @@ -939,6 +939,7 @@ class GradientMapValueStream(ValueStream): self._gradient_store = gradient_store self._inner_stream: Optional[ValueStream] = None self._stops: list = [] + self._input_value: float = 0.0 self._resolve_gradient() def _resolve_gradient(self) -> None: @@ -979,11 +980,16 @@ class GradientMapValueStream(ValueStream): r, g, b = self.get_color() return (0.299 * r + 0.587 * g + 0.114 * b) / 255.0 + def get_input_value(self) -> float: + """Return the last input interpolation factor (0.0–1.0).""" + return self._input_value + def get_color(self) -> tuple: if self._inner_stream is None: return (128, 128, 128) t = max(0.0, min(1.0, self._inner_stream.get_value())) + self._input_value = t stops = self._stops if not stops: return (128, 128, 128) diff --git a/server/src/wled_controller/static/js/features/value-sources.ts b/server/src/wled_controller/static/js/features/value-sources.ts index 2c47cee..6a923e8 100644 --- a/server/src/wled_controller/static/js/features/value-sources.ts +++ b/server/src/wled_controller/static/js/features/value-sources.ts @@ -813,13 +813,16 @@ export function testValueSource(sourceId: any) { _testVsWs.onmessage = (event) => { try { const data = JSON.parse(event.data); - _testVsLatest = data.value; - _testVsHistory.push(data.value); + // For color sources with an input value (e.g. gradient_map), + // chart the input interpolation factor instead of luminance + const chartValue = data.input_value !== undefined ? data.input_value : data.value; + _testVsLatest = chartValue; + _testVsHistory.push(chartValue); if (_testVsHistory.length > VS_HISTORY_SIZE) { _testVsHistory.shift(); } - if (data.value < _testVsMinObserved) _testVsMinObserved = data.value; - if (data.value > _testVsMaxObserved) _testVsMaxObserved = data.value; + if (chartValue < _testVsMinObserved) _testVsMinObserved = chartValue; + if (chartValue > _testVsMaxObserved) _testVsMaxObserved = chartValue; if (data.raw_value !== undefined) { _testVsRawLatest = data.raw_value; if (data.raw_range) _testVsRawRange = data.raw_range; @@ -1134,8 +1137,11 @@ export function createValueSourceCard(src: ValueSource) { const haName = haSrc ? haSrc.name : ((src as any).ha_source_id || '-'); const entityId = (src as any).entity_id || ''; const attr = (src as any).attribute; + const haBadge = haSrc + ? `${ICON_HOME} ${escapeHtml(haName)}` + : `${ICON_HOME} ${escapeHtml(haName)}`; propsHtml = ` - ${ICON_HOME} ${escapeHtml(haName)} + ${haBadge} ${ICON_LINK} ${escapeHtml(entityId)}${attr ? '.' + escapeHtml(attr) : ''} ${ICON_MOVE_VERTICAL} ${(src as any).min_ha_value ?? 0}\u2013${(src as any).max_ha_value ?? 100} `; @@ -1149,9 +1155,15 @@ export function createValueSourceCard(src: ValueSource) { const gradientCss = stops.length >= 2 ? `linear-gradient(to right, ${stops.map((s: any) => `rgb(${s.color.join(',')}) ${(s.position * 100).toFixed(0)}%`).join(', ')})` : '#333'; + const inputBadge = inputVs + ? `${ICON_LINK} ${escapeHtml(inputName)}` + : `${ICON_LINK} ${escapeHtml(inputName)}`; + const gradBadge = grad + ? `${ICON_RAINBOW} ${escapeHtml(gradName)}` + : `${ICON_RAINBOW} ${escapeHtml(gradName)}`; propsHtml = ` - ${ICON_LINK} ${escapeHtml(inputName)} - ${ICON_RAINBOW} ${escapeHtml(gradName)} + ${inputBadge} + ${gradBadge}
`; } else if (src.source_type === 'css_extract') { @@ -1160,8 +1172,11 @@ export function createValueSourceCard(src: ValueSource) { const ledStart = (src as any).led_start ?? 0; const ledEnd = (src as any).led_end ?? -1; const rangeLabel = ledEnd < 0 ? `${ledStart}\u2013all` : `${ledStart}\u2013${ledEnd}`; + const cssBadge = cssSrc + ? `${ICON_DROPLETS} ${escapeHtml(cssName)}` + : `${ICON_DROPLETS} ${escapeHtml(cssName)}`; propsHtml = ` - ${ICON_DROPLETS} ${escapeHtml(cssName)} + ${cssBadge} ${ICON_MOVE_VERTICAL} LED ${rangeLabel} `; } diff --git a/server/src/wled_controller/static/locales/en.json b/server/src/wled_controller/static/locales/en.json index 04fee6d..340aa23 100644 --- a/server/src/wled_controller/static/locales/en.json +++ b/server/src/wled_controller/static/locales/en.json @@ -1463,6 +1463,9 @@ "value_source.type.gradient_map.desc": "Maps numeric value through a color gradient", "value_source.type.css_extract": "Strip Extract", "value_source.type.css_extract.desc": "Extracts color from a color strip source", + "value_source.gradient_map.input": "Input Value Source", + "value_source.gradient_map.gradient": "Gradient", + "value_source.css_extract.source": "Color Strip Source", "value_source.ha_source": "HA Connection:", "value_source.ha_source.hint": "Home Assistant connection to read entities from", "value_source.entity_id": "Entity:",