Files
Monitorink/CLAUDE.md

7.8 KiB
Raw Permalink Blame History

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Vue d'ensemble

Monitorink transforme une Kobo Libra 2 (e-reader e-ink) en écran de monitoring domestique. Le backend agrège plusieurs sources (usage Claude + Codex, météo, NAS, ratios trackers torrent), rend un dashboard HTML, le capture en PNG niveaux de gris, et le sert à la Kobo qui l'affiche puis se rendort. La fonctionnalité phare est le % de tokens restants des abonnements Claude/Codex (les API ne renvoient qu'une utilisation en %, pas de compteur absolu).

Trois zones :

  • backend/ — serveur FastAPI Python qui agrège les données et génère l'image.
  • kobo/ — scripts shell client pour la Kobo (overlay sur usetrmnl/trmnl-kobo).
  • dev/ — utilitaires de dev (preview.py rendu hors-ligne, probe_usage.py sonde API).

Commandes

Le venv local est backend/.venv (Python 3.9 sur le Mac de dev ; le conteneur, lui, est en 3.x via l'image Playwright). Toujours utiliser le binaire du venv.

# Dev backend (depuis backend/)
cd backend
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
.venv/bin/python -m playwright install chromium      # navigateur pour le rendu
cp ../.env.example ../.env                            # puis compléter
.venv/bin/uvicorn app:app --reload --port 8080
# -> http://localhost:8080/image.png  et  /debug.html (HTML brut, itération design rapide)

# Aperçu design hors-ligne (données fictives, aucune dépendance réseau) — depuis la racine
backend/.venv/bin/python dev/preview.py docs/preview.png

# Sonde l'endpoint /usage de Claude (debug auth/refresh)
backend/.venv/bin/python dev/probe_usage.py

# Vérif syntaxe rapide (pas de suite de tests dans ce repo)
backend/.venv/bin/python -m py_compile backend/render.py backend/integrations/*.py
sh -n kobo/monitorinkloop.sh                          # lint shell des scripts Kobo

# Déploiement serveur
docker compose up -d --build
# Valider le rendu de la config compose sans .env réel :
HOME=/tmp MONITORINK_DOMAIN=d.example.com MONITORINK_NETWORK=net docker compose config

Il n'y a pas de suite de tests automatisée : la validation se fait via py_compile, dev/preview.py (rendu visuel), et le test réel sur l'appareil.

Architecture du rendu

Le flux central (backend/render.py) :

  1. build_context() récupère toutes les sources en parallèle (asyncio.gather) et assemble le contexte du template Jinja2.
  2. Le template templates/dashboard.html est rendu en HTML, canevas paysage 1680×1264.
  3. Playwright (Chromium) capture le HTML en PNG via page.set_content() (pas de base URL → les polices woff2 sont embarquées en data-URI base64 par fonts.py).
  4. Le PNG est converti en niveaux de gris (mode 'L') puis pivoté de 90° (config.rotate) pour le panneau e-ink physiquement en portrait (1264×1680).

config.py charge toute la configuration depuis l'environnement / .env (dataclass Config gelée, instance singleton config). Aucune valeur sensible n'est versionnée. Les trackers sont configurés dynamiquement via MONITORINK_TRACKERS=clé1,clé2 + un bloc d'env par clé.

Endpoints (backend/app.py)

  • GET /image.png — dashboard PNG complet, avec cache TTL (cache_ttl_seconds). La Kobo pousse sa batterie ici (?bat=0-100&chg=0|1) — voir integrations/kobo.py.
  • GET /frame.meta + GET /frame.pngrefresh partiel e-ink (voir ci-dessous).
  • GET /debug.html — HTML brut sans screenshot, pour itérer sur le design.
  • GET /health — sonde.

Refresh partiel e-ink (backend/frame.py)

Les full refresh e-ink « flashent » (waveform complète qui efface le ghosting) — visuellement gênant. frame.py calcule côté serveur (PIL dispo, pas la Kobo) le diff entre le rendu courant et le précédent par client, détecte les bandes horizontales modifiées disjointes, et renvoie soit des crops partiels (sans flash), soit un full. Un full n'est forcé qu'au 1er lancement/reset et toutes les full_refresh_interval_minutes. La Kobo parse le bloc texte trivial de /frame.meta (MODE SEQ NREGIONS + lignes i x y w h) puis fetch chaque région via /frame.png?region=i.

Intégrations (backend/integrations/)

Toutes suivent le même contrat : async def fetch_*() renvoie une dataclass avec un champ ok ; en erreur transitoire elles replient sur la dernière valeur connue plutôt que d'afficher une erreur à l'écran. Toutes sont optionnelles sauf Claude : env vide = section masquée.

  • claude_usage.py — LA pièce délicate. GET api.anthropic.com/api/oauth/usage exige le scope OAuth user:profile, que claude setup-token n'a PAS (403). On utilise donc un login Claude isolé dédié (CLAUDE_CONFIG_DIR séparé, monté sur /creds/.credentials.json). Le backend lit/refresh ce seul fichier, jamais le ~/.claude partagé. Points sensibles : le refresh token est rotatif (on ne re-soumet JAMAIS le même token → reuse-detection), refresh proactif bien avant l'expiration (~8 h de vie), backoff exponentiel sur échec (anti-429), et distinction stricte entre erreur transitoire (429/réseau → backoff + repli cache) et fatale (invalid_grant/401 → _RefreshFatal, alerte « login requis » à l'écran, jamais de re-soumission en boucle). Écriture des credentials atomique (tmp + os.replace, mode 0600).
  • codex.py — usage ChatGPT/Codex via chatgpt.com/backend-api/wham/usage. Token lu dans un auth.json maintenu frais par un processus externe (monté en lecture seule) ; Monitorink ne refresh pas. Extrait chatgpt_account_id du JWT (header requis).
  • trackers.py — ratio des trackers torrent privés. Architecture multi-fetcher : chaque TrackerSpec.type choisit une fonction dans _FETCHERS (unit3d_nuxt, torr9, tr4ker, yggreborn). Le ratio n'est PAS lisible au token API → login session (CSRF + cookies) propre à chaque techno. Cache long (tracker_ttl_seconds, défaut 1 h) persisté sur disque (/data) pour survivre aux redéploiements sans reloguer. Pour ajouter un tracker : écrire _fetch_xxx(spec) renvoyant un TrackerStat et l'enregistrer dans _FETCHERS.
  • weather.py — Open-Meteo (sans clé). nas.py — endpoint HTTP maison /api/status.
  • kobo.py — inverse des autres : la Kobo pousse sa batterie (elle seule la connaît), on stocke la dernière valeur horodatée en mémoire pour le prochain rendu.

Client Kobo (kobo/)

monitorinkloop.sh est la boucle d'affichage, overlay réutilisant les binaires ARM (fbink, busybox_kobo) et helpers WiFi de usetrmnl/trmnl-kobo (à copier dans bin/, scripts/ — non versionnés ici). Cycle : WiFi up → fetch /frame.meta → affiche (full/partial via fbink) → WiFi down → suspend rtcwake -m mem → réveil RTC. Détails critiques dans kobo/README.md :

  • Piège EPDC/VEE : juste après un refresh e-ink, le pilote EPDC refuse de suspendre (haute tension VEE pas redescendue) → suspend_for() attend ~12 s puis retente le suspend (sinon le CPU tournait 24/24 et vidait la batterie).
  • Reboot : 3 appuis rapides sur un bouton de page (reboot_watcher.sh, evdev EV_KEY 193/194). Sur batterie : un appui power d'abord (réveil) puis les 3 appuis.

Conventions

  • e-ink = noir & blanc purs, zéro gris (le gris fantôme au refresh partiel). La hiérarchie passe par taille/graisse, le « consommé » par des hachures. Polices : Archivo (mots) + JetBrains Mono (nombres tabulaires), vendorisées en woff2 dans backend/static/fonts/.
  • Tout le texte utilisateur et les commentaires sont en français (accents inclus).
  • Modifier dashboard.html → vérifier le rendu avec dev/preview.py avant de commit.