14 KiB
Lecciones Aprendidas — Sistema de Vigilancia
Fecha: 02 agosto 2026
Hardware
-
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.
-
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.
-
numpy 2.5 no compila en ARM (Pi 3): Las extensiones C fallan en armhf. Solución: usar
mathpuro de Python para generar audio, o instalar numpy desde apt (python3-numpysi está disponible). -
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
-
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). -
netplan SÍ se aplica en Raspberry Pi OS Trixie: Las configuraciones de
/etc/netplan/*.yamlson generadas por cloud-init y NetworkManager. La confignetwork-configdel boot partition se aplica. -
PEP 668 en Raspberry Pi OS: pip install requiere
--break-system-packageso usar un venv.
SSH y Acceso Remoto
-
ProxyJump SSH: Desde el portátil,
ssh minipcconecta vía VPS (ProxyJump vps) → Pi. Config en~/.ssh/config. El VPS usa la llaveC:\Users\juanm\Documents\GitHub\contabo. -
Autossh tunnel inverso:
autossh -M 0 -N -R 2222:localhost:22 -R 8600:localhost:8600 root@VPS. NecesitaServerAliveInterval 30yExitOnForwardFailure yes. -
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
-
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). -
Canal de Telegram vs bot: Los mensajes en canales llegan como
channel_post, nomessage. El bot debe manejar ambos:upd.get("message") or upd.get("channel_post"). -
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
-
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.
-
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 entv_cert/y es persistente (no caduca con reinicios). -
POWER es toggle: No es unidireccional. Si la TV está encendida se apaga, si está apagada se enciende. Para controlarlo: verificar
is_onantes de enviar POWER.
Sirena y Audio
-
numpy roto en ARM → sirena con math: El pitido de emergencia (wail) se sintetiza con
math.sinen bucle, sin numpy. Funciona bien. -
pychromecast con netplan: Las conexiones de netplan usan
netplan-wlan0-*. El DHCP puede fallar en wlan0. Solución: IP fija o usar eth0. -
ffmpeg funciona en ARM (paquete apt): Versión 7.1.5 compilada para armhf. Funciona para capturar frames RTSP y procesar multimedia.
Despliegue
-
systemd en Pi: La forma fiable de escribir la unidad es via SFTP +
sudo cp(elteeconsudo -Sconsume el stdin y la unidad no se escribe). -
PEP 668:
pip3 install --user --break-system-packagesnecesario en Raspberry Pi OS para instalar paquetes Python. -
cloud-init: El
user-dataynetwork-configse procesan en el primer arranque. Si el primer arranque ya pasó, hay que re-grabar la SD para que se apliquen los cambios. -
userconf.txt: Método fiable para crear el usuario
pien el primer arranque. Se escribe en la partición boot:pi:<hash_sha512>. Funciona incluso si cloud-init falla.
Cámaras
- Panorámica con ffmpeg: Sin OpenCV en la Pi, se usan
ffmpeg -i <rtsp_url> -frames:v 1 snapshot.jpgpara capturar frames de las cámaras Tapo.
Monitoreo
-
Bot de Telegram en la Pi: Responde a
estado,temperatura,tv,alarma,youtube,panorama. Usarequestspara getUpdates (long polling) ysendMessage/sendPhotopara responder. -
Alerta de temperatura: Monitor en background (cada 5 min) que envía alerta si supera el umbral (80°C default).
Bugfixes (02 agosto tarde)
-
El bot corría una copia vieja (unit systemd):
pi-bot.serviceapuntaba a/home/pi/minipc/pi_bot.py(raíz, sinlimpiar), mientras el código nuevo estaba en/home/pi/minipc/vigilancia/pi_bot.py. El comandolimpiarno 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: actualizarWorkingDirectoryyExecStartavigilancia/+daemon-reload+ restart. -
Telegram reprocesa en ráfaga al perder el offset: El bot guardaba
offsetsolo en memoria. Al reiniciar (offset=None)getUpdatesdevuelve todos los updates pendientes no confirmados y los re-ejecuta de golpe (disparópanorama,tv,youtube,limpiezarepetidos). Fix: persistir el offset en un archivo (bot.offset) concargar_offset()/guardar_offset(), guardando tras cada update. Al desplegar, capturar el último update congetUpdates?offset=-1para no reprocesar. -
pychromecast en proceso long-running deja zeroconf roto: El comando
tvgeneraba el audio (anuncio.mp3) pero la TV no reproducía:AssertionError: Zeroconf instance loop must be running, was it already stopped?yFailed to connect to service ... retrying. Causa: cadaget_chromecasts()deja al objetoChromecastcon threads de reconexión que usan un zeroconf ya parado trasstop_discovery(). Funciona en proceso limpio pero falla en tv-api long-running. Fix: desconectar cada cast (cast.disconnect()) antes destop_discovery()en los 3 puntos (anuncio_en_teles,youtube_en_teles,anuncio_simple).
Acceso a la Pi
-
SSH por password con paramiko: Desde el portátil la llave no está configurada para
pi, perouser pi / password raspberryfunciona via paramiko a192.168.50.187(y.251). El sudo necesitaecho 'raspberry' | sudo -S <cmd>. Para escribir archivos consudo teesin romper el quoting, usar base64:echo <b64> | base64 -d > archivo. -
stdout de tv-api en systemd está buffered: Los
print()de tv_api no salen enjournalctlhasta flush. Para ver el resultado del casting, usar un proceso limpio o logear a archivo.
Reboot resiliente (02 agosto noche)
-
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. -
Sintaxis de opciones de autossh: Debe ser
-o ServerAliveInterval=30(con=). Con espacio (-o ServerAliveInterval 30) ssh falla conno argument after keyword serveraliveintervaly entra en bucle de restart. Cuando hay 2 túneles compitiendo daremote port forwarding failed for listen port 2222— limpiar TODOS los procesos (pkill -9 -f autosshypkill -9 -f "ssh.*-R") antes de relanzar el servicio. -
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. -
Tras un reboot real se valida todo: pi-bot, tv-api y autossh-tunel están
enabled(arrancan solos). El bot conserva el offset enbot.offsetasí 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)
- 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.
- 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 conffmpeg.
Videoclub / NAS Synology (03-04 agosto)
-
DSM 7 exige
_sidcomo query parameter en las APIs de Download Station (GET):task.cgi?api=...&method=...&_sid=<sid>. Enviar la cookiesid=<x>sola devuelve error 105 ("sin permiso") y FileStation da 403. El login conformat=cookiedevuelve el sid correctamente, pero hay que pasarlo como_siden la URL de cada llamada. -
El error 105 de Download Station es de sesión/permiso, no del usuario: Con
_siden query funciona aunque el usuariojjminguezno sea admin.listde tareas devuelve 13 tareas (Lilo y Stitch, BLUELOCK, Heidi, etc.), todas del usuariojjminguez. -
Despliegue de archivos a la Pi sin git:
scpdirecto al túnel inverso falla (scp: Connection closed). Fiable: copiar al VPS (scplocal→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) ysudo cpal destino. Verificar conmd5sum. -
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\videocon: (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 useda error 67 ("nombre de red no encontrado"). Script:vigilancia/montar_nas.ps1+ acceso directo en Startup (la tarea programada exige admin). -
Reiniciar autossh de la Pi:
systemctl restart autossh-tunelpuede fallar conremote port forwarding failed for listen port 2222porque 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). -
Comando
pelisen pi_bot: Lista novedades con buena calidad en castellano del catálogo Torrentio/RealDebrid con el comandodescargar <url>ya montado para copiar/pegar. Filtros: castellano = regexEsp|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ó elano_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(leeos.environprimero, luego.env). -
El token RD y las credenciales NAS están en el
.envde 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 dondeNAS_HTTPS=0TORRENTIO_RD_TOKEN=...quedaron pegados en una línea — ojo al editar el.envpor SSH. -
Comando
seriesen pi_bot: Comopelispero solo series del catálogo, con el comandodescargar <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 eslanguage=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()envigilancia/pelis.py. -
Persistencia en la Pi: Los servicios
pi-bot,tv-apiyautossh-tunelestánenabled(arrancan solos al boot). El código desplegado vive en/home/pi/minipc/vigilancia/(dueñopi), no en /tmp. Verificar siempresystemctl is-enabledtras desplegar. -
Comando
serie <nombre>en pi_bot: Busca una serie por nombre en el catálogo y lista TODAS sus temporadas/capítulos con el enlacedescargar <url>debajo de cada uno. Funciones nuevas enpelis.py:SERIE_EP_RE/CAP_RE(patronesS01E05yCap.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). LosEXTRA_SUCIOse ampliaron con webdl, x264/x265, aac, resoluciones, spanish, cast/dual/lat, es/en, audio/sub/subs, web/dl. -
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 coninvalid 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 hagabase64 -d /tmp/x.b64 > destino && python3 -m py_compile && systemctl restart. Elbase64: invalid inputque sale al final es ruido residual del pipe, no error real.