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.
212 lines
7.3 KiB
Bash
Executable File
212 lines
7.3 KiB
Bash
Executable File
#!/bin/sh
|
|
# Monitorink — boucle d'affichage e-ink sur Kobo Libra 2.
|
|
#
|
|
# Overlay sur https://github.com/usetrmnl/trmnl-kobo : réutilise ses binaires ARM
|
|
# (bin/fbink, bin/busybox_kobo) et ses helpers WiFi (scripts/*.sh). On remplace l'appel
|
|
# API TRMNL par un simple fetch de notre image de dashboard.
|
|
# Lancé par monitorink.sh (via NickelMenu). Logs -> ../monitorink.log
|
|
|
|
BASE="$(dirname "$0")"
|
|
cd "$BASE" || exit 1
|
|
|
|
IMAGE_URL="${MONITORINK_URL:-https://monitorink.homelab.nestor-server.fr/image.png}"
|
|
# Endpoints du refresh partiel, dérivés de l'URL image (.../image.png -> .../frame.meta|frame.png).
|
|
BASE_URL="${IMAGE_URL%/image.png}"
|
|
META_URL="$BASE_URL/frame.meta"
|
|
FRAME_URL="$BASE_URL/frame.png"
|
|
CLIENT="${MONITORINK_CLIENT:-kobo}"
|
|
REFRESH="${MONITORINK_REFRESH:-600}"
|
|
TMP="/tmp/monitorink.png"
|
|
|
|
FBINK="./bin/fbink/fbink"
|
|
BUSYBOX="./bin/busybox_kobo"
|
|
|
|
log() { echo "[$(date '+%H:%M:%S')] $*"; sync; }
|
|
|
|
read_battery() {
|
|
# Renvoie "CAP|CHG" (ex. "85|0"), CHG=1 si en charge. Vide si introuvable.
|
|
# On lit capacity + status dans le même dossier /sys/class/power_supply/*.
|
|
for d in /sys/class/power_supply/*/; do
|
|
[ -r "${d}capacity" ] || continue
|
|
cap=$(cat "${d}capacity" 2>/dev/null)
|
|
chg=0
|
|
if [ -r "${d}status" ]; then
|
|
case "$(cat "${d}status" 2>/dev/null)" in
|
|
Charging|Full) chg=1 ;;
|
|
esac
|
|
fi
|
|
echo "${cap}|${chg}"
|
|
return 0
|
|
done
|
|
echo ""
|
|
}
|
|
|
|
bat_query() {
|
|
# Renvoie "bat=CAP&chg=CHG" pour pousser la batterie au backend (vide si introuvable).
|
|
bat="$(read_battery)"
|
|
if [ -n "$bat" ]; then
|
|
cap="${bat%%|*}"; chg="${bat##*|}"
|
|
echo "bat=${cap}&chg=${chg}"
|
|
fi
|
|
}
|
|
|
|
http_get() {
|
|
# $1=url, $2=fichier de sortie ("-" = stdout). busybox wget puis fallback curl.
|
|
url="$1"; out="$2"
|
|
if [ "$out" = "-" ]; then
|
|
"$BUSYBOX" wget -q -T 30 -O - "$url" 2>/dev/null && return 0
|
|
command -v curl >/dev/null 2>&1 && curl -fsSL -m 30 "$url" 2>/dev/null && return 0
|
|
else
|
|
"$BUSYBOX" wget -q -T 30 -O "$out" "$url" 2>/dev/null && return 0
|
|
command -v curl >/dev/null 2>&1 && curl -fsSL -m 30 -o "$out" "$url" 2>/dev/null && return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
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" -
|
|
}
|
|
|
|
# Ferme les FD hérités pour ne pas bloquer l'éjection USB.
|
|
exec 3>&- 2>/dev/null
|
|
|
|
log "boucle démarrée — BASE=$BASE URL=$IMAGE_URL refresh=${REFRESH}s"
|
|
log "fbink présent: $([ -x "$FBINK" ] && echo oui || echo NON) ; busybox: $([ -x "$BUSYBOX" ] && echo oui || echo NON)"
|
|
|
|
display_full() {
|
|
# Full refresh : clear + waveform complète (le "flash" e-ink), efface le ghosting.
|
|
"$FBINK" -g file="$TMP",valign=CENTER,halign=CENTER -c -f
|
|
log "display full rc=$?"
|
|
}
|
|
|
|
display_partial() {
|
|
# Refresh partiel : dessine le crop à l'offset (x,y) ; ni -c ni -f -> seule cette zone est
|
|
# rafraîchie, en waveform partielle (sans flash). $1=x $2=y (coords portrait, origine HG).
|
|
"$FBINK" -g file="$TMP",x="$1",y="$2"
|
|
log "display partial x=$1 y=$2 rc=$?"
|
|
}
|
|
|
|
offline() {
|
|
log "hors ligne"
|
|
"$FBINK" -pmh "Monitorink hors ligne ($(date '+%H:%M'))"
|
|
}
|
|
|
|
show_frame() {
|
|
# Récupère le crop/full image stocké côté serveur et l'affiche selon le mode.
|
|
# $1=mode $2=x $3=y
|
|
if ! http_get "$FRAME_URL?client=$CLIENT" "$TMP"; then
|
|
log "frame.png KO (mode=$1)"
|
|
[ "$1" = "full" ] && offline
|
|
return 1
|
|
fi
|
|
log "frame.png OK ($(wc -c < "$TMP" 2>/dev/null) octets)"
|
|
if [ "$1" = "partial" ]; then
|
|
display_partial "$2" "$3"
|
|
else
|
|
display_full
|
|
fi
|
|
}
|
|
|
|
frontlight_off() {
|
|
# Éteint le rétroéclairage (frontlight). Le nœud sysfs exact dépend du modèle Kobo,
|
|
# donc on écrit 0 dans tous les contrôleurs présents.
|
|
for b in /sys/class/backlight/*/brightness; do
|
|
[ -w "$b" ] && echo 0 > "$b" 2>/dev/null
|
|
done
|
|
}
|
|
|
|
suspend_for() {
|
|
# Séquence Kobo éprouvée (trmnl-kobo) : state-extended=1 AVANT rtcwake, puis
|
|
# suspend manuel (echo mem) si rtcwake n'a pas vraiment suspendu.
|
|
secs="$1"
|
|
sync
|
|
echo 1 > /sys/power/state-extended 2>/dev/null
|
|
start=$(date +%s)
|
|
"$BUSYBOX" rtcwake -a -s "$secs" -m mem 2>/dev/null
|
|
elapsed=$(( $(date +%s) - start ))
|
|
log "rtcwake elapsed=${elapsed}s"
|
|
if [ "$elapsed" -le 10 ]; then
|
|
log "suspend manuel via echo mem"
|
|
sleep 1; sync; sleep 2
|
|
m_start=$(date +%s)
|
|
echo mem > /sys/power/state 2>/dev/null
|
|
log "echo mem elapsed=$(( $(date +%s) - m_start ))s (≈${secs}=OK, ≈0=USB branché/suspend bloqué)"
|
|
fi
|
|
echo 0 > /sys/power/state-extended 2>/dev/null
|
|
# Repli : si rien n'a réellement suspendu (USB branché, etc.), on attend le temps
|
|
# restant en sleep pour ne pas boucler en continu (martèlement backend + batterie).
|
|
total=$(( $(date +%s) - start ))
|
|
if [ "$total" -lt "$secs" ]; then
|
|
rem=$(( secs - total ))
|
|
log "suspend incomplet (${total}s) -> sleep ${rem}s"
|
|
sleep "$rem"
|
|
fi
|
|
}
|
|
|
|
has_ip() { ip addr show 2>/dev/null | grep -o 'inet [0-9.]*' | grep -qv '127.0'; }
|
|
|
|
wifi_up() {
|
|
./scripts/enable-wifi.sh >/dev/null 2>&1
|
|
./scripts/force-wifi-connection.sh >/dev/null 2>&1
|
|
# Attend l'association + bail DHCP (jusqu'à ~24 s).
|
|
i=0
|
|
while [ "$i" -lt 12 ]; do
|
|
./scripts/obtain-ip.sh >/dev/null 2>&1
|
|
has_ip && return 0
|
|
sleep 2; i=$((i + 1))
|
|
done
|
|
return 1
|
|
}
|
|
|
|
wifi_down() {
|
|
# Coupe la radio avant le suspend (helpers trmnl, env Nickel siphonné en amont).
|
|
./scripts/release-ip.sh >/dev/null 2>&1
|
|
./scripts/disable-wifi.sh >/dev/null 2>&1
|
|
}
|
|
|
|
# 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
|
|
./scripts/ledToggle.sh on 2>/dev/null
|
|
|
|
wifi_up
|
|
|
|
meta="$(fetch_meta)"
|
|
if [ -z "$meta" ]; then
|
|
# WiFi peut-être tombé : on tente une reconnexion puis un re-essai.
|
|
log "meta KO -> reconnexion WiFi"
|
|
wifi_up
|
|
meta="$(fetch_meta)"
|
|
fi
|
|
|
|
if [ -n "$meta" ]; then
|
|
# shellcheck disable=SC2086
|
|
set -- $meta # MODE X Y W H SEQ
|
|
mode="$1"; mx="$2"; my="$3"
|
|
log "meta: mode=$mode x=$mx y=$my w=$4 h=$5 seq=$6"
|
|
case "$mode" in
|
|
noop) log "aucun changement -> pas de refresh" ;;
|
|
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
|
|
fi
|
|
|
|
./scripts/ledToggle.sh off 2>/dev/null
|
|
wifi_down # coupe la radio avant le suspend
|
|
suspend_for "$REFRESH" # rtcwake -m mem, WiFi éteint pendant ~5 min
|
|
done
|