En esta publicación te mostraré cómo enviar datos usando LoRaWAN con el Thinknode G1 Gateway. LoRa (Long Range) es un método inalámbrico para enviar pequeñas cantidades de datos a distancias mucho mayores (varios kilómetros) que las que se pueden lograr con WiFi o Bluetooth, por ejemplo.
LoRaWAN es un protocolo de transmisión que utiliza LoRa. Permite construir sistemas IoT complejos con comunicación segura entre dispositivos y aplicaciones en la nube. Si quieres monitorizar sensores a través de internet, LoRaWAN es lo que necesitas.
Los dispositivos finales (sensores) envían sus datos a un Gateway, que los reenvía a un Servidor de Red. Un Servidor de Aplicaciones puede entonces acceder al Servidor de Red para procesar los datos, por ejemplo, mostrando un gráfico con datos de temperatura.

Ten en cuenta que los sensores transmiten datos al Gateway vía LoRa, mientras que el Gateway se comunica con el Servidor de Red mediante conexión Wi-Fi, Ethernet o celular. El Gateway es esencialmente un puente entre LoRa e internet.
En las siguientes secciones usaremos un ESP32 con un módulo LoRa SX1276 para transmitir datos ambientales como temperatura y humedad medidos por un BME280 vía LoRaWAN a través de un Gateway Thinknode G1.
Conectaremos este Gateway a un Servidor de Red en The Things Network, donde podrás monitorizar los datos ambientales o acceder a ellos desde una aplicación web para su procesamiento.
¡Empecemos!
Partes necesarias
Usé un ESP32 Lite como microprocesador para el nodo LoRa, pero cualquier otro ESP32 funcionará bien. Si quieres usar un Arduino u otro microprocesador, necesitarás una placa que funcione a 3.3V.
En cuanto al módulo transceptor LoRa SX1276, ¡cuidado con la versión que compres! Depending on the country las frecuencias permitidas son diferentes. Es 868MHz en Europa, 915MHz en Norteamérica y 433MHz en Asia.
La descripción del módulo indica la frecuencia o tiene un número como 868 o 915 en el nombre. Yo listé un módulo de 868MHz, ya que estoy en Europa. Pero también puedes conseguir este módulo para la banda de 915MHz.
De igual forma, asegúrate de que el Gateway LoRaWAN que uses pueda operar en la banda de frecuencia requerida. El ThinkNode G1 Gateway listado abajo soporta las frecuencias 868MHz y 915MHz.
Para el sensor ambiental, elegí el BME280, que mide temperatura, humedad, presión atmosférica y altitud. Asegúrate de comprar la versión de 3.3V, ya que lo conectaremos al ESP32.

ThinkNode G1 LoRaWAN Gateway

Módulo LoRa 868/915M SX1276

ESP32 lite

Cable de datos USB

Sensor BME280

Juego de cables Dupont

Protoboard
Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.
¿Qué es LoRaWAN?
LoRaWAN significa Long Range Wide Area Network. Es un protocolo inalámbrico diseñado específicamente para el Internet de las Cosas (IoT), que permite a los dispositivos comunicarse a largas distancias — hablamos de varios kilómetros, incluso en áreas urbanas — usando muy poca energía.
A diferencia de WiFi o Bluetooth, que son ideales para comunicación de corto alcance y alto ancho de banda, LoRaWAN se centra en el alcance y la eficiencia. Un sensor típicamente envía pequeños paquetes de datos cada pocos minutos u horas y puede funcionar con una pequeña batería durante años.
Es ideal para dispositivos alimentados por batería que envían poca información, como sensores de temperatura, rastreadores GPS o monitores de humedad del suelo, a distancias que Wi-Fi o Bluetooth no pueden alcanzar. Por otro lado, no puedes enviar imágenes o video por LoRa.
Supongamos que tienes un sensor de temperatura en un lugar remoto. No hay WiFi pero quieres monitorizar la temperatura cada 15 minutos. ¿Qué haces?
Conectas un sensor de temperatura a un ESP32 que está conectado a un módulo LoRa (como el SX1276). Envía los datos inalámbricamente vía LoRa a un gateway LoRaWAN, en nuestro caso un Thinknode G1. Este gateway puede escuchar señales de múltiples sensores a kilómetros de distancia y reenvía sus datos al servidor de red. El gateway es esencialmente un puente hacia la web.
Puedes ejecutar tu propio servidor de red o usar uno público. Aquí usaremos The Things Network (TTN), una infraestructura LoRaWAN gratuita y de código abierto. Desde TTN, los datos pueden visualizarse en un panel, enviarse a un servicio en la nube o usarse para activar otras acciones — como enviar alertas o encender un ventilador. Y dado que el Servidor de Red funciona en la nube, tus datos pueden procesarse, visualizarse o usarse desde cualquier parte del mundo.
En las siguientes secciones configuramos el Gateway, lo conectamos a The Things Network y construimos el nodo final LoRa con un ESP32 como microcontrolador, el SX1276 como transceptor LoRa y el BME280 como sensor ambiental.
El ThinkNode G1 Gateway
ThinkNode G1 de Elecrow es un gateway LoRaWAN para interiores diseñado para conectarse a varios servidores de red. Este gateway soporta múltiples métodos de conexión para configuración como WiFi, Bluetooth y Ethernet. Soporta transmisión de 8 canales y utiliza tecnología inalámbrica LoRa para lograr transmisión de datos a larga distancia.

El gateway está equipado con un concentrador LoRa SX1302 y dos chips SX1250, ofreciendo 10 rutas de demodulación paralelas programables. Soporta bandas de frecuencia ISM globales, con un rango de frecuencia de 815MHz a 960MHz.
Especificaciones técnicas
La siguiente tabla de la página del producto Elecrow lista los detalles técnicos del Thinknode G1 Gateway:
| Procesador | CPU/SoC | MT7628N (MIPS24KEc@580MHz CPU) |
| Memoria del sistema | 128 MB DDR2 | |
| Almacenamiento | 32MB Nor Flash | |
| Software de configuración | WEB | |
| Comunicación | ||
| Wi-Fi | Soporta estándares inalámbricos IEEE 802.11 b/g/n, antena integrada | |
| Cableado | Soporta estándares cableados IEEE 802.3, IEEE 802.3u, RJ45 (10M / 100M) | |
| Compatible con Bluetooth | Bluetooth compatible dual-mode (BR/EDR+BLE) 5.0 BLE, antena cerámica integrada | |
| LoRaWAN | Chip baseband | SX1302, usando módulo baseband LR1302 |
| Canales | 8 canales | |
| Protocolo de nodo | Soporta Clase A/Clase B/Clase C | |
| Banda de frecuencia | EU868/US915 | |
| Sensibilidad | -125dBm @125KHz/SF7 -139dBm @125KHz/SF12 | |
| Potencia de transmisión | Máximo 26 dBm | |
| Antena | 1. Antena de goma tipo stick (matching), ganancia 3dBi, impedancia 50 ohm; 2. Antena externa con base (opcional), ganancia 3dBi, impedancia 50 ohm; | |
| Interfaz reservada | Conector DC | Entrada de alimentación, DC 12V – 2A |
| Ethernet | RJ45 (10M / 100 M) | |
| Conector de antena LoRa | Conector hembra RP-SMA | |
| Conector de antena GPS | Conector hembra RP-SMA | |
| Interfaz Type-C | Usado para control de fondo, conexión al panel de configuración o para grabar firmware, depuración | |
| Ranura para tarjeta Micro SD | SÍ | |
| Ranura para tarjeta Nano SIM | SÍ | |
| Luz indicadora reservada | Luz de estado del dispositivo | SÍ |
| 4G | SÍ (requiere personalización) | |
| WLAN | SÍ | |
| LoRa | SÍ | |
| PWR | SÍ | |
| Botón | Botón de reinicio | |
| Tamaño | 140*140*38mm | |
| Material de la carcasa | ABS+PC (carcasa), PC esmerilado (guía de luz) | |
| Entrada de alimentación | 12V-2A | |
| Temperatura de operación | -20℃~55℃ | |
| Temperatura de almacenamiento | -30℃~70℃ | |
Para información adicional consulta ThinkNode G1 Datasheet.
Luces indicadoras
El Thinknode G1 tiene cinco luces indicadoras. Las cuatro frontales indican si el gateway tiene energía y está conectado a LoRa, WLAN o LTE. La foto abajo muestra la parte superior del G1 con las cuatro luces indicadoras en el frente de la carcasa:

El gran LED en forma de Y en la parte superior puede iluminarse en diferentes colores y parpadear a distintas velocidades. La tabla a continuación describe el significado de las señales:

Conectores
En la parte trasera del Thinknode G1 encontrarás los conectores para la fuente de alimentación, Ethernet (ETH), USB-C, la antena LoRa, ranuras para Micro SD y tarjeta Nano SIM:

También en la parte trasera hay un botón para reiniciar o entrar en modo configuración. La siguiente tabla describe cómo cambiar entre modos:

Configuración del hardware
Primero conecta la antena y luego la fuente de alimentación. El LED de energía debería ponerse verde y el indicador superior estará rojo hasta que el gateway esté configurado correctamente.

Para la configuración, puedes conectar al gateway vía cable Ethernet (ETH) o WIFI. El User Manual del ThinkNode G1 describe ambos métodos. En la siguiente sección te muestro cómo configuré el software del ThinkNode G1 usando la conexión WIFI.
Configuración del software
Para configurar vía Wi-Fi, mantén presionado el botón en la parte trasera del ThinkNode G1 hasta que el indicador LED superior se ponga azul (esto toma unos 5 segundos):

Acceso a la interfaz de usuario
Luego escanea tu red Wi-Fi para un nuevo punto de acceso llamado ThinkNode-G1_XXXXXX y conéctate a él:

Abre tu navegador y ve a la dirección IP 192.168.1.1. Deberías ver una pantalla de inicio de sesión, donde introduces «root» como usuario y también como contraseña:

Conectar a Wi-Fi
Para conectar el Gateway a tu red Wi-Fi ve a Network -> Wireless

y haz clic en Scan para buscar tu red Wi-Fi:

En la lista haz clic en tu red Wi-Fi y en el diálogo siguiente introduce la contraseña para acceder.

Configurar LoRaWAN
Luego necesitamos configurar la interfaz LoRa y la frecuencia. Ve a LoRaWAN -> LoRa Gateway

En el diálogo siguiente selecciona WIFI como interfaz LoRa y EU868 como plan de frecuencia para Europa. En Norteamérica debes seleccionar US915. Configura el modo LoRa a Packet Forwarder:

(Alternativamente, si quieres configurar el gateway como estación base, consulta el Webtutorial).
Mantén los demás parámetros como están pero asegúrate de que la dirección del servidor esté configurada a eu1.cloud.thethings.network (si vives en Europa). La necesitaremos para conectar el gateway a The Things Network.
Finalmente presiona Guardar y Aplicar, lo que reiniciará el Gateway y, si tienes suerte, eso es todo.

Sin embargo, tuve problemas y tuve que ingresar también el servidor DNS, de lo contrario mi G1 no se conectaba a The Things Network.
Servidor DNS
Para agregar o editar el servidor DNS ve a Network -> Interfaces y presiona el botón Editar para LAN:

En el diálogo haz clic en la pestaña Configuración avanzada y edita los campos Usar servidores DNS personalizados:

Agregué 8.8.8.8 (Google) y 1.1.1.1 (Cloudflare) como servidores DNS y finalmente mi gateway se conectó a The Things Network.
Conectando el Gateway a The Things Network
The Things Network (TTN) te ofrece un Servidor de Red público que permite enviar datos de sensores desde un dispositivo LoRa, por ejemplo temperatura, a un sitio web donde puedes ver los datos o procesarlos con una aplicación web.
El primer paso es conectar nuestro ThinkNode G1 Gateway a TTN. Ve a la siguiente URL: https://eu1.cloud.thethings.network/console/, haz clic en el botón azul Añadir y selecciona Añadir nuevo gateway:

Introduce el EUI de tu Gateway, que está impreso en la parte trasera del ThinkNode G1 Gateway o se encuentra en la página de estado de la interfaz de configuración del gateway. Después de ingresar el EUI presiona Confirmar:

Esto ampliará el diálogo y podrás ingresar un ID único para el Gateway, por ejemplo el EUI del gateway con el prefijo ‘eui’ y un nombre para el Gateway, yo elegí Maet-ThinkNode-G1 para esta prueba. También debes seleccionar un plan de frecuencia que coincida con la frecuencia de tu gateway (Europa 863-870 MHz), por ejemplo:

Mantén todo lo demás igual y presiona Registrar gateway. Si todo funciona correctamente verás una página de estado que muestra un estado verde indicando que el Gateway está conectado:

Si llegaste hasta aquí, felicidades. Lo peor ya pasó ; )
Crear una aplicación
The Things Network (TTN) organiza los dispositivos finales (sensores, actuadores) en llamadas «Aplicaciones». Su función es decodificar datos y opcionalmente reenviarlos a servidores externos vía Webhooks o MQTT.
Las aplicaciones, gateways y dispositivos finales interactúan así: El dispositivo final envía datos (uplink) vía LoRa. Luego el Gateway recibe los datos y los reenvía a TTN. Finalmente el Servidor de Red de TTN enruta los datos a una aplicación específica usando el DevEUI del dispositivo.
[End Device] ←→ [Gateway] ←→ [TTN Network Server] ←→ [Application]
Así que, antes de poder recibir y monitorizar datos de sensores en TTN, necesitamos crear una aplicación. Para ello ve a https://eu1.cloud.thethings.network/console/applications/add, que abrirá el siguiente diálogo:

Introduce un ID de aplicación, por ejemplo env-sensor-network y un nombre de aplicación, por ejemplo Environmental Sensors como se muestra arriba.
Registrar dispositivo final
Luego vamos a añadir y registrar nuestro dispositivo final. En la página de tu aplicación (https://eu1.cloud.thethings.network/console/applications/env-sensor-network) deberías ver un recuadro con un botón azul etiquetado «+ Registrar dispositivo final»:

Presiona este botón y verás el siguiente diálogo:

Introduce los datos de tu dispositivo final, especialmente el plan de frecuencia, la versión de LoRaWAN y el JoinEUI. El JoinEUI (antes llamado AppEUI) es un número con el siguiente formato: 70B3D57EDxxxxxxx. Reemplaza ‘xxxxxxx’ con un número hexadecimal para crear un JoinEUI único para tu dispositivo final, por ejemplo 70B3D57ED0000001.
Cuando termines, haz clic en el botón Confirmar a la derecha, lo que ampliará el diálogo y te permitirá generar un DevEUI y una AppKey. Finalmente, debes darle a tu dispositivo final un ID único:

Presiona Register end device y luego deberías ver la información del dispositivo que creaste:

La información más importante son el AppEUI, DevEUI y AppKey. Necesitarás estas credenciales en el código del dispositivo final. Permiten que el dispositivo se autentique en The Things Network.
Construyendo un dispositivo final LoRaWAN con SX1276 y ESP32
Puedes comprar muchos dispositivos LoRaWAN preconstruidos con todo tipo de sensores y funciones, pero son caros. Para una visión general, consulta el Device Repository for LoRaWAN de The Things Network. En este tutorial construiremos nuestro propio dispositivo final, que no es difícil y mucho más barato ; )
Usaremos un ESP32 como microcontrolador y un módulo transceptor SX1276 para transmitir datos vía LoRa. Si necesitas más información sobre el SX1276, consulta el tutorial Long range communication with LoRa SX1276 and ESP32. A continuación, seré breve y solo te mostraré cómo conectar el SX1276 al ESP32. Aquí está la tabla de conexiones:
| SX1276 | ESP32 |
|---|---|
| MOSI | 23 |
| MSIO | 19 |
| SCK | 18 |
| RST | 17 |
| NSS | 5 |
| DIO0 | 4 |
| DIO1 | 16 |
| GND | GND |
| VCC | 3.3V |
Asegúrate de conectar VCC al pin de salida 3.3V de tu ESP32. Además de la interfaz SPI (MOSI, MISO, SCK, RST, NSS), las conexiones digitales IO en los pines DIO0 y DIO1 también son esenciales. La imagen abajo muestra el diagrama completo de conexiones:

Como se mencionó antes, debes usar un módulo transceptor SX1276 que opere en la LoRa frequency that is permitted in your country. En el diagrama de conexiones arriba puedes ver que el SX1276 está marcado como 868M, lo que significa que usa la banda de 868MHz para Europa. Para Norteamérica necesitarás un módulo de 915MHz.
La frecuencia del SX1276, el Gateway y la configuración en The Things Network deben coincidir. En mi caso, todos están configurados a 868MHz.
Enviando datos de prueba vía LoRaWAN
Ahora escribamos el código para enviar algunos datos de prueba desde nuestro dispositivo final (ESP32 + SX1276) vía LoRaWAN a TTN.
Pero primero necesitarás instalar una librería para LoRaWAN y usaremos la MCCI LoRaWAN LMIC library. Solo abre el LIBRARY MANAGER, busca «mcci lorawan lmic library» y presiona INSTALL. La imagen abajo muestra una instalación exitosa:

Nota: LoRaWAN define tres clases de dispositivos: Clase A, Clase B y Clase C, cada una con diferentes compromisos entre consumo de energía y disponibilidad de downlink. Clase A es el modo predeterminado y más eficiente energéticamente, donde un dispositivo solo puede recibir mensajes downlink en dos ventanas cortas después de transmitir un uplink. Esta clase es ideal para sensores alimentados por batería y está totalmente soportada por la librería LMIC.
La Clase B añade ventanas de recepción programadas usando balizas periódicas enviadas por el gateway, permitiendo un acceso downlink más predecible a costa de mayor consumo. Los dispositivos Clase C mantienen sus ventanas de recepción abiertas casi todo el tiempo, permitiendo comunicación downlink de baja latencia pero requieren alimentación constante (por ejemplo, dispositivos conectados a la red). Sin embargo, la librería LMIC solo soporta Clase A.
Configuración de la librería
Luego necesitamos configurar la librería LMIC para la frecuencia LoRa, versión y chip que usamos. La librería LMIC contiene un archivo lmic_project_config.h que normalmente encontrarás en la siguiente ruta en Windows:
...\OneDrive\Documents\Arduino\libraries\MCCI_LoRaWAN_LMIC_library\project_config
Abre este archivo y edita su contenido como se muestra a continuación:
#define CFG_eu868 1 //#define CFG_us915 1 //#define CFG_au915 1 //#define CFG_as923 1 //#define CFG_kr920 1 //#define CFG_in866 1 #define CFG_sx1276_radio 1 //#define CFG_sx1261_radio 1 //#define CFG_sx1262_radio 1 //#define ARDUINO_heltec_wifi_lora_32_V3 //#define LMIC_USE_INTERRUPTS #define LMIC_LORAWAN_SPEC_VERSION LMIC_LORAWAN_SPEC_VERSION_1_0_3
De nuevo, si no vives en Europa, deberás elegir la frecuencia LoRa para tu país (Europa -> CFG_eu868, Norteamérica -> CFG_us915, …). Asegúrate de comentar todas las demás frecuencias.
Estamos usando el chip SX1276, así que también definimos CFG_sx1276_radio 1. Si usas otro chip LoRa, deberás cambiar esto aquí también.
Código para enviar datos con LoRaWAN
El siguiente código envía cada minuto un valor contador como cadena, por ejemplo «Counter = 5» desde nuestro dispositivo final (ESP32 + SX1276) vía el ThinkNode Gateway a TTN, donde podemos inspeccionarlo. Más sobre esto después. Por ahora, echa un vistazo rápido al código antes de discutir sus detalles:
#include <lmic.h>
#include <hal/hal.h>
static const u1_t PROGMEM APPEUI[8] = { 0x01, 0x00, 0x00, 0xD0, 0x7E, 0xD5, 0xB3, 0x70 };
void os_getArtEui(u1_t* buf) {
memcpy_P(buf, APPEUI, 8);
}
static const u1_t PROGMEM DEVEUI[8] = { 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28};
void os_getDevEui(u1_t* buf) {
memcpy_P(buf, DEVEUI, 8);
}
static const u1_t PROGMEM APPKEY[16] = { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18 };
void os_getDevKey(u1_t* buf) {
memcpy_P(buf, APPKEY, 16);
}
static osjob_t sendjob;
const unsigned TX_INTERVAL = 60; // Send every minute
const lmic_pinmap lmic_pins = {
.nss = 5,
.rxtx = LMIC_UNUSED_PIN,
.rst = 17,
.dio = { 4, 16, LMIC_UNUSED_PIN }
};
void onEvent(ev_t ev) {
Serial.printf("%d: ", os_getTime());
switch (ev) {
case EV_JOINING:
Serial.println("EV_JOINING");
break;
case EV_JOINED:
Serial.println("EV_JOINED");
LMIC_setLinkCheckMode(0);
break;
case EV_JOIN_FAILED:
Serial.println("EV_JOIN_FAILED");
break;
case EV_TXCOMPLETE:
Serial.println("EV_TXCOMPLETE");
if (LMIC.dataLen) {
Serial.printf("> Downlink %d bytes\n", LMIC.dataLen);
Serial.print("> Data: ");
for (int i = 0; i < LMIC.dataLen; i++) {
Serial.printf("%02X ", LMIC.frame[LMIC.dataBeg + i]);
}
Serial.println();
}
os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(TX_INTERVAL), do_send);
break;
case EV_TXSTART:
Serial.println(F("EV_TXSTART"));
break;
case EV_TXCANCELED:
Serial.println("EV_TXCANCELED");
break;
case EV_JOIN_TXCOMPLETE:
Serial.println("EV_JOIN_TXCOMPLETE: no JoinAccept");
break;
default:
Serial.print("Unknown event: ");
Serial.println((unsigned)ev);
break;
}
}
void do_send(osjob_t* j) {
static uint16_t counter = 0;
static char mydata[25];
if (LMIC.opmode & OP_TXRXPEND) {
Serial.println(F("OP_TXRXPEND, not sending"));
} else {
snprintf(mydata, sizeof(mydata), "Counter = %u", counter);
LMIC_setTxData2(1, (uint8_t*)mydata, strlen(mydata), 0);
Serial.printf("> Data: %s\n", mydata);
counter++;
}
}
void setup() {
Serial.begin(115200);
os_init_ex(&lmic_pins);
LMIC_reset();
do_send(&sendjob);
}
void loop() {
os_runloop_once();
}
Librerías
El código comienza incluyendo las librerías LMIC necesarias:
#include <lmic.h> #include <hal/hal.h>
Estas librerías nos dan todo lo necesario para comunicarnos con el chip SX1276 y gestionar la funcionalidad LoRaWAN. El encabezado lmic.h maneja la lógica del protocolo, mientras que hal/hal.h conecta LMIC con el hardware (en este caso, nuestro ESP32 y SX1276).
Autenticación
La autenticación LoRaWAN comienza con tres claves críticas: APPEUI, DEVEUI y APPKEY. Estas son credenciales del dispositivo usadas para unirse a una red LoRaWAN usando OTAA (Over-The-Air Activation). Ingresaste/generaste estas claves cuando creaste el dispositivo final en TTN y puedes verlas en la información del dispositivo:

Sin embargo, la librería LMIC espera los bytes para APPEUI y DEVEUI en orden LSB primero (Least Significant Byte first), lo que significa que debes invertir los bytes para APPEUI y DEVEUI en el código. Por ejemplo, si el AppEUI en la información del dispositivo en TTN es 70 B3 D5 7E D0 00 00 01 como se muestra arriba, en el código se convierte en:
APPEUI[8] = { 0x01, 0x00, 0x00, 0xD0, 0x7E, 0xD5, 0xB3, 0x70 };
Lo mismo aplica para DEVUI pero no para APPKEY – este permanece en el mismo orden de bytes.
Constantes de claves
El código crea arrays constantes para estas claves de autenticación en el orden correcto y luego llama a una función que copia la clave en un buffer que LMIC usa durante el proceso de unión. Estas funciones aseguran que tu ESP32 pueda autenticarse de forma segura con tu servidor de red LoRaWAN, como The Things Network.
Abajo la constante para APPEUI con la función «copy» os_getArtEui():
static const u1_t PROGMEM APPEUI[8] = { 0x01, 0x00, 0x00, 0xD0, 0x7E, 0xD5, 0xB3, 0x70 };
void os_getArtEui(u1_t* buf) {
memcpy_P(buf, APPEUI, 8);
}
La directiva PROGMEM almacena los datos en memoria flash para ahorrar RAM. La misma lógica aplica para el EUI del dispositivo y la clave de aplicación. Solo recuerda que APPEUI y DEVEUI están en orden de bytes invertido pero no APPKEY:
static const u1_t PROGMEM DEVEUI[8] = { 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28};
void os_getDevEui(u1_t* buf) {
memcpy_P(buf, DEVEUI, 8);
}
static const u1_t PROGMEM APPKEY[16] = { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18 };
void os_getDevKey(u1_t* buf) {
memcpy_P(buf, APPKEY, 16);
}
Obviamente, deberás usar los valores APPEUI, DEVEUI y APPKEY para tu aplicación y dispositivo final TTN.
Constantes
Luego, el código define un objeto global de tarea y un intervalo de transmisión.
static osjob_t sendjob; const unsigned TX_INTERVAL = 60; // Send every minute
El objeto sendjob mantiene la tarea programada para ejecución posterior, mientras que TX_INTERVAL establece la frecuencia con la que quieres transmitir datos — en este caso, cada 60 segundos.
Mapeo de pines
Ahora definimos el mapeo de pines entre el ESP32 y el módulo LoRa SX1276:
const lmic_pinmap lmic_pins = {
.nss = 5,
.rxtx = LMIC_UNUSED_PIN,
.rst = 17,
.dio = { 4, 16, LMIC_UNUSED_PIN }
};
Esta estructura indica a LMIC qué pines GPIO están conectados a las líneas de control del SX1276. Por ejemplo, nss es el pin chip-select SPI, rst es Reset, y dio corresponde a los pines de interrupción usados por el radio.
onEvent
La función onEvent() maneja varios eventos LoRaWAN — desde solicitudes de unión hasta finalización de transmisiones.
void onEvent(ev_t ev) {
Serial.printf("%d: ", os_getTime());
switch (ev) {
case EV_JOINING:
Serial.println("EV_JOINING");
break;
case EV_JOINED:
Serial.println("EV_JOINED");
LMIC_setLinkCheckMode(0);
break;
...
}
}
Esta función te ayuda a monitorear lo que hace tu dispositivo. Por ejemplo, EV_JOINED significa que el dispositivo se conectó exitosamente a la red, y desactivamos el modo de verificación de enlace con LMIC_setLinkCheckMode(0) para ahorrar tiempo de aire. Cuando EV_TXCOMPLETE se activa, el dispositivo terminó de enviar datos y programamos la siguiente transmisión:
case EV_TXCOMPLETE:
Serial.println("EV_TXCOMPLETE");
if (LMIC.dataLen) {
Serial.printf("> Downlink %d bytes\n", LMIC.dataLen);
Serial.print("> Data: ");
for (int i = 0; i < LMIC.dataLen; i++) {
Serial.printf("%02X ", LMIC.frame[LMIC.dataBeg + i]);
}
Serial.println();
}
os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(TX_INTERVAL), do_send);
break;
El caso EV_TXCOMPLETE también verifica si el módulo LoRa recibió datos (downlink). Si hay un downlink (LMIC.dataLen > 0) el código imprime los bytes de los datos como números hexadecimales.
Puedes enviar datos desde TTN haciendo clic en la pestaña Mensajes de tu dispositivo final:

do_send
La lógica principal de transmisión ocurre dentro de la función do_send().
void do_send(osjob_t* j) {
static uint16_t counter = 0;
static char mydata[25];
if (LMIC.opmode & OP_TXRXPEND) {
Serial.println(F("OP_TXRXPEND, not sending"));
} else {
snprintf(mydata, sizeof(mydata), "Counter = %u", counter);
LMIC_setTxData2(1, (uint8_t*)mydata, strlen(mydata), 0);
Serial.printf("> Data: %s\n", mydata);
counter++;
}
}
Esta función construye una cadena como "Counter = 23" y la pone en cola para transmisión. Primero verifica si otra transmisión está pendiente — LoRaWAN es estricto con los tiempos. Si está libre, envía el paquete en el puerto 1 sin confirmación (el último argumento 0 significa uplink no confirmado).
setup
La función setup() inicia todo:
void setup() {
Serial.begin(115200);
os_init_ex(&lmic_pins);
LMIC_reset();
do_send(&sendjob);
}
Inicializa el puerto serial para depuración, configura la pila LMIC con el mapeo de pines correcto, reinicia el estado interno de LMIC y programa la primera transmisión.
loop
Finalmente, la función loop() mantiene el runtime de LMIC activo.
void loop() {
os_runloop_once();
}
A diferencia de los bucles tradicionales de Arduino, LMIC requiere esta llamada dentro de loop() para mantener el programador de tareas y el sistema de eventos funcionando correctamente.
Monitor serial
Si subes el código y abres el Monitor Serial deberías ver los siguientes mensajes apareciendo:

Mensajes uplink
Con esta configuración, tu ESP32 y SX1276 enviarán una cadena contador vía LoRaWAN cada minuto. Si vas a TTN puedes elegir ver los mensajes de datos uplink enviados desde nuestro dispositivo final. Selecciona Aplicaciones y haz clic en Datos en vivo:

En el lado derecho verás el mensaje con marca de tiempo enviado desde nuestro ESP32:

Formateador de carga útil
Sin embargo, no muestra los datos reales del mensaje, nuestro valor contador. Esto es porque TTN no sabe cómo decodificar los datos en el mensaje. Necesitarás especificar o implementar un formateador de carga útil para poder ver los datos.
Abre la barra lateral, selecciona Aplicación, haz clic en Formateadores de carga útil y selecciona Uplink:

Puedes seleccionar un formateador existente o implementar uno propio. Implementaremos uno propio y por eso seleccionamos «Formateador Javascript personalizado» como Tipo de formateador:

Para «Código del formateador» introduce el siguiente código, que simplemente convierte los bytes que enviamos de vuelta a caracteres:
function decodeUplink(input) {
let str = "";
for (let i = 0; i < input.bytes.length; i++) {
str += String.fromCharCode(input.bytes[i]);
}
return { data: { message: str } };
}
Si ahora miras los mensajes uplink de nuevo encontrarás un campo Payload al final, que muestra la cadena con el valor contador que envía el dispositivo final:

En la siguiente sección añadiremos un sensor ambiental al ESP32 y cambiaremos el código para enviar datos de temperatura, humedad y presión atmosférica vía LoRaWAN a TTN.
Enviando datos ambientales vía LoRaWAN
Una aplicación clásica para LoRaWAN es enviar datos ambientales como temperatura, humedad y presión atmosférica desde un dispositivo final LoRa a TTN. En esta sección añadiremos el sensor BME280 a nuestro circuito y ampliaremos el código para enviar los datos medidos por el sensor.
Si quieres más información sobre el BME280 consulta el How To Use BME280 Pressure Sensor o el Temperature Plotter on e-Paper Display tutoriales.
Conectando BME280 al ESP32
El sensor BME280 tiene una interfaz I2C y es fácil de añadir al circuito. La siguiente tabla muestra todas las conexiones que debes hacer, incluyendo las existentes para el SX1276:
| SX1276 | BME280 | ESP32 |
|---|---|---|
| MOSI | – | 23 |
| MSIO | – | 19 |
| SCK | – | 18 |
| RST | – | 17 |
| NSS | – | 5 |
| DIO0 | – | 4 |
| DIO1 | – | 16 |
| – | SCL | 25 |
| – | SDA | 33 |
| GND | GND | GND |
| VCC | VCC | 3.3V |
Y aquí el cableado completo entre BME280, SX1276 y el ESP32:

Código para enviar datos ambientales vía LoRaWAN
Luego instalaremos dos librerías: La Adafruit_BME280 para leer datos del BME280:

y la CayenneLPP library, que formatea datos de sensores para transmisiones LoRaWAN:

El siguiente código es un cambio y extensión simple del código anterior. En lugar de enviar un contador, enviamos mediciones de temperatura, humedad y presión atmosférica del sensor BME280. Échale un vistazo rápido y luego discutiremos las diferencias con el código anterior:
#include <lmic.h>
#include <hal/hal.h>
#include <CayenneLPP.h>
#include <Adafruit_BME280.h>
static const u1_t PROGMEM APPEUI[8] = { 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18 };
void os_getArtEui(u1_t* buf) {
memcpy_P(buf, APPEUI, 8);
}
static const u1_t PROGMEM DEVEUI[8] = { 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28};
void os_getDevEui(u1_t* buf) {
memcpy_P(buf, DEVEUI, 8);
}
static const u1_t PROGMEM APPKEY[16] = { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18 };
void os_getDevKey(u1_t* buf) {
memcpy_P(buf, APPKEY, 16);
}
static osjob_t sendjob;
const unsigned TX_INTERVAL = 60; // Send every minute
const lmic_pinmap lmic_pins = {
.nss = 5,
.rxtx = LMIC_UNUSED_PIN,
.rst = 17,
.dio = { 4, 16, LMIC_UNUSED_PIN }
};
CayenneLPP lpp(16);
Adafruit_BME280 bme;
void initBMESensor() {
Wire.begin(33, 25); // Software I2C for BME280
bme.begin(0x76, &Wire);
bme.setSampling(Adafruit_BME280::MODE_FORCED,
Adafruit_BME280::SAMPLING_X1, // temperature
Adafruit_BME280::SAMPLING_X1, // pressure
Adafruit_BME280::SAMPLING_X1, // humidity
Adafruit_BME280::FILTER_OFF);
}
void onEvent(ev_t ev) {
Serial.printf("%d: ", os_getTime());
switch (ev) {
case EV_JOINING:
Serial.println("EV_JOINING");
break;
case EV_JOINED:
Serial.println("EV_JOINED");
LMIC_setLinkCheckMode(0);
break;
case EV_JOIN_FAILED:
Serial.println("EV_JOIN_FAILED");
break;
case EV_TXCOMPLETE:
Serial.println("EV_TXCOMPLETE");
if (LMIC.dataLen) {
Serial.printf("> Downlink %d bytes\n", LMIC.dataLen);
Serial.print("> Data: ");
for (int i = 0; i < LMIC.dataLen; i++) {
Serial.printf("%02X ", LMIC.frame[LMIC.dataBeg + i]);
}
Serial.println();
}
os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(TX_INTERVAL), do_send);
break;
case EV_TXSTART:
Serial.println(F("EV_TXSTART"));
break;
case EV_TXCANCELED:
Serial.println("EV_TXCANCELED");
break;
case EV_JOIN_TXCOMPLETE:
Serial.println("EV_JOIN_TXCOMPLETE: no JoinAccept");
break;
default:
Serial.print("Unknown event: ");
Serial.println((unsigned)ev);
break;
}
}
void do_send(osjob_t* j) {
bme.takeForcedMeasurement();
float temp = bme.readTemperature();
float hum = bme.readHumidity();
float pres = bme.readPressure() / 100.0;
lpp.reset();
lpp.addTemperature(1, temp); // channel 1
lpp.addRelativeHumidity(2, hum); // channel 2
lpp.addBarometricPressure(3, pres); // channel 3
if (LMIC.opmode & OP_TXRXPEND) {
Serial.println(F("OP_TXRXPEND, not sending"));
} else {
LMIC_setTxData2(1, lpp.getBuffer(), lpp.getSize(), 0);
Serial.printf("> Temperature: %.2f\n", temp);
Serial.printf("> Humidity: %.2f\n", hum);
Serial.printf("> Pressure: %.2f\n", pres);
}
}
void setup() {
Serial.begin(115200);
initBMESensor();
os_init_ex(&lmic_pins);
LMIC_reset();
do_send(&sendjob);
}
void loop() {
os_runloop_once();
}
El código usa las mismas claves para APPEUI, DEVEUI y APPKEY que antes. La principal extensión es una función que inicializa el sensor BME280 y un cambio en la función que envía los datos.
initBMESensor
La función initBMESensor() inicializa el BME280 usando I2C por software. En lugar de los pines I2C por defecto del ESP32, especifica GPIO 33 para SDA y GPIO 25 para SCL:
void initBMESensor() {
Wire.begin(33, 25); // Software I2C for BME280
bme.begin(0x76, &Wire);
bme.setSampling(Adafruit_BME280::MODE_FORCED,
Adafruit_BME280::SAMPLING_X1, // temperature
Adafruit_BME280::SAMPLING_X1, // pressure
Adafruit_BME280::SAMPLING_X1, // humidity
Adafruit_BME280::FILTER_OFF);
}
Ten en cuenta que uso la dirección I2C 0x76 al configurar el sensor vía bme.begin(0x76, &Wire). Tu sensor puede tener una dirección I2C diferente. Por ejemplo, 0x77 también es común para el BME280.
El sensor está configurado en «modo forzado», lo que significa que solo mide cuando se le indica explícitamente. Este enfoque ahorra energía. El muestreo está al mínimo (1x) para temperatura, presión y humedad, y el filtro interno está desactivado.
do_send
La función do_send() primero obtiene mediciones de temperatura, humedad y presión atmosférica del sensor BME280
void do_send(osjob_t* j) {
bme.takeForcedMeasurement();
float temp = bme.readTemperature();
float hum = bme.readHumidity();
float pres = bme.readPressure() / 100.0;
Luego formatea los datos para la transmisión LoRaWAN añadiendo temperatura, humedad y presión atmosférica como diferentes canales al paquete de datos:
lpp.reset(); lpp.addTemperature(1, temp); // channel 1 lpp.addRelativeHumidity(2, hum); // channel 2 lpp.addBarometricPressure(3, pres); // channel 3
Finalmente, los datos se envían como el paquete usual de bytes. Además, el código imprime los datos en el Monitor Serial:
if (LMIC.opmode & OP_TXRXPEND) {
Serial.println(F("OP_TXRXPEND, not sending"));
} else {
LMIC_setTxData2(1, lpp.getBuffer(), lpp.getSize(), 0);
Serial.printf("> Temperature: %.2f\n", temp);
Serial.printf("> Humidity: %.2f\n", hum);
Serial.printf("> Pressure: %.2f\n", pres);
}
}
Para más información consulta el tutorial Send Environmental Data with LoRa que hace algo similar pero usa LoRa puro en lugar de LoRaWAN.
Monitor Serial
Si subes y ejecutas el código deberías ver la siguiente salida en tu Monitor Serial:

Formateador de carga útil
Si quieres inspeccionar los datos en TTN necesitarás especificar un formateador de carga útil como antes. Sin embargo, en este caso es más sencillo. No necesitamos escribir nuestro propio formateador Javascript personalizado sino que podemos usar el formateador CayenneLPP:

En la vista general del dispositivo puedes ver los datos ambientales formateados en los tres canales:

Y eso es todo. Ahora sabes cómo construir tu propio dispositivo final LoRaWAN para enviar datos ambientales a través del ThinkNode G1 Gateway a TTN.
Conclusiones
Este artículo presentó el Thinknode G1 Gateway. Un gateway LoRaWAN te permite enviar datos desde sensores LoRa a internet. Construimos nuestro propio sensor LoRa con un ESP32, SX1276 y sensor BME280 y lo usamos para enviar temperatura, humedad y presión a The Things Network.
Apenas hemos arañado la superficie de LoRaWAN y te animo a explorar la información en www.thethingsnetwork.org/docs/lorawan para aprender más. Para más guía sobre cómo configurar el ThinkNode G1 consulta el Manual y el Webtutorial.
Si solo quieres transmitir datos entre dos dispositivos LoRa, LoRaWAN probablemente sea excesivo y te conviene usar LoRa puro. Echa un vistazo al Long range communication with LoRa SX1276 and ESP32 y al Send Environmental Data with LoRa.
Finalmente, si prefieres un Arduino en lugar del ESP32, necesitarás usar un convertidor de nivel lógico o un Arduino que funcione a 3.3 Voltios, ya que el módulo SX1276 no funciona con 5V. Consulta el tutorial Interface Arduino Uno with ST7735 TFT using Level Shifter para más información.
Si tienes alguna pregunta, no dudes en dejarla en la sección de comentarios.
¡Feliz bricolaje ; )
Enlaces
Algunos enlaces que encontré útiles al escribir este artículo:
- ThinkNode G1 Gateway Setup
- ThinkNode G1 LoRaWAN gateway setup and testing
- Maximizing LoRaWAN coverage with an indoor gateway – ThinkNode G1 setup and review
- 5 Things You Need to Know about LoRaWAN-based Gateways
- Set Up the LoRa Module Network to Communicate with the ThinkNode Gateway
- How to use ESP32 for LoRa communication with the Things Network
- MCCI LoRaWAN LMIC Library
- CayenneLPP Library

