Add adaptive FPS and honest device reachability during streaming
DDP uses fire-and-forget UDP, so when a WiFi device becomes overwhelmed by sustained traffic, sends appear successful while the device is actually unreachable. This adds: - HTTP liveness probe (GET /json/info, 2s timeout) every 10s during streaming, exposed as device_streaming_reachable in target state - Adaptive FPS (opt-in): exponential backoff when device is unreachable, gradual recovery when it stabilizes — finds sustainable send rate - Honest health checks: removed the lie that forced device_online=true during streaming; now runs actual health checks regardless - Target editor toggle, FPS display shows effective rate when throttled, health dot reflects streaming reachability, red highlight when unreachable - Auto-backup scheduling support in settings modal Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -207,20 +207,32 @@ function _updateRunningMetrics(enrichedRunning) {
|
||||
// Update text values (use cached refs, fallback to querySelector)
|
||||
const cached = _metricsElements.get(target.id);
|
||||
const fpsEl = cached?.fps || document.querySelector(`[data-fps-text="${target.id}"]`);
|
||||
if (fpsEl) fpsEl.innerHTML = `${fpsCurrent}<span class="dashboard-fps-target">/${fpsTarget}</span>`
|
||||
+ `<span class="dashboard-fps-avg">avg ${fpsActual}</span>`;
|
||||
if (fpsEl) {
|
||||
const effFps = state.fps_effective;
|
||||
const fpsTargetLabel = (effFps != null && effFps < fpsTarget)
|
||||
? `${fpsCurrent}<span class="dashboard-fps-target">/${effFps}↓${fpsTarget}</span>`
|
||||
: `${fpsCurrent}<span class="dashboard-fps-target">/${fpsTarget}</span>`;
|
||||
const unreachableClass = state.device_streaming_reachable === false ? ' fps-unreachable' : '';
|
||||
fpsEl.innerHTML = `<span class="${unreachableClass}">${fpsTargetLabel}</span>`
|
||||
+ `<span class="dashboard-fps-avg">avg ${fpsActual}</span>`;
|
||||
}
|
||||
|
||||
const errorsEl = cached?.errors || document.querySelector(`[data-errors-text="${target.id}"]`);
|
||||
if (errorsEl) errorsEl.textContent = `${errors > 0 ? ICON_WARNING : ICON_OK} ${errors}`;
|
||||
|
||||
// Update health dot
|
||||
// Update health dot — prefer streaming reachability when processing
|
||||
const isLed = target.target_type === 'led' || target.target_type === 'wled';
|
||||
if (isLed) {
|
||||
const row = cached?.row || document.querySelector(`[data-target-id="${target.id}"]`);
|
||||
if (row) {
|
||||
const dot = row.querySelector('.health-dot');
|
||||
if (dot && state.device_last_checked != null) {
|
||||
dot.className = `health-dot ${state.device_online ? 'health-online' : 'health-offline'}`;
|
||||
if (dot) {
|
||||
const streamReachable = state.device_streaming_reachable;
|
||||
if (state.processing && streamReachable != null) {
|
||||
dot.className = `health-dot ${streamReachable ? 'health-online' : 'health-offline'}`;
|
||||
} else if (state.device_last_checked != null) {
|
||||
dot.className = `health-dot ${state.device_online ? 'health-online' : 'health-offline'}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user