Automatización IoT

Integración MQTT de ChirpStack para LoRaWAN: reenvía uplinks decodificados a un broker en la nube

Usa la integración MQTT nativa de ChirpStack para publicar uplinks LoRaWAN decodificados y reenvíalos a un broker MQTT en la nube con un bridge de Mosquitto o un pequeño reenviador en Node.js.

Integración MQTT de ChirpStack que reenvía uplinks LoRaWAN decodificados desde un servidor de red local a 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

La integración MQTT de ChirpStack publica cada evento LoRaWAN de tu propio servidor de red en un broker MQTT, lo que la convierte en el punto de partida natural para llevar los datos de los sensores de campo a paneles y automatizaciones. En este tutorial configurarás la integración en ChirpStack v4, decodificarás los payloads con un códec del perfil de dispositivo y reenviarás los uplinks decodificados a un broker en la nube de dos maneras: un bridge de Mosquitto sin código y un reenviador en Node.js que reestructura los datos en tu propio árbol de topics.

Cómo usa ChirpStack MQTT

ChirpStack es un servidor de red LoRaWAN de código abierto. En una instalación típica (por ejemplo, la configuración oficial con Docker Compose), MQTT aparece dos veces:

  1. Backend de gateways. Los bridges de los gateways publican tramas de radio en bruto en un broker Mosquitto local, y ChirpStack se suscribe a ellas.
  2. Integración MQTT. Tras la deduplicación, el descifrado y la decodificación, ChirpStack publica eventos de aplicación (uplinks, joins, confirmaciones, errores) en topics MQTT del mismo broker local.

Lo que te interesa son los eventos de la integración. Residen en un broker dentro de la red del servidor LoRaWAN, que normalmente no es accesible desde paneles, teléfonos u otras sedes. Reenviarlos a un broker en la nube resuelve esto sin exponer el propio servidor de red.

 Sensors ~~LoRa~~> Gateway --> local Mosquitto <--> ChirpStack
                                    |
                     application/.../event/up
                                    |
                  Mosquitto bridge or Node.js forwarder
                                    |   (TLS)
                                    v
                           Cloud MQTT broker --> Node-RED, dashboards, alerts

Paso 1: activar la integración MQTT de ChirpStack

En chirpstack.toml, la sección de integración tiene este aspecto en una instalación estándar:

[integration]
  enabled = ["mqtt"]

  [integration.mqtt]
    server = "tcp://mosquitto:1883/"
    json = true

Con json = true, los eventos son JSON en lugar de Protobuf, lo que resulta mucho más fácil de manejar. El topic de eventos por defecto es:

application/<application_id>/device/<dev_eui>/event/<event>

donde <event> es up, join, ack, txack, log, status o location. Reinicia ChirpStack después de modificar el archivo y observa el broker local en el servidor:

mosquitto_sub -h localhost -p 1883 -t 'application/+/device/+/event/up' -v

Paso 2: decodificar los payloads con un códec

Los payloads LoRaWAN en bruto son bytes, entregados codificados en base64 en el campo data. Añade un códec JavaScript al perfil de dispositivo (Device profiles > your profile > Codec, tipo JavaScript) para que ChirpStack añada un campo object decodificado a cada uplink:

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

A partir de entonces, un evento de uplink contiene, entre otros campos, deviceInfo.deviceName, deviceInfo.devEui, fCnt, fPort, time, object (tus valores decodificados) y rxInfo (una entrada por gateway con rssi y snr).

Opción A: reenviar con un bridge de Mosquitto

Si ChirpStack ya funciona con Mosquitto, un bridge es la vía más rápida: unas pocas líneas de configuración y Mosquitto reenvía los topics coincidentes al broker en la nube, reconectándose automáticamente. Crea /etc/mosquitto/conf.d/cloud-bridge.conf (o el equivalente en tu volumen de Docker):

connection connectmq
address broker.connectmq.com:1883
remote_clientid cs-bridge-c3h7k5
cleansession true
try_private false
bridge_protocol_version mqttv311
notifications true
notification_topic connectmq/demo/c3h7k5/bridge/state

# local:  application/<app>/device/<eui>/event/up
# remoto: connectmq/demo/c3h7k5/<app>/device/<eui>/event/up
topic +/device/+/event/up out 1 application/ connectmq/demo/c3h7k5/

La línea topic admite un patrón, una dirección, un QoS y dos prefijos. Mosquitto elimina el prefijo local application/ y añade el prefijo remoto, lo que mantiene el topic remoto en ocho niveles, el máximo permitido en el servidor MQTT público gratuito, que es solo para pruebas y prototipos, no para datos de producción. Sustituye c3h7k5 por tu propia cadena aleatoria; el área pública es compartida y cualquiera puede leerla. Se recomienda try_private false cuando el broker remoto no es Mosquitto.

Reinicia Mosquitto y suscríbete desde cualquier lugar:

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

Para producción, cambia a TLS y a las credenciales emitidas para tu broker privado:

connection connectmq
address broker.connectmq.com:8883
bridge_cafile /etc/ssl/certs/ca-certificates.crt
remote_clientid YOUR_CLIENT_ID
remote_username YOUR_USERNAME
remote_password YOUR_PASSWORD
cleansession true
try_private false
notifications true
notification_topic farm1/lorawan/bridge/state
topic +/device/+/event/up out 1 application/ farm1/lorawan/

La limitación de un bridge es que los payloads se reenvían sin cambios. Los eventos de ChirpStack son extensos, con datos en base64, información de transmisión y metadatos completos de los gateways, y los topics usan UUID de aplicación y DevEUI en lugar de nombres legibles.

Opción B: un reenviador MQTT de ChirpStack en Node.js

Cuando los consumidores deben ver datos compactos y con nombre, es mejor un pequeño reenviador. Este usa MQTT.js v5, se suscribe a los uplinks en el broker local y republica un registro limpio por dispositivo. Instálalo con npm install mqtt.

// forwarder.mjs
import mqtt from "mqtt";

const LOCAL_URL = process.env.LOCAL_URL ?? "mqtt://localhost:1883";
const OUT_URL = process.env.OUT_URL ?? "mqtt://broker.connectmq.com:1883";
const OUT_PREFIX = process.env.OUT_PREFIX ?? "connectmq/demo/c3h7k5/farm";

const out = mqtt.connect(OUT_URL, {
  clientId: process.env.OUT_CLIENT_ID ?? "cs-forwarder-c3h7k5",
  username: process.env.OUT_USER,
  password: process.env.OUT_PASS,
  protocolVersion: 5,
  will: { topic: `${OUT_PREFIX}/forwarder/status`, payload: "offline", qos: 1, retain: true },
});
out.on("connect", () => {
  console.log("cloud broker connected");
  out.publish(`${OUT_PREFIX}/forwarder/status`, "online", { qos: 1, retain: true });
});
out.on("error", (err) => console.error("cloud:", err.message));

const local = mqtt.connect(LOCAL_URL, { clientId: "cs-forwarder-local-c3h7k5" });
local.on("connect", () => {
  local.subscribe("application/+/device/+/event/up", { qos: 1 });
  console.log("local broker connected");
});

local.on("message", (topic, payload) => {
  let ev;
  try {
    ev = JSON.parse(payload.toString());
  } catch {
    return console.warn("non-JSON event on", topic);
  }
  if (!ev.object) {
    return console.warn(`${ev.deviceInfo?.deviceName}: no decoded object, check the codec`);
  }
  const rx = ev.rxInfo ?? [];
  const best = rx.reduce((a, g) => ((g.rssi ?? -999) > (a.rssi ?? -999) ? g : a), {});
  const name = ev.deviceInfo.deviceName.replace(/[^A-Za-z0-9_-]/g, "_");
  const record = {
    ts: ev.time,
    dev_eui: ev.deviceInfo.devEui,
    ...ev.object,
    f_cnt: ev.fCnt,
    rssi: best.rssi ?? null,
    snr: best.snr ?? null,
    gateways: rx.length,
  };
  out.publish(`${OUT_PREFIX}/${name}/telemetry`, JSON.stringify(record), { qos: 1, retain: true });
  console.log(name, record);
});

Ejecútalo junto a ChirpStack:

node forwarder.mjs
# producción
OUT_URL=mqtts://broker.connectmq.com:8883 OUT_USER=YOUR_USERNAME \
OUT_PASS=YOUR_PASSWORD OUT_CLIENT_ID=YOUR_CLIENT_ID OUT_PREFIX=farm1/north \
node forwarder.mjs

Los nombres de dispositivo se depuran antes de incluirlos en el topic, ya que caracteres como +, # y / romperían la jerarquía. Los registros se retienen para que los paneles vean de inmediato la última lectura de cada sensor, aunque los dispositivos LoRaWAN puedan informar solo cada 15 minutos.

Cómo elegir entre ambas opciones

Criterio Bridge de Mosquitto Reenviador en Node.js
Esfuerzo de configuración Un archivo de configuración Un pequeño script y un servicio
Payload Evento completo de ChirpStack Registro compacto que defines tú
Nombres de topic ID de aplicación y DevEUI Nombres de dispositivo legibles, tu jerarquía
Piezas móviles Ninguna aparte de Mosquitto Un proceso adicional que supervisar
Downlinks Añadir una línea topic con dirección in Añadir una suscripción y una publicación

Muchas instalaciones empiezan con el bridge para que los datos fluyan y pasan a un reenviador cuando se multiplican los consumidores. El tutorial de The Things Network MQTT usa la misma idea de reenviador en Python, algo útil si tu explotación combina redes LoRaWAN comunitarias y privadas.

Para poner en cola un downlink, publica en el topic de comandos del dispositivo en el broker local:

mosquitto_pub -h localhost -p 1883 \
  -t 'application/YOUR_APP_ID/device/0004a30b001c1234/command/down' \
  -m '{"devEui":"0004a30b001c1234","confirmed":false,"fPort":2,"data":"Hg=="}'

data está en base64 (Hg== es el byte único 30, por ejemplo un intervalo de envío en minutos). Los dispositivos de clase A lo reciben tras su siguiente uplink. Reenvía comandos de downlink desde la nube solo si confías en todos los clientes que pueden publicar en ellos, y valídalos en el reenviador.

Solución de problemas

  1. No llega nada a event/up. Comprueba que el dispositivo se ha unido (busca event/join), que la integración está activada y que json = true si esperas JSON.
  2. Falta object. El códec no existe, ha fallado o ha devuelto errores; los mensajes event/log muestran los errores del códec.
  3. El bridge se conecta y luego se cae. remote_clientid duplicado, credenciales incorrectas o falta el archivo de CA para el puerto 8883. El log de Mosquitto muestra el motivo.
  4. Mensajes rechazados en el servidor público. El topic supera los ocho niveles o el cliente supera los diez mensajes por segundo; usa prefijos más cortos y no reenvíes allí los eventos log.

Supervisión de la ruta de reenvío

Una cadena LoRaWAN tiene varios puntos donde puede fallar en silencio: un gateway se queda sin alimentación, ChirpStack deja de decodificar tras editar un códec o el reenviador muere. Vigila cada capa por separado. La interfaz web de ChirpStack muestra la última vez que se vio cada gateway y las tramas por dispositivo. El topic status retenido del reenviador, respaldado por su last will, indica si el propio reenviador está vivo, y el topic de notificaciones del bridge hace lo mismo en la opción A. Por último, la antigüedad del mensaje telemetry retenido de cada dispositivo te dice si los datos siguen fluyendo de extremo a extremo.

En el lado de la nube, el explorador de topics en directo de ConnectMQ es la forma más rápida de confirmar que los registros llegan con la forma esperada tras cambiar un códec, y una regla de alerta por umbral sobre un campo como battery_v avisa con antelación antes de que un sensor deje de informar.

Notas para producción

Mantén privados el servidor de red y el broker de los gateways, y haz que el reenviador o el bridge sea la única vía de salida. Un broker MQTT gestionado se convierte entonces en el centro compartido para los datos LoRaWAN, los dispositivos Wi-Fi y los controladores por igual, y el artículo sobre el broker MQTT en la nube compara las opciones de alojamiento. Para saber qué hacer con los datos, la guía de IoT para agricultura inteligente y el montaje del sistema de riego automático muestran usos prácticos, y la guía de automatización IoT ofrece una visión más amplia.

Reenvía los datos de ChirpStack a ConnectMQ

ConnectMQ ofrece a tus datos LoRaWAN un broker MQTT privado protegido con TLS, con un explorador de topics en directo, un panel y reglas de alerta por umbral para valores como batería baja o heladas. Crea un broker Serverless gratuito en app.connectmq.com, sin tarjeta de crédito, y apunta a él tu bridge o tu reenviador.

Preguntas frecuentes

Por defecto, application/<application_id>/device/<dev_eui>/event/up, con payloads JSON cuando se establece json = true en la integración MQTT.

¿Puede ChirpStack publicar directamente en un broker en la nube?

Puedes apuntar el server de la integración a otro broker, pero mantenerlo local y reenviar solo los topics seleccionados te da control sobre lo que sale de la red.

¿Por qué los payloads de ChirpStack están en base64?

El campo data transporta los bytes LoRaWAN en bruto codificados en base64. Añade un códec al perfil de dispositivo para obtener los valores decodificados en el campo object.

¿El bridge de Mosquitto almacena mensajes en búfer cuando cae internet?

Con cleansession false y QoS 1, Mosquitto puede encolar mensajes para el bridge mientras el enlace está caído, dentro de los límites de cola configurados. Con cleansession true, los mensajes publicados durante la interrupción no se reenvían.

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.