Fix numpy serialization and add comprehensive error logging
Some checks failed
Validate / validate (push) Failing after 9s
Some checks failed
Validate / validate (push) Failing after 9s
Server fixes: - Fix numpy uint8 JSON serialization by converting to Python int - Change WLED payload to flat array format [r,g,b,r,g,b,...] - Add payload debugging logs (size, LED count, sample data) Web UI improvements: - Add comprehensive console logging for device errors - Log actual error messages from state.errors array - Log device operations (start/stop/add) with details - Fix password field form validation warning The HTTP API may have payload size limitations for large LED counts. Consider UDP protocols (DDP/E1.31) for better real-time performance. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -73,6 +73,10 @@ After restarting the server with new code:
|
|||||||
2. **Restart the server**
|
2. **Restart the server**
|
||||||
3. Test with `GET /api/v1/config/displays`
|
3. Test with `GET /api/v1/config/displays`
|
||||||
|
|
||||||
|
### Modifying server login:
|
||||||
|
1. Update the logic.
|
||||||
|
2. **Restart the server**
|
||||||
|
|
||||||
### Adding translations:
|
### Adding translations:
|
||||||
1. Add keys to `static/locales/en.json` and `static/locales/ru.json`
|
1. Add keys to `static/locales/en.json` and `static/locales/ru.json`
|
||||||
2. Add `data-i18n` attributes to HTML elements in `static/index.html`
|
2. Add `data-i18n` attributes to HTML elements in `static/index.html`
|
||||||
|
|||||||
@@ -234,26 +234,35 @@ class WLEDClient:
|
|||||||
if not 0 <= brightness <= 255:
|
if not 0 <= brightness <= 255:
|
||||||
raise ValueError(f"Brightness must be 0-255, got {brightness}")
|
raise ValueError(f"Brightness must be 0-255, got {brightness}")
|
||||||
|
|
||||||
# Validate pixel values
|
# Validate and convert pixel values to Python ints (handles numpy types)
|
||||||
|
# WLED expects a flat array: [r1, g1, b1, r2, g2, b2, ...]
|
||||||
|
flat_pixels = []
|
||||||
for i, (r, g, b) in enumerate(pixels):
|
for i, (r, g, b) in enumerate(pixels):
|
||||||
if not (0 <= r <= 255 and 0 <= g <= 255 and 0 <= b <= 255):
|
if not (0 <= r <= 255 and 0 <= g <= 255 and 0 <= b <= 255):
|
||||||
raise ValueError(f"Invalid RGB values at index {i}: ({r}, {g}, {b})")
|
raise ValueError(f"Invalid RGB values at index {i}: ({r}, {g}, {b})")
|
||||||
|
# Convert to Python int and flatten to [r, g, b, r, g, b, ...]
|
||||||
|
flat_pixels.extend([int(r), int(g), int(b)])
|
||||||
|
|
||||||
# Build WLED JSON state
|
# Build WLED JSON state with flat pixel array
|
||||||
payload = {
|
payload = {
|
||||||
"on": True,
|
"on": True,
|
||||||
"bri": brightness,
|
"bri": int(brightness), # Ensure brightness is also a Python int
|
||||||
"seg": [
|
"seg": [
|
||||||
{
|
{
|
||||||
"id": segment_id,
|
"id": segment_id,
|
||||||
"i": pixels, # Individual LED colors
|
"i": flat_pixels, # Individual LED colors as flat array
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Log payload details for debugging
|
||||||
|
logger.debug(f"Sending {len(pixels)} LEDs ({len(flat_pixels)} values) to WLED")
|
||||||
|
logger.debug(f"Payload size: ~{len(str(payload))} bytes")
|
||||||
|
logger.debug(f"First 3 LEDs: {flat_pixels[:9] if len(flat_pixels) >= 9 else flat_pixels}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self._request("POST", "/json/state", json_data=payload)
|
await self._request("POST", "/json/state", json_data=payload)
|
||||||
logger.debug(f"Sent {len(pixels)} pixel colors to WLED device")
|
logger.debug(f"Successfully sent pixel colors to WLED device")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -392,6 +392,28 @@ async function loadDevices() {
|
|||||||
});
|
});
|
||||||
const metrics = await metricsResponse.json();
|
const metrics = await metricsResponse.json();
|
||||||
|
|
||||||
|
// Log device info, especially if there are errors
|
||||||
|
if (metrics.errors_count > 0) {
|
||||||
|
console.warn(`[Device: ${device.name || device.id}] Has ${metrics.errors_count} error(s)`);
|
||||||
|
|
||||||
|
// Log recent errors from state
|
||||||
|
if (state.errors && state.errors.length > 0) {
|
||||||
|
console.error('Recent errors:');
|
||||||
|
state.errors.forEach((error, index) => {
|
||||||
|
console.error(` ${index + 1}. ${error}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log last error from metrics
|
||||||
|
if (metrics.last_error) {
|
||||||
|
console.error('Last error:', metrics.last_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log full state and metrics for debugging
|
||||||
|
console.log('Full state:', state);
|
||||||
|
console.log('Full metrics:', metrics);
|
||||||
|
}
|
||||||
|
|
||||||
return { ...device, state, metrics };
|
return { ...device, state, metrics };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to load state for device ${device.id}:`, error);
|
console.error(`Failed to load state for device ${device.id}:`, error);
|
||||||
@@ -492,6 +514,7 @@ function attachDeviceListeners(deviceId) {
|
|||||||
|
|
||||||
// Device actions
|
// Device actions
|
||||||
async function startProcessing(deviceId) {
|
async function startProcessing(deviceId) {
|
||||||
|
console.log(`[Device: ${deviceId}] Starting processing...`);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE}/devices/${deviceId}/start`, {
|
const response = await fetch(`${API_BASE}/devices/${deviceId}/start`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -504,19 +527,22 @@ async function startProcessing(deviceId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
|
console.log(`[Device: ${deviceId}] Processing started successfully`);
|
||||||
showToast('Processing started', 'success');
|
showToast('Processing started', 'success');
|
||||||
loadDevices();
|
loadDevices();
|
||||||
} else {
|
} else {
|
||||||
const error = await response.json();
|
const error = await response.json();
|
||||||
|
console.error(`[Device: ${deviceId}] Failed to start:`, error);
|
||||||
showToast(`Failed to start: ${error.detail}`, 'error');
|
showToast(`Failed to start: ${error.detail}`, 'error');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to start processing:', error);
|
console.error(`[Device: ${deviceId}] Failed to start processing:`, error);
|
||||||
showToast('Failed to start processing', 'error');
|
showToast('Failed to start processing', 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function stopProcessing(deviceId) {
|
async function stopProcessing(deviceId) {
|
||||||
|
console.log(`[Device: ${deviceId}] Stopping processing...`);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE}/devices/${deviceId}/stop`, {
|
const response = await fetch(`${API_BASE}/devices/${deviceId}/stop`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -529,14 +555,16 @@ async function stopProcessing(deviceId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
|
console.log(`[Device: ${deviceId}] Processing stopped successfully`);
|
||||||
showToast('Processing stopped', 'success');
|
showToast('Processing stopped', 'success');
|
||||||
loadDevices();
|
loadDevices();
|
||||||
} else {
|
} else {
|
||||||
const error = await response.json();
|
const error = await response.json();
|
||||||
|
console.error(`[Device: ${deviceId}] Failed to stop:`, error);
|
||||||
showToast(`Failed to stop: ${error.detail}`, 'error');
|
showToast(`Failed to stop: ${error.detail}`, 'error');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to stop processing:', error);
|
console.error(`[Device: ${deviceId}] Failed to stop processing:`, error);
|
||||||
showToast('Failed to stop processing', 'error');
|
showToast('Failed to stop processing', 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -697,6 +725,8 @@ async function handleAddDevice(event) {
|
|||||||
const url = document.getElementById('device-url').value;
|
const url = document.getElementById('device-url').value;
|
||||||
const led_count = parseInt(document.getElementById('device-led-count').value);
|
const led_count = parseInt(document.getElementById('device-led-count').value);
|
||||||
|
|
||||||
|
console.log(`Adding device: ${name} (${url}, ${led_count} LEDs)`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE}/devices`, {
|
const response = await fetch(`${API_BASE}/devices`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -710,11 +740,14 @@ async function handleAddDevice(event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
|
const result = await response.json();
|
||||||
|
console.log('Device added successfully:', result);
|
||||||
showToast('Device added successfully', 'success');
|
showToast('Device added successfully', 'success');
|
||||||
event.target.reset();
|
event.target.reset();
|
||||||
loadDevices();
|
loadDevices();
|
||||||
} else {
|
} else {
|
||||||
const error = await response.json();
|
const error = await response.json();
|
||||||
|
console.error('Failed to add device:', error);
|
||||||
showToast(`Failed to add device: ${error.detail}`, 'error');
|
showToast(`Failed to add device: ${error.detail}`, 'error');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -266,32 +266,34 @@
|
|||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h2 data-i18n="auth.title">🔑 Login to WLED Controller</h2>
|
<h2 data-i18n="auth.title">🔑 Login to WLED Controller</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<form id="api-key-form" onsubmit="submitApiKey(event)">
|
||||||
<p class="modal-description" data-i18n="auth.message">
|
<div class="modal-body">
|
||||||
Please enter your API key to authenticate and access the WLED Screen Controller.
|
<p class="modal-description" data-i18n="auth.message">
|
||||||
</p>
|
Please enter your API key to authenticate and access the WLED Screen Controller.
|
||||||
<div class="form-group">
|
</p>
|
||||||
<label for="api-key-input" data-i18n="auth.label">API Key:</label>
|
<div class="form-group">
|
||||||
<div class="password-input-wrapper">
|
<label for="api-key-input" data-i18n="auth.label">API Key:</label>
|
||||||
<input
|
<div class="password-input-wrapper">
|
||||||
type="password"
|
<input
|
||||||
id="api-key-input"
|
type="password"
|
||||||
data-i18n-placeholder="auth.placeholder"
|
id="api-key-input"
|
||||||
placeholder="Enter your API key..."
|
data-i18n-placeholder="auth.placeholder"
|
||||||
autocomplete="off"
|
placeholder="Enter your API key..."
|
||||||
>
|
autocomplete="off"
|
||||||
<button type="button" class="password-toggle" onclick="togglePasswordVisibility()">
|
>
|
||||||
👁️
|
<button type="button" class="password-toggle" onclick="togglePasswordVisibility()">
|
||||||
</button>
|
👁️
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<small class="input-hint" data-i18n="auth.hint">Your API key will be stored securely in your browser's local storage.</small>
|
||||||
</div>
|
</div>
|
||||||
<small class="input-hint" data-i18n="auth.hint">Your API key will be stored securely in your browser's local storage.</small>
|
<div id="api-key-error" class="error-message" style="display: none;"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="api-key-error" class="error-message" style="display: none;"></div>
|
<div class="modal-footer">
|
||||||
</div>
|
<button type="button" class="btn btn-secondary" onclick="closeApiKeyModal()" id="modal-cancel-btn" data-i18n="auth.button.cancel">Cancel</button>
|
||||||
<div class="modal-footer">
|
<button type="submit" class="btn btn-primary" data-i18n="auth.button.login">Login</button>
|
||||||
<button class="btn btn-secondary" onclick="closeApiKeyModal()" id="modal-cancel-btn" data-i18n="auth.button.cancel">Cancel</button>
|
</div>
|
||||||
<button class="btn btn-primary" onclick="submitApiKey()" data-i18n="auth.button.login">Login</button>
|
</form>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -425,7 +427,11 @@
|
|||||||
document.body.classList.remove('modal-open');
|
document.body.classList.remove('modal-open');
|
||||||
}
|
}
|
||||||
|
|
||||||
function submitApiKey() {
|
function submitApiKey(event) {
|
||||||
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
const input = document.getElementById('api-key-input');
|
const input = document.getElementById('api-key-input');
|
||||||
const error = document.getElementById('api-key-error');
|
const error = document.getElementById('api-key-error');
|
||||||
const key = input.value.trim();
|
const key = input.value.trim();
|
||||||
@@ -454,15 +460,6 @@
|
|||||||
startAutoRefresh();
|
startAutoRefresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle Enter key in modal
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
document.getElementById('api-key-input').addEventListener('keypress', (e) => {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
submitApiKey();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user