Tutorial: Failover en Raspberry Pi mediante Llamada Telefónica

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 se 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

Este script 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 = "+34600000000"
PUERTO_MODEM = "/dev/ttyS0" 
INTERFAZ_FIBRA = "eth0"
TOKEN_TELEGRAM = "Tu_TOKEN"
ID_CHAT = "Tu_ID"

# Variables de estado
fibra_estaba_caida = False 
llamada_realizada_con_exito = False
ultimo_test_salud = 0

def comprobar_salud_modem():
    """Verifica si el módem responde al comando AT básico."""
    ser = None
    try:
        ser = serial.Serial(PUERTO_MODEM, 115200, timeout=2)
        ser.write(b"AT\r")
        time.sleep(0.5)
        resp = ser.read_all().decode(errors='ignore')
        return "OK" in resp
    except:
        return False
    finally:
        if ser and ser.is_open:
            ser.close()

def comprobar_fibra():
    try:
        # 1 intento de ping, espera 3 segundos
        comando = ["ping", "-I", INTERFAZ_FIBRA, "-c", "1", "-W", "3", "1.1.1.1"]
        resultado = subprocess.run(comando, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        return resultado.returncode == 0
    except:
        return False

def realizar_llamada_perdida():
    ser = None
    try:
        print(f"\n[{time.strftime('%H:%M:%S')}] ALERTA: Sin internet. Accediendo al módem...", flush=True)
        ser = serial.Serial(PUERTO_MODEM, 115200, timeout=5)
        
        registrado = False
        # MEJORA: Recuperamos los intentos visuales para el log
        for intento in range(1, 16):
            ser.write(b"AT+CREG?\r")
            time.sleep(1)
            resp = ser.read_all().decode(errors='ignore')
            
            # Verificamos si el módem está registrado en red local o roaming
            if any(x in resp for x in ["+CREG: 0,1", "+CREG: 0,5", "+CREG: 1,1", "+CREG: 1,5"]):
                print(f"    [+] Módem en red (Intento {intento}).", flush=True)
                registrado = True
                break
            else:
                print(f"    [...] Buscando antena... ({intento}/15)", flush=True)
                time.sleep(2)

        if not registrado:
            # MEJORA: Reset de radio (Soft Reset) si no hay red tras 15 intentos
            print(f"    [{time.strftime('%H:%M:%S')}] [!] Error: No se encontró red GSM. Reiniciando stack...", flush=True)
            ser.write(b"AT+CFUN=1,1\r") 
            return False 

        # Si hay red, procedemos a marcar
        print(f"    Marcando a {TELEFONO}...", flush=True)
        ser.write(b"AT+CHUP\r") # Colgar por si acaso
        time.sleep(1)
        ser.write(f"ATD{TELEFONO};\r".encode())
        time.sleep(4)
        
        respuesta = ser.read_all().decode(errors='ignore')
        if "OK" in respuesta or "VCON" in respuesta:
            print(f"    [OK] Llamada en curso. Sonando 25 seg...", flush=True)
            time.sleep(25) 
            ser.write(b"ATH\r") # Colgar tras la llamada perdida
            print(f"    [{time.strftime('%H:%M:%S')}] Llamada finalizada con éxito.", flush=True)
            return True
        else:
            print(f"    [!] Error al marcar: {respuesta.strip()}", flush=True)
            return False
    except Exception as e:
        print(f"\n[{time.strftime('%H:%M:%S')}] [!] Error hardware: {e}", flush=True)
        return False
    finally:
        if ser and ser.is_open:
            ser.close()

# --- BUCLE PRINCIPAL ---
print("--- Vigilante M2M Silencioso, Eterno y Seguro Activo ---", flush=True)



while True:
    hay_internet = comprobar_fibra()
    ahora = time.time()

    # MEJORA: Test de Salud silencioso cada 10 minutos
    if (ahora - ultimo_test_salud > 600) or (not hay_internet and not fibra_estaba_caida):
        if not comprobar_salud_modem():
            print(f"[{time.strftime('%H:%M:%S')}] [!] El módem no responde. Reiniciando servicio...", flush=True)
            sys.exit(1) # Systemd reinicia el proceso
        ultimo_test_salud = ahora

    if not hay_internet:
        if not fibra_estaba_caida:
            # MEJORA: Confirmación antifalsos positivos
            print(f"[{time.strftime('%H:%M:%S')}] Caída detectada. Confirmando...", flush=True)
            time.sleep(5)
            if not comprobar_fibra():
                fibra_estaba_caida = True
                if realizar_llamada_perdida():
                    llamada_realizada_con_exito = True
        
        elif not llamada_realizada_con_exito:
            # Reintento si la llamada falló previamente
            if realizar_llamada_perdida():
                llamada_realizada_con_exito = True

    elif hay_internet and fibra_estaba_caida:
        # MEJORA: Informe de retorno con log de Telegram
        print(f"[{time.strftime('%H:%M:%S')}] Red recuperada. Reseteando estados...", flush=True)
        
        try:
            url = f"https://api.telegram.org/bot{TOKEN_TELEGRAM}/sendMessage"
            r = requests.post(url, data={"chat_id": ID_CHAT, "text": "✅ Fibra recuperada"}, timeout=10)
            if r.status_code == 200:
                print(f"[{time.strftime('%H:%M:%S')}] [Telegram] Mensaje de recuperación enviado.", flush=True)
            else:
                print(f"[{time.strftime('%H:%M:%S')}] [Telegram] Error API: {r.status_code}", flush=True)
        except:
            print(f"[{time.strftime('%H:%M:%S')}] [Telegram] Fallo de conexión al enviar aviso.", flush=True)
            
        fibra_estaba_caida = False
        llamada_realizada_con_exito = False 
    
    # Ciclo de espera de 15 segundos (aproximadamente 20 seg sumando el ping)
    time.sleep(15)

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. Paciencia de Registro: El script no marca a ciegas. Primero «pregunta» al módem si tiene cobertura real. Si no la encuentra a la primera, realiza hasta 15 comprobaciones seguidas, dando tiempo al hardware para sincronizarse con la torre de telefonía más cercana.
  2. Auto-Recuperación (Soft Reset): Si tras esos intentos sigue sin red (por un error de lectura de la SIM o un bloqueo lógico del chip), el script envía un comando de reinicio profundo (AT+CFUN=1,1). Esto «despierta» al módem y fuerza una nueva búsqueda de señal, solucionando bloqueos sin que tengas que tocar físicamente la Raspberry Pi.
  3. Test de Salud Silencioso: Comprueba cada 10 minutos si el módem responde AT. Si no lo hace, se reinicia. Pero lo hace sin imprimir nada si todo va bien, para no ensuciar el buffer.
  4. Flush=True y Timeouts: Todas las impresiones tienen su flush=True y las conexiones serie/web tienen sus timeout para que nada se quede «colgado» eternamente.
  5. Reintento Inteligente: El sistema es persistente pero educado. Si la llamada falla por falta de cobertura, el script seguirá intentándolo en los siguientes ciclos de vigilancia. Sin embargo, en cuanto logra que tu móvil suene con éxito una sola vez, el script marca la misión como cumplida y deja de llamar para no saturar tu línea ni gastar saldo innecesario.
  6. Informe de Retorno (Telegram): Una vez que la alerta ha sido dada por voz, el script se queda en modo escucha. En el momento en que detecta que la fibra ha vuelto a la vida, aprovecha la conexión recuperada para enviarte un mensaje detallado por Telegram. Así, no solo sabes cuándo se cae la red, sino también el momento exacto en el que todo vuelve a la normalidad.

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 reinicio automaticamente 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
feb 04 21:22:13 raspberrypi python3[3929150]: [21:22:13] Caída detectada. Confirmando...
feb 04 21:22:21 raspberrypi python3[3929150]: [21:22:21] ALERTA: Sin internet. Accediendo al módem...
feb 04 21:22:22 raspberrypi python3[3929150]:     [...] Buscando antena... (1/15)
feb 04 21:22:25 raspberrypi python3[3929150]:     [...] Buscando antena... (2/15)
feb 04 21:22:28 raspberrypi python3[3929150]:     [...] Buscando antena... (3/15)
feb 04 21:22:31 raspberrypi python3[3929150]:     [...] Buscando antena... (4/15)
feb 04 21:22:34 raspberrypi python3[3929150]:     [...] Buscando antena... (5/15)
feb 04 21:22:37 raspberrypi python3[3929150]:     [...] Buscando antena... (6/15)
feb 04 21:22:40 raspberrypi python3[3929150]:     [...] Buscando antena... (7/15)
feb 04 21:22:43 raspberrypi python3[3929150]:     [...] Buscando antena... (8/15)
feb 04 21:22:46 raspberrypi python3[3929150]:     [...] Buscando antena... (9/15)
feb 04 21:22:49 raspberrypi python3[3929150]:     [...] Buscando antena... (10/15)
feb 04 21:22:52 raspberrypi python3[3929150]:     [...] Buscando antena... (11/15)
feb 04 21:22:55 raspberrypi python3[3929150]:     [...] Buscando antena... (12/15)
feb 04 21:22:58 raspberrypi python3[3929150]:     [+] Módem en red (Intento 13).
feb 04 21:22:58 raspberrypi python3[3929150]:     Marcando a +34600000000...
feb 04 21:23:03 raspberrypi python3[3929150]:     [OK] Llamada en curso. Sonando 25 seg...
feb 04 21:23:28 raspberrypi python3[3929150]:     [21:23:28] Llamada finalizada con éxito.
feb 04 21:23:43 raspberrypi python3[3929150]: [21:23:43] Red recuperada. Reseteando estados...
feb 04 21:23:43 raspberrypi python3[3929150]: [21:23:43] [Telegram] Mensaje de recuperación enviado.

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”

Responder a SKYNET Cancelar respuesta

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

Scroll al inicio