Add dynamic brightness value source support for KC targets, fix subtab selector collision
Extend value source brightness modulation to Key Colors targets (matching LED target support). Also fix stream subtab CSS selector collision that broke target subtab selection, and use 🔢 emoji for value source UI elements. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -96,9 +96,11 @@ class KCTargetProcessor(TargetProcessor):
|
||||
):
|
||||
super().__init__(target_id, ctx, picture_source_id)
|
||||
self._settings = settings
|
||||
self._brightness_vs_id = settings.brightness_value_source_id if settings else ""
|
||||
|
||||
# Runtime state
|
||||
self._live_stream: Optional[LiveStream] = None
|
||||
self._value_stream = None # active brightness value stream
|
||||
self._previous_colors: Optional[Dict[str, Tuple[int, int, int]]] = None
|
||||
self._latest_colors: Optional[Dict[str, Tuple[int, int, int]]] = None
|
||||
self._ws_clients: List = []
|
||||
@@ -156,6 +158,16 @@ class KCTargetProcessor(TargetProcessor):
|
||||
logger.error(f"Failed to initialize live stream for KC target {self._target_id}: {e}")
|
||||
raise RuntimeError(f"Failed to initialize live stream: {e}")
|
||||
|
||||
# Acquire value stream for brightness modulation (if configured)
|
||||
if self._brightness_vs_id and self._ctx.value_stream_manager:
|
||||
try:
|
||||
self._value_stream = self._ctx.value_stream_manager.acquire(
|
||||
self._brightness_vs_id, self._target_id
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to acquire value stream {self._brightness_vs_id}: {e}")
|
||||
self._value_stream = None
|
||||
|
||||
# Reset metrics
|
||||
self._metrics = ProcessingMetrics(start_time=datetime.utcnow())
|
||||
self._previous_colors = None
|
||||
@@ -192,6 +204,14 @@ class KCTargetProcessor(TargetProcessor):
|
||||
logger.warning(f"Error releasing live stream for KC target: {e}")
|
||||
self._live_stream = None
|
||||
|
||||
# Release value stream
|
||||
if self._value_stream is not None and self._ctx.value_stream_manager:
|
||||
try:
|
||||
self._ctx.value_stream_manager.release(self._brightness_vs_id, self._target_id)
|
||||
except Exception as e:
|
||||
logger.warning(f"Error releasing value stream: {e}")
|
||||
self._value_stream = None
|
||||
|
||||
logger.info(f"Stopped KC processing for target {self._target_id}")
|
||||
self._ctx.fire_event({"type": "state_change", "target_id": self._target_id, "processing": False})
|
||||
|
||||
@@ -199,8 +219,37 @@ class KCTargetProcessor(TargetProcessor):
|
||||
|
||||
def update_settings(self, settings) -> None:
|
||||
self._settings = settings
|
||||
# Keep _brightness_vs_id in sync (hot-swap handled separately)
|
||||
self._brightness_vs_id = settings.brightness_value_source_id if settings else ""
|
||||
logger.info(f"Updated KC target settings: {self._target_id}")
|
||||
|
||||
def update_brightness_value_source(self, vs_id: str) -> None:
|
||||
"""Hot-swap the brightness value source for a running KC target."""
|
||||
old_vs_id = self._brightness_vs_id
|
||||
self._brightness_vs_id = vs_id
|
||||
vs_mgr = self._ctx.value_stream_manager
|
||||
|
||||
if not self._is_running or vs_mgr is None:
|
||||
return
|
||||
|
||||
# Release old stream
|
||||
if self._value_stream is not None and old_vs_id:
|
||||
try:
|
||||
vs_mgr.release(old_vs_id, self._target_id)
|
||||
except Exception as e:
|
||||
logger.warning(f"Error releasing old value stream {old_vs_id}: {e}")
|
||||
self._value_stream = None
|
||||
|
||||
# Acquire new stream
|
||||
if vs_id:
|
||||
try:
|
||||
self._value_stream = vs_mgr.acquire(vs_id, self._target_id)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to acquire value stream {vs_id}: {e}")
|
||||
self._value_stream = None
|
||||
|
||||
logger.info(f"Hot-swapped brightness VS for KC target {self._target_id}: {old_vs_id} -> {vs_id}")
|
||||
|
||||
# ----- State / Metrics -----
|
||||
|
||||
def get_state(self) -> dict:
|
||||
@@ -220,6 +269,7 @@ class KCTargetProcessor(TargetProcessor):
|
||||
"timing_total_ms": round(metrics.timing_total_ms, 1) if self._is_running else None,
|
||||
"last_update": metrics.last_update,
|
||||
"errors": [metrics.last_error] if metrics.last_error else [],
|
||||
"brightness_value_source_id": self._brightness_vs_id,
|
||||
}
|
||||
|
||||
def get_metrics(self) -> dict:
|
||||
@@ -333,11 +383,17 @@ class KCTargetProcessor(TargetProcessor):
|
||||
s = self._settings
|
||||
calc_fn = calc_fns.get(s.interpolation_mode, calculate_average_color)
|
||||
|
||||
# Effective brightness: static setting * value stream
|
||||
eff_brightness = s.brightness
|
||||
vs = self._value_stream
|
||||
if vs is not None:
|
||||
eff_brightness *= vs.get_value()
|
||||
|
||||
# CPU-bound work in thread pool
|
||||
colors, colors_arr, frame_timing = await asyncio.to_thread(
|
||||
_process_kc_frame,
|
||||
capture, rect_names, rect_bounds, calc_fn,
|
||||
prev_colors_arr, s.smoothing, s.brightness,
|
||||
prev_colors_arr, s.smoothing, eff_brightness,
|
||||
)
|
||||
|
||||
prev_colors_arr = colors_arr
|
||||
|
||||
Reference in New Issue
Block a user