Commit Graph

28 Commits

Author SHA1 Message Date
8a0730d91b Remove idle color feature, simplify power to turn-off only, fix settings serial port bug
- Remove static/idle color from entire stack (storage, API, processing, UI, CSS, locales)
- Simplify device power button to turn-off only (send black frame, no toggle)
- Send black frame on serial port close (AdalightClient.close)
- Fix settings modal serial port dropdown showing WLED devices due to stale deviceType

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 04:04:28 +03:00
1f6c913343 Move FPS from color strip source to target; dynamic capture rate
FPS is a consumption property (how fast to send to a device), not a
production property. Two targets sharing the same source may need
different FPS. This moves the fps field from PictureColorStripSource
to WledPictureTarget across the full stack.

The capture stream now auto-adjusts its rate to max(all connected
target FPS values) via ColorStripStreamManager tracking per-consumer
FPS. UI updates: FPS slider in target editor, FPS badge on target
cards, LED count repositioned in CSS editor, consistent speed icons.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 03:46:08 +03:00
1204676c30 Fix serial send bloat when sharing CSS stream with higher-LED device
When two targets share the same color strip source, configure() resizes
the stream to the last caller's LED count. If a WLED device (934 LEDs)
starts after a serial device (fewer LEDs), the serial target sends the
full 934-LED frame over serial, massively inflating send time.

Add _fit_to_device() to truncate/tile colors to the target's actual
device LED count before sending, so each consumer only transmits what
its device needs regardless of the shared stream's current size.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 03:12:45 +03:00
67d141b75b Show pipeline timing breakdown for non-picture source targets
Non-picture sources (static, gradient, color_cycle) returned empty
timing from get_last_timing(), causing timing_total_ms to be null and
hiding the entire timing section in the UI. Now timing_total_ms falls
back to send_ms when no CSS pipeline timing exists. Frontend timing
bar/legend segments are conditionally rendered to avoid null labels.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 02:55:02 +03:00
7c0c064453 Fix FPS drops caused by brightness endpoint polling WLED device
The GET /devices/{id}/brightness endpoint was making an HTTP request to
the ESP32 over WiFi on every frontend poll (~3s), causing 150ms async
event loop jitter that froze the LED processing loop. Cache brightness
server-side after first fetch/set, add frontend dedup guard, reduce
get_device_info() frequency, and add processing loop timing diagnostics.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 02:43:03 +03:00
55a9662234 Add animation effects + double-buffered FPS optimization
- Add 5 new animation effects (strobe, sparkle, pulse, candle, rainbow
  fade) to both static and gradient color strip streams
- Fix FPS drops (30→25) by using 5ms re-poll on frame skip instead of
  full frame_time, preventing synchronization misses between animation
  thread and processing loop
- Double-buffer animation output arrays to eliminate per-frame numpy
  allocations and reduce GC pressure
- Use uint16 integer math for gradient brightness scaling instead of
  float32 intermediates
- Update animation type dropdowns and locale strings (en + ru)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 01:57:43 +03:00
84f063eee9 WGC capture fixes + high-resolution timer pacing for all loops
- Fix WGC capture_frame() returning stale frames (80k "frames" in 2s)
  by tracking new-frame events; return None when no new frame arrived
- Add draw_border config passthrough with Win11 22H2+ platform check
- Add high_resolution_timer() utility (timeBeginPeriod/EndPeriod)
- Switch all processing loops from time.time() to time.perf_counter()
- Wrap all loops with high_resolution_timer() for ~1ms sleep precision
- Add animation speed badges to static/gradient color strip cards

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 01:23:56 +03:00
1604855935 Fix ColorCycleColorStripStream not auto-sizing to device LED count
configure() was only called for Static and Gradient streams, leaving
ColorCycle at its default led_count=1 — all other LEDs sent as black.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 22:16:48 +03:00
c31818a20d Add color_cycle as standalone source type; UI polish
- color_cycle is now a top-level source type (alongside picture/static/gradient)
  with a configurable color list and cycle_speed; defaults to full rainbow spectrum
- ColorCycleColorStripSource + ColorCycleColorStripStream: smooth 30 fps interpolation
  between user-defined colors, one full cycle every 20s at speed=1.0
- Removed color_cycle animation sub-type from StaticColorStripStream
- Color cycle editor: compact horizontal swatch layout, proper module-scope fix
  (colorCycleAdd/Remove now exposed on window, DOM-synced before mutations)
- Animation enabled + Frame interpolation checkboxes use toggle-switch style
- Removed Potential FPS metric from targets and KC targets metric grids

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 22:14:42 +03:00
872949a7e1 Add frame interpolation postprocessing filter + KC hot-settings
Frame interpolation filter (frame_interpolation):
- New PostprocessingFilter with supports_idle_frames = True
- Backward-blend algorithm: blends frame N-1 → N over one capture
  interval, producing smooth output on idle ticks at ≤1 frame of lag
- Detects new vs idle frames via cheap 64-byte signature comparison
- No options; registered alongside other built-in filters

ProcessedLiveStream idle-tick support:
- Detects supports_idle_frames filters at construction (_has_idle_filters)
- target_fps returns 2× source rate when idle filters are present
- _process_loop runs at 2× rate; idle ticks copy cached source frame
  and run full filter chain, publishing result only when a filter
  returned actual interpolated output (not a pass-through)
- Pass-through idle ticks leave _latest_frame unchanged so consumers
  correctly deduplicate via object identity

KC target hot-settings:
- brightness, smoothing, interpolation_mode now read from self._settings
  each frame instead of captured as stale locals at loop startup
- Changes take effect within one frame without stop/restart

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 21:01:45 +03:00
55e25b8860 Frame interpolation, FPS hot-update, timing metrics, KC brightness fixes
- CSS: add frame interpolation option — blends between consecutive captured
  frames on idle ticks so LED output runs at full target FPS even when
  capture rate is lower (e.g. capture 30fps, output 60fps)
- WledTargetProcessor: re-read stream.target_fps each loop tick so FPS
  changes to the CSS source take effect without restarting the target
- WledTargetProcessor: restore per-stage timing metrics on target card by
  pulling extract/map/smooth/total from CSS stream get_last_timing()
- TargetProcessingState schema: add missing timing_extract_ms,
  timing_map_leds_ms, timing_smooth_ms, timing_total_ms fields
- KC targets: add extraction FPS badge to target card props row
- KC targets: fix 500 error when changing brightness — update_fields now
  accepts (and ignores) WLED-specific kwargs
- KC targets: fix partial key_colors_settings update wiping pattern_template_id
  — update route merges only explicitly-set fields using model_dump(exclude_unset=True)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 20:29:22 +03:00
7479b1fb8d CSS: add GradientColorStripSource with visual editor
- Backend: GradientColorStripSource storage model, GradientColorStripStream
  with numpy interpolation (bidirectional stops, auto-size from device LED count),
  ColorStop Pydantic schema, API create/update/guard routes
- Frontend: gradient editor modal (canvas preview, draggable markers, stop rows),
  CSS hard-edge card swatch, locale keys (en + ru)
- Fixes: stop row mousedown no longer rebuilds DOM (buttons now clickable),
  position input max-width, bidir/remove button static width

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 19:35:41 +03:00
2a8e2daefc CSS: add StaticColorStripSource type with auto-sized LED count
Introduces a new 'static' source type that fills all device LEDs with a
single constant RGB color — no screen capture or processing required.

- StaticColorStripSource storage model (color + led_count=0 auto-size)
- StaticColorStripStream: no background thread, configure() sizes to device
  LED count at processor start; hot-updates preserve runtime size
- ColorStripStreamManager dispatches static sources (no LiveStream needed)
- WledTargetProcessor calls stream.configure(device_led_count) on start
- API schemas/routes: source_type Literal["picture","static"]; color field;
  overlay/calibration-test endpoints return 400 for static
- Frontend: type selector modal, color picker, type-aware card rendering
  (🎨 icon + color swatch), LED count field hidden for static type
- Locale keys: color_strip.type, color_strip.static_color (en + ru)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 17:49:48 +03:00
018bedf9f6 Overlay: fix 404, crash on repeat, missing edge test colors, device reset on stop
- Target overlay works without active processing: route pre-loads calibration
  and display info from the CSS store, passes to processor as fallback
- Fix server crash on repeated overlay: replace per-window tk.Tk() with single
  persistent hidden root; each overlay is a Toplevel child dispatched via
  root.after() — eliminates Tcl interpreter crashes on Windows
- Fix edge test colors not lighting up: always call set_test_mode regardless
  of processing state (was guarded by 'not proc.is_running'); pass calibration
  so _send_test_pixels knows which LEDs map to which edges
- Fix device reset on overlay stop: keep idle serial client cached after
  clearing test mode; start_processing() already closes it before connecting

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 17:16:10 +03:00
a3aeafef13 CSS: add led_count field; calibration dialog improvements; color corrections collapsible section
- Add explicit led_count to PictureColorStripSource (0 = auto from calibration)
- Stream pads with black or truncates to match led_count exactly
- Calibration dialog: show led_count input above visual editor in CSS mode
- Calibration dialog: pre-populate led_count with effective count (cal sum) when stored value is 0
- Calibration dialog: sync preview label live as led_count input changes
- CSS editor: group brightness/saturation/gamma into collapsible "Color Corrections" section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 16:42:32 +03:00
7de3546b14 Introduce ColorStripSource as first-class entity
Extracts color processing and calibration out of WledPictureTarget into a
new PictureColorStripSource entity, enabling multiple LED targets to share
one capture/processing pipeline.

New entities & processing:
- storage/color_strip_source.py: ColorStripSource + PictureColorStripSource models
- storage/color_strip_store.py: JSON-backed CRUD store (prefix css_)
- core/processing/color_strip_stream.py: ColorStripStream ABC + PictureColorStripStream (runs border-extract → map → smooth → brightness/sat/gamma in background thread)
- core/processing/color_strip_stream_manager.py: ref-counted shared stream manager

Modified storage/processing:
- WledPictureTarget simplified to device_id + color_strip_source_id + standby_interval + state_check_interval
- Device model: calibration field removed
- WledTargetProcessor: acquires ColorStripStream from manager instead of running its own pipeline
- ProcessorManager: wires ColorStripStreamManager into TargetContext

API layer:
- New routes: GET/POST/PUT/DELETE /api/v1/color-strip-sources, PUT calibration/test
- Removed calibration endpoints from /devices
- Updated /picture-targets CRUD for new target structure

Frontend:
- New color-strips.js module with CSS editor modal and card rendering
- Calibration modal extended with CSS mode (css-id hidden field + device picker)
- targets.js: Color Strip Sources section added to LED tab; target editor/card updated
- app.js: imports and window globals for CSS + showCSSCalibration
- en.json / ru.json: color_strip.* and targets.section.color_strips keys added

Data migration runs at startup: existing WledPictureTargets are converted to
reference a new PictureColorStripSource created from their old settings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 15:49:47 +03:00
c4e0257389 Polymorphism Phase 2 + remove unused gamma/saturation fields
ProcessorManager: replace all isinstance checks with property-based
dispatch via base TargetProcessor (device_id, led_client,
get_display_index, update_device, update_calibration).

Remove gamma/saturation from ProcessingSettings, ColorCorrection
schema, serialization, and migration — these were never used in the
processing pipeline and are handled by postprocessing template filters.
Delete dead apply_color_correction() function.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 02:34:03 +03:00
fbf597dc29 Optimize streaming pipeline and capture hot paths
- Replace asyncio.to_thread with dedicated ThreadPoolExecutor (skip
  per-frame context copy overhead)
- Move brightness scaling into _process_frame thread (avoid extra
  numpy array copies on event loop)
- Remove PIL intermediate in MSS capture (direct bytes→numpy)
- Unify median/dominant pixel mapping to numpy arrays (eliminate
  Python list-of-tuples path and duplicate Phase 2/3 code)
- Cache CalibrationConfig.segments property (avoid ~240 rebuilds/sec)
- Make KC WebSocket broadcasts concurrent via asyncio.gather
- Fix fps_samples list.pop(0) → deque(maxlen=10) in both processors
- Cache time.time() calls to reduce redundant syscalls per frame
- Log event queue drops instead of silently discarding

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 22:55:21 +03:00
ff4e054ef8 Show edge test colors on LED device when overlay is active
Lights up device LEDs with calibration edge colors (top=red, right=green,
bottom=blue, left=yellow) when the overlay is started, and clears them when
the overlay is stopped. Skips if the target is currently processing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 14:39:37 +03:00
bef28ece5c Add static color support, HAOS light entity, and real-time profile updates
- Add static_color capability to WLED and serial providers with native
  set_color() dispatch (WLED uses JSON API, serial uses idle client)
- Encapsulate device-specific logic in providers instead of device_type
  checks in ProcessorManager and API routes
- Add HAOS light entity for devices with brightness_control + static_color
  (Adalight/AmbiLED get light entity, WLED keeps number entity)
- Fix serial device brightness and turn-off: pass software_brightness
  through provider chain, clear device on color=null, re-send static
  color after brightness change
- Add global events WebSocket (events-ws.js) replacing per-tab WS,
  enabling real-time profile state updates on both dashboard and profiles tabs
- Fix profile activation: mark active when all targets already running,
  add asyncio.Lock to prevent concurrent evaluation races, skip process
  enumeration when no profile has conditions, trigger immediate evaluation
  on enable/create/update for instant target startup
- Add reliable server restart script (restart.ps1)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 14:23:47 +03:00
f83cd81937 Extract SerialDeviceProvider base class and power off serial devices on shutdown
Create SerialDeviceProvider as the common base for Adalight and AmbiLED
providers, replacing the misleading Adalight→AmbiLED inheritance chain.
Subclasses now only override device_type and create_client(). Also send
explicit black frames to all serial LED devices during server shutdown.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 03:04:27 +03:00
ef925ad0a9 Fix Adalight power toggle using cached idle client and tracked state
Serial devices now route power on/off through the cached idle client
instead of opening a new serial connection (which caused PermissionError).
Adds tracked power_on state to DeviceState since Adalight has no
hardware power query.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 02:26:57 +03:00
46be9922bd Add brightness control to Key Colors targets with HAOS integration
Adds brightness (0.0-1.0) to KeyColorsSettings, applies scaling in the
KC processing pipeline after smoothing, exposes a slider on the WebUI
target card, and creates a HA number entity for KC target brightness.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 02:26:46 +03:00
390f71ebae Cache idle LED clients to avoid repeated Arduino resets
Adalight devices trigger a ~3s bootloader reset on every serial
connection open. Add a persistent idle client cache in ProcessorManager
so calibration test toggles, static color changes, and auto-restore
reuse an existing connection instead of creating a new one each time.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 00:09:55 +03:00
6e973965b1 Fix calibration side test not applying LED offset rotation
The test mode pixel array was built from segment positions but never
rotated by the configured offset, causing LEDs to light up on the
wrong side. Apply the same offset rotation used in the normal
rendering pipeline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 21:22:00 +03:00
29d9b95885 Add profile system for automatic target activation
Profiles monitor running processes and foreground windows to
automatically start/stop targets when conditions are met.
Includes profile engine, platform detector (WMI), REST API,
process browser endpoint, and calibration persistence fix.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 15:12:34 +03:00
d6cf45c873 Add static color for simple devices, change auto-shutdown to auto-restore
- Add `static_color` capability to Adalight provider with `set_color()` method
- Add `static_color` field to Device model, DeviceState, and API schemas
- Add GET/PUT `/devices/{id}/color` API endpoints
- Change auto-shutdown behavior: restore device to idle state instead of
  powering off (WLED uses snapshot/restore, Adalight sends static color
  or black frame)
- Rename `_auto_shutdown_device_if_idle` to `_restore_device_idle_state`
- Add inline color picker on device cards for devices with static_color
- Add auto_shutdown toggle to device settings modal
- Update labels from "Auto Shutdown" to "Auto Restore" (en + ru)
- Remove backward-compat KC aliases from ProcessorManager
- Align card action buttons to bottom with flex column layout

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 13:42:05 +03:00
fc779eef39 Refactor core/ into logical sub-packages and split filter files
Reorganize the flat core/ directory (17 files) into three sub-packages:
- core/devices/ — LED device communication (led_client, wled/adalight clients, providers, DDP)
- core/processing/ — target processing pipeline (processor_manager, target processors, live streams, settings)
- core/capture/ — screen capture & calibration (screen_capture, calibration, pixel_processor, overlay)

Also split the monolithic filters/builtin.py (460 lines, 8 filters) into
individual files: brightness, saturation, gamma, downscaler, pixelate,
auto_crop, flip, color_correction.

Includes the ProcessorManager refactor from target-centric architecture:
ProcessorManager slimmed from ~1600 to ~490 lines with unified
_processors dict replacing duplicate _targets/_kc_targets dicts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 12:03:29 +03:00