Add OpenRGB per-zone LED control with separate/combined modes and zone preview
- Zone picker UI in device add/settings modals with per-zone checkbox selection - Combined mode: pixels distributed sequentially across zones - Separate mode: full effect resampled independently to each zone via linear interpolation - Per-zone LED preview in target cards: one canvas strip per zone with hover overlay labels - Zone badges on device cards enriched with actual LED counts from OpenRGB API - Fix stale led_count by using device_led_count discovered at connect time Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -58,6 +58,8 @@ class DeviceState:
|
||||
test_calibration: Optional[CalibrationConfig] = None
|
||||
# Tracked power state for serial devices (no hardware query)
|
||||
power_on: bool = True
|
||||
# OpenRGB zone mode: "combined" or "separate"
|
||||
zone_mode: str = "combined"
|
||||
|
||||
|
||||
class ProcessorManager:
|
||||
@@ -160,6 +162,7 @@ class ProcessorManager:
|
||||
test_mode_active=ds.test_mode_active,
|
||||
send_latency_ms=send_latency_ms,
|
||||
rgbw=rgbw,
|
||||
zone_mode=ds.zone_mode,
|
||||
)
|
||||
|
||||
# ===== EVENT SYSTEM (state change notifications) =====
|
||||
@@ -200,6 +203,7 @@ class ProcessorManager:
|
||||
baud_rate: Optional[int] = None,
|
||||
software_brightness: int = 255,
|
||||
auto_shutdown: bool = False,
|
||||
zone_mode: str = "combined",
|
||||
):
|
||||
"""Register a device for health monitoring."""
|
||||
if device_id in self._devices:
|
||||
@@ -213,6 +217,7 @@ class ProcessorManager:
|
||||
baud_rate=baud_rate,
|
||||
software_brightness=software_brightness,
|
||||
auto_shutdown=auto_shutdown,
|
||||
zone_mode=zone_mode,
|
||||
)
|
||||
|
||||
self._devices[device_id] = state
|
||||
|
||||
@@ -74,6 +74,7 @@ class DeviceInfo:
|
||||
test_mode_active: bool = False
|
||||
send_latency_ms: int = 0
|
||||
rgbw: bool = False
|
||||
zone_mode: str = "combined"
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
@@ -65,6 +65,7 @@ class WledTargetProcessor(TargetProcessor):
|
||||
self._overlay_active = False
|
||||
self._needs_keepalive = True
|
||||
|
||||
self._effective_led_count: int = 0
|
||||
self._resolved_display_index: Optional[int] = None
|
||||
|
||||
# Fit-to-device linspace cache (per-instance to avoid cross-target thrash)
|
||||
@@ -106,11 +107,24 @@ class WledTargetProcessor(TargetProcessor):
|
||||
baud_rate=device_info.baud_rate,
|
||||
send_latency_ms=device_info.send_latency_ms,
|
||||
rgbw=device_info.rgbw,
|
||||
zone_mode=device_info.zone_mode,
|
||||
)
|
||||
await self._led_client.connect()
|
||||
|
||||
# Use client-reported LED count if available (more accurate than stored)
|
||||
client_led_count = self._led_client.device_led_count
|
||||
effective_led_count = client_led_count if client_led_count and client_led_count > 0 else device_info.led_count
|
||||
self._effective_led_count = effective_led_count
|
||||
|
||||
if effective_led_count != device_info.led_count:
|
||||
logger.info(
|
||||
f"Target {self._target_id}: device reports {effective_led_count} LEDs "
|
||||
f"(stored: {device_info.led_count}), using actual count"
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Target {self._target_id} connected to {device_info.device_type} "
|
||||
f"device ({device_info.led_count} LEDs)"
|
||||
f"device ({effective_led_count} LEDs)"
|
||||
)
|
||||
self._device_state_before = await self._led_client.snapshot_device_state()
|
||||
self._needs_keepalive = "standby_required" in get_device_capabilities(device_info.device_type)
|
||||
@@ -132,8 +146,8 @@ class WledTargetProcessor(TargetProcessor):
|
||||
|
||||
try:
|
||||
stream = await asyncio.to_thread(css_manager.acquire, self._css_id, self._target_id)
|
||||
if hasattr(stream, "configure") and device_info.led_count > 0:
|
||||
stream.configure(device_info.led_count)
|
||||
if hasattr(stream, "configure") and self._effective_led_count > 0:
|
||||
stream.configure(self._effective_led_count)
|
||||
css_manager.notify_target_fps(self._css_id, self._target_id, self._target_fps)
|
||||
|
||||
self._resolved_display_index = getattr(stream, "display_index", None)
|
||||
@@ -254,7 +268,7 @@ class WledTargetProcessor(TargetProcessor):
|
||||
return
|
||||
|
||||
device_info = self._ctx.get_device_info(self._device_id)
|
||||
device_leds = device_info.led_count if device_info else 0
|
||||
device_leds = getattr(self, '_effective_led_count', None) or (device_info.led_count if device_info else 0)
|
||||
|
||||
# Release old stream
|
||||
if self._css_stream is not None and old_css_id:
|
||||
@@ -533,7 +547,7 @@ class WledTargetProcessor(TargetProcessor):
|
||||
prev_frame_time_stamp = time.perf_counter()
|
||||
loop = asyncio.get_running_loop()
|
||||
_init_device_info = self._ctx.get_device_info(self._device_id)
|
||||
_total_leds = _init_device_info.led_count if _init_device_info else 0
|
||||
_total_leds = getattr(self, '_effective_led_count', None) or (_init_device_info.led_count if _init_device_info else 0)
|
||||
|
||||
# Stream reference — re-read each tick to detect hot-swaps
|
||||
stream = self._css_stream
|
||||
|
||||
Reference in New Issue
Block a user