Integración de Gateway ChirpStack R1X con SenseCAP S2101
Introducción
Esta guía te lleva paso a paso para configurar una solución completa de gateway LoRaWAN usando ChirpStack en el controlador edge Seeed reComputer R11, alimentado por Raspberry Pi. Con el módulo concentrador LoRa WM1302, el dispositivo R1X funciona como un gateway potente capaz de comunicación inalámbrica confiable de largo alcance. Al configurar el Semtech Packet Forwarder, los datos LoRa pueden transmitirse sin problemas a ChirpStack, que gestiona las capas de red y aplicación. Usaremos Docker para simplificar la instalación y despliegue de los servicios ChirpStack, asegurando una configuración modular y escalable. Finalmente, el sistema se integra con MQTT, habilitando transmisión segura y en tiempo real de datos IoT desde dispositivos LoRa como el sensor SenseCAP S2101 a aplicaciones accesibles desde cualquier lugar del mundo.
Hardware Requerido
reComputer R1X | Módulo Gateway LoRaWAN WM1302 | SenseCAP S2101 |
---|---|---|
![]() | ![]() | ![]() |
Guía de Instalación de Docker
1. Actualizar Paquetes del Sistema
sudo apt update
sudo apt upgrade
2. Instalar Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
3. Agregar Usuario al Grupo Docker
sudo usermod -aG docker ${USER}
4. Reiniciar Sistema
sudo reboot
5. Verificar Instalación
docker run hello-world
6. Instalar Docker Compose
sudo apt install docker-compose
Perfecto, reformatearé tu sección de configuración del Packet Forwarder en el mismo estilo de wiki estructurado que estás usando:
Ejecutar Packet Forwarder
El concentrador LoRa WM1302 requiere el Semtech Packet Forwarder para retransmitir datos entre el módulo LoRa y ChirpStack. El reComputer R11 proporciona una guía de configuración precompilada para módulos LoRa.
Consulta la Wiki oficial de Seeed para los pasos de instalación: Guía del Módulo LoRa Seeed reComputer R11
Una vez instalado, sigue los pasos a continuación para configurar y ejecutar el Packet Forwarder.
1. Modificar Configuración
Abre el archivo de configuración correspondiente a tu región LoRa. Por ejemplo, para US915:
nano global_conf.json.sx1250.US915
Actualiza la sección gateway_conf para apuntar a tu servidor ChirpStack:
"gateway_conf": {
"gateway_ID": "AA555A0000000000",
/* change with default server address/ports */
"server_address": "localhost",
"serv_port_up": 1700,
"serv_port_down": 1700
}
Reemplaza
AA555A0000000000
con tu ID de Gateway real. Lo mantendremos como está Usa el archivo JSON correcto para tu región LoRaWAN, dependiendo del módulo que hayas comprado.
Guarda el archivo y sal:
- Presiona CTRL + X,
- Luego Y,
- Y finalmente Enter.
2. Iniciar Packet Forwarder
Ejecuta el Packet Forwarder usando la configuración actualizada:
./lora_pkt_fwd -c global_conf.json.sx1250.US915
Aquí está tu sección "Crear Gateway" en el mismo estilo de wiki:
Iniciar Gateway
Después de instalar ChirpStack, puedes registrar tu gateway LoRa R11 y comenzar a procesar datos.
Iniciar Servicios ChirpStack
Si no están ejecutándose ya, lanza todos los servicios ChirpStack:
sudo docker-compose up -d
Verifica que los contenedores estén ejecutándose:
sudo docker ps
Acceder a la Interfaz Web de ChirpStack
- Abre un navegador web y navega a:
http://localhost:8080/
- Inicia sesión con las credenciales predeterminadas:
Username: admin
Password: admin
Agregar tu Gateway
- En la interfaz de ChirpStack, ve a Gateways → Create Gateway
-
Ingresa los siguientes detalles:
- Gateway ID:
AA555A0000000000
(reemplaza con tu ID de Gateway real) - Name: Dale un nombre descriptivo a tu gateway
- Gateway ID:
-
Haz clic en Create Gateway para registrarlo.
-
Después de esto, podrás ver el gateway en la interfaz de ChirpStack
Agregar Perfil de Dispositivo
Para conectar un dispositivo LoRaWAN (ej., SenseCAP S2101) a ChirpStack, primero necesitas crear un Perfil de Dispositivo.
-
Navega a Device Profiles → Create Device Profile
-
Ingresa los siguientes detalles:
- Name: Dale un nombre descriptivo a tu perfil de dispositivo
- Region: Selecciona la región/sub-banda que coincida con tu dispositivo y gateway (ej.,
US915
)
-
Navega a la pestaña Codec:
- Selecciona JavaScript Functions
- Pega el codec para tu dispositivo
⚠️ El codec es específico para tu dispositivo LoRa. Por ejemplo, si estás usando Seeed S201x, puedes usar el código a continuación. Si estás usando un dispositivo diferente, consulta al fabricante para el codec correcto.
- Copia y pega el codec en la sección Uplink/Downlink Codec y guarda el perfil.
.js
function decodeUplink(input) {
return Decode(input.fPort, input.bytes, input.variables);
}
function Decode(fPort, bytes, variables) {
var bytesString = bytes2HexString(bytes).toLocaleUpperCase();
var fport = parseInt(fPort);
var decoded = {
valid: true,
err: 0,
payload: bytesString,
messages: []
};
// CRC check
if (!crc16Check(bytesString)) {
decoded['valid'] = false;
decoded['err'] = -1; // "crc check fail."
return { data: decoded };
}
// Length Check
if ((bytesString.length / 2 - 2) % 7 !== 0) {
decoded['valid'] = false;
decoded['err'] = -2; // "length check fail."
return { data: decoded };
}
// Cache sensor id
var sensorEuiLowBytes;
var sensorEuiHighBytes;
// Handle each frame
var frameArray = divideBy7Bytes(bytesString);
for (var forFrame = 0; forFrame < frameArray.length; forFrame++) {
var frame = frameArray[forFrame];
var channel = strTo10SysNub(frame.substring(0, 2));
var dataID = strTo10SysNub(frame.substring(2, 6));
var dataValue = frame.substring(6, 14);
var realDataValue = isSpecialDataId(dataID) ? ttnDataSpecialFormat(dataID, dataValue) : ttnDataFormat(dataValue);
if (checkDataIdIsMeasureUpload(dataID)) {
decoded.messages.push({
type: 'report_telemetry',
measurementId: dataID,
measurementValue: realDataValue
});
} else if (isSpecialDataId(dataID) || dataID === 5 || dataID === 6) {
switch (dataID) {
case 0x00: // node version
var versionData = sensorAttrForVersion(realDataValue);
decoded.messages.push({
type: 'upload_version',
hardwareVersion: versionData.ver_hardware,
softwareVersion: versionData.ver_software
});
break;
case 1: // sensor version
break;
case 2: // sensor eui low
sensorEuiLowBytes = realDataValue;
break;
case 3: // sensor eui high
sensorEuiHighBytes = realDataValue;
break;
case 7: // battery + interval
decoded.messages.push({
type: 'upload_battery',
battery: realDataValue.power
}, {
type: 'upload_interval',
interval: parseInt(realDataValue.interval) * 60
});
break;
case 9:
decoded.messages.push({
type: 'model_info',
detectionType: realDataValue.detectionType,
modelId: realDataValue.modelId,
modelVer: realDataValue.modelVer
});
break;
case 0x120: // remove sensor
decoded.messages.push({
type: 'report_remove_sensor',
channel: 1
});
break;
default:
break;
}
} else {
decoded.messages.push({
type: 'unknown_message',
dataID: dataID,
dataValue: dataValue
});
}
}
if (sensorEuiHighBytes && sensorEuiLowBytes) {
decoded.messages.unshift({
type: 'upload_sensor_id',
channel: 1,
sensorId: (sensorEuiHighBytes + sensorEuiLowBytes).toUpperCase()
});
}
return { data: decoded };
}
// ---------- Utils ----------
function crc16Check(data) {
return true;
}
function bytes2HexString(arrBytes) {
var str = '';
for (var i = 0; i < arrBytes.length; i++) {
var num = arrBytes[i];
var tmp = (num < 0 ? (255 + num + 1) : num).toString(16);
if (tmp.length === 1) tmp = '0' + tmp;
str += tmp;
}
return str;
}
function divideBy7Bytes(str) {
var frameArray = [];
for (var i = 0; i < str.length - 4; i += 14) {
frameArray.push(str.substring(i, i + 14));
}
return frameArray;
}
function littleEndianTransform(data) {
var arr = [];
for (var i = 0; i < data.length; i += 2) {
arr.push(data.substring(i, i + 2));
}
return arr.reverse();
}
function strTo10SysNub(str) {
var arr = littleEndianTransform(str);
return parseInt(arr.join(''), 16);
}
function checkDataIdIsMeasureUpload(dataId) {
return parseInt(dataId) > 4096;
}
function isSpecialDataId(dataID) {
switch (dataID) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 7:
case 9:
case 0x120:
return true;
default:
return false;
}
}
function ttnDataSpecialFormat(dataId, str) {
var strReverse = littleEndianTransform(str);
if (dataId === 2 || dataId === 3) {
return strReverse.join('');
}
var str2 = toBinary(strReverse);
var arr = [];
switch (dataId) {
case 0: case 1: // versions
for (var k = 0; k < str2.length; k += 16) {
var tmp = str2.substring(k, k + 16);
tmp = (parseInt(tmp.substring(0, 8), 2) || 0) + '.' + (parseInt(tmp.substring(8, 16), 2) || 0);
arr.push(tmp);
}
return arr.join(',');
case 4:
for (var i = 0; i < str2.length; i += 8) {
var item = parseInt(str2.substring(i, i + 8), 2);
arr.push(item < 10 ? '0' + item : item.toString());
}
return arr.join('');
case 7:
return {
interval: parseInt(str2.substr(0, 16), 2),
power: parseInt(str2.substr(-16, 16), 2)
};
case 9:
return {
detectionType: parseInt(str2.substring(0, 8), 2),
modelId: parseInt(str2.substring(8, 16), 2),
modelVer: parseInt(str2.substring(16, 24), 2)
};
}
}
function ttnDataFormat(str) {
var strReverse = littleEndianTransform(str);
var str2 = toBinary(strReverse);
if (str2[0] === '1') {
var arr = str2.split('').map(b => b === '1' ? 0 : 1);
var val = parseInt(arr.join(''), 2) + 1;
return parseFloat('-' + val / 1000);
}
return parseInt(str2, 2) / 1000;
}
function sensorAttrForVersion(dataValue) {
var arr = dataValue.split(',');
return { ver_hardware: arr[0], ver_software: arr[1] };
}
function toBinary(arr) {
return arr.map(item => {
var bin = parseInt(item, 16).toString(2).padStart(8, '0');
return bin;
}).join('');
}
Agregar Dispositivo
Una vez que se crea el Perfil de Dispositivo, puedes registrar tu dispositivo LoRaWAN con ChirpStack.
- Navega a Tenant → Application y haz clic en Add Application
- Ingresa un Nombre para tu aplicación y guárdala
- Abre tu aplicación recién creada y haz clic en Add Device
-
Ingresa los siguientes detalles:
- Device EUI: Pega el EUI de tu dispositivo LoRa (encontrado en la hoja de datos del dispositivo o software de configuración, ej., aplicación SenseCAP)
- Device Profile: Selecciona el perfil de dispositivo que creaste anteriormente
- Ingresa la Application Key y haz clic en Submit
⚠️ El Device EUI y Application Key se pueden obtener de la hoja de datos de tu dispositivo LoRa o software de configuración. Para dispositivos SenseCAP, puedes usar la aplicación SenseCAP para ver o reconfigurar estas configuraciones.
Aquí tienes una versión pulida de tu sección "Verificar Estado del Dispositivo" en tu estilo wiki, manteniéndola consistente con las secciones anteriores:
Verificar Estado del Dispositivo
Después de agregar tu dispositivo LoRaWAN, puedes verificar que esté correctamente conectado y transmitiendo datos.
-
Navega a tu aplicación y selecciona el dispositivo que agregaste
-
Ve a la pestaña Events
- Deberías ver un paquete de unión cuando el dispositivo se une exitosamente a la red
- Haz clic en los paquetes para ver información detallada
- Por ejemplo, puedes ver los datos de temperatura y humedad reportados por dispositivos como el SenseCAP S2101
Integración MQTT
ChirpStack usa MQTT para transmitir datos de dispositivos LoRaWAN a aplicaciones o paneles de control. Puedes monitorear estos mensajes en tiempo real.
-
Conecta tu PC a la misma red que el gateway reComputer R11
-
Usa un cliente MQTT como MQTT Explorer para suscribirte a temas
-
Configura el cliente MQTT:
- Host: Dirección IP de tu reComputer R11
- Port:
1883
-
Una vez conectado, verás un árbol de temas representando tus dispositivos, por ejemplo:
application/c853ffcd-53f0-4de3-83b9-5467ff895f76/device/2cf7f1c043500402/event/up
- Expandir el tema mostrará mensajes de enlace ascendente que contienen datos del sensor, como temperatura y humedad para dispositivos como el SenseCAP S2101
Integración Node-RED
Puedes visualizar datos de dispositivos LoRaWAN en Node-RED usando nodos MQTT y funciones personalizadas.
-
Abre Node-RED y arrastra un nodo MQTT IN al flujo
-
Configura el nodo MQTT:
- Server: IP de tu reComputer R11 (ej.,
10.0.0.208
) - Port:
1883
- Topic:
application/+/device/+/event/up
- Server: IP de tu reComputer R11 (ej.,
-
Agrega un nodo Function para decodificar la carga útil del mensaje MQTT
- Por ejemplo, extrae temperatura y humedad del objeto JSON
// Get the JSON payload
let data = msg.payload;
if (typeof data === "string") {
try {
data = JSON.parse(data);
} catch (e) {
node.error("Invalid JSON", msg);
return [null, null];
}
}
// Check if "object" and "messages" exist
if (!data.object || !Array.isArray(data.object.messages)) {
node.warn("No messages found in payload");
return [null, null];
}
// Find the two measurements
let tempMsg = null;
let humMsg = null;
data.object.messages.forEach(m => {
if (m.type === "report_telemetry") {
if (m.measurementId === 4097) {
tempMsg = { topic: "temperature", payload: m.measurementValue };
} else if (m.measurementId === 4098) {
humMsg = { topic: "humidity", payload: m.measurementValue };
}
}
});
// Return 2 outputs: [temperature, humidity]
return [tempMsg, humMsg];
-
Conecta dos nodos de salida desde el nodo Function, uno para temperatura y otro para humedad
-
Conecta cada salida a un nodo Gauge o cualquier otro nodo de visualización en Node-RED para mostrar las lecturas del sensor
Soporte Técnico y Discusión de Productos
¡Gracias por elegir nuestros productos! Estamos aquí para brindarte diferentes tipos de soporte para asegurar que tu experiencia con nuestros productos sea lo más fluida posible. Ofrecemos varios canales de comunicación para satisfacer diferentes preferencias y necesidades.