feat: new value source types (HA entity, gradient map, strip extract) + UI fixes
Lint & Test / test (push) Successful in 1m27s
Lint & Test / test (push) Successful in 1m27s
New value source types: - ha_entity: reads numeric values from HA entity state/attribute, normalizes via min/max range, applies EMA smoothing. EntitySelect for HA connection and entity selection with live entity list fetching. - gradient_map: maps a float value source (0-1) through a gradient entity. EntitySelect for both input source and gradient with inline previews. - css_extract: extracts single color by averaging LED range from a color strip source. EntitySelect for source selection. Value source type picker: - Filter tabs (All / Numeric / Color) above the icon grid - showTypePicker extended with filterTabs + onFilterChange support Palette selectors converted to EntitySelect: - Effect palette, gradient preset, and audio palette selectors now use command-palette style EntitySelect with gradient strip previews Tab indicator fixes: - Icon now updates on tab switch (was passing no args to updateTabIndicator) - Visible with any background effect active, not just Noise Field - Noise Field is the default background effect for new users Dashboard section collapse fix: - Split header into clickable toggle (chevron+label) and non-clickable actions area — buttons no longer trigger collapse/expand Discriminated union fix (422 errors): - source_type/target_type now always included in update payloads for: CSS editor, LED target, HA light target, simple calibration, advanced calibration
This commit is contained in:
@@ -1,38 +1,231 @@
|
||||
# BindableFloat — Universal Value Source Binding
|
||||
# New Value Source Types + Filter Support
|
||||
|
||||
## ALL PHASES COMPLETE
|
||||
## Feature 1: HA Value Source (`ha_entity`)
|
||||
|
||||
### Phase 1: Core Infrastructure
|
||||
A value source that reads numeric values from a Home Assistant entity's state or attribute. Allows binding any scalar property in the system to a live HA sensor/entity value.
|
||||
|
||||
- [x] `storage/bindable.py` — BindableFloat dataclass + `bfloat()` extraction helper
|
||||
- [x] WledOutputTarget, HALightOutputTarget, HALightMapping — brightness/transition
|
||||
- [x] All 15 CSS source types — smoothing, sensitivity, intensity, scale, speed, etc.
|
||||
- [x] API schemas + routes updated
|
||||
- [x] output_target_store create/update
|
||||
- [x] processor_manager add_target / add_ha_light_target
|
||||
### Configuration
|
||||
- `ha_source_id: str` — HA connection entity (EntitySelect picker)
|
||||
- `entity_id: str` — HA entity (EntitySelect picker, populated from `/api/v1/home-assistant/sources/{id}/entities`)
|
||||
- `attribute: str` — optional attribute name (text input or dropdown populated from entity attributes)
|
||||
- `min_ha_value: float` — raw HA value corresponding to output 0.0
|
||||
- `max_ha_value: float` — raw HA value corresponding to output 1.0
|
||||
- `smoothing: float` — EMA smoothing factor (0..1)
|
||||
- `return_type: "float"` — always float
|
||||
|
||||
### Phase 2: Runtime Resolution
|
||||
### Backend
|
||||
|
||||
- [x] WledTargetProcessor — BindableFloat brightness, acquire/release value streams
|
||||
- [x] HALightTargetProcessor — BindableFloat brightness + transition
|
||||
- [x] All CSS streams use `bfloat()` to extract static values from BindableFloat properties
|
||||
- [x] scene_activator — brightness_changed flag
|
||||
- [x] ColorStripStream base class — `resolve()`, `set_value_stream()`, `remove_value_stream()`
|
||||
- [x] ColorStripStreamManager — `_bind_value_streams()` / `_release_value_streams()` on acquire/release
|
||||
- [x] All stream hot loops call `self.resolve(prop, static)` for dynamic runtime binding
|
||||
- [x] KeyColorsColorStripStream — fixed to inherit from ColorStripStream
|
||||
- [ ] **Storage model** — `HAEntityValueSource` subclass in `storage/value_source.py`
|
||||
- Fields: `ha_source_id`, `entity_id`, `attribute`, `min_ha_value`, `max_ha_value`, `smoothing`
|
||||
- Register in `_VALUE_SOURCE_MAP` as `"ha_entity"`
|
||||
- `to_dict()` / `from_dict()` / `_parse_common_fields()`
|
||||
|
||||
### Phase 3: Frontend
|
||||
- [ ] **Store** — add `"ha_entity"` case in `ValueSourceStore.create_source()` and `update_source()`
|
||||
- Validate: `ha_source_id` must be non-empty, `entity_id` must be non-empty
|
||||
|
||||
- [x] TypeScript BindableFloat type + `bindableValue()` / `bindableSourceId()` helpers
|
||||
- [x] targets.ts, ha-light-targets.ts, color-strips.ts — save/load/display
|
||||
- [x] Graph connections — value source edges for ALL bindable CSS properties
|
||||
- [x] Graph layout — edge creation for CSS + target bindable properties
|
||||
- [x] custom_components/select.py — HA integration backward compat
|
||||
- [ ] **API schemas** — `HAEntityValueSourceCreate`, `HAEntityValueSourceResponse` in `api/schemas/value_sources.py`
|
||||
- Add to `ValueSourceCreate` / `ValueSourceResponse` discriminated unions
|
||||
- Fields: `ha_source_id`, `entity_id`, `attribute` (optional), `min_ha_value`, `max_ha_value`, `smoothing`
|
||||
|
||||
### Phase 4: BindableScalarWidget
|
||||
- [ ] **API routes** — add `HAEntityValueSource` → response builder in `_RESPONSE_MAP`
|
||||
|
||||
- [x] `core/bindable-scalar.ts` — reusable widget (slider + VS picker toggle)
|
||||
- [x] CSS styles (`.bindable-toggle`, `.bindable-slider-row`, `.bindable-vs-row`)
|
||||
- [x] All 11 CSS editor sliders converted (smoothing, sensitivity, intensity, scale, speed, wind, temp_influence, timeout)
|
||||
- [x] HTML templates updated with container divs
|
||||
- [ ] **Stream** — `HAEntityValueStream` in `core/processing/value_stream.py`
|
||||
- `start()`: acquire HA runtime via `ha_manager.acquire(ha_source_id)`
|
||||
- `get_value()`: read `ha_manager.get_state(ha_source_id, entity_id)` → extract state or attribute → clamp/normalize to [0,1] via min/max range → apply EMA smoothing
|
||||
- `stop()`: release HA runtime
|
||||
- `update_source()`: hot-update parameters
|
||||
- Add to `ValueStreamManager._create_stream()`
|
||||
|
||||
### Frontend
|
||||
|
||||
- [ ] **TypeScript type** — `HAEntityValueSource` interface in `types.ts`
|
||||
- `source_type: 'ha_entity'`, `return_type: 'float'`
|
||||
- Fields: `ha_source_id`, `entity_id`, `attribute`, `min_ha_value`, `max_ha_value`, `smoothing`
|
||||
- Add to `ValueSourceType` union and `ValueSource` union
|
||||
|
||||
- [ ] **Icon** — add `ha_entity: _svg(P.home)` to `_valueSourceTypeIcons` in `icons.ts`
|
||||
|
||||
- [ ] **i18n** — add keys in `en.json`:
|
||||
- `value_source.type.ha_entity`: "Home Assistant Entity"
|
||||
- `value_source.type.ha_entity.desc`: "Reads value from a Home Assistant sensor or entity attribute"
|
||||
- `value_source.ha_source`: "HA Connection:"
|
||||
- `value_source.entity_id`: "Entity:"
|
||||
- `value_source.attribute`: "Attribute (optional):"
|
||||
- `value_source.min_ha_value`: "Min HA Value:"
|
||||
- `value_source.max_ha_value`: "Max HA Value:"
|
||||
|
||||
- [ ] **Editor modal** — add `ha_entity` section to `value-source-editor.html`
|
||||
- HA connection selector (EntitySelect from HA sources cache)
|
||||
- Entity selector (EntitySelect populated from HA entities endpoint)
|
||||
- Attribute text input (optional)
|
||||
- Min/Max HA value range inputs
|
||||
- Smoothing slider
|
||||
|
||||
- [ ] **Editor logic** — add `ha_entity` handler in `value-sources.ts`
|
||||
- `onValueSourceTypeChange()`: show/hide ha_entity section
|
||||
- `_typeHandlers['ha_entity']`: load/reset/getPayload
|
||||
- EntitySelect for HA source + EntitySelect for entity (refreshes when HA source changes)
|
||||
- Auto-name: "{entity_friendly_name}" or "{entity_id}"
|
||||
|
||||
- [ ] **Card renderer** — show HA source link + entity ID + attribute (if set) + range
|
||||
- [ ] **VS_TYPE_KEYS** — add `'ha_entity'` to the array
|
||||
|
||||
---
|
||||
|
||||
## Feature 2: Lerp Color Value Source (`gradient_map`)
|
||||
|
||||
A color value source that maps a numeric value source's output through a color gradient. Given a float value source (0..1), interpolates the color at that position in a user-defined gradient.
|
||||
|
||||
### Configuration
|
||||
- `value_source_id: str` — reference to a float-returning value source (EntitySelect)
|
||||
- `stops: List[ColorStop]` — gradient color stops `[{position: float, color: [R,G,B]}]` (reuse existing `ColorStop` model from color strip sources)
|
||||
- `easing: str` — interpolation mode: "linear", "step" (reuse existing easing modes)
|
||||
- `return_type: "color"` — always color
|
||||
|
||||
### Backend
|
||||
|
||||
- [ ] **Storage model** — `GradientMapValueSource` subclass in `storage/value_source.py`
|
||||
- Fields: `value_source_id`, `stops` (list of dicts with `position` + `color`), `easing`
|
||||
- Register in `_VALUE_SOURCE_MAP` as `"gradient_map"`
|
||||
|
||||
- [ ] **Store** — add `"gradient_map"` case in `create_source()` / `update_source()`
|
||||
- Validate: at least 2 stops, `value_source_id` non-empty
|
||||
|
||||
- [ ] **API schemas** — `GradientMapValueSourceCreate`, `GradientMapValueSourceResponse`
|
||||
- Reuse `ColorStop` schema from color_strip_sources schemas (or define minimal version)
|
||||
- Add to discriminated unions
|
||||
|
||||
- [ ] **API routes** — add to `_RESPONSE_MAP`
|
||||
|
||||
- [ ] **Stream** — `GradientMapValueStream` in `value_stream.py`
|
||||
- `start()`: acquire the referenced value stream via `ValueStreamManager.acquire(value_source_id)`
|
||||
- `get_value()`: return BT.601 luminance of current color
|
||||
- `get_color()`: call `inner_stream.get_value()` → interpolate through gradient stops → return RGB tuple
|
||||
- Reuse `_compute_gradient_colors()` logic from color_strip_stream.py (or a shared helper for single-point interpolation)
|
||||
- `stop()`: release inner value stream
|
||||
- `update_source()`: hot-update stops/easing, re-acquire if value_source_id changed
|
||||
|
||||
### Frontend
|
||||
|
||||
- [ ] **TypeScript type** — `GradientMapValueSource` interface
|
||||
- `source_type: 'gradient_map'`, `return_type: 'color'`
|
||||
- Fields: `value_source_id`, `stops: ColorStop[]`, `easing`
|
||||
|
||||
- [ ] **Icon** — add `gradient_map: _svg(P.rainbow)` to `_valueSourceTypeIcons`
|
||||
|
||||
- [ ] **i18n** — add keys:
|
||||
- `value_source.type.gradient_map`: "Gradient Map"
|
||||
- `value_source.type.gradient_map.desc`: "Maps a numeric value through a color gradient"
|
||||
- `value_source.input_source`: "Input Value Source:"
|
||||
- `value_source.gradient_stops`: "Gradient:"
|
||||
- `value_source.easing`: "Interpolation:"
|
||||
|
||||
- [ ] **Editor modal** — add `gradient_map` section
|
||||
- Value source selector (EntitySelect from float value sources)
|
||||
- Gradient stop editor (reuse gradient stop UI from CSS editor if possible, or build minimal version: list of position + color picker rows)
|
||||
- Easing selector (IconSelect: linear, step)
|
||||
- Live gradient preview bar (CSS linear-gradient from stops)
|
||||
|
||||
- [ ] **Editor logic** — `_typeHandlers['gradient_map']`: load/reset/getPayload
|
||||
- [ ] **Card renderer** — CSS gradient preview bar + input source link + stop count
|
||||
- [ ] **VS_TYPE_KEYS** — add `'gradient_map'`
|
||||
|
||||
---
|
||||
|
||||
## Feature 3: CSS Extraction Color Value Source (`css_extract`)
|
||||
|
||||
A color value source that extracts a single color from a color strip source by averaging a range of LEDs. Useful for deriving a single color signal from an existing color strip.
|
||||
|
||||
### Configuration
|
||||
- `color_strip_source_id: str` — reference to a color strip source (EntitySelect)
|
||||
- `led_start: int` — start of LED range (0-based, optional, default 0)
|
||||
- `led_end: int` — end of LED range (exclusive, optional, default -1 = whole strip)
|
||||
- `return_type: "color"` — always color
|
||||
|
||||
### Backend
|
||||
|
||||
- [ ] **Storage model** — `CSSExtractValueSource` subclass in `storage/value_source.py`
|
||||
- Fields: `color_strip_source_id`, `led_start`, `led_end`
|
||||
- Register as `"css_extract"`
|
||||
|
||||
- [ ] **Store** — add `"css_extract"` case in `create_source()` / `update_source()`
|
||||
- Validate: `color_strip_source_id` non-empty
|
||||
|
||||
- [ ] **API schemas** — `CSSExtractValueSourceCreate`, `CSSExtractValueSourceResponse`
|
||||
- Add to discriminated unions
|
||||
|
||||
- [ ] **API routes** — add to `_RESPONSE_MAP`
|
||||
|
||||
- [ ] **Stream** — `CSSExtractValueStream` in `value_stream.py`
|
||||
- `start()`: acquire color strip stream via `ColorStripStreamManager.acquire(color_strip_source_id, led_count=needed)`
|
||||
- `get_color()`: read strip colors → average the specified LED range → return single RGB tuple
|
||||
- `get_value()`: BT.601 luminance of extracted color
|
||||
- `stop()`: release color strip stream
|
||||
- `update_source()`: hot-update range, re-acquire if source changed
|
||||
- **Note**: Needs access to `ColorStripStreamManager` — may need to inject it into `ValueStreamManager` or pass via constructor
|
||||
|
||||
### Frontend
|
||||
|
||||
- [ ] **TypeScript type** — `CSSExtractValueSource` interface
|
||||
- `source_type: 'css_extract'`, `return_type: 'color'`
|
||||
- Fields: `color_strip_source_id`, `led_start`, `led_end`
|
||||
|
||||
- [ ] **Icon** — add `css_extract: _svg(P.eyedropper)` (or `P.pipette` if available, else `P.palette`)
|
||||
|
||||
- [ ] **i18n** — add keys:
|
||||
- `value_source.type.css_extract`: "Strip Color Extract"
|
||||
- `value_source.type.css_extract.desc`: "Extracts a single color from a color strip source"
|
||||
- `value_source.color_strip_source`: "Color Strip Source:"
|
||||
- `value_source.led_start`: "LED Start:"
|
||||
- `value_source.led_end`: "LED End (-1 = all):"
|
||||
|
||||
- [ ] **Editor modal** — add `css_extract` section
|
||||
- Color strip source selector (EntitySelect from color strip sources cache)
|
||||
- LED start/end numeric inputs
|
||||
- Optional: live color preview swatch
|
||||
|
||||
- [ ] **Editor logic** — `_typeHandlers['css_extract']`: load/reset/getPayload
|
||||
- [ ] **Card renderer** — color strip source link + LED range badge
|
||||
- [ ] **VS_TYPE_KEYS** — add `'css_extract'`
|
||||
|
||||
---
|
||||
|
||||
## Feature 4: Value Source Type Filter in Icon Grid
|
||||
|
||||
Add a filter/category system to the value source type IconSelect so users can filter by return type or category.
|
||||
|
||||
### Implementation
|
||||
|
||||
- [ ] **Add filter tabs** above the value source type icon grid in the editor modal
|
||||
- "All" (default) — show all types
|
||||
- "Float" — show float-returning types: static, animated, audio, adaptive_time, adaptive_scene, daylight, ha_entity
|
||||
- "Color" — show color-returning types: static_color, animated_color, adaptive_time_color, gradient_map, css_extract
|
||||
|
||||
- [ ] **IconSelect enhancement** — either:
|
||||
- Option A: Add `groups` support to IconSelect (items grouped by category with filter tabs)
|
||||
- Option B: Filter `VS_TYPE_KEYS` before building items, with toggle buttons above the grid
|
||||
- Decision: Option B is simpler and follows existing patterns — add filter buttons that rebuild the icon grid
|
||||
|
||||
- [ ] **i18n** — add keys:
|
||||
- `value_source.filter.all`: "All"
|
||||
- `value_source.filter.float`: "Float"
|
||||
- `value_source.filter.color`: "Color"
|
||||
|
||||
---
|
||||
|
||||
## Implementation Order
|
||||
|
||||
1. **Feature 4** (filter) — smallest, unblocks better UX for the growing type list
|
||||
2. **Feature 1** (ha_entity) — standalone float type, no cross-dependencies
|
||||
3. **Feature 3** (css_extract) — needs ColorStripStreamManager injection
|
||||
4. **Feature 2** (gradient_map) — needs float VS reference + gradient UI
|
||||
|
||||
## Cross-Cutting Concerns
|
||||
|
||||
- All new types need entries in `_VALUE_SOURCE_MAP` (backend) and `VS_TYPE_KEYS` (frontend)
|
||||
- All new types need `_RESPONSE_MAP` entries in routes
|
||||
- All new types need `ValueStreamManager._create_stream()` factory case
|
||||
- All new types need icon in `_valueSourceTypeIcons`
|
||||
- All new types need i18n keys in `en.json` (and `ru.json`, `zh.json` — can defer translations)
|
||||
- `ValueSourceStore` referential integrity check on delete should verify new references (ha_entity → ha_source, gradient_map → value_source, css_extract → color_strip_source)
|
||||
- Graph editor: new edge types for ha_entity → HA source node, gradient_map → value source node, css_extract → color strip node
|
||||
|
||||
Reference in New Issue
Block a user