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:
- De clase A (Baseline): La más soportada y la que supone mayor ahorro de energia (el dispositivo pasa a modo escuchar o Ventana RX solo después de enviar datos al gateway), esto hace que solo pueda escuchar algo después de enviar información:
- Se abren dos ventanas de escucha, previo delay después del envío, (RX1 y, si no se obtiene respuesta, RX2), si no hay confirmación en esas dos ventanas se pierde la oportunidad de obtener el mensaje hasta el siguiente mensaje.
- Utiles para enviar información en intervalos separados de tiempo o cuando se reciba un evento (Ej: La temperatura bajo de 21º).
- De clase B (Beacon): Tiene soporte para recibir paquetes del gateway de forma periodica, permite más espacios entre los ascendentes (enviados, uplinks) y descendentes (recibidos, downlink).
- Se permite abrir ventanas de forma periodica en horas programadas, para esto el gateway envía beacons (balizas para comprobar cuando el dispositivo esta en modo escucha).
- Se reduce la latencia de mensajes pero consume más energia.
- De clase C (Continous): Se dispone de recepción de paquetes continua, el dispositivo solo deja de escuchar en el momento que envía alguna información.
*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:
- La potencia máxima de emisión es 25 mW (a priori no es un problema, los dispositivos no suelen llegar a tanto).
- Y lo que es más importante: La regla del 1%. Esta regla indica que no podemos transmitir más que el 1% del tiempo, es decir, si enviar un paquete nos lleva 10ms tendremos que esperar 900ms para enviar el siguiente.
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 indica la velocidad de envío de los datos y permite manejar el Spreading Factor, se indica con un número entero entre el 0 y el 6 (ambos incluidos).
- El SpreadingFactor indica el Factor de esparcimiento, tiene el formato SFX siendo X un número entre el 7 (minimo factor de esparcimiento) y 12 (máximo factor). Cuanto mayor sea el SpreadingFactor más lejos podrán llegar los paquetes pero lo harán a una velocidad menor (datarate menor).
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
- Para el caso del dispositivo Arduino se ha utilizado Visual Studio Code con la Extensión PlatformIO.
- Para el caso del dispositivo PyCom se ha utilizado Atom con la Extensión Pymakr (Pycom MicroPython V1.20.1.r2 y Pybytes V1.3.0).
¿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.
- El dispositivo PyCom se trata de una placa de desarrollo Fipy con conectividad WiFi, blueetooth, LoRa y Sigfox que se conecta a una placa Pytrack.
- El dispositivo Arduino se trata de una placa WiFi LoRa 32 que emula a la placa Heltec WiFi LoRa 32 (V1), el diagrama de entrada y salida de pines se encuentra en el repositorio con el nombre WiFi LoRa 32 Pinout Diagram.jpg, que se muestra a continuación.
- Adicionalmente se ha conectado un sensor de temperatura y humedad AM2315, aunque por el momento no funciona.
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).
- Funciona para las versiones LoRa 1.0.2 y 1.0.3. (para dispositivos comerciales consultar en la documentación la versíon de LoRa soportada).
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:
- ABP: "Activation by Personalization", se hardcodea tanto la dirección del dispositivo como las claves de seguridad de este. Es más simple por prescindir del paso previo de autenticación, pero no es seguro.
- OTAA: "Over-The-Air Activation", es el metodo seguro de conectarse con el servidor. El dispositivo en primer lugar realiza una operación de autenticación en la red (la petición pasa por el gateway), la dirección del dispositivo es dinamica y las claves de seguridad se negocian con el dispositivo.
*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).
- Debemos dirigirnos a su página oficial: https://www.thethingsnetwork.org/, una vez en ella nos registramos en el botón Sign Up.
- Cuando nos registremos y accedamos a nuestra cuenta de usuario, podremos acceder a nuestra consola donde elegiremos el cluster que mejor nos convenga (en este caso el Europeo).
- La consola se encuentra dividida en dos grandes apartados: Aplicaciones y Gateways, en el primero definiremos las aplicaciones y sus dispositivos finales y en la segunda, como bien indica, los gateways.
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 +.
- La oferta de dispositivos no es muy amplia, si tu dispositivo no se encuentra en la lista añadelo manualmente.
- Indica una versión LoRaWAN 1.0.2 o 1.0.3 y una configuración regional de tipo REV A.
- Escoge la frecuencia que vayas a utilizar, en este caso la Europea: 863-870MHz.
- Los siguientes IDs son importantes (si no dispones de ellos puedes hacer que TTS los genere por ti):
- DevEUI: 64 bits que funcionan como identificador único de tu dispositivo final.
- AppEUI: 64 bits que funcionan como identificador único de la aplicación, puedes rellenarlo con 0s.
- AppKey: Clave que utilizará el dispositivo en el proceso OTAA de autenticació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:
- Gateway EUI: 64 bits que funcionan como identificador único del gateway en hexadecimal, normalmente se introduce la MAC del dispositivo. Si no la conoces en el apartado que explica el gateway PyCom se detallará como obtenerlo.
- Gateway server address: Dirección del servidor al que enviar los datos, en este caso eu1.cloud.thethings.network
- Frecuency Plan: Una vez más escoge el que mas te convenga.
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:
- En el caso de enviar un simple hola mundo u otro texto plano tenemos la siguiente funcion:
function Decoder(bytes, port) {
// Decode plain text; for testing only
return {
myTestValue: String.fromCharCode.apply(null, bytes)
};
}
- Para mandar información codificada de sensores conectadas al dispositivo, se puede acceder a la funciones de codificación y descodificación de la libreria arduino LoRa Serialization, incluida en el directorio /Decoders-Integrations del presente repositorio.
- Archivo
arduino_ttn_decoder.js
- Archivo
Fijese 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.
- Accede a https://pybytes.pycom.io/settings/lora-settings y crea tu cuenta.
- Una vez accedido al servicio, dirigete a la pestaña settings y selecciona LORA SETTINGS.
- Escoge PyCom Chirpstack Server (fijese que tambié se tiene soporte para The Things Network), escoge LoRa OTAA, la frecuencia deseada (en este caso EU868) y clica en Save.
- Si es la primera vez que realizas este proceso, recibirás en tu correo electronico tu clave para acceder al servidor: https://loraserver.pycom.io/
La aplicación es similar a la detallada en el apartado de The Things Network.
- Para crear una aplicacion nos dirigimos al menu con el mismo nombre y clicamos en el boton +.
- Una vez creada, puedes acceder a ella y añadir nodos clicando en el botón +.
- Introduce un nombre al dispositivo y una descripción.
- Deberás indicar un Device EUI: Se trata de 64 bits en hexadecimal que funcionan como identificador único (puedes hacer que ChirpStack lo genere por ti clicando en la flecha en bucle)
- Puedes mantener el resto de valores por defecto.
- Necesitarás conocer la Application Key del dispositivo para hacer uso de OTAA, esta la encontrarás accediendo al dispositivo y, dentro de él, en la pestaña Keys (OTAA)
- Cambia el perfil del dispositivo a OTAA en la pestaña General de la configuración si aún no se encuentra en ese perfil.
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:
- LoRaWAN MAC version: 1.0.2 o 1.0.3 con Regional Parameters revisión de tipo A
- ADR Algorith por defecto y activar Device Supports OTAA en la pestaña Join (OTAA / ABP) si no esta activado.
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:
- Para mandar un simple Hola mundo u otro texto plano, podemos indicar la siguiente funcion:
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;
}
- Para mandar información codificada de sensores conectadas al dispositivo, se puede acceder a la funciones de codificación y descodificación de la libreria arduino LoRa Serialization, incluida en el directorio /Decoders-Integrations del presente repositorio.
- Archivo
arduino_chirpstark_decoder.js
- Archivo
Fijese 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:
- Dispositivos LoRa y Gateway: Ya definidos.
- Gateway Bridge: Primer componente de Chirpstack, recibe la información de todos los gateway, la procesa y la envía al servidor MQTT de mensajeria.
- Network server: Siguiente componente, monitoriza la red y los dispositivos conectados a este. Es capaz de eliminar duplicados (los mensajes de un nodo puede ser captados por más de un gateway) y consolidar la información para ponerla a disposición del servidor de aplicaciones. También es el encargado de autenticar los dispositivos y de enviar los mensajes descendientes.
- Application Server: Tercer componente. Permite crear las aplicaciones (grupos de dispositivos finales que envian informacion). Es capaz de relacionar la información con el dispositivo final y de almacenarla.
- Integraciones: Las más importantes (y necesarias para lanzar el servidor) son:
- Broker MQTT: Servicio de mensajeria interna para los componentes de Chirpstack y gateways (cola de mensajeria, en este caso se usa Eclipse Mosquitto).
- Redis: Motor de base de datos en memoria que gestiona la información de los dispositivos y las aplicaciones creadas.
- PostgreSQL: Almacena la configuración de ChirpStack (organizaciones, aplicaciones, usuarios y el historico de información).
- Pueden añadirse más integraciones: Enviar información a InfluxDB, activar eventos para realizar peticiones Http...
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:
- Gateway Bridge: https://www.chirpstack.io/gateway-bridge/install/config/
- Network server: https://www.chirpstack.io/network-server/install/config/
- Application server: https://www.chirpstack.io/application-server/install/config/
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:
- Network-Server: Precisamos de añadir primero este componente, accedemos al apartado correspondiente, clicamos en el botón + y en la dirección del servidor introducimos chirpstack-network-server:8000 o lo que corresponda en nuestro caso (debido a usar Docker, si lo lanzamos todo en local deberemos introducir localhost).
- Service-Profile: De forma analoga al paso anterior, accedemos a la ventana de creación de un perfil de servicio. Introducimos el nombre que queramos y seleccionamos como Networ-server el que creamos en el paso anterior usando el desplegable... Podemos dejar el resto de parametros por defecto.
- Device-Profile: Ahora crearemos un perfil para los dispositivos finales, en este caso solo nos interesa un perfil para usar OTAA.
- Indicamos el nombre que queramos (una buena práctica es indicar OTAA en él).
- Seleccionamos el Network-server creado previamente y una versión de LoRaWAN MAC igual a 1.0.2 o 1.0.3.
- Parametros regionales de tipo A.
- Algoritmo ADR (confirmación de paquetes) por defecto, Max EIRP y Uplink Interval puede mantenerse en 0.
- En la pestaña Join (OTAA / ABP) habilitamos el check Device supports OTAA
- Gateway-Profile: No es necesario definir uno pues el Gateway no precisa de ello, pero podemos crearlo sin problema y después asignarlo.
- Se define el tiempo de cada intervalo en el que el Gateway envia su información al servidor.
- Se define que canales de la banda estan habilitados.
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.
- Config: En este archivo es el archivo configurable para hacer funcioanr tu gateway.
- Main: Archivo principal que lanza el Gateway.
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:
- En las primeras lineas deberás descomentar las correspondientes al servicio que usarás (The Things network o Chirpstack), a continuación las lines de código de la segunda opción (fijese como para cumplir el formato de Gateway EUI de los servidores se debe rellenar el ID de forma que los seis primeros y los seis ultimos digitos pertenezcan a la MAC del dispositivo, pero los restantes se rellena con Fs).
- Si has configurado un servidor privado de ChirpStack, deberás indicar la IP (sin puerto) Network-Server en la variable SERVER.
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
- El puerto puede mantenerse en 1700, es el que usan ambos servicios.
- Tras ello, se configura el servidor para el reloj, la red WiFi (junto con el Timeout para determinar el error) como la frecuencia de trabajo (en este caso la europea: 865Mhz).
- También se define el Spreading Factor (Factor de esparcimiento): Cuanto mayor sea el valor mayor distancia se cubrira, pero se tendra menos velocidad de transmisión aún, se indica como SF7B125 a SF15B125.
- El Datarate (DR) es un valor que acompaña al spreading Factor.
- Más información aquí.
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
###
- La función que se encuentra al final del archivo config imprimira por pantalla la Gateway EUI que puedes copiar y pegar en la configuración del servidor.
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:
- Los pines fisicos se indican en el archivo LoRaWAN/LoRaArduinoClient/include/hal.h.
- La configuracion LoRaWAN se realiza en el archivo LoRaWAN/LoRaArduinoClient/src/loraWAN.cpp.
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).
- APP_EUI: Los 64 bits que identifican a la aplicación (indicado en formato LSB), en TTS puede generarse desde su consola o rellenarlos con 0s. Si usas Chirpstack, dejalo relleno con 0s.
- DEV_EUI: Indicador del dispositivo, usaremos la MAC o lo copiaremos de la consola de Chirpstack o TTS como en este ejemplo (ojo: En formato LSB, puedes modificar la vista de MSB a LSB en la consola o simplemente darle la vuelta a los bytes).
- APP_KEY: Clave para la autenticación mediante OTAA, copiar de la consola de TTS o Chirpstack (En formato MSB, es decir, tal y como está).
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):
- La banda europea tiene 10 canales, siendo 0 el primero.
- El Spreading Factor (Factor de esparcimiento): Cuanto mayor sea el valor mayor distancia se cubrira, pero se tendra menos velocidad de transmisión aún, se indica como SF7B125 a SF15B125.
- El Datarate (DR) es un valor que acompaña al spreading Factor.
- Más información aquí.
/******* 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:
- Chirpstack: Accede al listado de aplicaciones y clica en la aplicación que deseas integrar, en la pestaña integrations verás el listado de aplicaciones con las que podemos conectarnos.
- The Things Network: Accede a la pestaña de aplicaciones y selecciona la que deseas integrar. En el menú izquierdo verás una opción de Integrations donde se desplegarán todos los servicios a los que podemos conectarnos.
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.
- Escogeremos un formato (Payload Marshaled) de tipo JSON.
- Añaderemos las cabeceras Accept y Content-Type, ambas con valor application/json (también podremos añadir la cabecera con el token de autenticación si fuese necesario).
- Añaderemos como Endpoint la URL a la que buscamos que el servidor lance una petición POST.
- En el caso de The Things network podemos definir una URL base y, cada tipo de mensaje que lance el evento (casillas activadas) una uri diferente.
- En el caso de Chirpstack podemos definir más de una URL, separandolas por el caracter " , ".
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
:
- chirpstack-application-server.toml:
[application_server.integration.mqtt] server="tcp://mosquitto:1883" username="chirpstack_as" password="pass"
- chirpstack-gateway-bridge.toml:
[integration.mqtt.auth.generic] servers=["tcp://mosquitto:1883"] username="chirpstack_gw" password="pass"
- chirpstack-network-server.toml:
[network_server.gateway.backend.mqtt] server="tcp://mosquitto:1883" username="chirpstack_ns" password="pass"
- mosquitto.conf:
listener 1883 password_file /mosquitto/config/passwd acl_file /mosquitto/config/acls allow_anonymous false
- Adicionalmente, en el archivo docker-compose.yml de la carpeta raiz del proyecto recordar añadir los volumenes de los archivos passwd y acls, tal que:
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
- Adicionalmente, en el archivo docker-compose.yml de la carpeta raiz del proyecto recordar añadir los volumenes de los archivos passwd y acls, tal que:
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:
- En estos ejemplos se envia cada minuto y se visualiza esta perdida, aproximadamente solo llegan al Gateway uno de cada diez paquetes que el nodo envía)
- Forzar a utilizar una única frecuencia y un único canal mitiga considerablemente la perdida de paquetes pero a coste de perder posible disponibilidad y de romper la regla del 1%.
- Si se trabaja con Gateways comerciales este problema se soluciona y no hay apenas perdida de paquetes.
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.
- Javier Álvarez de la Fuente - Investigación inicial para Fundación CTIC - JavierAl95
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).