Visual brightness curve editor (Lovelace card)
Refs #147, #117.
Problem
The text-based 60:100 config works, but it doesn't scale. You can't see the curve shape until you save and test, you can't compare lights side by side, and the auto-interpolation is completely invisible to the user. Anyone with more than a handful of bulbs (see @fcassata's 16-bulb setup in #117) is flying blind.
Proposal
A custom Lovelace card — not a panel — that renders interactive brightness curves per Lightener device.
Card over panel because:
- No sidebar clutter, no panel registration overhead
- User decides where it lives (config tab, room view, wherever)
- Matches how other config-oriented cards ship (scheduler-card, mini-graph-card)
- Simpler HACS distribution
What this looks like
┌─────────────────────────────────────┐
│ Living Room ⚙️ │
│ │
│ 100% ┤ ●──────│ ← Ceiling LEDs
│ │ ╱ │
│ │ ╱ │
│ 50% ┤ ╱ │
│ │ ╱ ● ─ ─ ─ ─ ─ ─ ●──────│ ← Sofa lamp
│ │╱ │
│ 0% ┤● │
│ └──┬────┬────┬────┬────┬──────│
│ 0% 20% 40% 60% 80% 100% │
│ Group brightness → │
│ │
│ ● Ceiling LEDs ● Sofa lamp │
│ ● Main ceiling │
└─────────────────────────────────────┘
- Draggable control points per curve (tap + drag)
- All lights overlaid on one graph — the whole point is visual comparison
- Interpolation line redraws in real-time as you drag
- Touch-friendly (tablet dashboards)
- Curve hide/show toggles per light (legend click) to keep dense overlays readable
Curve model and edit rules
Matching Lightener's existing behavior:
- Interpolation: Piecewise-linear between defined points (same as today's auto-interpolation)
- Bounds: X-axis 1–100 (group %), Y-axis 0–100 (light %)
- Implicit endpoint:
100:100 is always present (Lightener adds this automatically); users can override it but not remove it
- Monotonic X required: No two points can share the same X value
- Add points: Tap/click on the curve line to insert a new control point
- Remove points: Double-tap or long-press a point to delete it (implicit endpoints can't be deleted)
- Snapping: Points snap to integer percentages (matching the
X:Y format's integer constraint)
- Drag constraints: Points can't be dragged past their neighbors on the X-axis
Data flow and permissions
The card does not write to config entries directly. Instead:
- Reads: Can use HA's existing
config_entries/get WS command to retrieve current curve data — no custom handler needed for reads
- Writes: Dedicated
lightener/save_curves WS command registered with @websocket_api.require_admin. The backend validates the curve (bounds, monotonic X, integer values), calls async_update_entry(), and triggers a config reload. Returns success or a structured error
- Non-admin users see the card as read-only — curves render but control points aren't draggable and Save is hidden
- Unsaved changes: Visual indicator (dot on Save button or similar) when the in-memory curve diverges from persisted config. Explicit Save/Cancel — no auto-persist on drag
Implementation
| Layer |
Stack |
Scope |
| Frontend |
Lit web component, SVG graph, vanilla pointer events |
~400 LOC TS |
| Data |
config_entries/get for reads; custom lightener/save_curves for validated writes |
— |
| Backend |
WS handler in __init__.py with @require_admin, validation, async_update_entry() |
~50 LOC Python |
| Build |
Rollup or rspack + TypeScript → single JS file |
HACS-ready |
No breaking changes. Text-based config flow stays untouched — the card is purely additive.
Prior art
Extensibility
Worth noting: @joshmwilliams' color calibration fork uses the same X:Y pattern for CCT/RGB mapping. The graph component would be directly reusable there — different axis labels, same interaction model.
Before I start prototyping
- Card vs. panel — any objections to the card approach?
- Separate repo (
lightener-card) or bundled into main integration?
- Any concerns about the WS command / admin-gating approach for writes?
- Anything else you'd want in v1 scope?
Happy to build this. Just want alignment before writing code.
cc @Salamandar @joshmwilliams
Visual brightness curve editor (Lovelace card)
Refs #147, #117.
Problem
The text-based
60:100config works, but it doesn't scale. You can't see the curve shape until you save and test, you can't compare lights side by side, and the auto-interpolation is completely invisible to the user. Anyone with more than a handful of bulbs (see @fcassata's 16-bulb setup in #117) is flying blind.Proposal
A custom Lovelace card — not a panel — that renders interactive brightness curves per Lightener device.
Card over panel because:
What this looks like
Curve model and edit rules
Matching Lightener's existing behavior:
100:100is always present (Lightener adds this automatically); users can override it but not remove itX:Yformat's integer constraint)Data flow and permissions
The card does not write to config entries directly. Instead:
config_entries/getWS command to retrieve current curve data — no custom handler needed for readslightener/save_curvesWS command registered with@websocket_api.require_admin. The backend validates the curve (bounds, monotonic X, integer values), callsasync_update_entry(), and triggers a config reload. Returns success or a structured errorImplementation
config_entries/getfor reads; customlightener/save_curvesfor validated writes__init__.pywith@require_admin, validation,async_update_entry()No breaking changes. Text-based config flow stays untouched — the card is purely additive.
Prior art
Extensibility
Worth noting: @joshmwilliams' color calibration fork uses the same
X:Ypattern for CCT/RGB mapping. The graph component would be directly reusable there — different axis labels, same interaction model.Before I start prototyping
lightener-card) or bundled into main integration?Happy to build this. Just want alignment before writing code.
cc @Salamandar @joshmwilliams