Para trabajar con LoRaWAN en un PLC necesitas cuatro cosas: una radio LoRa en el PLC, una pila LoRaWAN (en el programa del PLC o dentro del módulo de radio), un gateway con cobertura y un servidor de red, como The Things Network, que conoce las claves del dispositivo y envía los datos a tu aplicación. El PLC envía paquetes pequeños, el gateway los recibe y el servidor de red los comprueba, los descifra y los entrega.
Esta guía recorre el camino completo con un M-Duino de la familia LoRa (Arduino PLC) y la librería MCCI LoRaWAN LMIC: la elección entre OTAA y ABP, el registro del dispositivo en The Things Network, el mapa de pines, el ciclo de envío, los límites de tiempo en el aire y la decodificación del payload. Las secciones sobre activación, tiempo en el aire y payloads sirven para cualquier nodo LoRaWAN, también si no programas en Arduino.
¿Cómo envía datos un PLC por LoRaWAN?
LoRa es la modulación de radio. LoRaWAN es el protocolo de red que funciona sobre ella: define la capa MAC (Media Access Control), la activación de los dispositivos, la seguridad y el papel de los gateways y los servidores. Una red LoRaWAN es una estrella de estrellas: los dispositivos finales hablan con los gateways por radio, y los gateways hablan con un servidor de red por IP.
- 1 · PLCM-Duino LoRaLee sus entradas, prepara un payload pequeño y lo envía por radio. Aquí la librería LMIC ejecuta la pila LoRaWAN en el propio PLC.
- 2 · RadioGatewayRecibe todos los paquetes LoRa a su alcance en sus canales y los reenvía por Ethernet, Wi-Fi o 4G.
- 3 · RedServidor de redThe Things Network comprueba el código de integridad del mensaje y el contador de tramas, y elimina los duplicados de varios gateways.
- 4 · AplicaciónServidor de aplicaciónDescifra el payload y ejecuta tu payload formatter para convertir los bytes en valores.
- 5 · Tu sistemaSCADA, base de datosRecibe los datos decodificados mediante una integración, por ejemplo MQTT o un webhook.
Ejemplo Un camino posible. Los uplinks van de izquierda a derecha. Un downlink solo puede llegar a un dispositivo de Clase A en las dos ventanas cortas de recepción que siguen a cada uplink.
Cada sesión LoRaWAN 1.0.x usa dos claves AES-128. La clave de sesión de red (NwkSKey) protege la integridad del mensaje entre el dispositivo y el servidor de red. La clave de sesión de aplicación (AppSKey) cifra el payload desde el dispositivo hasta el servidor de aplicación, así que los gateways y el servidor de red solo ven un payload cifrado.
El alcance depende de la altura de la antena, el terreno, los obstáculos y el spreading factor. Va de cientos de metros dentro de edificios a varios kilómetros en campo abierto. Un spreading factor más alto llega más lejos, pero cada paquete pasa más tiempo en el aire.
OTAA o ABP: elige la activación antes de escribir el código
Un dispositivo LoRaWAN necesita claves de sesión antes de poder enviar. Las obtiene de una de estas dos formas. Over-The-Air Activation (OTAA) crea claves de sesión nuevas en un procedimiento de join. Activation By Personalization (ABP) guarda claves de sesión fijas en el dispositivo. Las claves que copias en el sketch, y el propio sketch (ttn-otaa o ttn-abp), dependen de esta elección.
| OTAA | ABP | |
|---|---|---|
| Valores guardados en el PLC | DevEUI, JoinEUI (también llamado AppEUI) y la clave raíz AppKey | DevAddr, NwkSKey y AppSKey |
| Procedimiento de join | Sí. El dispositivo envía un join request y la red responde con un join accept | No. El dispositivo puede enviar enseguida |
| Claves de sesión | Nuevas en cada join, derivadas de la AppKey | Las mismas durante toda la vida del dispositivo |
| Parámetros de red | El join accept puede llevar la lista de canales y los parámetros de recepción | Los canales y los parámetros de recepción se fijan en el código |
| Contadores de tramas tras reiniciar el PLC | Un join nuevo abre una sesión nueva con contadores nuevos | Los contadores vuelven a 0 si no los guardas, y la red descarta las tramas que ya ha visto |
| Adecuado para | Dispositivos instalados y producción | Pruebas cortas de laboratorio |
Para un PLC en campo, usa OTAA. La AppKey nunca viaja por el aire, y el dispositivo puede volver a hacer join tras un corte de alimentación o un cambio de red. Si usas ABP, cada dispositivo necesita su propio DevAddr y sus propias claves, y debes guardar los contadores de tramas en memoria no volátil.
Hardware y software para el ejemplo con M-Duino LoRa
- Un PLC de la familia M-Duino LoRa con una antena para tu banda. Nuestro ejemplo usa un M-Duino 21+ LoRa. La familia se basa en Arduino Mega 2560 (ATmega2560, 256 KB de flash, 8 KB de SRAM), consulta el datasheet de la familia M-Duino LoRa.
- Arduino IDE con el paquete de placas de Industrial Shields. En Herramientas, elige la placa M-Duino LoRa family y tu modelo. Sigue la guía de primeros pasos si todavía no tienes las placas instaladas.
- La librería MCCI LoRaWAN LMIC, versión de Industrial Shields, desde GitHub (versión 6.0.1-is1 en el momento de escribir este artículo). Es compatible con LoRaWAN 1.0.2 y 1.0.3. La región y la radio se configuran en
project_config/lmic_project_config.h: por defecto esCFG_eu868. Para Norteamérica, cámbialo aCFG_us915. - Una cuenta en The Things Network y un gateway con cobertura, propio o de la comunidad.
Registra el PLC como dispositivo final en The Things Network
El servidor de red debe conocer tu dispositivo antes del primer join. En la consola de The Things Network:
- Crea una aplicación. El nombre es libre.
- Haz clic en "+ Register end device" y elige "Enter end device specifics manually".
- Frequency plan: "Europe 863-870 MHz (SF9 for RX2 - recommended)" para EU868. Si estás fuera de Europa, elige el plan de tu región.
- LoRaWAN version: "LoRaWAN Specification 1.0.2". Regional Parameters version: "RP001 Regional Parameters 1.0.2".
- JoinEUI (AppEUI): usa el valor del proveedor de tu módulo si lo tienes. Si no, se acepta un valor de todo ceros.
- Genera el DevEUI y la AppKey, y registra el dispositivo.
Después puedes indicar la ubicación del dispositivo y un payload formatter (ver más abajo). La consola muestra cada clave con un selector de orden de bytes (msb o lsb) y una vista de array en estilo C. Lo necesitarás en el paso siguiente.
Mapa de pines LMIC en M-Duino LoRa y Ardbox LoRa
Abre el ejemplo desde Archivo, Ejemplos, MCCI LoRaWAN LMIC library, y elige ttn-otaa o ttn-abp. Los dos sketches tienen la misma estructura. Cambian las claves del principio y el setup.
La librería se comunica con la radio LoRa por SPI. Tienes que indicarle qué pines de Arduino controlan el chip select (NSS) y el reset de la radio:
- Familia M-Duino LoRa: chip select en el pin 12, reset en el pin 13.
- Familia Ardbox LoRa: chip select en el pin 23, reset en el pin 3. No uses el pin 3 para nada más en tu programa, por ejemplo como entrada de interrupción.
La versión de Industrial Shields de la librería no necesita las líneas DIO de la radio, así que se quedan como LMIC_UNUSED_PIN. Sin LMIC_USE_INTERRUPTS, la librería lee los flags de interrupción de la radio por SPI cada vez que se ejecuta os_runloop_once(). Por eso tu loop() debe llamarla a menudo y no bloquearse nunca.
// Pin mapping for the M-Duino LoRa family
const lmic_pinmap lmic_pins = {
.nss = 12, // radio chip select
.rxtx = LMIC_UNUSED_PIN,
.rst = 13, // radio reset
.dio = {LMIC_UNUSED_PIN, LMIC_UNUSED_PIN, LMIC_UNUSED_PIN},
};
En el M-Duino LoRa, el pin 13 es el reset de la radio. Algunos ejemplos genéricos de Arduino hacen parpadear el pin 13 como LED de estado. No copies ese código aquí, porque reinicia la radio.
Claves del dispositivo en el sketch: OTAA y ABP
Claves OTAA
Sustituye FILLMEIN por los valores de la consola. En la librería LMIC, el DevEUI y el JoinEUI van en orden little-endian (primero el byte menos significativo, "lsb" en la consola). La AppKey va en orden big-endian ("msb" en la consola).
static const u1_t PROGMEM APPEUI[8] = { FILLMEIN }; // JoinEUI, lsb
static const u1_t PROGMEM DEVEUI[8] = { FILLMEIN }; // DevEUI, lsb
static const u1_t PROGMEM APPKEY[16] = { FILLMEIN }; // AppKey, msb
Con OTAA, la primera llamada a do_send() inicia el join. Tras el join accept, The Things Network envía al dispositivo los canales EU868 adicionales, así que no hace falta fijarlos en el código.
Claves ABP
Para ABP, registra el dispositivo con activación ABP en la consola y copia el DevAddr, la NwkSKey y la AppSKey. Las dos claves van en orden big-endian. El DevAddr es un número de 32 bits, por ejemplo 0x260B1234.
static const PROGMEM u1_t NWKSKEY[16] = { FILLMEIN }; // msb
static const u1_t PROGMEM APPSKEY[16] = { FILLMEIN }; // msb
static const u4_t DEVADDR = FILLMEIN; // unique for every device
Un dispositivo ABP arranca solo con los tres canales EU868 por defecto. El ejemplo ttn-abp añade los demás canales de The Things Network después de LMIC_setSession(), fija la velocidad de RX2 en SF9 y el uplink en SF7 a 14 dBm:
#if defined(CFG_eu868)
LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI); // g-band
LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK, DR_FSK), BAND_MILLI); // g2-band
#else
# error Region not supported
#endif
LMIC_setLinkCheckMode(0);
LMIC.dn2Dr = DR_SF9; // The Things Network uses SF9 for RX2
LMIC_setDrTxpow(DR_SF7, 14); // uplink data rate and power
El ciclo de envío de LMIC: do_send, onEvent y os_runloop_once
El ejemplo envía el texto "Hello, world!" (13 bytes) cada TX_INTERVAL segundos. Tres funciones hacen funcionar el ciclo.
do_send() pone en cola un uplink en el puerto 1, salvo que haya una transmisión pendiente:
static uint8_t mydata[] = "Hello, world!";
static osjob_t sendjob;
// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
const unsigned TX_INTERVAL = 60;
void do_send(osjob_t* j){
// Check if there is not a current TX/RX job running
if (LMIC.opmode & OP_TXRXPEND) {
Serial.println(F("OP_TXRXPEND, not sending"));
} else {
// Prepare upstream data transmission at the next possible time.
LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0);
Serial.println(F("Packet queued"));
}
// Next TX is scheduled after TX_COMPLETE event.
}
onEvent() no sirve solo para depurar. Cuando llega el evento EV_TXCOMPLETE, programa el siguiente do_send(). Sin esta función, el PLC envía un paquete y se para. Esta es una versión reducida del gestor de eventos del ejemplo de la librería:
void onEvent(ev_t ev) {
switch (ev) {
case EV_JOINED:
Serial.println(F("EV_JOINED"));
LMIC_setLinkCheckMode(0);
break;
case EV_TXCOMPLETE:
Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
if (LMIC.dataLen) {
Serial.print(F("Received "));
Serial.print(LMIC.dataLen);
Serial.println(F(" bytes of payload"));
}
// Schedule next transmission
os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
break;
default:
break;
}
}
os_runloop_once() ejecuta el planificador de LMIC. Llámala en cada loop() y lee las entradas del PLC con temporizadores basados en millis(), no con delay():
void setup() {
Serial.begin(9600);
os_init(); // LMIC init
LMIC_reset(); // reset the MAC state
do_send(&sendjob); // first send (starts the join with OTAA)
}
void loop() {
os_runloop_once();
}
Tiempo en el aire, duty cycle y política de uso justo de The Things Network
Cada paquete LoRa ocupa el canal durante un tiempo que crece con el tamaño del payload y el spreading factor. Ese tiempo en el aire tiene dos límites:
- Duty cycle normativo. En la banda EU863-870, las sub-bandas que usan los canales LoRaWAN tienen un duty cycle del 1% (ETSI EN 300 220-2). Tras 1 s de transmisión, el dispositivo debe quedarse en silencio 99 s en esa sub-banda.
- Política de uso justo de The Things Network. En la red comunitaria, cada nodo dispone de 30 s de tiempo en el aire de uplink cada 24 horas y de 10 mensajes de downlink como máximo cada 24 horas.
Cálculo Tiempo en el aire del mensaje del ejemplo ("Hello, world!", 13 bytes de payload más 13 bytes de cabeceras LoRaWAN), ancho de banda de 125 kHz, coding rate 4/5, 8 símbolos de preámbulo, cabecera explícita y CRC:
| Spreading factor | Tiempo en el aire por uplink | Tiempo en el aire al día, 1 uplink cada 60 s | Uplinks al día dentro de 30 s |
|---|---|---|---|
| SF7 | 61,7 ms | 88,8 s | 486 (uno cada 178 s) |
| SF9 | 205,8 ms | 296,4 s | 145 (uno cada 596 s) |
| SF10 | 411,6 ms | 592,8 s | 72 (uno cada 1200 s) |
| SF12 | 1646,6 ms | 2371,1 s | 18 (uno cada 4800 s) |
Por tanto, el intervalo de 60 s del ejemplo sirve para una prueba en banco, pero en The Things Network es demasiado frecuente para un dispositivo que funciona cada día. Para un PLC en campo, envía menos y más pequeño: un payload binario de pocos bytes en lugar de texto, y un intervalo adecuado a tu spreading factor. LoRaWAN transporta datos de proceso y alarmas. No es un enlace para lazos de control rápidos.
Payload formatter para The Things Network
La red entrega bytes en bruto. Un payload formatter en la consola los convierte en campos. Para este ejemplo de texto, elige el formatter JavaScript personalizado para uplinks y pega este código. Convierte cada byte en un carácter:
function decodeUplink(input) {
if (input.bytes && input.bytes.length > 0) {
const decodedChars = [];
for (let i = 0; i < input.bytes.length; i++) {
const charValue = String.fromCharCode(input.bytes[i]);
decodedChars.push(charValue);
}
return {
data: {
decodedChars: decodedChars.join('')
},
warnings: [],
errors: []
};
} else {
return {
data: {},
warnings: ["No bytes in the input"],
errors: []
};
}
}
Para datos reales del PLC, empaqueta los valores en bits y bytes en el sketch y decodifícalos con desplazamientos y máscaras en el formatter. El ejemplo de empaquetado de bits en un payload LoRaWAN mete 11 entradas digitales, 2 flags de fallo y un valor analógico de 10 bits en 4 bytes.
Primer uplink: qué comprobar y errores habituales
Abre el Monitor Serie. Con OTAA deberías ver EV_JOINING, después EV_JOINED, y luego Packet queued y EV_TXCOMPLETE una vez por intervalo. En la consola, la pestaña Live data del dispositivo muestra el join accept y cada uplink. Con el formatter anterior, el campo decodificado muestra "Hello, world!".
| Síntoma | Causa habitual | Qué hacer |
|---|---|---|
Join requests en la consola, pero sin EV_JOINED | Orden de bytes incorrecto en DevEUI o JoinEUI, AppKey incorrecta | Copia DevEUI y JoinEUI como lsb y la AppKey como msb |
| Nada en la consola | Ningún gateway con cobertura, o un plan de frecuencias que no coincide con lmic_project_config.h | Comprueba la cobertura del gateway y usa la misma región en la librería y en la consola |
| Join aceptado en la consola, pero el PLC no lo recibe | La ventana de recepción se abre demasiado tarde o demasiado pronto | Añade LMIC_setClockError() después de LMIC_reset() para ampliar las ventanas de recepción. Consulta la sección Timing de la documentación de la librería |
| Dispositivo ABP: uplinks ignorados tras un reinicio | El contador de tramas ha vuelto a 0 | Guarda el contador o, solo para una prueba de laboratorio, activa "Resets frame counters" en los parámetros MAC del dispositivo |
| Menos uplinks de los esperados | Límites de duty cycle en la librería | Usa un intervalo más largo o un spreading factor más bajo |
LoRaWAN en ESP32 PLC y Raspberry PLC
La parte de red es la misma para cualquier PLC: la misma activación, las mismas claves, planes de frecuencias, límites de tiempo en el aire y payload formatters. Lo que cambia es el dispositivo. En la familia M-Duino LoRa, la librería LMIC ejecuta la pila LoRaWAN en el microcontrolador del PLC. En la familia ESP32 PLC, la placa de expansión LoRa usa un módulo Microchip RN2483 (UE y Asia) o RN2903 (EE. UU. y Australia), que lleva la pila LoRaWAN dentro y recibe comandos ASCII por UART, según el datasheet de la familia ESP32 PLC. Tu programa envía comandos como "join" y "send", y el módulo hace el resto.
Los modelos ESP32 PLC y Raspberry PLC ofrecen una placa de comunicación LoRa en versión EU868 o US902. Consulta la familia ESP32 PLC y la familia Raspberry PLC, y estas guías:
- Cómo trabajar con LoRaWAN y un ESP32 PLC
- Librería LoRa RN2483 para PLC basados en ESP32
- LoRaWAN en Raspberry Pi
- Tareas FreeRTOS en un ESP32 PLC con un watchdog LoRaWAN, con un nuevo join OTAA cada hora (PLC Solution Library)
- Estación de dos bombas con telemetría LoRaWAN en un ESP32 PLC (PLC Solution Library)
Mira la telemetría LoRaWAN a escala de red. El simulador de telemetría de sitios remotos reproduce la red de agua de un municipio con RTU ESP32 PLC sobre 4G y LoRaWAN. Corta un enlace y observa el store and forward, las alarmas y la detección de fugas por caudal nocturno.
Abre el simulador de telemetríaEnlaces LoRa en casos de estudio con PLC
Estos casos de estudio usan LoRa para traer datos de PLC desde lugares sin cable:
- Planta de hormigón: un M-Duino dosifica cada material y envía los registros de cada amasada a un servidor por LoRa, para prever producción, facturación y averías.
- Bomba de calor geotérmica: un M-Duino vigila presión y temperatura, y LoRa es uno de los enlaces con sitios remotos.
- Paneles solares autónomos: un PLC por grupo de paneles informa a un PLC maestro por LoRa.
Más sobre LoRa y comunicaciones industriales
Preguntas frecuentes sobre LoRaWAN y PLC
¿Qué diferencia hay entre LoRa y LoRaWAN?
LoRa es la modulación de radio que transporta los bits. LoRaWAN es el protocolo de red sobre LoRa: activación de dispositivos, seguridad AES-128, contadores de tramas, gateways y servidores de red. Dos PLC pueden hablar punto a punto solo con LoRa. Para usar The Things Network u otra red LoRaWAN, el PLC necesita una pila LoRaWAN.
¿OTAA o ABP para un PLC industrial?
Usa OTAA. Crea claves de sesión nuevas en cada join, no envía la clave raíz por el aire y se recupera tras un reinicio. ABP sirve para pruebas cortas de laboratorio, pero necesita claves fijas y únicas por dispositivo y contadores de tramas guardados en memoria no volátil.
¿Por qué mi PLC no hace join en The Things Network?
Revisa primero el orden de bytes: en la librería LMIC, DevEUI y JoinEUI van en lsb y la AppKey en msb. Después comprueba que la región de lmic_project_config.h coincide con el plan de frecuencias de la consola y que hay un gateway con cobertura.
¿Cada cuánto puede enviar datos un PLC por LoRaWAN en The Things Network?
La política de uso justo da a cada nodo 30 s de tiempo en el aire de uplink al día. Un payload de 13 bytes en SF7 tarda 61,7 ms, así que caben unos 486 uplinks al día. En SF12 tarda 1646,6 ms, así que caben unos 18 al día.
¿Puede un PLC LoRaWAN recibir órdenes?
Sí, como downlinks. Un dispositivo de Clase A solo escucha en dos ventanas cortas de recepción después de cada uplink, así que una orden espera al siguiente uplink. The Things Network permite 10 downlinks por nodo y día. Usa los downlinks para consignas y configuración, no para control rápido.
¿Qué plan de frecuencias necesito?
El del país donde se instala el PLC: por ejemplo, EU868 en Europa y US915 en Norteamérica. El hardware de radio, la región de la librería y el plan de frecuencias de la consola deben coincidir. Las placas LoRa de Industrial Shields existen en versiones para distintas bandas, por ejemplo EU868 y US902.