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:
@@ -100,6 +100,7 @@ class PictureTargetStore:
|
||||
keepalive_interval: float = 1.0,
|
||||
state_check_interval: int = DEFAULT_STATE_CHECK_INTERVAL,
|
||||
min_brightness_threshold: int = 0,
|
||||
adaptive_fps: bool = False,
|
||||
key_colors_settings: Optional[KeyColorsSettings] = None,
|
||||
description: Optional[str] = None,
|
||||
picture_source_id: str = "",
|
||||
@@ -133,6 +134,7 @@ class PictureTargetStore:
|
||||
keepalive_interval=keepalive_interval,
|
||||
state_check_interval=state_check_interval,
|
||||
min_brightness_threshold=min_brightness_threshold,
|
||||
adaptive_fps=adaptive_fps,
|
||||
description=description,
|
||||
auto_start=auto_start,
|
||||
created_at=now,
|
||||
@@ -170,6 +172,7 @@ class PictureTargetStore:
|
||||
keepalive_interval: Optional[float] = None,
|
||||
state_check_interval: Optional[int] = None,
|
||||
min_brightness_threshold: Optional[int] = None,
|
||||
adaptive_fps: Optional[bool] = None,
|
||||
key_colors_settings: Optional[KeyColorsSettings] = None,
|
||||
description: Optional[str] = None,
|
||||
auto_start: Optional[bool] = None,
|
||||
@@ -199,6 +202,7 @@ class PictureTargetStore:
|
||||
keepalive_interval=keepalive_interval,
|
||||
state_check_interval=state_check_interval,
|
||||
min_brightness_threshold=min_brightness_threshold,
|
||||
adaptive_fps=adaptive_fps,
|
||||
key_colors_settings=key_colors_settings,
|
||||
description=description,
|
||||
auto_start=auto_start,
|
||||
|
||||
@@ -20,6 +20,7 @@ class WledPictureTarget(PictureTarget):
|
||||
keepalive_interval: float = 1.0 # seconds between keepalive sends when screen is static
|
||||
state_check_interval: int = DEFAULT_STATE_CHECK_INTERVAL
|
||||
min_brightness_threshold: int = 0 # brightness below this → 0 (disabled when 0)
|
||||
adaptive_fps: bool = False # auto-reduce FPS when device is unresponsive
|
||||
|
||||
def register_with_manager(self, manager) -> None:
|
||||
"""Register this WLED target with the processor manager."""
|
||||
@@ -33,6 +34,7 @@ class WledPictureTarget(PictureTarget):
|
||||
state_check_interval=self.state_check_interval,
|
||||
brightness_value_source_id=self.brightness_value_source_id,
|
||||
min_brightness_threshold=self.min_brightness_threshold,
|
||||
adaptive_fps=self.adaptive_fps,
|
||||
)
|
||||
|
||||
def sync_with_manager(self, manager, *, settings_changed: bool,
|
||||
@@ -46,6 +48,7 @@ class WledPictureTarget(PictureTarget):
|
||||
"keepalive_interval": self.keepalive_interval,
|
||||
"state_check_interval": self.state_check_interval,
|
||||
"min_brightness_threshold": self.min_brightness_threshold,
|
||||
"adaptive_fps": self.adaptive_fps,
|
||||
})
|
||||
if css_changed:
|
||||
manager.update_target_css(self.id, self.color_strip_source_id)
|
||||
@@ -57,7 +60,7 @@ class WledPictureTarget(PictureTarget):
|
||||
def update_fields(self, *, name=None, device_id=None, color_strip_source_id=None,
|
||||
brightness_value_source_id=None,
|
||||
fps=None, keepalive_interval=None, state_check_interval=None,
|
||||
min_brightness_threshold=None,
|
||||
min_brightness_threshold=None, adaptive_fps=None,
|
||||
description=None, auto_start=None, **_kwargs) -> None:
|
||||
"""Apply mutable field updates for WLED targets."""
|
||||
super().update_fields(name=name, description=description, auto_start=auto_start)
|
||||
@@ -75,6 +78,8 @@ class WledPictureTarget(PictureTarget):
|
||||
self.state_check_interval = state_check_interval
|
||||
if min_brightness_threshold is not None:
|
||||
self.min_brightness_threshold = min_brightness_threshold
|
||||
if adaptive_fps is not None:
|
||||
self.adaptive_fps = adaptive_fps
|
||||
|
||||
@property
|
||||
def has_picture_source(self) -> bool:
|
||||
@@ -90,6 +95,7 @@ class WledPictureTarget(PictureTarget):
|
||||
d["keepalive_interval"] = self.keepalive_interval
|
||||
d["state_check_interval"] = self.state_check_interval
|
||||
d["min_brightness_threshold"] = self.min_brightness_threshold
|
||||
d["adaptive_fps"] = self.adaptive_fps
|
||||
return d
|
||||
|
||||
@classmethod
|
||||
@@ -116,6 +122,7 @@ class WledPictureTarget(PictureTarget):
|
||||
keepalive_interval=data.get("keepalive_interval", data.get("standby_interval", 1.0)),
|
||||
state_check_interval=data.get("state_check_interval", DEFAULT_STATE_CHECK_INTERVAL),
|
||||
min_brightness_threshold=data.get("min_brightness_threshold", 0),
|
||||
adaptive_fps=data.get("adaptive_fps", False),
|
||||
description=data.get("description"),
|
||||
auto_start=data.get("auto_start", False),
|
||||
created_at=datetime.fromisoformat(data.get("created_at", datetime.utcnow().isoformat())),
|
||||
|
||||
Reference in New Issue
Block a user