Initial commit: Paperwall, a standalone e-ink dashboard
Navidrome now-playing, clock, and weather for a Waveshare epd4in0e panel, with a local web UI and install/uninstall scripts for the Pi.
This commit is contained in:
commit
66464291e1
33 files changed
+2215
No files matched your search
@@ -0,0 +1,87 @@
|
||||
"""Single JSON config file, loaded once and kept in memory.
|
||||
|
||||
Unlike InkyPi's Config (which we spent a long time debugging), writes happen
|
||||
immediately on every change from the web UI, and the scheduler re-reads the
|
||||
in-memory structure fresh on every tick -- there's no "did the setting
|
||||
actually take effect" gap, because there's no long sleep to get stuck in.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import threading
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_CONFIG_PATH = Path(__file__).resolve().parent / "config.json"
|
||||
|
||||
DEFAULT_ROTATION = {
|
||||
"screens": ["clock", "weather"],
|
||||
"interval_seconds": 300,
|
||||
}
|
||||
|
||||
|
||||
class Config:
|
||||
def __init__(self, screens: dict, path: Path = DEFAULT_CONFIG_PATH):
|
||||
self.path = path
|
||||
self._lock = threading.Lock()
|
||||
self._data = self._load(screens)
|
||||
|
||||
def _load(self, screens: dict) -> dict:
|
||||
if self.path.exists():
|
||||
with open(self.path) as f:
|
||||
data = json.load(f)
|
||||
else:
|
||||
data = {}
|
||||
|
||||
data.setdefault("rotation", dict(DEFAULT_ROTATION))
|
||||
data.setdefault("navidrome_priority", True)
|
||||
data.setdefault("settings", {})
|
||||
for screen_id, screen in screens.items():
|
||||
data["settings"].setdefault(screen_id, {})
|
||||
for key, value in screen.default_settings().items():
|
||||
data["settings"][screen_id].setdefault(key, value)
|
||||
|
||||
return data
|
||||
|
||||
def save(self) -> None:
|
||||
with self._lock:
|
||||
tmp_path = self.path.with_suffix(".json.tmp")
|
||||
with open(tmp_path, "w") as f:
|
||||
json.dump(self._data, f, indent=2)
|
||||
tmp_path.replace(self.path)
|
||||
logger.debug("Config written to %s", self.path)
|
||||
|
||||
# ------------------------------------------------------------ screens
|
||||
|
||||
def get_screen_settings(self, screen_id: str) -> dict:
|
||||
"""Returns the live, mutable settings dict for a screen -- screens are
|
||||
allowed to write ephemeral state into it (e.g. Navidrome's last-known
|
||||
track for the idle fallback)."""
|
||||
with self._lock:
|
||||
return self._data["settings"].setdefault(screen_id, {})
|
||||
|
||||
def update_screen_settings(self, screen_id: str, updates: dict) -> None:
|
||||
with self._lock:
|
||||
self._data["settings"].setdefault(screen_id, {}).update(updates)
|
||||
self.save()
|
||||
|
||||
# ----------------------------------------------------------- rotation
|
||||
|
||||
def get_rotation(self) -> dict:
|
||||
with self._lock:
|
||||
return dict(self._data["rotation"])
|
||||
|
||||
def set_rotation(self, screens: list[str], interval_seconds: int) -> None:
|
||||
with self._lock:
|
||||
self._data["rotation"] = {"screens": screens, "interval_seconds": interval_seconds}
|
||||
self.save()
|
||||
|
||||
def get_navidrome_priority(self) -> bool:
|
||||
with self._lock:
|
||||
return bool(self._data["navidrome_priority"])
|
||||
|
||||
def set_navidrome_priority(self, enabled: bool) -> None:
|
||||
with self._lock:
|
||||
self._data["navidrome_priority"] = enabled
|
||||
self.save()
|
||||
Reference in new issue
Block a user