#!/system/bin/sh
# vend_run.sh - captura + reenvio AUTONOMO desde la placa WM55L22.
# Hace 3 cosas, sin tocar los puertos (no compite con com.weimi):
#   1) strace -y (root) ESPIA lo que com.weimi lee/escribe en ttyS1/ttyS3 -> log
#   2) cada $INTERVAL seg toma las lineas NUEVAS del log y filtra solo eventos
#      (saldo 0x71, vuelto 0x72, tarjeta 0x28, motor 0x43, estado 0x4a/0x7c)
#   3) las POSTea (curl estatico) a tu servidor recibir_raw.php
# Reusa tu config: el servidor parsea el texto crudo. Si la app reinicia,
# re-engancha el strace. Se pausa si la placa pierde red (wifi wlan0 O ethernet eth0).
#
# AJUSTA estas variables:
URL="https://vending.maexspe.com/recibir_raw.php"
DBURL="https://vending.maexspe.com/recibir_db.php"   # endpoint del inventario
LATIDOURL="https://vending.maexspe.com/latido.php"   # heartbeat (visibilidad: agente vivo)
TOKEN="maexspe-weimi-K7m2Rq9x"
CURL=/data/local/tmp/curl            # curl estatico arm64 que subiste por adb
# La placa NO resuelve DNS desde un binario crudo (Android no le pasa el resolver a
# procesos lanzados por su). Por eso fijamos la IP del servidor y usamos --resolve.
# Si el hosting cambia de IP, actualiza SRVIP (mira: nslookup vending.maexspe.com).
SRVHOST=vending.maexspe.com
SRVIP=66.228.61.234
RESOLVE="--resolve $SRVHOST:443:$SRVIP"
# BD de la maquina (confirma el nombre con: ls /data/data/com.weimi/databases/)
DBPATH=/data/data/com.weimi/databases/Wm2022Database
SQLITE3=/system/bin/sqlite3          # para fusionar el WAL (borrador) antes de subir
# ---------------------------------------------------------------------------
LOG=/data/local/tmp/vend_capture.log
LINEF=/data/local/tmp/vend_forward.line
STATUS=/data/local/tmp/vend_forward.status
CHUNK=/data/local/tmp/vend_forward.chunk
DBTMP=/data/local/tmp/inv.db
INTERVAL=20

# Solo eventos utiles: cabecera EE/FF 01 00 00 00 <seq> <cmd=71|72|28|43|7c|4a>
EVENTS='"\\x(ee|ff)\\x01\\x00\\x00\\x00\\x..\\x(71|72|28|43|7c|4a)'

# --- Una sola instancia -----------------------------------------------------
# El receptor de arranque (VendBoot) y el boton de la app pueden lanzar esto
# varias veces. Si ya hay una instancia viva, salimos para no duplicar strace.
LOCK=/data/local/tmp/vend_run.lock
if [ -f "$LOCK" ]; then
  OLDPID=$(cat "$LOCK" 2>/dev/null)
  # Solo es "ya hay instancia viva" si el PID existe Y su cmdline ES vend_run.sh.
  # BUG 2026-06-19: tras un reboot el PID viejo del lock se RECICLA a otro proceso del
  # sistema -> "kill -0" daba falso positivo -> el script se auto-abortaba sin relanzar
  # la captura (quedo muda 4 dias). Validar la cmdline evita el falso positivo.
  if [ -n "$OLDPID" ] && kill -0 "$OLDPID" 2>/dev/null \
     && tr '\0' ' ' < "/proc/$OLDPID/cmdline" 2>/dev/null | grep -q vend_run.sh; then
    exit 0
  fi
fi
echo $$ > "$LOCK"
# ---------------------------------------------------------------------------

echo "=== vend_run start $(date '+%F %T') ===" >> "$LOG"

# Lanzar tambien el agente de comandos remotos (si esta presente). Tiene su propio
# lock, asi que no se duplica. Asi el arranque del boot levanta captura + agente.
[ -f /data/local/tmp/vend_agent.sh ] && nohup sh /data/local/tmp/vend_agent.sh >/dev/null 2>&1 &

# (1) Watchdog de strace en segundo plano: re-engancha si com.weimi reinicia.
(
  while true; do
    if ! ip -o -4 addr show 2>/dev/null | grep -E 'wlan0|eth0' | grep -q inet; then
      sleep 10; continue
    fi
    PID=$(pgrep -f com.weimi:motor_serial | head -1)
    if [ -n "$PID" ]; then
      echo "=== attach $(date '+%F %T') pid=$PID ===" >> "$LOG"
      strace -y -tt -xx -s 512 -f -e trace=read,write \
        -P /dev/ttyS1 -P /dev/ttyS3 -p "$PID" 2>> "$LOG"
      echo "=== detach $(date '+%F %T') ===" >> "$LOG"
    fi
    sleep 2
  done
) &

# --- Copia de la BD segura ante WAL ----------------------------------------
# La BD de com.weimi esta en modo WAL: los cambios recientes (ventas, rellenos)
# viven en el archivo "-wal" (un cuaderno de borrador) y NO en el archivo
# principal hasta que la app hace "checkpoint". Si copiaramos solo el archivo
# principal, nos perderiamos lo ultimo -> inventario erroneo (bug 2026-06-13).
# Solucion: usar el comando ".backup" de sqlite3. Abre la BD viva como una
# 2da conexion (en WAL no estorba a com.weimi), VE el borrador, y escribe una
# copia limpia y completa en un solo archivo. Deja $DBTMP listo para subir.
# Devuelve 1 si no se pudo generar la copia.
copia_bd() {
  rm -f "$DBTMP" "$DBTMP-wal" "$DBTMP-shm" 2>/dev/null
  # La BD de com.weimi vive en el mount-namespace de SU app y bajo SELinux enforcing: el
  # agente (lo lanza NUESTRA app) NO la ve. Cruzamos ambas barreras con runcon (gafete su
  # + todas las categorias MLS) + nsenter -t 1 -m (entrar al namespace del init, donde SI
  # se ve /data/data/com.weimi). Verificado 2026-06-16. El .backup escribe $DBTMP en
  # /data/local/tmp con etiqueta legible por el agente -> luego se sube por curl.
  PRIV="runcon u:r:su:s0:c0.c1023 nsenter -t 1 -m --"
  if [ -x "$SQLITE3" ]; then
    $PRIV "$SQLITE3" "$DBPATH" ".backup $DBTMP" 2>/dev/null
    [ -s "$DBTMP" ] && return 0
    echo "$(date '+%F %T') WARN: .backup (runcon+nsenter) fallo, uso copia cruda" >> "$LOG"
  else
    echo "$(date '+%F %T') WARN: sin sqlite3, inventario puede ir incompleto por WAL" >> "$LOG"
  fi
  $PRIV cp "$DBPATH" "$DBTMP" 2>/dev/null && [ -s "$DBTMP" ] && return 0
  return 1
}

# (1b) Watchdog del AGENTE + adb, en segundo plano. El agente (vend_agent.sh) es el
# canal remoto por HTTPS; si se cae, perdemos el control remoto. Y com.ztl.helper
# (anti-tamper del fabricante) apaga el adb solo. Cada 120s: si el agente no esta vivo,
# lo relanza; si el adb quedo apagado, lo re-habilita (la maquina es del operador).
(
  while true; do
    sleep 120
    # --- agente ---
    if [ -f /data/local/tmp/vend_agent.sh ] && [ -z "$(pgrep -f /data/local/tmp/vend_agent.sh)" ]; then
      rm -f /data/local/tmp/vend_agent.lock            # quitar lock posiblemente rancio
      nohup sh /data/local/tmp/vend_agent.sh >/dev/null 2>&1 &
      echo "$(date '+%F %T') watchdog: agente caido -> relanzado" >> "$LOG"
    fi
    # --- adb (solo si quedo apagado, para no reiniciar adbd en vano) ---
    if [ "$(settings get global adb_enabled 2>/dev/null)" != "1" ]; then
      settings put global development_settings_enabled 1 2>/dev/null
      settings put global adb_enabled 1 2>/dev/null
      setprop persist.adb.tcp.port 5555 2>/dev/null
      setprop service.adb.tcp.port 5555 2>/dev/null
      setprop ctl.restart adbd 2>/dev/null
      echo "$(date '+%F %T') watchdog: adb estaba apagado -> re-habilitado (5555)" >> "$LOG"
    fi
    # --- mantener NUESTRA app fuera del "estado detenido" (sin abrir pantalla) ---
    # Un broadcast a nuestro receptor con --include-stopped-packages la "despierta" y
    # limpia FLAG_STOPPED, para que un reboot posterior SI dispare BOOT_COMPLETED.
    # Accion propia (no protegida) -> root puede enviarla sin problemas.
    am broadcast --include-stopped-packages \
      -n com.maexspe.monitor/.BootReceiver -a com.maexspe.monitor.WAKE >/dev/null 2>&1
    # --- latido al servidor (visibilidad: el panel sabe que seguimos vivos) ---
    BAT=$(dumpsys battery 2>/dev/null | sed -n 's/.*level: *\([0-9][0-9]*\).*/\1/p' | head -1)
    IPW=$(ip -o -4 addr show wlan0 2>/dev/null | awk '{print $4}'; ip -o -4 addr show eth0 2>/dev/null | awk '{print $4}')
    AGV=0; [ -n "$(pgrep -f /data/local/tmp/vend_agent.sh)" ] && AGV=1
    "$CURL" -k -s -m 20 $RESOLVE -X POST \
      --data "ts=$(date '+%F %T')&bat=$BAT&ip=$IPW&agente=$AGV" \
      "$LATIDOURL?token=$TOKEN" >/dev/null 2>&1
  done
) &

# (3b) Detector de MOVIMIENTO de inventario. Cada $CHECKINV seg consulta (LOCAL, sin SIM)
# un "hash" del stock; si CAMBIO (venta en efectivo, RELLENO, ajuste manual) sube la BD al
# toque, sin esperar el sondeo de 15 min. Solo sube cuando de verdad hubo cambio. La
# consulta es liviana -> $CHECKINV se puede bajar a 10s sin problema (la placa lo aguanta;
# lo que gasta SIM es la SUBIDA, que solo ocurre ante un cambio real). El hash pondera por
# uid -> cambia si cambia CUALQUIER slot (no se cancela una baja con un relleno en otro).
CHECKINV=15
DBTMP2=/data/local/tmp/wm_chg.db
PRIV2="runcon u:r:su:s0:c0.c1023 nsenter -t 1 -m --"
(
  LASTH=""
  while true; do
    sleep "$CHECKINV"
    # OJO: NO usar [ -f "$DBPATH" ] aqui: vend_run corre en el namespace de la app, donde
    # /data/data/com.weimi NO se ve. El query/backup cruzan el muro con nsenter; el guard
    # daria falso y nunca subiria. Si la BD no esta, el query devuelve vacio -> continue.
    H=$($PRIV2 "$SQLITE3" "$DBPATH" "SELECT IFNULL(SUM(currStock*(uid+1)),-1)||'/'||IFNULL(SUM(currStock),-1) FROM AisleEntity" 2>/dev/null)
    [ -z "$H" ] && continue
    if [ -n "$LASTH" ] && [ "$H" != "$LASTH" ]; then
      rm -f "$DBTMP2" "$DBTMP2-wal" "$DBTMP2-shm" 2>/dev/null
      if $PRIV2 "$SQLITE3" "$DBPATH" ".backup $DBTMP2" 2>/dev/null && [ -s "$DBTMP2" ]; then
        "$CURL" -k -s -m 60 $RESOLVE -X POST --data-binary @"$DBTMP2" "$DBURL?token=$TOKEN" > "$STATUS" 2>&1
        echo "$(date '+%F %T') inventario subido (movimiento $LASTH->$H) resp=$(cat "$STATUS")" >> "$LOG"
      fi
    fi
    LASTH="$H"
  done
) &

# (2)+(3) Reenviador: solo lineas nuevas (por numero de linea), filtradas, via curl.
N=$(cat "$LINEF" 2>/dev/null || echo 0)
case "$N" in ''|*[!0-9]*) N=0 ;; esac

while true; do
  sleep "$INTERVAL"
  [ -f "$LOG" ] || continue
  TOTAL=$(wc -l < "$LOG" 2>/dev/null || echo 0)
  # si el log se achico (rotacion/reinicio), empezar de cero
  [ "$TOTAL" -lt "$N" ] && N=0
  [ "$TOTAL" -le "$N" ] && continue

  sed -n "$((N+1)),${TOTAL}p" "$LOG" 2>/dev/null \
    | grep -E "$EVENTS" > "$CHUNK" 2>/dev/null

  if [ -s "$CHUNK" ]; then
    # -k = no verifica el certificado (curl estatico sin CA bundle); sigue cifrado.
    "$CURL" -k -s -m 30 $RESOLVE -X POST --data-binary @"$CHUNK" \
      "$URL?token=$TOKEN" > "$STATUS" 2>&1
    echo "$(date '+%F %T') enviadas=$(wc -l < "$CHUNK") resp=$(cat "$STATUS")" >> "$LOG"

    # Subir la BD (=inventario real) cuando hubo una VENTA, para que el DETALLE por
    # producto (delta de inventario que calcula recibir_db.php) aparezca JUSTO despues de
    # la venta y no recien en la proxima subida periodica. Dispara con:
    #   (a) motor 0x43 (sondeo periodico / venta en efectivo), o
    #   (b) un TOKEN cashless = 0x28 con el prefijo de emisor 6117 8163. Las ventas QR/
    #       tarjeta dejan ese token; el poll de tarjeta en reposo va en ceros (no matchea).
    # Si el emisor cambiara el prefijo, ajustar TOKENHEX (mira ver.php: hex de un 0x28 real).
    TOKENHEX='\x61\x17\x81\x63'
    # NO chequear [ -f "$DBPATH" ]: vend_run vive en el namespace de la app y ahi la BD no
    # se ve (copia_bd la alcanza con runcon+nsenter). Ese guard rompia el auto-upload.
    if grep -qF '\x43\x00\x02' "$CHUNK" || grep -qF "$TOKENHEX" "$CHUNK"; then
      sleep 6                                  # dar tiempo al despacho + a que reste el stock
      if copia_bd; then
        "$CURL" -k -s -m 60 $RESOLVE -X POST --data-binary @"$DBTMP" \
          "$DBURL?token=$TOKEN" > "$STATUS" 2>&1
        echo "$(date '+%F %T') inventario subido (venta) resp=$(cat "$STATUS")" >> "$LOG"
      fi
    fi
  fi

  N=$TOTAL
  echo "$N" > "$LINEF"
done
