From 5925b0f9d210db363aaff7d0465e8277de957206 Mon Sep 17 00:00:00 2001 From: jerem Date: Mon, 15 Jun 2026 19:38:15 +0200 Subject: [PATCH] Reboot Kobo: 3 appuis bouton de page (evdev EV_KEY) au lieu du triple-tap tactile 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/app.py | 7 +- backend/frame.py | 10 ++- backend/templates/dashboard.html | 2 +- kobo/README.md | 20 +++++- kobo/monitorink.sh | 3 +- kobo/monitorinkloop.sh | 5 ++ kobo/reboot_watcher.sh | 107 ++++++++++++++++++++++++------- 7 files changed, 122 insertions(+), 32 deletions(-) diff --git a/backend/app.py b/backend/app.py index 5ad7eb4..49a3037 100644 --- a/backend/app.py +++ b/backend/app.py @@ -48,12 +48,15 @@ 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: +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) + 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"}) diff --git a/backend/frame.py b/backend/frame.py index 967f0f3..68e9945 100644 --- a/backend/frame.py +++ b/backend/frame.py @@ -42,11 +42,17 @@ def _changed_bbox(prev: Image.Image, cur: Image.Image) -> tuple[int, int, int, i return diff.getbbox() -async def compute_frame(client: str) -> dict: +async def compute_frame(client: str, reset: bool = False) -> dict: """Rend le dashboard, calcule le diff vs l'image précédente du client, met à jour son état - et renvoie {mode, x, y, w, h, seq}. Le PNG correspondant est stocké pour /frame.png.""" + et renvoie {mode, x, y, w, h, seq}. Le PNG correspondant est stocké pour /frame.png. + + reset=True (envoyé par la Kobo au 1er cycle après un (re)démarrage) oublie l'image + précédente : l'écran a été effacé par le reboot, un diff partiel se poserait sur une base + erronée. On force alors un full refresh propre.""" state = _clients.setdefault(client, _ClientState()) async with state.lock: + if reset: + state.prev_image = None # -> force_full ci-dessous cur = await render.render_image() full_w, full_h = cur.size state.seq += 1 diff --git a/backend/templates/dashboard.html b/backend/templates/dashboard.html index a07e7c5..e080842 100644 --- a/backend/templates/dashboard.html +++ b/backend/templates/dashboard.html @@ -174,7 +174,7 @@ diff --git a/kobo/README.md b/kobo/README.md index fd4bf42..1b8bc71 100644 --- a/kobo/README.md +++ b/kobo/README.md @@ -45,8 +45,19 @@ afin de ne pas réinventer (et risquer de casser) la gestion WiFi/suspend spéci - **Mode prod** (réglages par défaut) : refresh **5 min** (`MONITORINK_REFRESH=300`), **rétroéclairage éteint** (frontlight à 0), et **WiFi cyclé** — la radio est rallumée le temps du fetch puis coupée pendant le suspend `rtcwake -m mem` pour économiser la batterie. -- Un **swipe** sur l'écran réveille le device et force un rafraîchissement. -- Pour **arrêter** : maintenir le bouton power (le device se réveille sous Nickel). +- **Redémarrer / restaurer Nickel** : **3 appuis rapides (< 3 s) sur un bouton de page** + (frontal). `reboot_watcher.sh` lit l'evdev et déclenche un reboot logiciel propre sur 3 press + EV_KEY (codes 193/194). Sur **batterie** le device est en suspend profond et les boutons + frontaux ne le réveillent pas : faire d'abord **un appui power** (réveil), puis les 3 appuis + dans la fenêtre d'éveil. Filet de secours : appui power **long** = reboot matériel. + - Pourquoi pas le bouton power directement : sur ce Kobo (mx6sll) le power n'émet que des + scancodes bruts `MSC_SCAN`, parasités par l'état de charge USB (peu fiable). Les boutons + frontaux émettent des `EV_KEY` propres. + - `reboot_watcher.sh` tourne en arrière-plan avec une boucle de respawn (il ne reste jamais + mort). Calage/debug : lancer avec `MONITORINK_PWR_DEBUG=1` pour logger chaque évènement + (`type/code/val`), puis repasser à `0`. +- `finger_trace` (ancien déclencheur triple-tap tactile) n'est **plus utilisé** : on peut le + retirer de `bin/fbink/`. - Logs : `/tmp/monitorink.log` (effacés au reboot). ## À valider sur l'appareil (cf. plan, tâche 8) @@ -57,4 +68,9 @@ afin de ne pas réinventer (et risquer de casser) la gestion WiFi/suspend spéci - Stabilité de `rtcwake -m mem` sur FW 4.38.23171 (sinon le fallback `state-extended` prend le relais ; en dernier recours `sleep`). - Cycle WiFi : confirmer qu'au réveil la reconnexion DHCP aboutit en < ~24 s (`has_ip` OK). +- Boutons (reboot) : sur ce Kobo, `find_button_dev` retient `/dev/input/event3` et les boutons + de page émettent `EV_KEY` codes 193/194 (struct `input_event` 16 o). Sur un autre modèle, + recaler via `MONITORINK_PWR_DEBUG=1` (codes/format dans le log). **Sur batterie**, vérifier + le geste réveil-power → 3 appuis frontaux dans la fenêtre d'éveil (les frontaux ne réveillent + pas du suspend). - Autonomie réelle sur 24 h. diff --git a/kobo/monitorink.sh b/kobo/monitorink.sh index 51118d6..f2ed837 100755 --- a/kobo/monitorink.sh +++ b/kobo/monitorink.sh @@ -53,7 +53,8 @@ fi # Synchronise l'horloge RTC (sinon rtcwake calcule mal le réveil). hwclock -w -u 2>/dev/null -# Watcher triple-tap -> reboot (en arrière-plan, écran tactile libre car Nickel est mort). +# Watcher bouton power -> reboot logiciel (en arrière-plan, Nickel mort). +# Premier calage : préfixer par MONITORINK_PWR_DEBUG=1 pour logger device/format dans le log. sh "$BASE/reboot_watcher.sh" & # Boucle bloquante. À la sortie (STOP tue monitorinkloop.sh), on reboot pour restaurer Nickel. diff --git a/kobo/monitorinkloop.sh b/kobo/monitorinkloop.sh index 33c0076..8f7fbdc 100755 --- a/kobo/monitorinkloop.sh +++ b/kobo/monitorinkloop.sh @@ -65,7 +65,10 @@ http_get() { fetch_meta() { # Récupère la ligne "MODE X Y W H SEQ" du backend (avec batterie + client). Vide si KO. + # Au 1er cycle après un (re)démarrage (FIRST=1), on demande reset=1 : l'écran a été effacé + # par le reboot, on force un full refresh côté serveur pour éviter un partiel sur base erronée. murl="$META_URL?client=$CLIENT" + [ "${FIRST:-0}" = 1 ] && murl="$murl&reset=1" q="$(bat_query)"; [ -n "$q" ] && murl="$murl&$q" http_get "$murl" - } @@ -170,6 +173,7 @@ wifi_down() { # MODE PROD : frontlight éteint, WiFi cyclé (off pendant le suspend), rtcwake mem. frontlight_off +FIRST=1 # 1er cycle après lancement -> demande un full refresh (reset=1) au backend while true; do log "--- itération ---" frontlight_off # réaffirme après chaque réveil @@ -195,6 +199,7 @@ while true; do partial) show_frame partial "$mx" "$my" ;; *) show_frame full ;; # full ou valeur inattendue -> full refresh sûr esac + FIRST=0 # meta obtenue : le reset n'a plus lieu d'être pour les cycles suivants else log "meta ECHEC" offline diff --git a/kobo/reboot_watcher.sh b/kobo/reboot_watcher.sh index 5cc80d4..f5141a7 100755 --- a/kobo/reboot_watcher.sh +++ b/kobo/reboot_watcher.sh @@ -1,33 +1,92 @@ #!/bin/sh -# Monitorink — redémarrage par TRIPLE-TAP (restaure Nickel / relance proprement). -# Lancé en arrière-plan par monitorink.sh (après que Nickel a été tué -> écran tactile libre). -# Utilise finger_trace (FBInk) pour détecter les touchers ; pas de mapping de coordonnées. +# Monitorink — reboot logiciel par TRIPLE-APPUI sur un BOUTON FRONTAL (restaure Nickel). +# Lancé en arrière-plan par monitorink.sh. Remplace l'ancien triple-tap finger_trace qui +# barbouillait l'écran de points noirs. Appui long sur power = reboot matériel = filet de +# secours ultime. +# +# Pourquoi les boutons frontaux et pas le power : sur ce Kobo (mx6sll) le bouton power n'émet +# que des scancodes bruts EV_MSC/MSC_SCAN, parasités par l'état de charge USB (impossible à +# distinguer de façon fiable). Les boutons frontaux, eux, émettent des EV_KEY PROPRES +# (type=1) codes 193 (KEY_F23) et 194 (KEY_F24), press(val=1)/release(val=0) nets, sans aucun +# parasite. On compte 3 appuis (press) sur l'un OU l'autre en moins de 3 s. +# +# NB suspend : les boutons frontaux ne réveillent pas l'appareil du suspend profond (seul le +# power le fait). En usage sur secteur (USB) l'appareil ne suspend pas vraiment -> le watcher +# tourne en continu et le triple-appui marche tout le temps. Sur batterie, réveiller d'abord +# par un appui power, puis faire les 3 appuis frontaux dans la fenêtre d'éveil. BASE="$(dirname "$0")" cd "$BASE" || exit 1 LOG="$BASE/monitorink.log" -FT="./bin/fbink/finger_trace" FBINK="./bin/fbink/fbink" +BB="./bin/busybox_kobo" +DEBUG="${MONITORINK_PWR_DEBUG:-0}" # 1 = logge chaque évènement pour caler device/codes +BTN_A=193 # bouton frontal 1 (KEY_F23) +BTN_B=194 # bouton frontal 2 (KEY_F24) -[ -x "$FT" ] || { echo "[watcher] finger_trace absent" >> "$LOG"; exit 0; } -echo "[watcher] triple-tap reboot actif" >> "$LOG"; sync +# Repère le périphérique input des boutons (gpio-keys). Repli : event0. +find_button_dev() { + for ev in /dev/input/event*; do + [ -e "$ev" ] || continue + case "$(cat "/sys/class/input/${ev##*/}/device/name" 2>/dev/null)" in + *gpio*key*|*power*|*Power*|*pmic*|*keys*) echo "$ev"; return 0 ;; + esac + done + [ -e /dev/input/event0 ] && echo /dev/input/event0 +} -count=0 -last=0 -"$FT" 2>/dev/null | while read -r line; do - case "$line" in - *UP*|*RELEASE*|*Release*) - now=$(date +%s) - [ $((now - last)) -gt 3 ] && count=0 - count=$((count + 1)) - last=$now - echo "[watcher] tap $count/3" >> "$LOG"; sync - if [ "$count" -ge 3 ]; then - echo "[watcher] TRIPLE-TAP -> reboot" >> "$LOG"; sync - "$FBINK" -c -pmh "Redemarrage..." 2>/dev/null - sleep 1; sync - reboot - fi - ;; - esac +graceful_reboot() { + echo "[watcher] TRIPLE-APPUI frontal -> reboot logiciel" >> "$LOG"; sync + "$FBINK" -c -pmh "Redemarrage..." 2>/dev/null + sleep 1; sync + reboot +} + +echo "[watcher] reboot triple-appui frontal actif" >> "$LOG"; sync + +# Boucle de respawn : si le lecteur meurt (réinit input au réveil, fd fermé), on le relance. +# Le watcher ne reste jamais mort -> le reboot logiciel est toujours disponible. +while true; do + DEV="$(find_button_dev)" + if [ -z "$DEV" ]; then + echo "[watcher] aucun input bouton trouvé, retry 5s" >> "$LOG"; sync + sleep 5; continue + fi + echo "[watcher] écoute $DEV" >> "$LOG"; sync + + # IMPORTANT : ouvrir le périphérique UNE seule fois (FD 4) et lire séquentiellement. + # Rouvrir à chaque évènement (dd if=$DEV) crée un gap qui fait perdre des évènements. + # FD persistant = flux continu, zéro perte, zéro buffering. + if ! exec 4< "$DEV"; then + echo "[watcher] ouverture $DEV KO -> retry 5s" >> "$LOG"; sync + sleep 5; continue + fi + + # Lit le flux evdev par enregistrements de 16 octets (struct input_event 32 bits : + # timeval 8o + type 2o + code 2o + value 4o). od -> 16 octets décimaux (little-endian). + count=0 + last=0 + while rec="$("$BB" dd bs=16 count=1 <&4 2>/dev/null | "$BB" od -An -tu1)"; do + [ -z "$rec" ] && break # fd fermé/EOF -> respawn + # shellcheck disable=SC2086 + set -- $rec # $1..$16 + [ "$#" -ge 13 ] || continue + type=$(( $9 + ${10} * 256 )) + code=$(( ${11} + ${12} * 256 )) + val="${13}" + [ "$DEBUG" = 1 ] && { echo "[ft] type=$type code=$code val=$val" >> "$LOG"; sync; } + # On ne compte que les PRESS (val=1) EV_KEY (type=1) d'un bouton frontal. + [ "$type" -eq 1 ] && [ "$val" -eq 1 ] || continue + [ "$code" -eq "$BTN_A" ] || [ "$code" -eq "$BTN_B" ] || continue + now=$(date +%s) + [ $((now - last)) -gt 3 ] && count=0 # reset si > 3 s depuis le dernier appui + count=$((count + 1)) + last=$now + echo "[watcher] appui $count/3" >> "$LOG"; sync + [ "$count" -ge 3 ] && graceful_reboot + done + + exec 4<&- + echo "[watcher] lecteur input arrêté -> respawn" >> "$LOG"; sync + sleep 1 done