v2.1.0 — Persistent WebSocket & .qxf Fixture Parser
Highlights
Persistent WebSocket Architecture — The control server now maintains a single long-lived WebSocket to QLC+ on a dedicated background asyncio loop. This eliminates the CLOSE_WAIT socket leak that previously caused QLC+ to stop accepting connections after ~50 requests, requiring manual restarts.
.qxf Fixture Definition Parser — Channel roles (dimmer, warm, cool, amber, strobe, macro, etc.) are now resolved from the authoritative QLC+ fixture definition files on disk instead of being guessed from fixture name + channel count. This means the AI and the UI both know exactly what each channel does on every fixture.
Enriched AI Scene Generation — The LLM prompt now includes full per-channel metadata (name, role, preset, group, colour) for every fixture. The system prompt teaches the AI to pick channels by semantic role, never write to configuration-only channels, and correctly mix warm/cool/amber on fixtures that lack RGB (like the SlimPAR Pro W).
What's New
Control Server (control-server/)
- Single persistent WebSocket on a dedicated asyncio thread — no more per-request connect/close cycle
- Explicit
ws.close()on connection drop prevents CLOSE_WAIT accumulation - Health check (
/api/status) now reports the persistent connection's live state instead of opening a fresh TCP probe - New
fixture_definitions.pymodule: parses all.qxffiles from/usr/share/qlcplus/fixtures/and~/.qlcplus/fixtures/ GET /api/fixturesnow includeschannel_infoper fixture (name, role, preset, group, colour, offset)GET /api/fixture_channels/<id>— per-fixture channel breakdown endpointPOST /api/fixture_definitions/reload— force cache rebuildapply_color_live()zeros all non-color/non-dimmer channels on absolute color sets (prevents strobe/macro bleed-through)- Fade uses the persistent connection (3s fade now takes 3s, was 33s before)
debug=Falsein production (no stat reloader doubling connections)
AI Scene Generation (scripts/lib/)
- New
extract_fixtures.py— emits enriched fixture JSON using the.qxfparser ai_extract_fixtures()now calls the Python helper for authoritative channel info- Completely rewritten system prompt with channel-role semantics, concrete examples, and explicit rules about strobe/macro channels
- Fallback heuristic updated: Pro W correctly identified as
warm_cool_amber(was falsely claiming RGB)
UI (control-server/templates/index.html)
- Channel labels sourced from
channel_infoAPI (authoritative .qxf names) - SlimPAR Pro W correctly shows: Dimmer, Warm White, Cool White, Amber, Color Macro, Strobe, Auto Programs, Auto Speed, Dimmer Speed Mode
- SlimPAR Pro H correctly shows: Dimmer, Red, Green, Blue, Amber, White, UV
- Each slider gets
data-roleattribute for future color-coding
WiFi
- Added River's Way network at priority 200 (highest) via NetworkManager
- Verified persistence through reboot (netplan-backed)
Documentation
- New
docs/CONTROL_SERVER_ARCHITECTURE.md— full architecture doc - Updated
.kiro/steering/files (product, structure, tech) - README: expanded architecture diagram, refreshed project structure, added AI config table, updated completed features list
- GitHub repo: updated description and added 10 topic tags
Bug Fixes
- Fixed CLOSE_WAIT socket leak that wedged QLC+ after ~50 WebSocket operations
- Fixed fade taking 33s instead of 3s (was opening a new WS per step)
- Fixed status endpoint falsely reporting "timed out" while commands worked
- Fixed Pro W triggering strobe when AI generated "warm" scenes (wrong channel mapping)
- Fixed Pro H channel labels (was showing Color Macros/Strobe instead of Amber/White/UV in 7-Ch mode)
- Fixed
apply_color_liveleaving stale macro/strobe values from previous scenes - Allowed mDNS (port 5353/UDP) through firewall in harden script
Breaking Changes
None. All CLI commands, API endpoints, and configuration variables are backward-compatible with v2.0.0.