Automatización IoT

Integración MQTT de The Things Network: lleva los uplinks LoRaWAN a tu propio broker

Suscríbete al servidor MQTT de The Things Network, decodifica los uplinks LoRaWAN con un payload formatter y republica JSON limpio de sensores en tu propio árbol de topics con un breve puente en Python.

Puente MQTT de The Things Network que lleva los uplinks de sensores LoRaWAN desde un gateway hasta el árbol de topics de un broker MQTT en la nube
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

El servidor MQTT de The Things Network ofrece a cada aplicación un flujo en directo de sus uplinks LoRaWAN, lo que facilita incorporar datos de sensores de largo alcance a tus propios sistemas. En este tutorial escribirás un payload formatter, te conectarás al servidor MQTT de TTN con una API key y ejecutarás un pequeño puente en Python que republica las lecturas decodificadas como JSON limpio en el árbol de topics de tu propio broker, donde los paneles, Node-RED y las automatizaciones pueden usarlas junto a tus demás dispositivos.

Por qué llevar TTN a tu propio broker

The Things Network (TTN) se encarga de la parte de radio de LoRaWAN: gateways, activación de dispositivos, deduplicación y descifrado. Su servidor MQTT publica cada uplink como un gran documento JSON que contiene metadatos de cada gateway que escuchó el paquete. Eso es excelente para depurar, pero incómodo como modelo de datos a largo plazo:

  • los topics son los de TTN (v3/<app>@ttn/devices/<device>/up), no la jerarquía de tu instalación;
  • los payloads son grandes y anidados, mientras que la mayoría de los consumidores solo quieren tres o cuatro valores;
  • tus dispositivos Wi-Fi, PLC y controladores ya viven en otro broker.

Un puente resuelve los tres problemas. Se suscribe a TTN, extrae lo importante y publica mensajes compactos en el mismo árbol de topics que todo lo demás. Los consumidores nunca necesitan credenciales de TTN y, si más adelante pasas a un servidor de red privado, solo cambia el puente. La guía de IoT para agricultura inteligente explica dónde encaja esto en un diseño para toda la finca, del campo agroindustrial al invernadero.

Paso 1: decodificar el payload en TTN

Los dispositivos LoRaWAN envían bytes compactos. Añade un payload formatter en la consola de TTN en Applications > your app > Payload formatters > Uplink, tipo Custom Javascript formatter. Este ejemplo decodifica un nodo de suelo que envía humedad (uint16, décimas de porcentaje), temperatura del suelo (int16, décimas de grado) y batería (uint16, milivoltios):

function decodeUplink(input) {
  var b = input.bytes;
  if (input.fPort !== 1 || b.length < 6) {
    return { errors: ["unexpected port or length"] };
  }
  var moisture = ((b[0] << 8) | b[1]) / 10;
  var tRaw = (b[2] << 8) | b[3];
  if (tRaw & 0x8000) tRaw -= 0x10000;
  var battery = ((b[4] << 8) | b[5]) / 1000;
  return {
    data: {
      moisture_pct: moisture,
      soil_temp_c: tRaw / 10,
      battery_v: battery
    },
    warnings: [],
    errors: []
  };
}

Usa el panel Test con un payload hexadecimal como 0136 008C 0E74 para confirmar la salida antes de desplegar cualquier dispositivo. El objeto decodificado aparece en cada uplink como uplink_message.decoded_payload.

Paso 2: obtener las credenciales MQTT de TTN

En la consola, abre Integrations > MQTT. Verás:

Ajuste Valor
Servidor Tu clúster, por ejemplo eu1.cloud.thethings.network
Puertos 1883 (sin cifrar) y 8883 (TLS)
Usuario <application-id>@ttn
Contraseña Una API key; haz clic en Generate new API key

Copia la API key de inmediato; TTN solo la muestra una vez. Usa siempre el puerto 8883 con TTN, ya que la clave da acceso a los datos de tu aplicación. La sección sobre el servidor MQTT de la documentación oficial de The Things Stack enumera el conjunto completo de topics, incluidos los eventos de join, downlink y servicio.

El topic de uplink para una aplicación es:

v3/<application-id>@ttn/devices/<device-id>/up

Puedes comprobar el flujo antes de escribir código:

mosquitto_sub -h eu1.cloud.thethings.network -p 8883 --capath /etc/ssl/certs \
  -u 'my-farm-app@ttn' -P 'NNSXS.YOUR_TTN_API_KEY' \
  -t 'v3/my-farm-app@ttn/devices/+/up' -v

Paso 3: el puente MQTT de The Things Network en Python

El puente usa dos clientes paho-mqtt 2.x: uno suscrito a TTN y otro que publica en tu broker. Ejecútalo primero contra el servidor MQTT público gratuito, pensado para pruebas y no para datos de producción, sustituyendo t6n1b4 por tu propia cadena aleatoria, porque cualquiera puede leer el área pública.

import json
import os
import ssl

import paho.mqtt.client as mqtt

TTN_HOST = "eu1.cloud.thethings.network"
TTN_APP = os.environ.get("TTN_APP", "my-farm-app")
TTN_KEY = os.environ["TTN_API_KEY"]

OUT_HOST = os.environ.get("OUT_HOST", "broker.connectmq.com")
OUT_PORT = int(os.environ.get("OUT_PORT", "1883"))
OUT_USER = os.environ.get("OUT_USER")          # definir en producción
OUT_PASS = os.environ.get("OUT_PASS")
OUT_CLIENT_ID = os.environ.get("OUT_CLIENT_ID", "ttn-bridge-out-t6n1b4")
OUT_PREFIX = os.environ.get("OUT_PREFIX", "connectmq/demo/t6n1b4/farm")

# --- broker de salida --------------------------------------------------------
out = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=OUT_CLIENT_ID)
if OUT_USER:
    out.username_pw_set(OUT_USER, OUT_PASS)
if OUT_PORT == 8883:
    out.tls_set(cert_reqs=ssl.CERT_REQUIRED)
out.will_set(f"{OUT_PREFIX}/bridge/status", "offline", qos=1, retain=True)

def out_on_connect(client, userdata, flags, reason_code, properties):
    print("out broker:", reason_code)
    client.publish(f"{OUT_PREFIX}/bridge/status", "online", qos=1, retain=True)

out.on_connect = out_on_connect
out.connect(OUT_HOST, OUT_PORT, keepalive=60)
out.loop_start()

# --- TTN ---------------------------------------------------------------------
def ttn_on_connect(client, userdata, flags, reason_code, properties):
    print("ttn:", reason_code)
    client.subscribe(f"v3/{TTN_APP}@ttn/devices/+/up", qos=1)

def ttn_on_message(client, userdata, msg):
    try:
        up = json.loads(msg.payload)
        device = up["end_device_ids"]["device_id"]
        um = up["uplink_message"]
        decoded = um.get("decoded_payload")
    except (ValueError, KeyError) as exc:
        print("skipping malformed uplink:", exc)
        return
    if not decoded:
        print(f"{device}: no decoded payload (check the formatter)")
        return

    rx = um.get("rx_metadata", [])
    best = max(rx, key=lambda g: g.get("rssi", -999)) if rx else {}
    record = {
        "ts": up.get("received_at"),
        **decoded,
        "rssi": best.get("rssi"),
        "snr": best.get("snr"),
        "gateways": len(rx),
        "f_cnt": um.get("f_cnt"),
    }
    topic = f"{OUT_PREFIX}/{device}/telemetry"
    out.publish(topic, json.dumps(record), qos=1, retain=True)
    print(topic, record)

ttn = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
                  client_id=f"ttn-bridge-in-{TTN_APP}")
ttn.username_pw_set(f"{TTN_APP}@ttn", TTN_KEY)
ttn.tls_set(cert_reqs=ssl.CERT_REQUIRED)
ttn.on_connect = ttn_on_connect
ttn.on_message = ttn_on_message
ttn.connect(TTN_HOST, 8883, keepalive=60)
ttn.loop_forever()

Ejecútalo:

pip install paho-mqtt
export TTN_API_KEY='NNSXS.YOUR_TTN_API_KEY'
python ttn_bridge.py

Y observa la salida limpia desde otro terminal:

mosquitto_sub -h broker.connectmq.com -p 1883 -i watch-t6n1b4 \
  -t 'connectmq/demo/t6n1b4/farm/#' -v

Cada uplink se convierte en un mensaje retenido como {"ts":"2026-10-16T06:15:02Z","moisture_pct":31.0,"soil_temp_c":14.0,"battery_v":3.7,"rssi":-108,"snr":4.5,"gateways":2,"f_cnt":412}. Como es retenido, un panel que arranque más tarde ve de inmediato la última lectura de cada sensor, algo importante en dispositivos que solo informan cada 15 minutos.

Notas de diseño

¿Por qué incluir RSSI, SNR y el número de gateways? Son el primer aviso de una antena averiada, un gateway que se ha movido o un sensor que se hunde en el barro. Almacenarlos no cuesta casi nada.

¿Por qué mensajes retenidos? Los dispositivos LoRaWAN permanecen en silencio la mayor parte del tiempo. Sin retain, cualquier consumidor que se conecte entre uplinks no ve nada hasta el siguiente.

¿Por qué QoS 1 en ambos lados? El puente podría reiniciarse mientras TTN entrega mensajes; QoS 1 con un cliente que se reconecta reduce los huecos. Para conocer las ventajas e inconvenientes, consulta los niveles de QoS de MQTT explicados.

Profundidad de los topics. connectmq/demo/t6n1b4/farm/<device>/telemetry tiene seis niveles, holgadamente dentro del límite de ocho niveles del servidor público. En producción, usa tu propia jerarquía, como farm1/north/<device>/telemetry.

Tamaño de los mensajes. Un uplink en bruto de TTN con metadatos de varios gateways puede ocupar unos cuantos kilobytes. El registro republicado ocupa un par de cientos de bytes, muy por debajo del límite de 64 KB del área pública de pruebas y del límite de 256 KB del plan gratuito de ConnectMQ, y es mucho más barato de procesar para paneles y clientes móviles que funcionan con batería.

Gestión de huecos y duplicados

LoRaWAN es, por diseño, un enlace de radio con pérdidas: los dispositivos transmiten sin confirmación, y un paquete que ningún gateway escucha simplemente se pierde. El contador de tramas (f_cnt) incluido en cada registro permite a los consumidores detectar huecos. Si el contador salta de 410 a 413, se perdieron dos uplinks en la parte de radio, no en MQTT. Esa distinción ahorra tiempo cuando alguien informa de que faltan datos.

El puente también puede ver el mismo uplink dos veces si se reconecta a TTN mientras un mensaje QoS 1 está en tránsito. Los consumidores que almacenan datos deben tratar la combinación de ID de dispositivo y contador de tramas como clave única e ignorar las repeticiones. Ten en cuenta que el contador se reinicia cuando un dispositivo vuelve a unirse a la red, así que úsalo para detectar huecos en ventanas cortas y no como identificador permanente.

Para enviar un comando a un dispositivo (por ejemplo, un nuevo intervalo de envío), el puente puede suscribirse a un topic de comandos en tu broker y reenviarlo al topic de downlink de TTN:

import base64

def forward_downlink(device_id, port, raw_bytes):
    body = {"downlinks": [{
        "f_port": port,
        "frm_payload": base64.b64encode(raw_bytes).decode(),
        "priority": "NORMAL",
    }]}
    ttn.publish(f"v3/{TTN_APP}@ttn/devices/{device_id}/down/push",
                json.dumps(body), qos=1)

# ejemplo: fijar el intervalo en 30 minutos (un byte) en el puerto 2
# forward_downlink("soil-node-07", 2, bytes([30]))

Los dispositivos de clase A solo reciben downlinks después de su siguiente uplink, así que los comandos llegan con un retraso igual al intervalo de envío. Usa los downlinks con moderación; el tiempo de emisión y las reglas de ciclo de trabajo de LoRaWAN los hacen costosos.

Ejecución fiable

  1. Ejecuta el puente como servicio systemd o contenedor con reinicio automático.
  2. Guarda la API key de TTN en una variable de entorno o un almacén de secretos, nunca en el script.
  3. Concede a la API key solo los permisos que necesita (leer el tráfico de la aplicación y, si se usan, escribir downlinks).
  4. Supervisa bridge/status: el last will lo pone en offline si el proceso muere.
  5. Recuerda la política de uso justo de TTN para la red comunitaria; está pensada para sensores de baja frecuencia, no para dispositivos que envían mensajes sin parar.

Producción en un broker privado

Define OUT_HOST=broker.connectmq.com, OUT_PORT=8883, OUT_USER, OUT_PASS y OUT_CLIENT_ID con las credenciales emitidas para tu broker privado, y cambia OUT_PREFIX por tu propia jerarquía. El resto del script no cambia. Un broker MQTT gestionado ofrece a tus datos LoRaWAN, sensores Wi-Fi y controladores un hogar compartido y cifrado que no tienes que mantener. Si ejecutas tu propio servidor de red en lugar de TTN, el tutorial de ChirpStack MQTT muestra el puente equivalente, y el montaje del sensor de humedad del suelo MQTT es una alternativa Wi-Fi para campos cercanos a edificios. Encontrarás más patrones en la guía de automatización IoT, y la introducción a qué hace un broker MQTT te ayudará si MQTT es nuevo para ti.

Lleva tus datos LoRaWAN a ConnectMQ

ConnectMQ ofrece a tus datos de TTN un broker MQTT privado con TLS, un explorador de topics en directo, un panel y reglas de alerta por umbral para valores como batería baja o suelo seco. Crea un broker Serverless gratuito en app.connectmq.com, sin tarjeta de crédito, y apunta a él la salida del puente.

Preguntas frecuentes

¿Cuál es el usuario y la contraseña MQTT de TTN?

El usuario es el ID de tu aplicación seguido de @ttn (en la edición comunitaria de The Things Network), y la contraseña es una API key generada en Integrations > MQTT.

No hay ningún payload formatter de uplink configurado, o el que hay ha lanzado un error. Pruébalo en el panel del formatter de la consola con un payload hexadecimal real.

¿Puedo conectar TTN directamente a otro broker sin código?

TTN ofrece webhooks y otras integraciones, pero para publicar en tu propio broker MQTT con tu propia estructura de topics, un pequeño puente como este es la opción más sencilla y transparente.

¿El puente tiene que ejecutarse cerca del gateway?

No. Ambas conexiones van por internet, así que puede ejecutarse en cualquier servidor, una Raspberry Pi o un contenedor.

Artículos relacionados

Guía para configurar Zigbee2MQTT: del coordinador al panel

Instala Zigbee2MQTT con Docker, configura el coordinador y la conexión MQTT, empareja y renombra dispositivos, contrólalos por MQTT y consulta sus estados en un panel, probando primero en un servidor MQTT público gratuito.

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.