Screen.poll_interval was a fixed class attribute the scheduler read directly -- added Screen.get_poll_interval(settings) so a screen can make it settings-driven instead. WeatherScreen overrides it to read a new "update_minutes" field (default 30, matching the old hardcoded value). New number input on the Weather settings page. Caught a real edge case while testing: `raw or 30` treats an explicit 0 as falsy and silently replaces it with 30 instead of clamping to the 1-minute floor. Not reachable via the UI (min="1" on the input) but the method should still be correct standalone -- fixed with an explicit None-check instead.
35 lines
1.3 KiB
Python
35 lines
1.3 KiB
Python
"""Base interface every screen implements."""
|
|
|
|
from abc import ABC, abstractmethod
|
|
from typing import Any
|
|
|
|
from PIL import Image
|
|
|
|
|
|
class Screen(ABC):
|
|
id: str
|
|
display_name: str
|
|
poll_interval: int = 60 # seconds between check() calls
|
|
|
|
@abstractmethod
|
|
def check(self, settings: dict) -> Any:
|
|
"""Cheap call (ideally a single small request) that returns whatever
|
|
state this screen needs to render. Returning None means "nothing to
|
|
show" (only meaningful for preemptive screens like Navidrome)."""
|
|
|
|
@abstractmethod
|
|
def render(self, state: Any, settings: dict, resolution: tuple[int, int]) -> Image.Image:
|
|
"""Full draw. Only called when this screen is actually about to be
|
|
shown, so it's fine for this to be more expensive than check()."""
|
|
|
|
def default_settings(self) -> dict:
|
|
"""Settings this screen needs, with defaults, merged under config.json's
|
|
settings[self.id] on first run."""
|
|
return {}
|
|
|
|
def get_poll_interval(self, settings: dict) -> int:
|
|
"""Seconds between check() calls. Override to make it configurable
|
|
via settings (e.g. a user-facing "update every N minutes" field);
|
|
defaults to the fixed class-level poll_interval."""
|
|
return self.poll_interval
|