Refresh partiel e-ink : ne redessine que la zone changée, full refresh ~1h

Backend : endpoints /frame.meta (ligne 'MODE X Y W H SEQ') + /frame.png qui
servent un crop de la zone modifiée (diff PIL par client) ou l'image pleine.
Full refresh forcé tous les N cycles (MONITORINK_FULL_EVERY=12, ~1h) ou si la
zone change sur plus de 60% de l'écran. Mode 'noop' quand rien ne change.

Anti-429 : l'usage Claude est mis en cache (MONITORINK_USAGE_TTL=120s) avec
repli sur la dernière valeur connue en cas d'erreur transitoire.

Kobo : monitorinkloop.sh récupère meta puis png et fait un fbink partiel
(-g file=,x=,y=) sans flash, full refresh (-c -f) en mode full. Refresh 5 min.
This commit is contained in:
jerem
2026-06-15 18:42:32 +02:00
parent ce20d3675d
commit c7395d1c37
8 changed files with 257 additions and 27 deletions

View File

@@ -10,8 +10,9 @@ from __future__ import annotations
import time
from fastapi import FastAPI, Response
from fastapi.responses import HTMLResponse
from fastapi.responses import HTMLResponse, PlainTextResponse
import frame
import render
from config import config
from integrations import kobo
@@ -46,6 +47,26 @@ async def image(fresh: int = 0, bat: int | None = None, chg: int = 0) -> Respons
)
@app.get("/frame.meta", response_class=PlainTextResponse)
async def frame_meta(client: str = "kobo", bat: int | None = None, chg: 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.
kobo.record(bat, bool(chg))
info = await frame.compute_frame(client)
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()