# 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 5. **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 ipv4.method manual ipv4.addresses 192.168.50.251/24`). 6. **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. 7. **PEP 668 en Raspberry Pi OS**: pip install requiere `--break-system-packages` o usar un venv. ### SSH y Acceso Remoto 8. **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`. 9. **Autossh tunnel inverso**: `autossh -M 0 -N -R 2222:localhost:22 -R 8600:localhost:8600 root@VPS`. Necesita `ServerAliveInterval 30` y `ExitOnForwardFailure yes`. 10. **Clave SSH de la Pi en el VPS**: La línea completa `ssh-ed25519 ` debe estar en `/root/.ssh/authorized_keys`. Si solo se pone el base64 sin el prefijo, la autenticación falla. ### Telegram Bot 11. **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`). 12. **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")`. 13. **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 14. **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. 15. **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). 16. **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 17. **numpy roto en ARM → sirena con math**: El pitido de emergencia (wail) se sintetiza con `math.sin` en bucle, sin numpy. Funciona bien. 18. **pychromecast con netplan**: Las conexiones de netplan usan `netplan-wlan0-*`. El DHCP puede fallar en wlan0. Solución: IP fija o usar eth0. 19. **ffmpeg funciona en ARM** (paquete apt): Versión 7.1.5 compilada para armhf. Funciona para capturar frames RTSP y procesar multimedia. ### Despliegue 20. **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). 21. **PEP 668**: `pip3 install --user --break-system-packages` necesario en Raspberry Pi OS para instalar paquetes Python. 22. **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. 23. **userconf.txt**: Método fiable para crear el usuario `pi` en el primer arranque. Se escribe en la partición boot: `pi:`. Funciona incluso si cloud-init falla. ### Cámaras 24. **Panorámica con ffmpeg**: Sin OpenCV en la Pi, se usan `ffmpeg -i -frames:v 1 snapshot.jpg` para capturar frames de las cámaras Tapo. ### Monitoreo 25. **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. 26. **Alerta de temperatura**: Monitor en background (cada 5 min) que envía alerta si supera el umbral (80°C default). ### Bugfixes (02 agosto tarde) 27. **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. 28. **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. 29. **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 30. **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 `. Para escribir archivos con `sudo tee` sin romper el quoting, usar **base64**: `echo | base64 -d > archivo`. 31. **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) 32. **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`. 33. **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. 34. **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. 35. **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) 36. **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://:

@: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. 37. **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) 38. **DSM 7 exige `_sid` como query parameter** en las APIs de Download Station (GET): `task.cgi?api=...&method=...&_sid=`. Enviar la cookie `sid=` 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. 39. **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`. 40. **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`. 41. **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). 42. **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). 43. **Comando `pelis` en pi_bot**: Lista novedades con buena calidad en castellano del catálogo Torrentio/RealDebrid con el comando `descargar ` ya montado para copiar/pegar. Filtros: castellano = regex `Esp|Castellano|Span|dual|lat`; calidad mala excluida = `CAMRip|TS.|HDTS|Telesync|KinoRip|Screener|HDCAM`; año >= 2024. 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`). 44. **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.