L'ancien triple-tap via finger_trace dessinait des points noirs (outil de demo FBInk), ne respawnait pas (mort definitif si le process tombait) et le tactile ne reveille pas l'appareil. Le power, lui, n'emet que des scancodes MSC_SCAN parasites (etat de charge USB). Les boutons de page emettent des EV_KEY propres (codes 193/194). reboot_watcher.sh: lit l'evdev (FD persistant, pas de perte d'evenements), declenche sur 3 press EV_KEY < 3 s, boucle de respawn. Plus de finger_trace. Refresh: full force au (re)demarrage (reset=1 cote client -> oubli de prev_image cote serveur) pour eviter un refresh partiel pose sur un ecran efface par le reboot.
77 lines
2.6 KiB
Python
77 lines
2.6 KiB
Python
"""Serveur Monitorink : expose le dashboard en PNG pour la Kobo.
|
|
|
|
Endpoints :
|
|
GET /image.png -> dashboard 1264x1680 niveaux de gris (avec cache TTL)
|
|
GET /debug.html -> HTML brut (itération design, pas de screenshot)
|
|
GET /health -> sonde de vie
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
|
|
from fastapi import FastAPI, Response
|
|
from fastapi.responses import HTMLResponse, PlainTextResponse
|
|
|
|
import frame
|
|
import render
|
|
from config import config
|
|
from integrations import kobo
|
|
|
|
app = FastAPI(title="Monitorink", docs_url=None, redoc_url=None)
|
|
|
|
_cache: dict[str, object] = {"png": None, "ts": 0.0}
|
|
|
|
|
|
@app.get("/health")
|
|
async def health() -> dict:
|
|
return {"status": "ok"}
|
|
|
|
|
|
@app.get("/image.png")
|
|
async def image(fresh: int = 0, bat: int | None = None, chg: int = 0) -> Response:
|
|
# La Kobo pousse sa batterie ici (bat=0-100, chg=1 si en charge) à chaque fetch.
|
|
kobo.record(bat, bool(chg))
|
|
now = time.time()
|
|
cached = _cache["png"]
|
|
age = now - float(_cache["ts"])
|
|
if cached and not fresh and age < config.cache_ttl_seconds:
|
|
png = cached # type: ignore[assignment]
|
|
else:
|
|
png = await render.render_png()
|
|
_cache["png"] = png
|
|
_cache["ts"] = now
|
|
return Response(
|
|
content=png, # type: ignore[arg-type]
|
|
media_type="image/png",
|
|
headers={"Cache-Control": "no-store"},
|
|
)
|
|
|
|
|
|
@app.get("/frame.meta", response_class=PlainTextResponse)
|
|
async def frame_meta(
|
|
client: str = "kobo", bat: int | None = None, chg: int = 0, reset: int = 0
|
|
) -> Response:
|
|
# Refresh partiel : rend l'image, calcule la zone modifiée vs le dernier frame de ce client,
|
|
# et renvoie une ligne "MODE X Y W H SEQ" triviale à parser en shell busybox.
|
|
# MODE ∈ {full, partial, noop}. Le PNG correspondant est récupéré via /frame.png.
|
|
# reset=1 (1er cycle après un (re)démarrage Kobo) -> oublie l'état et force un full refresh.
|
|
kobo.record(bat, bool(chg))
|
|
info = await frame.compute_frame(client, reset=bool(reset))
|
|
line = f"{info['mode']} {info['x']} {info['y']} {info['w']} {info['h']} {info['seq']}"
|
|
return PlainTextResponse(line, headers={"Cache-Control": "no-store"})
|
|
|
|
|
|
@app.get("/frame.png")
|
|
async def frame_png(client: str = "kobo") -> Response:
|
|
# PNG décidé lors du dernier /frame.meta (crop en partial, image pleine en full).
|
|
png = frame.get_png(client)
|
|
if png is None:
|
|
return Response(status_code=503)
|
|
return Response(content=png, media_type="image/png", headers={"Cache-Control": "no-store"})
|
|
|
|
|
|
@app.get("/debug.html", response_class=HTMLResponse)
|
|
async def debug_html() -> str:
|
|
context = await render.build_context()
|
|
return render.render_html(context)
|