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:
2026-02-26 20:22:58 +03:00
parent f8656b72a6
commit cadef971e7
23 changed files with 873 additions and 21 deletions

View File

@@ -29,6 +29,8 @@ import wled_controller.core.audio # noqa: F401 — trigger engine auto-registra
from wled_controller.storage.value_source_store import ValueSourceStore
from wled_controller.storage.profile_store import ProfileStore
from wled_controller.core.profiles.profile_engine import ProfileEngine
from wled_controller.core.backup.auto_backup import AutoBackupEngine
from wled_controller.api.routes.system import STORE_MAP
from wled_controller.utils import setup_logging, get_logger
# Initialize logging
@@ -101,6 +103,14 @@ async def lifespan(app: FastAPI):
# Create profile engine (needs processor_manager)
profile_engine = ProfileEngine(profile_store, processor_manager)
# Create auto-backup engine
auto_backup_engine = AutoBackupEngine(
settings_path=Path("data/auto_backup_settings.json"),
backup_dir=Path("data/backups"),
store_map=STORE_MAP,
storage_config=config.storage,
)
# Initialize API dependencies
init_dependencies(
device_store, template_store, processor_manager,
@@ -114,6 +124,7 @@ async def lifespan(app: FastAPI):
value_source_store=value_source_store,
profile_store=profile_store,
profile_engine=profile_engine,
auto_backup_engine=auto_backup_engine,
)
# Register devices in processor manager for health monitoring
@@ -154,6 +165,9 @@ async def lifespan(app: FastAPI):
# Start profile engine (evaluates conditions and auto-starts/stops targets)
await profile_engine.start()
# Start auto-backup engine (periodic configuration backups)
await auto_backup_engine.start()
# Auto-start targets with auto_start=True
auto_started = 0
for target in targets:
@@ -172,6 +186,12 @@ async def lifespan(app: FastAPI):
# Shutdown
logger.info("Shutting down LED Grab")
# Stop auto-backup engine
try:
await auto_backup_engine.stop()
except Exception as e:
logger.error(f"Error stopping auto-backup engine: {e}")
# Stop profile engine first (deactivates profile-managed targets)
try:
await profile_engine.stop()