Files
biblioteca_conocimiento_lab…/docs/lecciones_aprendidas_vigilancia.md

14 KiB

Lecciones Aprendidas — Sistema de Vigilancia

Fecha: 02 agosto 2026

Hardware

  1. Raspberry Pi 3 NO soporta USB gadget — eso es solo Pi Zero/Zero 2/CM4. La Pi 3 necesita red (WiFi/Ethernet) para acceso remoto.

  2. Undervoltage en Pi 3: LED rojo parpadeante = cargador insuficiente. Necesita 5V/2.5A dedicado. Un cargador de móvil débil puede causar inestabilidad, reinicios y fallos en WiFi/USB.

  3. numpy 2.5 no compila en ARM (Pi 3): Las extensiones C fallan en armhf. Solución: usar math puro de Python para generar audio, o instalar numpy desde apt (python3-numpy si está disponible).

  4. OpenCV-python no compila en ARM: pip instala opencv-python-headless pero numpy roto impide su uso. Solución: usar ffmpeg (apt install ffmpeg) para capturar frames y procesar multimedia sin dependencias Python pesadas.

Red y WiFi

  1. DHCP por WiFi puede fallar en Pi 3: Aunque el router da DHCP en eth0, wlan0 puede quedarse en link-local (169.254.x.x). Solución: IP fija por nmcli (nmcli connection modify <uuid> ipv4.method manual ipv4.addresses 192.168.50.251/24).

  2. netplan SÍ se aplica en Raspberry Pi OS Trixie: Las configuraciones de /etc/netplan/*.yaml son generadas por cloud-init y NetworkManager. La config network-config del boot partition se aplica.

  3. PEP 668 en Raspberry Pi OS: pip install requiere --break-system-packages o usar un venv.

SSH y Acceso Remoto

  1. ProxyJump SSH: Desde el portátil, ssh minipc conecta vía VPS (ProxyJump vps) → Pi. Config en ~/.ssh/config. El VPS usa la llave C:\Users\juanm\Documents\GitHub\contabo.

  2. Autossh tunnel inverso: autossh -M 0 -N -R 2222:localhost:22 -R 8600:localhost:8600 root@VPS. Necesita ServerAliveInterval 30 y ExitOnForwardFailure yes.

  3. Clave SSH de la Pi en el VPS: La línea completa ssh-ed25519 <base64> debe estar en /root/.ssh/authorized_keys. Si solo se pone el base64 sin el prefijo, la autenticación falla.

Telegram Bot

  1. Canal IDD vs bot privado: Si hay múltiples bots usando el mismo token, compiten por getUpdates. Solución: bot dedicado para la Pi (@juanjer_casa_bot).

  2. Canal de Telegram vs bot: Los mensajes en canales llegan como channel_post, no message. El bot debe manejar ambos: upd.get("message") or upd.get("channel_post").

  3. systemd y stdout de threads: El stdout de threads daemon no siempre aparece en journalctl. Para debugging: logear a un archivo (bot.log).

TV y AndroidTVRemote

  1. ADB no funciona en TCL Smart TV Pro: No muestra el aviso de autorización por red (wireless debugging). Se revocaron autorizaciones, se toggled USB debugging, se intentó con claves del portátil — nada. Limitación del modelo.

  2. AndroidTVRemote2 funciona: Protocolo oficial de Google (emparejamiento por PIN TLS en puerto 6467). La API es: remote.is_on (propiedad), remote.send_key_command("POWER") (síncrono, NO async). El certificado se guarda en tv_cert/ y es persistente (no caduca con reinicios).

  3. POWER es toggle: No es unidireccional. Si la TV está encendida se apaga, si está apagada se enciende. Para controlarlo: verificar is_on antes de enviar POWER.

Sirena y Audio

  1. numpy roto en ARM → sirena con math: El pitido de emergencia (wail) se sintetiza con math.sin en bucle, sin numpy. Funciona bien.

  2. pychromecast con netplan: Las conexiones de netplan usan netplan-wlan0-*. El DHCP puede fallar en wlan0. Solución: IP fija o usar eth0.

  3. ffmpeg funciona en ARM (paquete apt): Versión 7.1.5 compilada para armhf. Funciona para capturar frames RTSP y procesar multimedia.

Despliegue

  1. systemd en Pi: La forma fiable de escribir la unidad es via SFTP + sudo cp (el tee con sudo -S consume el stdin y la unidad no se escribe).

  2. PEP 668: pip3 install --user --break-system-packages necesario en Raspberry Pi OS para instalar paquetes Python.

  3. cloud-init: El user-data y network-config se procesan en el primer arranque. Si el primer arranque ya pasó, hay que re-grabar la SD para que se apliquen los cambios.

  4. userconf.txt: Método fiable para crear el usuario pi en el primer arranque. Se escribe en la partición boot: pi:<hash_sha512>. Funciona incluso si cloud-init falla.

Cámaras

  1. Panorámica con ffmpeg: Sin OpenCV en la Pi, se usan ffmpeg -i <rtsp_url> -frames:v 1 snapshot.jpg para capturar frames de las cámaras Tapo.

Monitoreo

  1. Bot de Telegram en la Pi: Responde a estado, temperatura, tv, alarma, youtube, panorama. Usa requests para getUpdates (long polling) y sendMessage/sendPhoto para responder.

  2. Alerta de temperatura: Monitor en background (cada 5 min) que envía alerta si supera el umbral (80°C default).

Bugfixes (02 agosto tarde)

  1. El bot corría una copia vieja (unit systemd): pi-bot.service apuntaba a /home/pi/minipc/pi_bot.py (raíz, sin limpiar), mientras el código nuevo estaba en /home/pi/minipc/vigilancia/pi_bot.py. El comando limpiar no funcionaba porque el bot que corría NO lo tenía. Al desplegar archivos nuevos hay que revisar el unit de systemd (systemctl cat pi-bot / tv-api). Fix: actualizar WorkingDirectory y ExecStart a vigilancia/ + daemon-reload + restart.

  2. Telegram reprocesa en ráfaga al perder el offset: El bot guardaba offset solo en memoria. Al reiniciar (offset=None) getUpdates devuelve todos los updates pendientes no confirmados y los re-ejecuta de golpe (disparó panorama, tv, youtube, limpieza repetidos). Fix: persistir el offset en un archivo (bot.offset) con cargar_offset()/guardar_offset(), guardando tras cada update. Al desplegar, capturar el último update con getUpdates?offset=-1 para no reprocesar.

  3. pychromecast en proceso long-running deja zeroconf roto: El comando tv generaba el audio (anuncio.mp3) pero la TV no reproducía: AssertionError: Zeroconf instance loop must be running, was it already stopped? y Failed to connect to service ... retrying. Causa: cada get_chromecasts() deja al objeto Chromecast con threads de reconexión que usan un zeroconf ya parado tras stop_discovery(). Funciona en proceso limpio pero falla en tv-api long-running. Fix: desconectar cada cast (cast.disconnect()) antes de stop_discovery() en los 3 puntos (anuncio_en_teles, youtube_en_teles, anuncio_simple).

Acceso a la Pi

  1. SSH por password con paramiko: Desde el portátil la llave no está configurada para pi, pero user pi / password raspberry funciona via paramiko a 192.168.50.187 (y .251). El sudo necesita echo 'raspberry' | sudo -S <cmd>. Para escribir archivos con sudo tee sin romper el quoting, usar base64: echo <b64> | base64 -d > archivo.

  2. stdout de tv-api en systemd está buffered: Los print() de tv_api no salen en journalctl hasta flush. Para ver el resultado del casting, usar un proceso limpio o logear a archivo.

Reboot resiliente (02 agosto noche)

  1. El túnel autossh debe ser servicio systemd, no proceso manual: El autossh corría como proceso manual y NO se reiniciaba al reboot → la Pi quedaba inaccesible desde fuera. Convertido a /etc/systemd/system/autossh-tunel.service (enabled). Comando real del túnel: autossh -M 0 -N -o ServerAliveInterval=30 ... -R 2222:localhost:22 -R 8600:localhost:8600 root@VPS.

  2. Sintaxis de opciones de autossh: Debe ser -o ServerAliveInterval=30 (con =). Con espacio (-o ServerAliveInterval 30) ssh falla con no argument after keyword serveraliveinterval y entra en bucle de restart. Cuando hay 2 túneles compitiendo da remote port forwarding failed for listen port 2222 — limpiar TODOS los procesos (pkill -9 -f autossh y pkill -9 -f "ssh.*-R") antes de relanzar el servicio.

  3. Comandos Telegram para operar la Pi: Añadidos /servicios (estado pi-bot, tv-api, autossh-tunel + túnel) y /reboot/confirmar-reboot (reinicia la Pi con doble confirmación desde Telegram). Probado con reboot real: tras 1 min la Pi vuelve y todos los servicios + túnel operan solos.

  4. Tras un reboot real se valida todo: pi-bot, tv-api y autossh-tunel están enabled (arrancan solos). El bot conserva el offset en bot.offset así que no reprocesa nada. La TV, el túnel y el acceso remoto quedan operativos sin intervención manual.

Cámaras y localización (02 agosto noche)

  1. Localización de cámaras Tapo por escaneo de red: Escanear puertos 554 (RTSP) y 2020 (api Onvif/HC) en el rango 192.168.50.x. Para confirmar, probar ffmpeg -rtsp_transport tcp -i rtsp://<u>:<p>@<ip>:554/stream2 -frames:v 1 snapshot.jpg.
  • .227 = Salón (hermes211@gmail.com / juanah78)
  • .61 = Dormitorio (hermes211@gmail.com / Nelo@36666)
  • .134 = Niñas (jjminguez / juanah78)
  • .31 = NO es cámara: Synology DiskStation DSM (puerto 5000), RTSP 554 = Surveillance Station del NAS.
  1. Cada cámara Tapo tiene credenciales RTSP propias: No comparten usuario/pass necesariamente. El usuario RTSP puede ser el correo de la cuenta (hermes211%40gmail.com, con %40) o un usuario de cámara (jjminguez). Probarlas una a una con ffmpeg.

Videoclub / NAS Synology (03-04 agosto)

  1. DSM 7 exige _sid como query parameter en las APIs de Download Station (GET): task.cgi?api=...&method=...&_sid=<sid>. Enviar la cookie sid=<x> sola devuelve error 105 ("sin permiso") y FileStation da 403. El login con format=cookie devuelve el sid correctamente, pero hay que pasarlo como _sid en la URL de cada llamada.

  2. El error 105 de Download Station es de sesión/permiso, no del usuario: Con _sid en query funciona aunque el usuario jjminguez no sea admin. list de tareas devuelve 13 tareas (Lilo y Stitch, BLUELOCK, Heidi, etc.), todas del usuario jjminguez.

  3. Despliegue de archivos a la Pi sin git: scp directo al túnel inverso falla (scp: Connection closed). Fiable: copiar al VPS (scp local→VPS funciona), luego base64 expandido en el VPS: echo $(base64 -w0 /tmp/f.py) | base64 -d > /tmp/f.py (los $() inline se expanden en el VPS antes de llegar a la Pi) y sudo cp al destino. Verificar con md5sum.

  4. Unidad NAS montada en sobremesa via túnel SSH encadenado: La NAS (192.168.50.31) está en otra red que el sobremesa (192.168.1.239). Se montó Z:\\127.0.0.1\video con: (a) la Pi reenvía SMB del NAS al VPS por autossh: -R 1445:192.168.50.31:445; (b) túnel local en el PC: ssh -L 445:127.0.0.1:1445 root@185.187.169.109; (c) net use Z: \\127.0.0.1\video. Windows SMB usa SIEMPRE el puerto 445 — si el túnel escucha en otro puerto, net use da error 67 ("nombre de red no encontrado"). Script: vigilancia/montar_nas.ps1 + acceso directo en Startup (la tarea programada exige admin).

  5. Reiniciar autossh de la Pi: systemctl restart autossh-tunel puede fallar con remote port forwarding failed for listen port 2222 porque queda un proceso autossh viejo con los puertos. Fix: pkill -f autossh + pkill -f "ssh.*-R" y luego restart. Al matar el túnel se corta la propia conexión SSH (hay que reconectar).

  6. Comando pelis en pi_bot: Lista novedades con buena calidad en castellano del catálogo Torrentio/RealDebrid con el comando descargar <url> ya montado para copiar/pegar. Filtros: castellano = regex Esp|Castellano|Span|Spanish|dual|lat|ES|audio; calidad mala excluida = CAMRip|TS.|HDTS|Telesync|KinoRip|Screener|HDCAM|r5; sin límite de año (se quitó el ano_min=2024 — el usuario pidió no limitar). Se trocea en mensajes <=3500 chars (Telegram corta a ~4096). Adultos van en mensaje efímero que se borra a los 5 min (threading.Timer(300, ...) + deleteMessage) y NO se loguean. Módulo autónomo: vigilancia/pelis.py (lee os.environ primero, luego .env).

  7. El token RD y las credenciales NAS están en el .env de la Pi (/home/pi/minipc/vigilancia/.env): TORRENTIO_RD_TOKEN, NAS_HOST=192.168.50.31, NAS_PORT=5000, NAS_USER, NAS_PASS. Hubo un bug previo donde NAS_HTTPS=0TORRENTIO_RD_TOKEN=... quedaron pegados en una línea — ojo al editar el .env por SSH.

  8. Comando series en pi_bot: Como pelis pero solo series del catálogo, con el comando descargar <url> listo. La detección de castellano en series no sirve igual que en películas (sus nombres no traen marcadores Esp/Castellano). Criterio: la config ya es language=spanish, así que se aceptan las series con marcador positivo o inglés (INGLES: Eng, English, Inglés, EN) o neutro, y se excluyen las que tienen marcador claro de otro idioma (OTRO_IDIOMA: Ita\b, MIRCrew, RO\s?EN, South Wind, Silver Zero, Francais, Deutsch, Português). seleccionar_series() + generar_series() en vigilancia/pelis.py.

  9. Persistencia en la Pi: Los servicios pi-bot, tv-api y autossh-tunel están enabled (arrancan solos al boot). El código desplegado vive en /home/pi/minipc/vigilancia/ (dueño pi), no en /tmp. Verificar siempre systemctl is-enabled tras desplegar.

  10. Comando serie &lt;nombre&gt; en pi_bot: Busca una serie por nombre en el catálogo y lista TODAS sus temporadas/capítulos con el enlace descargar <url> debajo de cada uno. Funciones nuevas en pelis.py: SERIE_EP_RE/CAP_RE (patrones S01E05 y Cap.X), parsear_episodio(), serie_base() (título base antes del marcador), _limpiar_titulo_ep() (quita extensión, calidad, idiomas, \dx\d\d, corchetes y tokens sucios), agrupar_por_serie(), formatear_episodio(), buscar_serie(). generar_series() ahora agrupa por serie con sus capítulos debajo (2 mensajes ~3750 chars total). Los EXTRA_SUCIO se ampliaron con webdl, x264/x265, aac, resoluciones, spanish, cast/dual/lat, es/en, audio/sub/subs, web/dl.

  11. Despliegue de ficheros a la Pi fiable: NO usar echo $b64 | ssh VPS "sshpass ssh pi 'base64 -d | bash'" encadenado con comillas anidadas (rompe el base64 con invalid input). Método robusto: (1) mandar el fichero como base64 por stdin: Write-Output $b64 | ssh VPS "sshpass ssh pi 'cat > /tmp/x.b64'"; (2) mandar un script pequeño (también por stdin base64) que haga base64 -d /tmp/x.b64 > destino && python3 -m py_compile && systemctl restart. El base64: invalid input que sale al final es ruido residual del pipe, no error real.