Le backoff anti-429 du refresh OAuth vivait uniquement en mémoire : chaque
redéploiement le remettait à zéro et re-sollicitait IMMÉDIATEMENT l'endpoint de
refresh rate-limité, entretenant le 429 qu'on cherche justement à laisser retomber.
Persiste backoff_until + le palier exponentiel (failures) sur /data
(claude_oauth_state.json), écriture atomique best-effort à la manière du cache
trackers. Chargé une fois par process en tête de fetch_usage, sauvé à chaque
échec et effacé à chaque succès. Un token frais court-circuite de toute façon le
backoff, donc un re-login isolé débloque immédiatement même si une fenêtre court.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le refresh token est rotatif : la chaîne se régénère seule indéfiniment tant que
le nouveau token est persisté. La reconnexion manuelle n'était requise que lorsque
cet invariant cassait. Trois correctifs :
- Refresh PROACTIF : on rafraîchit dès qu'il reste < 2h sur le token (~8h de vie)
au lieu des 2 dernières minutes. Un échec transitoire a des heures de marge avant
que l'access token meure ; la fenêtre où un kill/timeout perd le token rotatif
fraîchement rotaté passe de ~8h à quelques ms. Réglable via
MONITORINK_CLAUDE_REFRESH_LEAD_MIN (défaut 120).
- Distinction FATAL vs TRANSITOIRE : 400 invalid_grant / 401 sur l'endpoint token
-> _RefreshFatal, sans backoff ni re-soumission en boucle (évite la reuse-detection
qui révoque toute la famille). 429/5xx/réseau gardent le backoff exponentiel.
- Visibilité + auto-réparation : le cas fatal affiche "Reconnexion Claude requise"
(pas de repli cache silencieux) et l'alerte se referme seule dès qu'un token frais
réapparaît sur disque (re-login isolé), sans redémarrer le conteneur.
Timeout du POST de refresh porté à 45s (réglable, MONITORINK_CLAUDE_REFRESH_TIMEOUT)
pour réduire la fenêtre de perte du token après rotation serveur.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le token d'accès vit ~8h ; à expiration, fetch_usage retentait un refresh à
chaque rendu (~15min) avec un backoff fixe de 5min toujours déjà expiré. Chaque
tentative re-saturait le rate-limit /v1/oauth/token -> 429 en boucle (>15h
observé), token jamais rafraîchi, usage figé sur la dernière valeur en cache.
- backoff exponentiel 10min -> 6h (au lieu de 5min fixes), réinitialisé sur succès
- respect de l'en-tête Retry-After quand il dépasse le palier
- logging succès/échec du refresh (le chemin n'en avait aucun -> diag à l'aveugle)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Trackers: I/O + jetons fusionnés sur une ligne (↑↓), ratio en héros (40px)
- Footer: grid-rows minmax(0,1fr) + .pane overflow:hidden -> ne quitte plus le canevas
- Retire la section Maison (HA) du template + le fetch homeassistant de render.py
Lecture des tuiles Upload/Download de /account/ (libellés Go/To gardés tels quels
via up_str/down_str pour conserver les décimales). Condition io -> has_io.
Type yggreborn : login form classique (identifier=EMAIL, password, csrf_token),
ratio lu dans l'en-tête (Ratio : X.XX). Pas de up/down ni jetons (ligne envoyé/
reçu rendue conditionnelle sur up/down>0).
Champ tokens optionnel sur TrackerStat (None = tracker sans jetons) ; torr9 le
remplit depuis jeton_balance de /users/me. Ligne « N jetons » conditionnelle sous
envoyé/reçu, masquée pour les trackers sans système de jetons (c411).
torr9 a une API Go dédiée (api.torr9.net) avec auth JWT (username/password).
Le ratio se calcule (total+bonus) up/down comme le frontend ; pas de champ
ratio dans l'API. Le passkey du compte ne sert qu'au RSS, pas au profil.
Nouveau module integrations/trackers.py : pour chaque tracker configuré (env
MONITORINK_TRACKERS + bloc par clé), récupère ratio/uploaded/downloaded. Type
unit3d_nuxt (c411) : login session (CSRF meta + /api/auth/login) car le ratio
n'est pas lisible au token API ; session réutilisée, résultat caché (TTL 30 min).
Section dashboard sous le NAS, style instrument 1-bit. Architecture par type pour
ajouter d'autres trackers ensuite.
Le bind-mount d'un fichier unique reste accroché à l'inode capturé au
démarrage ; Hermes réécrit auth.json par rename atomique -> le conteneur
servait un token périmé (401), la section Codex disparaissait au refresh.
Le full refresh apparaissait trop souvent: getbbox() renvoyait un seul
rectangle englobant tous les pixels modifiés, donc météo (haut) + heure de
MaJ (ailleurs) qui changeaient au même cycle produisaient un rectangle
quasi plein écran -> ratio > partial_max_ratio -> full forcé.
- frame.py: détection des bandes horizontales modifiées disjointes
(_changed_regions), refresh partiel serré par zone. Full basé sur le
temps écoulé (last_full_at + time.monotonic) au lieu d'un compteur de
cycles. État pngs/regions en liste, get_png(client, region).
- config.py: full_refresh_interval_minutes (MONITORINK_FULL_INTERVAL_MIN,
défaut 120). Suppression de partial_max_ratio.
- app.py: /frame.meta renvoie un bloc multi-ligne "MODE SEQ N" + N régions
"i x y w h"; /frame.png?region=i.
- monitorinkloop.sh: display_meta parse le bloc et fait N fbink partiels.
Validé sur une nuit (34 cycles, 34 suspends pleins ~901s) :
- délai de décharge VEE 8 -> 12s : le suspend réussit dès la 1re tentative
(les logs montraient tentative=1 KO / tentative=2 OK vers ~12s), un cycle
suspend/resume gâché en moins par itération. Retry conservé comme filet.
- DIAG verbeux remplacé par un filet compact (alim + dmesg PM/epdc), ne se
déclenche qu'en cas d'échec total des retries.
- log de la capacité batterie à chaque itération (CAP|CHG) pour suivre le
drain réel en %/h sur les prochaines sessions.
Cause racine de l'autonomie médiocre : la liseuse ne suspendait JAMAIS.
Juste après un refresh e-ink, le pilote EPDC (20f4000.epdc) refuse de
suspendre tant que la haute tension VEE n'est pas redescendue
("waiting for VEE stable ... please retry suspend later", error -2) :
le noyau avorte tout le suspend, on tombait dans le repli `sleep` et le
CPU tournait 24h/24 (0 suspend réussi sur 261 itérations dans les logs).
Correctif (suspend_for) : on laisse l'EPDC décharger VEE (~8 s) puis on
RÉESSAIE le suspend jusqu'à ce qu'il prenne, comme le suggère le noyau.
Résultat : rtcwake elapsed ~= REFRESH (vrai sommeil entre les refresh),
duty cycle éveillé ~100% -> ~3%.
Aussi :
- intervalle 5 min -> 15 min (moins de réveils).
- suspend_diag() one-shot conservé comme filet (ne se déclenche qu'en
cas d'échec total après les retries) — à retirer après validation 24 h.
Le remplissage noir partait du restant -> une barre quasi pleine se lisait à tort
comme « épuisé ». Le noir représente désormais la conso (gauche->droite), le blanc à
droite = budget restant ; le repère ▼ marque la ligne d'alerte (80 % conso = 20 % rest.).
Token expiré -> refresh tenté à chaque rendu, échec non mémorisé -> on martelait
platform.claude.com et le 429 s'entretenait. On impose désormais un backoff de 5 min
après un refresh échoué (et sur la voie de refresh forcé 401), pendant lequel on sert
la dernière valeur connue au lieu de re-tenter.
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.
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.