Raspberry Pi

Receptor LoRa MQTT con Raspberry Pi: reenvía paquetes SX127x a la nube

Recibe paquetes LoRa punto a punto en una Raspberry Pi con un HAT RFM95 o SX127x, iguala la configuración de radio con tus emisores y republica cada paquete como JSON en un broker MQTT.

Raspberry Pi con un módulo de radio LoRa y antena recibiendo paquetes de largo alcance, ilustrando Raspberry Pi LoRa MQTT
Pruébalo en tu propio broker MQTTConsigue un broker MQTT privado y gestionado con el plan Free y empieza con ejemplos de código listos para usar.
Pruébalo gratis Ejemplos de código Sin tarjeta

Un receptor Raspberry Pi LoRa MQTT es la forma más sencilla de recoger datos de sensores que están demasiado lejos para el Wi-Fi: un campo, una finca agrícola, un aparcamiento o el extremo opuesto de una planta grande. En este tutorial conectarás una radio basada en SX127x (RFM95W, RFM96W o un bonnet LoRa) a una Pi, recibirás paquetes punto a punto de tus propios emisores y republicarás cada uno como JSON en un broker MQTT. Empezarás con broker.connectmq.com y terminarás con una configuración lista para producción.

LoRa punto a punto frente a LoRaWAN

LoRa es una modulación de radio; LoRaWAN es un protocolo de red completo construido sobre ella, con registro de dispositivos, claves de cifrado, un servidor de red y gateways multicanal. Si ya operas una red LoRaWAN, tu servidor de red publica los uplinks en MQTT y lo que necesitas es la integración MQTT de ChirpStack.

LoRa punto a punto se salta todo eso. Tu emisor (a menudo un ESP32 o un Arduino con un módulo SX1276) transmite un paquete pequeño en una frecuencia fija con parámetros fijos, y un receptor de un solo canal lo escucha. Es fácil de entender y queda totalmente bajo tu control, lo que lo hace ideal para proyectos personales, prototipos y redes privadas de sensores en las que controlas ambos extremos. La contrapartida es que tú te encargas del direccionamiento, las comprobaciones de integridad y los límites de ciclo de trabajo.

Hardware

Elemento Notas
Raspberry Pi Cualquier modelo con el conector de 40 pines; una Zero 2 W es suficiente
Radio SX127x Adafruit LoRa Radio Bonnet, placa RFM95W o un módulo SPI similar
Antena Sintonizada para tu banda; nunca transmitas sin ella
Nodo emisor ESP32, Arduino o una segunda Pi con una radio compatible
Frecuencia 868 MHz en Europa, 915 MHz en Norteamérica, 433 MHz en algunas regiones; respeta la normativa local

El cableado de una placa suelta sigue los pines SPI: SCK a GPIO 11, MOSI a GPIO 10, MISO a GPIO 9, CS a CE1 (GPIO 7), RESET a GPIO 25, más 3,3 V y tierra. El bonnet de Adafruit usa exactamente esos pines, por eso el código de abajo los usa por defecto.

Instala las librerías

Activa SPI con sudo raspi-config (Interface Options, SPI) y crea un entorno con Adafruit Blinka, que proporciona los módulos board y busio en Linux, y el driver RFM9x:

sudo raspi-config nonint do_spi 0
ls /dev/spidev*
python3 -m venv ~/lora/.venv
source ~/lora/.venv/bin/activate
pip install adafruit-blinka adafruit-circuitpython-rfm9x "paho-mqtt>=2.0"

La guía de instalación de Blinka en la web de Adafruit indica pasos adicionales para algunos modelos de Pi, por ejemplo instalar lgpio en las versiones más recientes. Si import board falla, sigue esa guía antes de continuar.

Iguala la configuración de radio

Un receptor LoRa solo escucha paquetes enviados con la misma frecuencia, spreading factor, ancho de banda, coding rate, sync word y preámbulo. Una discrepancia no produce errores; produce silencio. Anota la configuración una vez y úsala en ambos lados.

Parámetro Valor de ejemplo Notas
Frecuencia 868,1 MHz Debe ser legal en tu región
Spreading factor 7 Más alto significa más alcance y más tiempo en el aire
Ancho de banda 125 kHz Valor por defecto habitual
Coding rate 4/5 Se configura como 5 en el driver
CRC Activado Actívalo en emisor y receptor
Cabecera Depende del emisor Consulta la nota sobre RadioHead más abajo

La trampa de la cabecera RadioHead. El driver RFM9x de Adafruit sigue la convención de RadioHead y, por defecto, elimina una cabecera de 4 bytes (to, from, id, flags) de los paquetes recibidos. Los emisores que usan la librería Arduino LoRa de Sandeep Mistry no añaden esa cabecera, así que los primeros cuatro bytes de tu payload desaparecen sin aviso. Llama a receive(with_header=True) cuando tu emisor no use RadioHead, como hace el script de abajo.

El script Raspberry Pi LoRa MQTT

El emisor de este ejemplo transmite un payload de texto corto como n1,21.4,63,3.92 (ID de nodo, temperatura, humedad, voltaje de batería). El receptor lo valida, añade metadatos de radio y lo publica en un topic por nodo. Los paquetes desconocidos o mal formados van a un topic raw aparte para depuración, en lugar de descartarse en silencio.

import json
import os
import time
import uuid

import adafruit_rfm9x
import board
import busio
import digitalio
import paho.mqtt.client as mqtt

BROKER = os.getenv("MQTT_HOST", "broker.connectmq.com")
PORT = int(os.getenv("MQTT_PORT", "1883"))
USERNAME = os.getenv("MQTT_USERNAME")
PASSWORD = os.getenv("MQTT_PASSWORD")
CLIENT_ID = os.getenv("MQTT_CLIENT_ID", f"lora-rx-{uuid.uuid4().hex[:8]}")
PREFIX = os.getenv("TOPIC_PREFIX", "connectmq/demo/l9r3x5/lora")
FREQ_MHZ = float(os.getenv("LORA_FREQ", "868.1"))

spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
cs = digitalio.DigitalInOut(board.CE1)
reset = digitalio.DigitalInOut(board.D25)
rfm9x = adafruit_rfm9x.RFM9x(spi, cs, reset, FREQ_MHZ)
rfm9x.spreading_factor = 7
rfm9x.signal_bandwidth = 125000
rfm9x.coding_rate = 5
rfm9x.enable_crc = True


def on_connect(client, userdata, flags, reason_code, properties):
    print("MQTT connected:", reason_code)
    client.publish(f"{PREFIX}/receiver/status", "online", qos=1, retain=True)


client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=CLIENT_ID)
if USERNAME:
    client.username_pw_set(USERNAME, PASSWORD)
if PORT == 8883:
    client.tls_set()
client.will_set(f"{PREFIX}/receiver/status", "offline", qos=1, retain=True)
client.reconnect_delay_set(1, 60)
client.on_connect = on_connect
client.connect_async(BROKER, PORT, keepalive=60)
client.loop_start()


def parse(text):
    parts = text.split(",")
    if len(parts) != 4 or not parts[0].startswith("n"):
        return None
    node, temp, hum, vbat = parts
    return node, {"temperature": float(temp), "humidity": float(hum), "vbat": float(vbat)}


print(f"listening on {FREQ_MHZ} MHz")
try:
    while True:
        packet = rfm9x.receive(timeout=5.0, with_header=True)
        if packet is None:
            continue
        meta = {"rssi": rfm9x.last_rssi, "snr": rfm9x.last_snr, "ts": int(time.time())}
        try:
            parsed = parse(packet.decode("ascii").strip())
        except (UnicodeDecodeError, ValueError):
            parsed = None
        if parsed:
            node, values = parsed
            client.publish(f"{PREFIX}/{node}", json.dumps({**values, **meta}), qos=1, retain=True)
            print(node, values, meta)
        else:
            client.publish(f"{PREFIX}/raw", json.dumps({"hex": packet.hex(), **meta}), qos=0)
except KeyboardInterrupt:
    pass
finally:
    client.publish(f"{PREFIX}/receiver/status", "offline", qos=1, retain=True)
    client.loop_stop()
    client.disconnect()

En un emisor ESP32 con la librería Arduino LoRa, la configuración equivalente es LoRa.begin(868.1E6), LoRa.setSpreadingFactor(7), LoRa.setSignalBandwidth(125E3), LoRa.setCodingRate4(5) y LoRa.enableCrc(). Después envía con LoRa.beginPacket(); LoRa.print("n1,21.4,63,3.92"); LoRa.endPacket();.

Observa los resultados desde cualquier equipo:

mosquitto_sub -h broker.connectmq.com -p 1883 -i lora-viewer-l9r3x5 -t "connectmq/demo/l9r3x5/lora/#" -v

Paquetes más robustos

El texto plano es fácil de depurar pero desperdicia tiempo en el aire, y el tiempo en el aire es el recurso escaso en LoRa. Cuando el enlace funcione, considera estas mejoras:

  1. Payloads binarios. Empaqueta los valores con un escalado fijo, por ejemplo la temperatura como int16 en centésimas. Una lectura de 4 campos pasa de unos 16 bytes a 7, lo que acorta el tiempo en el aire y alarga la batería.
  2. ID de nodo y número de secuencia. Un contador de secuencia permite al receptor detectar paquetes perdidos y duplicados, y te permite reportar la pérdida de paquetes por nodo como una métrica propia.
  3. Integridad. El CRC de la radio detecta la corrupción. Si hay vecinos que podrían transmitir con los mismos parámetros, añade un identificador corto o un HMAC para ignorar los paquetes que no son tuyos.
  4. Ciclo de trabajo. Muchas regiones limitan cuánto tiempo puede transmitir un dispositivo por hora en una subbanda. Los spreading factors altos y los envíos frecuentes pueden superarlo rápidamente, así que mantén payloads pequeños e intervalos razonables.
  5. Salud del receptor. Publica un heartbeat desde el receptor y registra la última vez que se vio cada nodo, para que un emisor caído y un receptor caído se distingan en tu dashboard.

Los valores de SNR y RSSI publicados con cada paquete son tu mejor herramienta para ubicar la antena. Un SNR positivo indica un enlace holgado; valores cercanos al límite de sensibilidad de tu spreading factor indican que estás cerca del borde del alcance.

Funcionamiento desatendido

Un receptor en la pared de un galpón o en un mástil debe sobrevivir a cortes de luz y caídas de red. Ejecuta el script como servicio systemd para que se reinicie automáticamente, guarda las lecturas en local cuando el enlace caiga con un buffer MQTT offline en SQLite y sigue los consejos de fiabilidad para Raspberry Pi sobre desgaste de la tarjeta SD y fuentes de alimentación. Para sitios remotos con conexión celular, MQTT encaja muy bien porque la conexión es solo saliente y la sobrecarga del protocolo es pequeña.

Más allá del servidor de pruebas

El servidor MQTT público gratuito es perfecto para afinar el enlace de radio, pero todo lo que se publica ahí es visible para cualquiera, así que no lo uses con datos de producción. Para un despliegue real, crea un broker MQTT gestionado en ConnectMQ, toma el nombre de usuario, la contraseña y el client ID generados en el portal e inicia el receptor con MQTT_PORT=8883 y esos valores en el entorno. TLS usa un certificado de Let's Encrypt, así que tls_set() funciona con el almacén de CA por defecto de la Pi. Un broker MQTT en la nube también permite que varios receptores en distintos sitios publiquen en un mismo árbol de topics que tus dashboards y reglas de alerta pueden leer en un solo lugar.

¿Eres nuevo en MQTT con la Pi? El tutorial de MQTT con Raspberry Pi cubre los fundamentos usados aquí, y la guía del gateway IoT con Raspberry Pi muestra cómo un receptor LoRa puede convivir con BLE, Modbus y otras entradas en un mismo dispositivo.

Envía tus datos LoRa a ConnectMQ

Crea un broker Serverless gratuito en app.connectmq.com, sin tarjeta de crédito, y tendrás credenciales TLS para tu receptor en minutos. El explorador de topics en vivo de ConnectMQ muestra cada nodo a medida que reporta, y las reglas de alerta por umbral pueden avisarte cuando un valor de temperatura o de batería cruza un límite.

Preguntas frecuentes

¿Puede un único receptor SX127x actuar como gateway LoRaWAN?

No de forma adecuada. Los gateways LoRaWAN escuchan en varios canales y spreading factors a la vez mediante chips concentradores como el SX1302. Un único SX127x escucha una sola configuración cada vez, lo que encaja con el uso punto a punto.

¿Qué alcance puedo esperar?

El alcance depende mucho de la altura de la antena, el terreno y el spreading factor. Con línea de vista y antenas elevadas se llega mucho más lejos que a través de edificios, así que mide RSSI y SNR en el sitio.

¿Por qué mi receptor recibe paquetes con bytes perdidos?

Casi siempre es la discrepancia de la cabecera RadioHead. Usa receive(with_header=True) cuando el emisor no añada la cabecera RadioHead de 4 bytes.

¿Puede la Pi enviar comandos de vuelta a los nodos?

Sí, el driver RFM9x puede transmitir. Suscríbete a un topic de comandos y llama a rfm9x.send(), pero recuerda que los nodos deben estar escuchando en ese momento, y los emisores a batería normalmente solo lo hacen brevemente justo después de transmitir.

Artículos relacionados

Fiabilidad de la Raspberry Pi: cómo usar una Pi 24/7 como gateway IoT

Una guía práctica para mantener un gateway MQTT con Raspberry Pi funcionando durante meses: desgaste del almacenamiento, sistemas de archivos de solo lectura, alimentación, refrigeración, watchdogs de hardware y software y monitorización remota de la salud.

9 min de lectura

Pico W vs Pico 2 W para MQTT: rendimiento, memoria y TLS de RP2040 frente a RP2350

La Pico 2 W mantiene el formato y el chip Wi-Fi de la Pico W, pero sustituye el RP2040 por el RP2350, más rápido. Esta comparativa analiza qué significa para los dispositivos MQTT: CPU, memoria, TLS, funciones de seguridad, soporte de software y qué placa comprar para cada proyecto.

9 min de lectura

Registro de accesos Raspberry Pi RFID MQTT con el lector RC522

Conecta un lector RFID RC522 a una Raspberry Pi, lee etiquetas en Python, compáralas con una lista blanca local y publica cada intento de acceso como un evento MQTT que pueden consumir registradores, dashboards y herramientas de alertas.

8 min de lectura

Todos los artículos →

Tu broker MQTT privado, a unos pocos clics

Crea una cuenta, obtén las credenciales de tu broker y conecta tu primer dispositivo, sin servidores que instalar, parchear ni escalar.