Codebase review: stability, performance, usability, and i18n fixes
Stability: - Fix race condition: set _is_running before create_task in target processors - Await probe task after cancel in wled_target_processor - Replace raw fetch() with fetchWithAuth() across devices, kc-targets, pattern-templates - Add try/catch to showTestTemplateModal in streams.js - Wrap blocking I/O in asyncio.to_thread (picture_targets, system restore) - Fix dashboardStopAll to filter only running targets with ok guard Performance: - Vectorize fire effect spark loop with numpy in effect_stream - Vectorize FFT band binning with cumulative sum in analysis.py - Rewrite pixel_processor with vectorized numpy (accept ndarray or list) - Add httpx.AsyncClient connection pooling with lock in wled_provider - Optimize _send_pixels_http to avoid np.hstack allocation in wled_client - Mutate chart arrays in-place in dashboard, perf-charts, targets - Merge dashboard 2-batch fetch into single Promise.all - Hoist frame_time outside loop in mapped_stream Usability: - Fix health check interval load/save in device settings - Swap confirm modal button classes (No=secondary, Yes=danger) - Add aria-modal to audio/value source editors, fix close button aria-labels - Add modal footer close button to settings modal - Add dedicated calibration LED count validation error keys i18n: - Replace ~50 hardcoded English strings with t() calls across 12 JS files - Add 50 new keys to en.json, ru.json, zh.json - Localize inline toasts in index.html with window.t fallback - Add data-i18n to command palette footer - Add localization policy to CLAUDE.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -404,8 +404,13 @@ class WLEDClient(LEDClient):
|
||||
"""
|
||||
try:
|
||||
# Build indexed pixel array: [led_index, r, g, b, ...]
|
||||
indices = np.arange(len(pixels), dtype=np.int32).reshape(-1, 1)
|
||||
indexed_pixels = np.hstack([indices, pixels.astype(np.int32)]).ravel().tolist()
|
||||
n = len(pixels)
|
||||
flat = np.empty(n * 4, dtype=np.int32)
|
||||
flat[0::4] = np.arange(n, dtype=np.int32)
|
||||
flat[1::4] = pixels[:, 0]
|
||||
flat[2::4] = pixels[:, 1]
|
||||
flat[3::4] = pixels[:, 2]
|
||||
indexed_pixels = flat.tolist()
|
||||
|
||||
# Build WLED JSON state
|
||||
payload = {
|
||||
|
||||
@@ -24,6 +24,25 @@ DEFAULT_SCAN_TIMEOUT = 3.0
|
||||
class WLEDDeviceProvider(LEDDeviceProvider):
|
||||
"""Provider for WLED LED controllers."""
|
||||
|
||||
def __init__(self):
|
||||
self._http_client: Optional[httpx.AsyncClient] = None
|
||||
self._client_lock = asyncio.Lock()
|
||||
|
||||
async def _get_client(self) -> httpx.AsyncClient:
|
||||
"""Return a shared HTTP client (connection-pooled, thread-safe init)."""
|
||||
if self._http_client is not None and not self._http_client.is_closed:
|
||||
return self._http_client
|
||||
async with self._client_lock:
|
||||
if self._http_client is None or self._http_client.is_closed:
|
||||
self._http_client = httpx.AsyncClient(timeout=5.0)
|
||||
return self._http_client
|
||||
|
||||
async def close(self):
|
||||
"""Close the shared HTTP client."""
|
||||
if self._http_client is not None and not self._http_client.is_closed:
|
||||
await self._http_client.aclose()
|
||||
self._http_client = None
|
||||
|
||||
@property
|
||||
def device_type(self) -> str:
|
||||
return "wled"
|
||||
@@ -158,46 +177,46 @@ class WLEDDeviceProvider(LEDDeviceProvider):
|
||||
|
||||
async def get_brightness(self, url: str) -> int:
|
||||
url = url.rstrip("/")
|
||||
async with httpx.AsyncClient(timeout=5.0) as http_client:
|
||||
resp = await http_client.get(f"{url}/json/state")
|
||||
resp.raise_for_status()
|
||||
state = resp.json()
|
||||
return state.get("bri", 255)
|
||||
client = await self._get_client()
|
||||
resp = await client.get(f"{url}/json/state")
|
||||
resp.raise_for_status()
|
||||
state = resp.json()
|
||||
return state.get("bri", 255)
|
||||
|
||||
async def set_brightness(self, url: str, brightness: int) -> None:
|
||||
url = url.rstrip("/")
|
||||
async with httpx.AsyncClient(timeout=5.0) as http_client:
|
||||
resp = await http_client.post(
|
||||
f"{url}/json/state",
|
||||
json={"bri": brightness},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
client = await self._get_client()
|
||||
resp = await client.post(
|
||||
f"{url}/json/state",
|
||||
json={"bri": brightness},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
|
||||
async def get_power(self, url: str, **kwargs) -> bool:
|
||||
url = url.rstrip("/")
|
||||
async with httpx.AsyncClient(timeout=5.0) as http_client:
|
||||
resp = await http_client.get(f"{url}/json/state")
|
||||
resp.raise_for_status()
|
||||
return resp.json().get("on", False)
|
||||
client = await self._get_client()
|
||||
resp = await client.get(f"{url}/json/state")
|
||||
resp.raise_for_status()
|
||||
return resp.json().get("on", False)
|
||||
|
||||
async def set_power(self, url: str, on: bool, **kwargs) -> None:
|
||||
url = url.rstrip("/")
|
||||
async with httpx.AsyncClient(timeout=5.0) as http_client:
|
||||
resp = await http_client.post(
|
||||
f"{url}/json/state",
|
||||
json={"on": on},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
client = await self._get_client()
|
||||
resp = await client.post(
|
||||
f"{url}/json/state",
|
||||
json={"on": on},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
|
||||
async def set_color(self, url: str, color: Tuple[int, int, int], **kwargs) -> None:
|
||||
"""Set WLED to a solid color using the native segment color API."""
|
||||
url = url.rstrip("/")
|
||||
async with httpx.AsyncClient(timeout=5.0) as http_client:
|
||||
resp = await http_client.post(
|
||||
f"{url}/json/state",
|
||||
json={
|
||||
"on": True,
|
||||
"seg": [{"col": [[color[0], color[1], color[2]]], "fx": 0}],
|
||||
},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
client = await self._get_client()
|
||||
resp = await client.post(
|
||||
f"{url}/json/state",
|
||||
json={
|
||||
"on": True,
|
||||
"seg": [{"col": [[color[0], color[1], color[2]]], "fx": 0}],
|
||||
},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
|
||||
Reference in New Issue
Block a user