Tutorial: Failover en Raspberry Pi mediante Llamada Telefónica

Nota, no recomiendo usar este hat si estás usando un disco SSD conectado a los puertos usb, si no es a través de un hub, debido a los picos de corriente que genera provocando desconexiones y problemas en los mismos.

En este tutorial, aprenderemos a configurar un sistema de alerta crítica. Si tu conexión a internet falla —ya sea por una avería de tu proveedor (ISP) o un fallo en tu propio router—, la Raspberry Pi detectará la caída de inmediato, te avisará mediante una llamada telefónica y te reportará via Telegram cuando la red sea restablecida.

Para que este sistema sea infalible, no podemos depender de la propia conexión de fibra que estamos monitorizando. Necesitamos una vía de comunicación externa. En este caso práctico, utilizaremos un módulo HAT 4G LTE CAT-1 (A7670E), junta a una tarjeta SIM M2M (machine to machine) como las que emplean Vodafone u Orange para alarmas profesionales, ascensores , etc. Estas tarjetas suelen estar limitadas a voz o SMS y no disponen de datos móviles. Aprovecharemos la robusta red 2G para garantizar que, en caso de caída de la conexión de fibra, la Raspberry Pi ejecute una llamada de voz a nuestro teléfono móvil de forma prioritaria.

¿Por qué es importante este sistema? Si confías en la domótica para la seguridad de tu hogar (alarmas, detectores de inundación o cámaras), una caída de la red, te deja «a ciegas». Saber que tu red ha caído en tiempo real te permite reaccionar antes de que sea tarde.

¿Cómo funciona el flujo de trabajo?

  1. Monitorización: La Raspberry Pi realiza «pings» constantes a servidores externos (como los DNS de Google o Cloudflare).
  2. Detección: Si el ping falla de forma consecutiva, el script activa el módulo A7670E.
  3. Alerta: El HAT ejecuta el comando AT de llamada y marca tu número de teléfono.
  4. Acción: Recibes la llamada y sabes, al instante, que algo va mal en casa.

Prepara el software de tu Raspberry.

Para entrar a las funciones del HAT necesitaremos instalar el programa minicom.

sudo apt-get install minicom

Para que nuestro script funcione, necesitas instalar la librería Python Serial:

sudo apt-get install python3-serial -y

Por alguna extraña razón, (que solo he podido observar en Raspberry Pi, ModemManager no se lleva bien con este HAT, intenta secuestrarlo constantemente, como si fuese un módem de los antiguos.

Por eso motivo, ya que no será necesario, optaré por desinstalarlo con:

sudo apt-get purge modemmanager -y

Permisos del usuario.

Tu usuario debe de tener permiso para usar el puerto serie (el módem) por defecto. Si no es así, el script intentará abrir el puerto y al no conseguirlo, se quedará esperando una respuesta que el sistema le bloqueará y no llegará a marcar.

Teclea el siguiente comando para ver los grupos a los que pertenece tu usuario.

groups

Comprueba que tu usuario pertenece al grupo «dialout», si no es así, deberás agregarlo con este comando:

sudo usermod -a -G dialout tu_usuario

Tras ejecutar este comando, reinicia tu Raspberry

sudo reboot

Forzar el modo de alta corriente (Si tu fuente es MUY buena)

En una Raspberry pi 4 es algo común, que si usas varios dispositivos conectados a los puertos USB, Pines GPIO, etc, sufras de cuelgues del sistema y se te queden tus proyectos mas tiesos que la mojama.

Si tienes la fuente oficial de 3A (o una mejor), puedes intentar decirle a la Pi que desbloquee el límite de los USB, aunque esto es automático hasta cierto punto, a veces ayuda asegurar el voltaje. Sin embargo, hay un detalle técnico importante: en la Raspberry Pi 4, el límite de corriente de los USB está fijado por hardware a 1.2A compartidos.

Pero, para «darle chicha» y asegurar que el sistema no entre en pánico cuando el SSD y el HAT 4G trabajen juntos, vamos a aplicar dos ajustes de «estabilidad máxima» que debes tener en cuenta.

1. Evitar el «ahorro de energía» del USB (El culpable del bloqueo)

Cuando el SSD y el HAT piden energía a la vez, el kernel de Linux puede intentar suspender el puerto USB para protegerse, y ahí es donde se congela la Pi. Vamos a prohibirle que lo haga.

Ejecuta este comando para editar el archivo de arranque:

sudo nano /boot/firmware/cmdline.txt

Al final de la línea de texto (sin pulsar Enter, todo en la misma línea), añade un espacio y esto:

usbcore.autosuspend=-1
2. Estabilidad de Voltaje (Config.txt)

Vamos a añadir un parámetro para que la CPU no baje su rendimiento bruscamente cuando detecte picos de consumo del USB, lo que evita que se cuelgue el sistema.

Edita el archivo de configuración:

sudo nano /boot/firmware/config.txt

Añade estas líneas al final:

# Forzar estabilidad de voltaje en puertos USB
avoid_warnings=1

El Hardware: HAT 4G LTE CAT-1 (A7670E)

El HAT 4G LTE CAT-1 para Raspberry Pi (Modelo A7670E), es un módulo compacto pero extremadamente versátil capaz de:

  • Realizar y recibir llamadas de voz.
  • Enviar y recibir SMS.
  • Navegar por internet mediante 4G.
  • Obtener datos de GPS

Nota: A partir de este momento, no es necesario tener conectado el cable USB al Módulo 4G, tan solo debe de estar conectado a los pines GPIO

Configurar la Raspberry para usar su puerto serie interno

Por defecto, la Raspberry usa sus pines de transmisión para la «consola» (donde ves letras al arrancar). Hay que liberarlo:

  1. Ejecuta:
    sudo raspi-config
  2. Ve a Interface Options -> Serial Port.
  3. ¿Deseas que la consola sea accesible por serie? NO.
  4. ¿Deseas que el hardware del puerto serie esté habilitado? .
  5. Reinicia la Raspberry.

Localizar el nuevo puerto

Una vez iniciado el sistema, vuelve a ingresar a la terminal para que podamos entrar por el puerto físico de los pines de la Raspberry, que suele ser /dev/ttyS0

sudo minicom -D /dev/ttyS0 -b 115200

Si todo va bien, entrarás a la terminal de configuración del módulo. Ahora, debemos comprobar el modo de funcionamiento en el que se encuentra el HAT 4G.

La terminal del módulo 4G

Ahora que estás en la terminal de programación del módulo, escribe el siguiente comando.

AT$MYCONFIG?

Si nos devuelve "usbnetmode"2, es porque el módulo está en modo Serial(modén) que es justo lo que necesitamos.

Pero, si nos devuelve "usbnetmode",0, es porque el módulo está en modo RNDIS (Red) y deberemos desactivarlo.

Cómo desactivar RNDIS

«Muchos tutoriales te obligan a usar el modo RNDIS, pero si tu objetivo es enviar alertas mediante llamadas, el modo Serie (usbnetmode,2) es el secreto para una estabilidad del 100% en placas Raspberry Pi, evitando conflictos de red innecesarios.»

Para dejarlo en modo «serie», que es el la opción indicada para hacer solo llamadas perdidas desde la Raspberry Pi 4, ejecuta esta secuencia exacta en tu terminal:

  1. Cambia al modo 2 (Modem/Serie):
    AT$MYCONFIG="usbnetmode",2
  2. Desactiva el intento de marcación automática:
    AT+DIALMODE=1
  3. Guarda los cambios en la memoria interna (Flash):
    AT&W
  4. Reinicia el módulo para que aplique los cambios:
    AT+CRESET

Para salir del terminal del HAT 4G pulsa Ctrl + A y luego Q y volverás a la terminal de Linux.

EL Script

Tras varias pruebas, este script en su versión 4.7, es una solución de monitorización en lenguaje Python, diseñada para informar cuando tu conexión de internet principal falla.

Crea un archivo llamado netguard.py

nano netguard.py

Pega el siguiente código.

No olvides sustituir tu número de teléfono, tu Token de Telegram y tu ID de Telegram.

import os
import time
import serial
import subprocess
import requests
import sys

# --- CONFIGURACIÓN ---
TELEFONO = "600000000"
PUERTO_MODEM = "/dev/serial0"
INTERFAZ_FIBRA = "eth0"
TOKEN_TELEGRAM = "Tu_Token"
ID_CHAT = "Tu_ID"

fibra_estaba_caida = False
llamada_realizada_con_exito = False

def comprobar_fibra():
    try:
        resultado = subprocess.run(
            ["ping", "-I", INTERFAZ_FIBRA, "-c", "2", "-W", "3", "1.1.1.1"],
            stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
        )
        return resultado.returncode == 0
    except:
        return False

def inicializar_modem(ser):
    """Configuración inicial recomendada para A7670E"""
    comandos = [
        b"AT+CMEE=2\r\n",      # Errores verbosos
        b"AT+CRC=1\r\n",       # Resultados extendidos (útil)
        b"AT+COLP=1\r\n",      # Muestra número cuando contestan (opcional)
        b"AT+CVHU=0\r\n",      # Modo hangup correcto
        b"AT+CLCC=1\r\n",      # (no siempre necesario pero no hace daño)
    ]
    for cmd in comandos:
        ser.write(cmd)
        time.sleep(0.4)
        ser.read_all()  # descartar respuesta

def realizar_llamada_perdida():
    ser = None
    try:
        print(f"\n[{time.strftime('%H:%M:%S')}] ALERTA: Sin internet → Iniciando llamada perdida...", flush=True)
        
        ser = serial.Serial(PUERTO_MODEM, 115200, timeout=1)
        inicializar_modem(ser)

        # Limpieza completa
        ser.reset_input_buffer()
        ser.reset_output_buffer()

        # Registro en red (mejor usar CEREG para LTE, pero CREG también funciona)
        print(" [+] Esperando cobertura...", flush=True)
        for i in range(30):
            ser.write(b"AT+CREG?\r\n")
            time.sleep(1)
            resp = ser.read_all().decode(errors='ignore')
            if ",1" in resp or ",5" in resp:
                print(f" [+] Registrado en red (intento {i+1})", flush=True)
                break
            print(f" [...] Buscando torre... ({i+1}/30)", flush=True)
        else:
            print(" [!] Sin cobertura. Reiniciando módulo...", flush=True)
            ser.write(b"AT+CFUN=1,1\r\n")
            return False

        # Colgar cualquier llamada residual
        ser.write(b"AT+CHUP\r\n")
        time.sleep(0.8)
        ser.read_all()

        # MARCAR
        print(f" 📞 Marcando a {TELEFONO}...", flush=True)
        ser.write(f"ATD{TELEFONO};\r\n".encode())
        time.sleep(1.5)  # esperar OK + VOICE CALL: BEGIN

        print(" [?] Llamada en curso. Esperando que cuelgues (máx 45s)...", flush=True)

        timeout = time.time() + 45
        llamada_activa = False

        while time.time() < timeout:
            # Polling CLCC + lectura de URCs
            ser.write(b"AT+CLCC\r\n")
            time.sleep(0.7)
            resp = ser.read_all().decode(errors='ignore').strip()

            # URC directo del módulo (lo más fiable)
            if "VOICE CALL: END" in resp:
                print(" ✅ Colgado detectado por URC 'VOICE CALL: END'", flush=True)
                return True

            # CLCC desapareció → colgado por receptor
            if "+CLCC:" in resp:
                llamada_activa = True
                print(" → Sonando / activa", flush=True)
            elif llamada_activa and "+CLCC:" not in resp and "OK" in resp:
                print(" ✅ Colgado detectado: CLCC desapareció", flush=True)
                return True

            # Backup: NO CARRIER
            if "NO CARRIER" in resp:
                print(" ✅ Colgado por NO CARRIER", flush=True)
                return True

        # Timeout → colgamos nosotros
        print(" ⏰ Timeout → colgando forzosamente", flush=True)
        ser.write(b"AT+CHUP\r\n")
        return False

    except Exception as e:
        print(f" [!] Error serie: {e}", flush=True)
        return False
    finally:
        if ser and ser.is_open:
            ser.close()

# --- BUCLE PRINCIPAL ---
print("--- Vigilante M2M v5.7 (A7670E optimizado) ---", flush=True)

while True:
    hay_internet = comprobar_fibra()

    if not hay_internet:
        if not fibra_estaba_caida:
            print(f"[{time.strftime('%H:%M:%S')}] Caída de fibra detectada.", flush=True)
            time.sleep(5)
            if not comprobar_fibra():  # doble chequeo
                fibra_estaba_caida = True
                if realizar_llamada_perdida():
                    llamada_realizada_con_exito = True
                else:
                    time.sleep(30)  # reintentar más lento si falló

        elif not llamada_realizada_con_exito:
            if realizar_llamada_perdida():
                llamada_realizada_con_exito = True
            else:
                time.sleep(60)

    elif hay_internet and fibra_estaba_caida:
        print(f"[{time.strftime('%H:%M:%S')}] ✅ Fibra recuperada. Avisando por Telegram...", flush=True)
        try:
            requests.post(
                f"https://api.telegram.org/bot{TOKEN_TELEGRAM}/sendMessage",
                data={"chat_id": ID_CHAT, "text": "✅ Fibra recuperada"},
                timeout=10
            )
        except:
            pass
        fibra_estaba_caida = False
        llamada_realizada_con_exito = False

    time.sleep(20)

Para guardar pulsa Ctrl + o y Ctrl + x para salir

No olvides hacerlo ejecutable con:

chmod +x netguard.py

¿Cómo funciona el script?

El programa actúa como un centinela que vigila la interfaz de fibra (eth0) cada 20 segundos mediante pings ligeros. Su inteligencia reside en cómo gestiona las alertas:

  1. El script actúa como un centinela incansable que vigila la interfaz de fibra (eth0) cada 20 segundos mediante pings ligeros. Su inteligencia reside en cómo gestiona las alertas de forma robusta y eficiente, aprovechando al máximo las capacidades del módulo A7670E (según el manual oficial SIMCom A76XX v1.09):
  2. Paciencia de Registro (mejorada): No marca a ciegas. Primero interroga al módem con AT+CREG? para confirmar cobertura real (registrado en red roaming o home). Realiza hasta 30 intentos (más margen que antes), con pausas de 1 segundo, dando tiempo al hardware para sincronizarse con la torre más cercana. Si falla tras los intentos, fuerza un reinicio profundo del módulo (AT+CFUN=1,1) para «despertarlo» y buscar señal de nuevo.
  3. Inicialización Proactiva del Módem: Antes de cualquier operación crítica, envía comandos de configuración recomendados por SIMCom: AT+CMEE=2 (errores verbosos), AT+CRC=1 (resultados extendidos), AT+COLP=1 (muestra número al contestar, opcional), AT+CVHU=0 (modo colgado correcto). Esto evita errores comunes y mejora la fiabilidad.
  4. Detección Inteligente de Colgado (el gran salto): La clave de v5.7 es la detección proactiva y prioritaria usando URCs nativos del módulo:
    • Tras ATD<numero>;, el módem envía automáticamente VOICE CALL: BEGIN cuando empieza a sonar.
    • Mientras la llamada está activa, polling frecuente con AT+CLCC para ver si sigue en lista (+CLCC: presente).
    • Cuando cuelgas desde tu móvil, llega el URC VOICE CALL: END → detección inmediata y casi infalible.
    • Fallbacks: si CLCC desaparece tras haber estado activo, o aparece NO CARRIER.
    • Timeout máximo de 45 segundos (más que suficiente para llamada perdida) → fuerza AT+CHUP si no cuelgas, evitando bloqueos eternos.
  5. Flush=True, Timeouts y Limpieza de Buffers: Todas las impresiones usan flush=True para logs inmediatos en journalctl. Las lecturas/escrituras serie tienen timeout=1 y se limpian buffers (reset_input_buffer()) antes y después de comandos clave. Nada se queda «colgado».
  6. Reintento Inteligente y Persistencia Educada: Si la llamada falla (sin cobertura, timeout, etc.), el script reintenta en el siguiente ciclo de vigilancia (cada ~20-60 s según estado). Pero una vez que logra que tu móvil suene con éxito (colgado detectado), marca llamada_realizada_con_exito = True y deja de llamar hasta que la fibra se recupere. Así evita saturar tu línea o gastar saldo innecesario.
  7. Informe de Retorno por Telegram: Tras la alerta por voz, el script entra en modo «escucha». En cuanto detecta que la fibra ha vuelto (doble chequeo ping para evitar falsos positivos), envía mensaje por Telegram: «✅ Fibra recuperada». Así sabes no solo la caída, sino también el momento exacto de recuperación.

Ademas:

  1. Doble verificación de caída (ping + 5 s espera + segundo ping) antes de alertar.
  2. Logs limpios: imprime solo lo esencial (puedes reducir aún más los «→ Sonando / activa» editando el contador).
  3. Manejo de excepciones y cierre seguro del puerto serie (finally con ser.close()).

En resumen: v5.7 es más fiable, rápida en detección y respetuosa con tu móvil y saldo, gracias a los URC nativos y la configuración proactiva del módulo.

Lanza tu script a ver que ocurre.

python3 netguard.py

Crea el archivo del servicio.

«Haciéndolo Robusto: Creando un Servicio en Systemd» «Un vigilante no sirve de nada si tienes que arrancarlo a mano. Al convertir nuestro script en un servicio de Linux, garantizamos que la vigilancia sea 24/7, incluso si hay un apagón y la Raspberry se reinicia sola. El sistema se encarga de que el script esté siempre vivo.»

Para que este script sea realmente autónomo, debería arrancar al inicio del sistema y si falla o se cierra por un error inesperado, Linux lo reinicia automáticamente en pocos segundos.

Lo ideal es crear un servicio en el sistema (Systemd).

Ejecuta este comando para crear el archivo de configuración:

sudo nano /etc/systemd/system/netguard.service

Copia y pega este contenido (asegúrate de que la ruta /home/tu_usuario/netguard.py sea la correcta donde guardaste el script):

[Unit]
Description=Servicio Vigilante de Fibra por GSM
After=network.target

[Service]
# Fuerza a Python a no guardar nada en memoria intermedia
Environment=PYTHONUNBUFFERED=1
ExecStart=/usr/bin/python3 -u /home/tu_usuario/netguard.py
WorkingDirectory=/home/tu_usuario
User=tu_usuario
# El grupo dialout es clave para que el usuario tenga permiso al módem
Group=dialout
Restart=always
RestartSec=10

# Estas líneas aseguran que Systemd vuelque todo al journal al instante
StandardOutput=journal
StandardError=journal
TTYPath=/dev/ttyS0

[Install]
WantedBy=multi-user.target

Nota: Cambia tu_usuario por el tuyo propio.

Activar y arrancar el servicio

Ahora dale las órdenes a la Raspberry para que lo ponga en marcha:

# Recargar el sistema para que vea el nuevo servicio
sudo systemctl daemon-reload

# Activar para que arranque siempre al encender la Pi
sudo systemctl enable netguard.service

# Arrancar el servicio ahora mismo
sudo systemctl start netguard.service

¿Cómo saber si está funcionando?

Puedes ver el estado del vigilante en tiempo real con este comando:

sudo systemctl status netguard.service

Y si quieres ver los «logs» (lo que el script va imprimiendo):

journalctl -u netguard.service -f

Un ejemplo tras varios dias de funcionamiento del script.

tu_usurio@raspberrypi:~ $ journalctl -u netguard.service -f
mar 01 15:53:48 tu_host python3[3651558]: --- Vigilante M2M v5.7 (A7670E optimizado) ---
mar 01 15:54:13 tu_host python3[3651558]: [15:54:13] Caída de fibra detectada.
mar 01 15:54:22 tu_host python3[3651558]: [15:54:22] ALERTA: Sin internet → Iniciando llamada perdida...
mar 01 15:54:24 tu_host python3[3651558]:  [+] Esperando cobertura...
mar 01 15:54:25 tu_host python3[3651558]:  [+] Registrado en red (intento 1)
mar 01 15:54:25 tu_host python3[3651558]:  📞 Marcando a 600000000...
mar 01 15:54:27 tu_host python3[3651558]:  [?] Llamada en curso. Esperando que cuelgues (máx 45s)...
mar 01 15:54:28 tu_host python3[3651558]:  → Sonando / activa
mar 01 15:54:28 tu_host python3[3651558]:  → Sonando / activa
mar 01 15:54:29 tu_host python3[3651558]:  → Sonando / activa
mar 01 15:54:30 tu_host python3[3651558]:  → Sonando / activa
mar 01 15:54:30 tu_host python3[3651558]:  → Sonando / activa
mar 01 15:54:31 tu_host python3[3651558]:  → Sonando / activa
mar 01 15:55:00 tu_host python3[3651558]:  ✅ Colgado detectado por URC 'VOICE CALL: END'
mar 01 15:55:21 tu_host python3[3651558]: [15:55:21] ✅ Fibra recuperada. Avisando por Telegram...

Troubleshooting (solución de problemas)

Si tu sistema sigue experimentando cuelgues o hace cosas extrañas cuando intenta hacer una llamada, prueba a usar un Hub USB con alimentación propia (La más segura). Conecta el SSD a un Hub USB que tenga su propia fuente de alimentación. Así, la energía para mover el módulo la da el Hub y no la Raspberry. La Pi solo se encarga de los datos. Por lo que, lo más efectivo es separar el consumo.

Prueba final:

Una vez que esté todo funcionando.

  1. Desconecta el cable Ethernet para provocar una llamada.
  2. Abre abre una terminal y escribe
    ls -R /
  3. Si ya no se bloquea, es que la fuente de la Pi 5 y el comando autosuspend=-1 han solucionado el conflicto de energía.

Miscelánea

Estos son los comandos clave para extraer estadísticas y gestionar tu Vigilante como un auténtico administrador de sistemas.


1. El «Resumen de Batalla» (Estadísticas rápidas)

Si quieres saber cuántas veces se ha caído la fibra o cuántas llamadas ha hecho el script sin leer miles de líneas, usa este comando. Filtrará el log y te dará solo los hitos importantes:

journalctl -u netguard.service | grep -E "Caída detectada|Llamada finalizada|Red recuperada"

Esto te mostrará una lista limpia con los días y horas exactas de cada incidente.


2. Comprobar el estado del «Motor»

Si alguna vez dudas de si el script sigue vivo (aunque ya hemos visto que es eterno), este comando te da el informe de salud de Systemd:

systemctl status netguard.service

Fíjate en la línea que dice Active: active (running) since.... Te dirá cuántos días lleva encendido sin interrupciones.


3. Reiniciar tras cambios (SIM, teléfono, etc.)

Si decides cambiar el número de teléfono o el token de Telegram en el archivo .py, no basta con guardar el archivo. Tienes que decirle a la Raspberry que cargue la nueva versión:

sudo systemctl restart netguard.service

1 comentario en “Tutorial: Failover en Raspberry Pi mediante Llamada Telefónica”

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio