Add DDP protocol support, fix event loop blocking, and add LED offset calibration
Some checks failed
Validate / validate (push) Failing after 8s

- Add DDP client for LED strips >500 LEDs (UDP port 4048), with automatic
  fallback from HTTP JSON API when LED count exceeds limit
- Wrap blocking operations (screen capture, image processing) in
  asyncio.to_thread() to prevent event loop starvation
- Turn on WLED device and enable live mode when starting DDP streaming
- Add LED strip offset field to calibration (rotates color array to match
  physical LED position vs start corner)
- Add server management scripts (start, stop, restart, background start)
- Fix WebUI auth error handling and auto-refresh loop
- Add development API key to default config
- Add i18n translations for offset field (en/ru)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-07 12:44:06 +03:00
parent ec3c40d59c
commit 579821a69b
15 changed files with 504 additions and 48 deletions

View File

@@ -2,6 +2,9 @@ const API_BASE = '/api/v1';
let refreshInterval = null;
let apiKey = null;
// Track logged errors to avoid console spam
const loggedErrors = new Map(); // deviceId -> { errorCount, lastError }
// Locale management
let currentLocale = 'en';
let translations = {};
@@ -181,6 +184,12 @@ function handle401Error() {
localStorage.removeItem('wled_api_key');
apiKey = null;
// Stop auto-refresh to prevent repeated 401 errors
if (refreshInterval) {
clearInterval(refreshInterval);
refreshInterval = null;
}
if (typeof updateAuthUI === 'function') {
updateAuthUI();
}
@@ -392,8 +401,14 @@ async function loadDevices() {
});
const metrics = await metricsResponse.json();
// Log device info, especially if there are errors
if (metrics.errors_count > 0) {
// Log device errors only when they change (avoid console spam)
const deviceKey = device.id;
const lastLogged = loggedErrors.get(deviceKey);
const hasNewErrors = !lastLogged ||
lastLogged.errorCount !== metrics.errors_count ||
lastLogged.lastError !== metrics.last_error;
if (metrics.errors_count > 0 && hasNewErrors) {
console.warn(`[Device: ${device.name || device.id}] Has ${metrics.errors_count} error(s)`);
// Log recent errors from state
@@ -412,6 +427,16 @@ async function loadDevices() {
// Log full state and metrics for debugging
console.log('Full state:', state);
console.log('Full metrics:', metrics);
// Update tracking
loggedErrors.set(deviceKey, {
errorCount: metrics.errors_count,
lastError: metrics.last_error
});
} else if (metrics.errors_count === 0 && lastLogged) {
// Clear tracking when errors are resolved
console.log(`[Device: ${device.name || device.id}] Errors cleared`);
loggedErrors.delete(deviceKey);
}
return { ...device, state, metrics };
@@ -763,7 +788,10 @@ function startAutoRefresh() {
}
refreshInterval = setInterval(() => {
loadDevices();
// Only refresh if user is authenticated
if (apiKey) {
loadDevices();
}
}, 2000); // Refresh every 2 seconds
}
@@ -840,6 +868,7 @@ async function showCalibration(deviceId) {
// Set layout
document.getElementById('cal-start-position').value = calibration.start_position;
document.getElementById('cal-layout').value = calibration.layout;
document.getElementById('cal-offset').value = calibration.offset || 0;
// Set LED counts per edge
const edgeCounts = { top: 0, right: 0, bottom: 0, left: 0 };
@@ -984,9 +1013,12 @@ async function saveCalibration() {
}
});
const offset = parseInt(document.getElementById('cal-offset').value || 0);
const calibration = {
layout: layout,
start_position: startPosition,
offset: offset,
segments: segments
};