Skip to content

v2.1.0 — Persistent WebSocket & .qxf Fixture Parser

Choose a tag to compare

@gfargo gfargo released this 14 May 22:39
· 51 commits to main since this release

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.py module: parses all .qxf files from /usr/share/qlcplus/fixtures/ and ~/.qlcplus/fixtures/
  • GET /api/fixtures now includes channel_info per fixture (name, role, preset, group, colour, offset)
  • GET /api/fixture_channels/<id> — per-fixture channel breakdown endpoint
  • POST /api/fixture_definitions/reload — force cache rebuild
  • apply_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=False in production (no stat reloader doubling connections)

AI Scene Generation (scripts/lib/)

  • New extract_fixtures.py — emits enriched fixture JSON using the .qxf parser
  • 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_info API (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-role attribute 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_live leaving 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.