El presente proyecto busca realizar una pequeña prueba de concepto de la tecnologia de transmisión LoRa para recabar información que podrá ser usada por la Fundación CTIC. Se enviará información desde un dispositivo final (Nodo o cliente), esta información será recogida por un dispositivo Gateway y este, a su vez, la reenviará a un servidor para almacenarla.
LoRa es una técnología inalambrica (como puede ser WiFi, Bluetooth, LTE, SigFox o Zigbee) que emplea un tipo de modulacion en radiofrecuencia patentada por Semtech.
Actualmente la tecnología esta administrada por la LoRa Alliance que certifica a todo fabricanete Hardware que busque trabajar con dicha tecnología.
LoRa tiene una alta tolerancia a las interferencias, una alta sensibilidad para recibir datos (-168db), un MUY bajo consumo (el dispositivo puede durar una decada con una sola bateria) y un alcance de 10 a 20km (dependiendo de si se dispone de visión directa o de la topologia del terreno). Por otro lado, nos encontramos una muy baja transferencia de datos (hasta 255 bytes).
Todo esto hace que esta técnologia de transferencia sea util para grandes distancias o redes IoT que no necesiten o no disponga de acceso a la corriente eléctrica.
LoRa funciona en las frecuencias libres de trabajo (estas son 868Mhz en Europa, 916Mhz en América o 433Mhz en Asia, por ejemplo) lo que, juntando a la no necesidad de disponer de un proveedor de red electrica y de un proveedor de telecomunicaciones (usamos radiofrecuencia), hace que se pueda emitir y enviar información sin riesgo.
Podemos comunicar a los dispositivos mediante LoRaMAC (también conocido mediante LoRa) o mediante LoRaWAN.
LoRaMAC: Utilizado para comunicar dos dispositivos entre sí con una topología punto a punto, uno envía información y otro la recoge (o viceversa).
LoRaWAN: Utilizado para comunicar una red de dispositivos. En este enfoque aparece la figura del Gateway (dispositivo que recoge la información de uno o varios dispositivos y la reenvía al servidor). En LoRaWAN la información debe ir filtrada para conocer que dispositivos debe escuchar el Gateway y cuales ignorar, pero esto se verá más adelante. Este enfoque utiliza una topología en estrella.
Por otro lado, en LoRaWAN, también se tienen tres tipos de dispositivos finales o nodos:
*En los ejemplos solo se tiene soporte para nodos de clase A y B (soportados por la libreria utilizada), pero solo se implementa el de tipo A.
Si se hace uso de la banda de frecuencia libre en Europa (868Mhz) se han de tener en cuenta algunas limitaciones:
En cada banda de frecuencia existen varios canales o sub-bandas, en el caso de la Europea (868Mhz) nos encontramos con 10 canales numerados del 0 al 9. Pero, por ejemplo en la Americana (915Mhz) podemos encontrarnos hasta 64.
El envio de información a través de un canal u otro es una tarea que suelen facilitar las librerias a utilizar.
En algunos End-Devices es posible modificar el DataRate o el SpreadingFactor del dispositivo.
El DataRate y el SpreadingFactor están relacionados: Un datarate de 0 indica un SF12 y un datarate de 5 indica un SF7. Todos a una frecuencia de 125kHz teniendo la siguiente excepción: un datarate de 6 indica un SF7 con 250kHz.
Se precisa de minimo dos dispositivos (una para el nodo y otro para el gateway, que debe tener además conectividad WiFi) y de una cuenta asociada en TTS (The Things Network) o Chirpstack (pudiendo usar un servidor propio en local). En este ejemplo resultan funcionales tanto los ejemplos LoRaMAC como LoRaWAN que hacen uso de un nodo Arduino y un Gateway Pycom.
Para comenzar basta con clonar el repositorio:
git clone https://github.com/Javieral95/Getting_Started_With_LoRa.git
Y después subir los proyectos pertinentes a los dispositivos
¿Por qué dos IDEs? Simple, la extensión Pymakr apenas funciona en Visual Studio Code. Sientete libre de usar el espacio de trabajo que te resulte más comodo.
Ambos dispositivos disponen de una antera LoRa conectada a ellos.
Nota: También se incluye en el repositorio el código para un ArduinoMKR1300 (más preparada para su uso como LoRa End-Device) y los códigos para utilizar una Pysense o Pytrack como End-device.

Los ejemplo LoRaMAC (se encuentran en la carpeta homónima) resultan funcionales haciendo uso de un dispositivo final Arduino y un Gateway PyCom.
El nodo tan solo envía información hardcodeada y el Gateway tan solo se conecta a LoRa y a WiFi, recibe la información Pycom e imprime los datos leidos (aunque tiene implementada la función de enviar los datos a la red).
Se prescinde del uso de un servidor en red.
Para saber más puedes acceder a la carpeta /LoRaMAC.
Para el uso de estos ejemplos (que resultan funcionales haciendo uso de un dispositivo final Arduino y de un Gateway Pycom) se precisa de un servidor para visualizar los datos. En este ejemplo se ha abordado el uso de The Things Network y de Chirpstack (anteriormente conocido como LoRaServer).
Para saber más puedes acceder a la carpeta /LoRaWAN y después seguir consultando esta documentación.
Existen dos tipos de autenticación en LoRaWAN:
*En los ejemplos por el momento solo se hace uso de OTAA.
Como ya se ha comentado anteriormente, precisaremos de un servidor. Para este ejemplo se ha usado una versión gratuita de The Things Network, un servidor Chirpstack propiedad de Pycom y otro desplegado en local.
Se trata de la alternativa más confiable, segura y mejor documentada. No obstante todo apunta a que dejará de ser abierta (ahora mismo se tiene un limite de 50 nodos por aplicación).
Crear una aplicación es sencillo, accedemos al menú y clicamos en el botón +. Una vez ahi indicamos el nombre de la aplicación, un ID unico y una descripción.
Cuando la aplicación sea creada, podremos añadir dispositivos finales (nodos) clicando en el botón +.
Los gateways son dispositivos que se encargan de enviar el tráfico que llega desde varios dispositivos finales (pertenecientes a varias aplicaciones) y las reenvia al servidor. Crear un Gateway también es sencillo, clica en el botón + y rellena el formulario prestando atención a los siguientes conceptos:
Para poder leer los datos que ha enviado el nodo al servidor se necesita descodificar el payload, en el caso de TTN lo haremos para cada dispositivo, en la pestaña Payload Formatters. Escogemos como formatter type la opción de javascript y:
function Decoder(bytes, port) {
// Decode plain text; for testing only
return {
myTestValue: String.fromCharCode.apply(null, bytes)
};
}
arduino_ttn_decoder.jsFijese en que todas las direcciones hexadecimales de The Things Network se encuentran en mayusculas, no es importante a la hora de programar los dispositivos pero se han sufrido errores en versiones pasadas.
Se trata de la alternativa open source, aún se encuentra en desarollo y su documentación no es tan buena. No obstante funciona y permite lanzar el servidor en local.
PyCom ofrece un servidor Chirpstack para conectar su dispositivo gateway.
La aplicación es similar a la detallada en el apartado de The Things Network.
Deberás dirigirte al apartado Device-profiles del servidor, una vez en él acceder al perfil que interesa (OTAA en este caso) y modificar las versiones:
Para crear un gateway accede a la pestaña del mismo nombre y clica el botón +. Rellena el formulario prestando especial atención al campo Gateway ID (64 bits en hexadecimal que identificanal gateway), puedes hacer que Chirpstack las generé por ti pero suele usarse la MAC del dispositivo (Si no la conoces en el apartado que explica el gateway PyCom se detallará como obtenerlo).
Puedes dejar el resto de valores por defecto.
Para poder leer los datos que ha enviado el nodo al servidor se necesita descodificar el payload, en el caso de Chirpstack lo haremos para cada perfil de dispositivo, en el apartado Device Profiles_ accedemos al perfil que nos interesa (en este caso OTAA) y accedemos a la pestaña Codec:
Escogemos en el desplegable Custom javascript codec functions y:
function Decode(fPort, bytes) {
var tempObj = new Object();
tempObj.data=bytes;
tempObj.decodedData = String.fromCharCode.apply(null, bytes);
tempObj.message = "Informacion recibida del nodo";
return tempObj;
}
arduino_chirpstark_decoder.jsFijese en que todas las direcciones hexadecimales de Chirpstack se encuentran en minusculas, , no es importante a la hora de programar los dispositivos pero se han sufrido errores en versiones pasadas.
Chirpstack proporciona una alternativa opensource para lanzar nuestro propio servidor privado de LoRaWAN, y nos permite hacerlo de forma simple y mediante contenedores.
Es por ello que se ha clonado en el presente repositorio otro repositorio propiedad del fundador de Chirpstack (brocaar) que permite esta operación: Chirpstack-docker. Lo encontramos en la carpeta chirpstack-docker.
Chirpstack tiene diversos componentes en su arquitectura para hacer que el servicio sea capaz de funcionar, son los siguientes:

La forma de desplegar el servidor en forma de contenedores nos permite abstraernos de mucho de los componentes de la arquitectura, no obstante se detallan a continuación:
Antes de desplegar, se debe configurar todos los parametros necesarios en los ficheros de configuración almacenados en el directorio configuration.
Puedes consultar la siguiente documentación oficial:
Nota: Los ficheros de configuración son sensibles a espacios en blanco o lineas vacías (se han encontrado en la configuración de Mosquitto), revise los archivos y eliminelas para evitar errores.
Como ya se ha comentado antes, el despliegue en contenedores es sencillo y se encuentra en el directorio chirpstack-docker.
Una vez que ya se configure lo necesario basta con colocarse en el directorio chirpstack-docker y lanzar:
docker-compose up
Con la configuración por defecto podrás acceder al servidor en la dirección localhost:8080. El usuario será admin y la contraseña admin.
Comenzemos a añadir la configuración básica:
Una vez que se ha configurado el servidor tendremos que registrar nuestros Gateways y crear aplicaciones para registrar nuestros dispositivos finales. Este proceso se realiza de forma análoga al explicado en el apartado anterior de la presente documentación: Chirpstack (LoRa Server).
Adicionalmente se deberá indicar la función que descodifica y codifica la información recibida, también se explica en el apartado anterior.
A continuación se detalla el código utilizado para lanzar la Gateway en una PyCom (Fipy con Pytrack). Este código se encuentra en LoRaWAN/LoRaPycomGateway.
Se ha hecho uso de la librería NanoGateway py que permite lanzar el gateway en cuestión de minutos.
Nano-Gateway convierte un dispositivo Pycom en un gateway sencillo que escucha tan solo un canal de la banda de frecuencia (monocanal), para escuchar más bandas en Pycom es posible que se precise de un gateway comercial.
En el archivo Config se encuentra todo lo necesario para personalizar el gateway:
WIFI_MAC = ubinascii.hexlify(machine.unique_id()) #.toUpper() para TTS
SERVER = 'loraserver.pycom.io' #(or url of your server)
GATEWAY_ID = WIFI_MAC[:6] + "ffff" + WIFI_MAC[6:12] #Minusculas: Chirpstack
NTP = "es.pool.ntp.org"
NTP_PERIOD_S = 3600
#WiFi settings (change it)
WLAN_SSID = "MyAwesomeWiFi" #"pycom-wifi"
WLAN_PASS = "CheckOutThisGoodPassword" #"securepassword"
WLAN_TIMEOUT_MS = 180000
### LoRaWAN for EU868 ###
LORA_FREQUENCY = 868500000
#Spreading Factor: (Higher value in SF=More distance but less speed transmision)
LORA_GW_DR = "SF7BW125" # DR_5,Can change in range: SF7 to SF15 (SF7B250 also exists)
LORA_NODE_DR = 5 #5 (6 uses 250Khz) for SF7, 4 for SF6.. all using 125Khz
###
def get_gateway_id():
print("Your gateway_id is: {}".format(GATEWAY_ID)) #The gateway is b'THIS_STRING'
return GATEWAY_ID
NOTA: Si conectas tu gateway a una red local sin conexión a internet, esta retornará un error a la hora de sincronizar los relojes. Puedes salir del paso comentando las siguientes lineas de código en la función start(self) del fichero nanogateway.py tal y como muestra el siguiente ejemplo:
# get a time sync
self._log('Syncing time with {} ...', self.ntp_server)
#self.rtc.ntp_sync(self.ntp_server, update_period=self.ntp_period)
#while not self.rtc.synced():
# utime.sleep_ms(50)
self._log("RTC NTP sync complete")
Varias funciones del archivo main no son utilizadas, tan solo es necesario lanzar el gateway de la siguiente manera y ya estará funcionando.
def init_loraWAN_gateway():
print("Initializing LoRaWAN nano Gateway")
nanogw = NanoGateway(
id=config.GATEWAY_ID,
frequency=config.LORA_FREQUENCY,
datarate=config.LORA_GW_DR,
ssid=config.WLAN_SSID,
password=config.WLAN_PASS,
server=config.SERVER,
port=config.PORT,
ntp_server=config.NTP,
ntp_period=config.NTP_PERIOD_S
)
print("Ok! Now you have a LoRaWAN Gateway! Lets start it, wait . . .")
pycom.rgbled(0xAA0000)
nanogw.start()
nanogw._log('. . . Yeah! Nano gateway is connected and running, enjoy the log:')
pycom.rgbled(0x000000)
La Pycom mantendra la luz roja hasta que consiga conectarse, una vez escuche peticiones de dispositivos parpadeará su led en color verde.
A continuación se detalla lo necesario para hacer funcionar el nodo de clase A usando Arduino.
Teoricamente usa todas los canales disponibles en la banda de frecuencia, más adelante se verá una forma de forzará utilizar tan solo uno (no recomendable).
Se ha hecho uso de la librería MCCI Arduino LoRaWAN que permite abstraerse de muchos aspectos de comunicación LoRa. Ha sido instalada mediante el gestor de librerias de PlatformIO.
Básicamente el código utilizado para el cliente arduino es el que se encuentra en el ejemplo ttn-otaa.ino de la libreria, salvo alguna modificación.
La configuración se realiza en dos ficheros diferentes:
Toda la configuración relacionada con LoRaWAN, como se ha indicado antes esta indicada en el archivo loraWAN.cpp. Al principio del documento se encuentra detallada que datos deben indicarse: APP_EUI, DEV_EUI y APP_KEY (ojo al formato que se indica a continuación).
A continuación el ejemplo en Chirpstack:
static const u1_t PROGMEM APPEUI[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
static const u1_t PROGMEM DEVEUI[8] = {0x7b, 0x6b, 0xff, 0x2c, 0x7b, 0x2c, 0x19, 0x5a};
static const u1_t PROGMEM APPKEY[16] = {0xbd, 0x21, 0x5a, 0x82, 0xb2, 0xf7, 0x92, 0xf3, 0xc7, 0xcb, 0xb2, 0x88, 0xc7, 0x55, 0x33, 0xe7};
Bajo toda la configuración de claves, en el fichero loraWan.cpp, podemos escoger si enviar texto plano o datos del sensor de temperatura y humedad. Descomentar la opción deseada:
/******* Send data config *******/
// Use this to send a Hello world in plain text
// static uint8_t mydata[] = "Hello World!";
// Use this to send sensor data
const int neededBytes = 4; // 4 bytes: 2 for temperature and 2 for humidity, can change this value
static byte mydata[neededBytes];
static LoraEncoder encoder(mydata);
Dependiendo de la información enviada se deberá de utilizar una función de descodificación u otra, tal y como se indico en los apartados de The Things Network y Chirpstack.
Como ya se ha visto anteriormente, la placa Pycom corriendo Nano-Gateway solo es capaz de leer en un canal mientras que el dispositivo final Arduino es capaz de emitir en todos los canales de la banda (Por ejemplo, en la banda europea hay 10 canales). Aunque no es recomendable (es posible que se incumpla la regla del 1%) puede forzarse a utilizar tan solo un canal y una frecuencia solo por temas de desarrollo y pruebas.
Para ello es necesario modificar el código de la biblioteca, más concretamente el archivo lorabase_eu868.h (en el caso de usar la frecuencia europea) y forzar de la siguiente manera la frecuencia deseada para emitir (observar como se ha hardcodeado todos los valores para indicar la frecuencia 868.Mhz):
enum {
EU868_F1 = 868500000, // g1 SF7-12
EU868_F2 = 868500000, // g1 SF7-12 FSK SF7/250
EU868_F3 = 868500000, // g1 SF7-12
EU868_F4 = 868500000, // g2 SF7-12
EU868_F5 = 868500000, // g2 SF7-12
EU868_F6 = 868500000, // g3 SF7-12
EU868_J4 = 868500000, // g2 SF7-12 used during join
EU868_J5 = 868500000, // g2 SF7-12 ditto
EU868_J6 = 868500000, // g2 SF7-12 ditto
};
enum {
EU868_FREQ_MIN = 868500000,
EU868_FREQ_MAX = 868500000
};
También se debería llamar a la siguiente función al inicio de LoRa (función LoraWan_startJob()):
// Define the single channel and data rate (SF) to use
void disableChannels(int selectedChannel, int dr)
{
// Disable all channels, except for the one defined above.
// ONLY FOR TESTING AND DEVELOPING!
for (int i = 0; i < 9; i++)
{ // For EU; for US use i<71
if (i != selectedChannel)
{
LMIC_disableChannel(i);
}
}
// Set data rate (SF) and transmit power for uplink
LMIC_setDrTxpow(dr, 14);
}
El canal y el datarate a configurar se encuentran al comienzo del fichero, en las lineas (Por defecto: canal 0 y el datarate deseado del Spreading Factor 7 cuyo valor es 5):
/******* Channel config (only change if you want to uses a single channel) *******/
const int channel = 0; // Use if you want to use only one Band's Channel.
const int dr = DR_SF7; // Use if you want to use a specific datarate (The spreading factor mark the dr's value).
Esto haría que la perdida de paquetes se reduzca considerablemente, aunque sigue habiendo algunos que el Gateway no recibe.
Tan solo copia el proyecto a tu placa Arduino.
La librería funciona mediante eventos, en este caso los más importantes serán el de autenticación (cuando se complete verás las claves en consola) y el de envío de datos.
El evento en el que se envia datos será EV_TXCOMPLETE en la funcion void onEvent(ev_t ev) del archivo loraWAN.cpp, observar que el evento incluye la "Ventana RX", momento en el que el dispositivo escucha.
case EV_TXCOMPLETE:
Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
if (LMIC.txrxFlags & TXRX_ACK)
Serial.println(F("Received ack"));
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;
La función, en el mismo fichero, donde se detallará que datos se envían es do_send (Comentar o descomentar las lineas que codifican la información si se desea enviar texto plano):
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
{
// Leer datos de sensor y codificar (Libreria LoRa_Serialization).
am2315_readedData data = readAM2315Data();
encoder.writeTemperature(data.temp);
encoder.writeHumidity(data.hum);
// Comentar las dos lineas "encoder" para enviar texto plano
// Send packet
LMIC_setTxData2(1, mydata, sizeof(mydata), 0);
if (isLoopRunning)
{
freq = String(LMIC.freq);
Serial.println("-->Packet queued using freq = " + freq);
// Prepare upstream data transmission at the next possible time.
printSensorInfoInDisplay(data.temp, data.hum);
printLoraSentInDisplay(freq);
}
}
// Next TX is scheduled after TX_COMPLETE event.
}
Nota: Se sufría un error que impedia al nodo recibir paquetes de vuelta, por lo que era imposible autenticar el dispositivo frente al servidor. Se ha tenido que añadir en el setup() del cliente (más concretamente en la función LoraWan_startJob() del archivo loraWan.cpp) la siguiente linea de código que aumenta en un 10% el error máximo del reloj:
LMIC_setClockError(MAX_CLOCK_ERROR * 10 / 100);
Tanto Chirpstack como The Things Network ofrecen una serie de integraciones enviar los datos que recibe nuestro servidor a otros servicios. Por ejemplo: Podemos enviar los datos a una base de datos InfluxDB, hacer uso de MQTT, conectarnos a AWS services o a Azure...
En este apartado se verá un caso práctico en el que podemos usar la integración de HTTP (Webhooks en The Things Network) y MQTT para enviar los datos que envian nuestros dispositivos y que recibe nuestro servidor a una aplicación propia.
Para acceder a las integraciones:
En caso de usar Chirpstack nos interesa seleccionar HTTP en el menú de integraciones, si de lo contrario usamos The Things Network seleccionaremos Webhooks y después Add webhook
En ambos servidores esta integración funciona de manera similar: Lanza un evento cada vez que un dispositivo de la aplicación envia información (en el caso de TTN deberemos marcar la casilla de Uplink message) y, con dicha información, se lanza una petición Http de tipo POST a la Url que indiquemos.
NOTA: Una buena práctica, bien para comprobar que el evento se lanza de forma correcta o bien para visualizar el formato de los datos es acceder al servicio PostBin, donde podemos crear un bin (URL temporal para recibir peticiones).
NOTA2: Si la aplicación a la que lanzarás la petición esta albergada en localhost y el servidor Chirpstack también (de forma Dockerizada como se muestra en esta documentación) tendrás que indicar la URL de la siguiente forma:
http://host.docker.internal:PUERTO/uri
Esta documentación solo abarca el uso de Chirpstack y TTN no se encuentra documentado para enviar datos (tiene un formato diferente en la petición).
Si se han descodificado los datos con los ejemplos del presente repositorio (carpeta Decoders-Integrations), obtendremos un cuerpo en la petición similar al siguiente:
{
"applicationID": 0,
"applicationName": "Name",
"deviceName": "DeviceName",
"devEUI": "BYTES_EUI",
"txInfo": [Object object],
"adr": true,
"dr": 5,
"fCnt": 24,
"fPort": 1,
"data": "DATA_WITHOUT_DECODE",
"objectJSON": {
"data":"DATA_WITHOUT_DECODE==",
"decodedData":{
"humidity":37,"temperature":23
},
"message":"Informacion recibida del nodo"
},
"tags": [Object object],
"confirmedUplink": false,
"devAddr": "BYTES_DEV_ADDR"
}
ObjectJSON es el objeto retornado por nuestra función Decoder.
Para leerlo, por ejemplo en una aplicación JavaScript bastaría con hacer algo parecido a lo siguiente (más en el archivo /Decoders-Integrations/arduino_Chirpstack_Http_Integration.js)
const { deviceName, objectJSON, devAddr} = req.body;
var sensorData = JSON.parse(objectJSON);
//devAddr esta codificado!
var temperature = sensorData.decodedData.temperature;
var humidity = sensorData.decodedData.humidity;
Realmente, a no ser que usemos MQTTS (Mqtt con TLS) no será necesario acceder a ninguna integración desde la aplicación web del servidor.
En este ejemplo suscribiremos nuestra aplicación al topico al que nuestro dispositivo final enviará los datos.
Si nuestra aplicación esta lanzada en local y el servidor Chirpstack también (dockerizado como hemos mostrado en esta documentación), el host del broker será la IP de la máquina WSL. Para conocer este dato lanzaremos:
wsl hostname -I
También habrá que realizar algunas configuraciones lanzando los siguientes comandos (1883 es el puerto de Mosquitto, si se usa otro modificarlo):
netsh interface portproxy add v4tov4 listenport=1883 listenaddress=0.0.0.0 connectport=1883 connectaddress=127.0.0.1
Podemos usar MQTT tal y como viene en el ejemplo de docker, con el parametro anonymous con valor true (sin usar ningún tipo de contraseña o lista de usuarios) o podemos configurar un listado de usuarios (cada uno con los topicos que pueden leer o escribir) con sus respectivas contraseñas (como indica la siguiente documentación).
Para ello, lanzaremos los siguientes comandos (podemos lanzarlos desde WSL), cada uno de ellos nos pedirá que introduzcamos una contraseña para cada usuario (en este ejemplo se ha usado pass para todos):
# Create a password file, with users chirpstack_gw, chirpstack_ns, chirpstack_as, bob and nodeApp
sudo mosquitto_passwd -c /etc/mosquitto/passwd chirpstack_gw
sudo mosquitto_passwd /etc/mosquitto/passwd chirpstack_ns
sudo mosquitto_passwd /etc/mosquitto/passwd chirpstack_as
sudo mosquitto_passwd /etc/mosquitto/passwd bob
sudo mosquitto_passwd /etc/mosquitto/passwd nodeApp
# Optional, Secure the password file
sudo chmod 600 /etc/mosquitto/passwd
Esto nos creará el fichero passwd que contendrá todos los usuarios y contraseñas, ahora podremos configurar un listado de acls en un fichero homónimo como el siguiente:
user chirpstack_gw
topic write gateway/+/event/+
topic read gateway/+/command/+
user chirpstackns
topic read gateway/+/event/+
topic write gateway/+/command/+
user chirpstack_as
topic write application/+/device/+/event/+
topic read application/+/device/+/command/+
user bob
topic read application/123/device/+/event/+
topic write application/123/device/+/command/+
user nodeApp
topic read application/+/device/#
topic write application/+/device/#
Ahora, deberemos modificar la configuración del servidor para utilizar estas credenciales modificando los ficheros albergados en /chirpstack-docker/configuration:
[application_server.integration.mqtt]
server="tcp://mosquitto:1883"
username="chirpstack_as"
password="pass"
[integration.mqtt.auth.generic]
servers=["tcp://mosquitto:1883"]
username="chirpstack_gw"
password="pass"
[network_server.gateway.backend.mqtt]
server="tcp://mosquitto:1883"
username="chirpstack_ns"
password="pass"
listener 1883
password_file /mosquitto/config/passwd
acl_file /mosquitto/config/acls
allow_anonymous false
mosquitto:
image: eclipse-mosquitto:2
ports:
- 1883:1883
volumes:
- ./configuration/eclipse-mosquitto/mosquitto.conf:/mosquitto/config/mosquitto.conf
- ./configuration/eclipse-mosquitto/passwd:/mosquitto/config/passwd
- ./configuration/eclipse-mosquitto/acls:/mosquitto/config/acls
En este ejemplo usaremos una aplicación NodeJS para conectarnos a nuestro servidor local Chirpstack dockerizado. Todo el código podemos encontrarlo en el archivo /Decoders-Integrations/arduino_Chirpstack_mqtt_Integration.js.
Lo primero, tendremos que instalar el paquete mqtt
npm install mqtt --save
Con él, ya podremos conectarnos al broker:
var mqtt = require('mqtt')
const host = 'WSL_IP'
const port = '1883' //or your port
const clientId = 'mqtt_NodeApp_' + Math.random().toString(16).slice(3)
const connectUrl = 'mqtt://' + host + ':' + port;
const client = mqtt.connect(connectUrl, {
clientId,
clean: true,
//username: "nodeApp", //Descomentar si usamos contraseñas y acls
//password: "pass", //Colocar el usuario y contraseña correspondiente
connectTimeout: 4000,
reconnectPeriod: 1000,
debug: true
})
Y suscribirnos al topico deseado (el caracter # es un wildcard multinivel, significa que leemos cualquier subtopico, mientras que el caracter + es un wildcard de un solo nivel).
const chirpstackApplicationID = 1; //Check url, for example: http://localhost:8080/#/organizations/1/applications. /1/ is the ID
const chirpstackDeviceID = "DEV_EUI";
const chirpstackReadAppTopic = "application/" + chirpstackApplicationID + "/device/#";
const chirpstackWriteAppTopic = "application/" + chirpstackApplicationID + "/device/"+chirpstackDeviceID+"/EXAMPLE";
Usaremos los siguientes eventos para ello:
//Evento al conectarse
client.on('connect', function () {
console.log("Connected")
client.subscribe(chirpstackReadAppTopic, function (err) {
if (!err) {
console.log("Subscribed to topic: "+chirpstackReadAppTopic)
//client.publish(chirpstackWriteAppTopic, 'Hello mqtt') //Podemso enviar un mensaje para debugear
}
else {
console.log("Error in connection:")
console.log(err)
}
})
})
//Evento al recibir un mensaje
client.on('message', function (topic, message) {
// El mensaje es un buffer, convertimos a String.
var stringMsg = message.toString();
console.log(topic + " - " + stringMsg)
insertSensorEntry_Mqtt(topic, stringMsg); //Funcion que lee el mensaje e inserta en base de datos
})
En ambos servidores (Chirpstack y The Things Network) la integración tiene por nombre MQTT, eso sí, antes de realizar ninguna integración debemos configurar los certificados.
A continuación se documentará como realizar la integración con MQTT en un servidor local de Chirpstack (para más info revisar el apartado ChirpStack privado en local de esta documentación).
Antes de generar los certificados, debemos tener instalado CFSSL & CFSSLJSON. Tras ello, clonaremos el siguiente repositorio propiedad del creador de Chirpstack y seguiremos los pasos de su documentación: Chirpstack-Certificates.
NOTA: Si se usa Windows, instalar los pre-requisitos en la máquina WSL pues se necesitará hacer uso del comando make.
Colocamos la carpeta certs generada con el proyecto Chirpstack-Certificates en nuestro proyecto Chirpstack-Docker. Después modificados el archivo docker-compose.yml para añadir a cada contenedor el volumen que contendrá los certificados correspondientes.
Seguimos siguiendo la documentación del proyecto Chirpstack-Certificates para realizar todas las modificaciones pertinentes en la configuración del servidor:
Como hemos visto anteriormente, el evento que se lanza al recibir un mensaje llama a una función que lee el mensaje recibido y lo descodifica.
El formato del mensaje recibido (si hemos usado los descodificadores del ejemplo) es una cadena de texto con el siguiente contenido:
{"applicationID":"1","applicationName":"APP_NAME","deviceName":"DEV_NAME","devEUI":"DEV_ADDRESS", "txInfo":{"frequency":868500000,"dr":5},"adr":true,"fCnt":2, "fPort":1,"data":"DATA","object":{"data":"DATA","decodedData":{"humidity":0,"temperature":-327},"message":"Informacion recibida del nodo"}}
Y es lo que buscamos leer en la siguiente función:
function insertSensorEntry_Mqtt(topic, msg){
console.log("INSERTAMOS DATO DE SENSOR RECIBIDO POR MQTT EN TOPICO: "+topic);
const parseMsg = JSON.parse(msg); //Recordar haber hecho un ToString al buffer antes!
var deviceName = parseMsg.deviceName;
var devAddr = parseMsg.devEUI; //No codificado
var temperature = parseMsg.object.decodedData.temperature;
var humidity = parseMsg.object.decodedData.humidity;
var success = true;
}
object es el objeto retornado por nuestra función Decoder.
Como bien se sabe, la tasa de transferencia de LoRA es muy baja, lo que provoca una gran perdida de paquetes y una enorme latencia cuando se envía información:
Algunos expertos indican que es necesario cierta distancia entre los dispositivos (30m y preferiblemente algún obstaculo entre ellos) para que la comunicación sea más fluida. No ha sido probado y solo se ha lanzado con las dos tarjetas en un extremo cada una de un piso.
Por otro lado se hace uso de versiones antiguas de LoRaWAN (1.0.2 y 1.0.3) que tienen problemas de seguridad que se solventan en parte en las siguientes versiones (1.0.4 y 1.1.0, esta última también implementa re-conectividad en caso de desconectarse de la red LoRaWAN), pero no se dispone de librerias para trabajar con ellas.
Esto no quita que esta técnología pueda ser muy interesante y útil en el futuro debido a no depender de proveedores externos (de comunicaciones y electricidad), siendo una opción ecónomica y muy llamativa para utilizar en proyectos IoT de grandes ciudades o entornos rurales.
Este proyecto ha sido realizado para la Fundación CTIC, su uso es libre y no es necesarío ningún crédito en su uso (Revisar las licencia de las librerias utilizadas).