Instalar en Raspberry Pi OS 64 bits:
Install using the apt repository
Grupo de Investigación de Redes de iNformación Inalámbricas
Instalar en Raspberry Pi OS 64 bits:
Install using the apt repository
Referencia: https://www.home-assistant.io/installation/raspberrypi-other
Instalar:
docker run -d \ --name homeassistant \ --privileged \ --restart=unless-stopped \ -e TZ=MY_TIME_ZONE \ -v /PATH_TO_YOUR_CONFIG:/config \ -v /run/dbus:/run/dbus:ro \ --network=host \ ghcr.io/home-assistant/home-assistant:stable
Actualizar:
# f this returns "Image is up to date" then you can stop here docker pull ghcr.io/home-assistant/home-assistant:stable # stop the running container docker stop homeassistant # remove it from Docker's list of containers docker rm homeassistant # finally, start a new one docker run -d \ --name homeassistant \ --restart=unless-stopped \ --privileged \ -e TZ=MY_TIME_ZONE \ -v /PATH_TO_YOUR_CONFIG:/config \ -v /run/dbus:/run/dbus:ro \ --network=host \ ghcr.io/home-assistant/home-assistant:stable
Esta sección corresponde a implementar un gestor de dispositivos IoT que puede ser implementado sobre un Raspberry o un PC. 
Hay que considerar el gestor/broker deberá permanecer encendido siempre , 24horas/días a la semana.
Por el consumo energético es conveniente usar un Raspberry Pi, sin embargo, para pruebas iniciales es posible implementarlo usando una PC con la versión de Raspberry Pi sobre PC.
Las instrucciones de instalación, actualización y configuración para Raspberry Pi se encuentran en la sección:
1.1 Raspberry Pi OS – Instalar
Para luego proceder a instalar MQTT versión Mosquitto para gestionar los mensajes de estado del sensor descritos en la Sección:
y finalmente instalar Home Assistant para gestionar mediante una página web los estados de los dispositivos, descritos en la Sección:
Redes de mayor alcance:
Para procesar el archivo del reporte.txt se puede leer mensaje por mensaje y extraer el registro de RSSI y SNR de cada dispositivo usando Python.
# procesar un archivo de mensajes mqtt
import numpy as np
import json as json
# INGRESO
# Parametros para la conexión
servidormqtt = "192.168.xx.xx"
topicolee = "#"
# Para el archivo.txt
nombrearchivo = 'unreporte.txt'
conteo = {"lineas":{'cuenta':0},
"gateway":{'cuenta':0},
"application":{'cuenta':0}
}
tipotrama = []
# para tabular
tabla = []
registros = {}
frecuencias = []
tramabuscada = ['application/1/device/c9c2a03913ac2ec5/event/up',
'application/1/device/b35915f22af620d1/event/up',
'application/1/device/b5a2349b828d97ea/event/up',
'application/1/device/f2f724028c0e996c/event/up']
# PROCEDIMIENTO
archivo = open(nombrearchivo,'r')
unalinea = archivo.readline()
while not(unalinea==''):
conteo["lineas"]['cuenta'] = conteo["lineas"]['cuenta'] + 1
unalinea = archivo.readline()
unalinea = unalinea.strip('\n')
desde = unalinea.find(" b'{")
untipotrama = unalinea[0:desde]
if not(untipotrama in tipotrama) and (untipotrama != ''):
tipotrama.append(untipotrama)
conteo[untipotrama] = {'cuenta':0}
for entrada in conteo:
if unalinea.startswith(entrada):
conteo[entrada]['cuenta'] = conteo[entrada]['cuenta']+1
for unapptrama in tramabuscada:
tamanot = len(unapptrama)
if unalinea.startswith(unapptrama):
datostxt = unalinea[tamanot+3:].strip("'")
datosjson = json.loads(datostxt)
undeviceName = datosjson["deviceName"]
unrssi = datosjson["rxInfo"][0]["rssi"]
unloRaSNR = datosjson["rxInfo"][0]["loRaSNR"]
unfrequency = datosjson["txInfo"]["frequency"]
undr = datosjson["txInfo"]["dr"]
if not(unfrequency in frecuencias):
frecuencias.append(unfrequency)
if undeviceName in registros:
registros[undeviceName]['conteo'] = registros[undeviceName]['conteo']+1
registros[undeviceName]['rssi'].append([unfrequency,unrssi,
unloRaSNR,undr])
else:
registros[undeviceName]={'rssi':[],
'conteo':0,
'resumen':[0,0,0,0],
}
archivo.close() # Cierra el archivo
# Calcula resumen por dispositivo
frecuencias.sort()
frecuencia = [902300000, 902500000, 902700000, 902900000,
903100000, 903300000, 903500000, 903700000]
canalobserva = len(frecuencia)
for dispositivo in registros:
vector = np.array(registros[dispositivo]['rssi'])
cuenta = np.zeros(canalobserva, dtype=int)
suma = np.zeros(canalobserva,dtype=int)
channelid = np.arange(1, canalobserva+1)
cadaRSSI = [[],[],[],[],[],[],[],[],[]]
cadaSNR = [[],[],[],[],[],[],[],[],[]]
cadadr = [[],[],[],[],[],[],[],[],[]]
resumen = []
# Clasificando
k = len(vector)
for j in range (0,k,1):
cual = frecuencia.index(vector[j][0])
valorRssi = vector[j][1]
valorSNR = vector[j][2]
valordr = vector[j][3]
cadaRSSI[cual].append(valorRssi)
cadaSNR[cual].append(valorSNR)
cadadr[cual].append(valordr)
for j in range(0,canalobserva,1):
vectorRSSI = np.array(cadaRSSI[j])
vectorSNR = np.array(cadaSNR[j])
vectordr = np.array(cadadr[j])
if len(vectorRSSI)>0:
promedioRSSI = np.average(vectorRSSI)
maximoRSSI = np.max(vectorRSSI)
cantidadRSSI = len(vectorRSSI)
minimoRSSI = np.min(vectorRSSI)
promedioSNR = np.average(vectorSNR)
maximoSNR = np.max(vectorSNR)
cantidadSNR = len(vectorSNR)
minimoSNR = np.min(vectorSNR)
promediodr = np.average(vectordr)
maximodr = np.max(vectordr)
cantidaddr = len(vectordr)
minimodr = np.min(vectordr)
resumen.append(np.array([ j+1,
cantidadRSSI,
promedioRSSI,
minimoRSSI,
maximoRSSI,
promedioSNR,
minimoSNR,
maximoSNR,
promediodr,
]))
resumen = np.array(resumen)
registros[dispositivo]['resumen'] = resumen
# SALIDA
np.set_printoptions(precision = 2)
print('conteo de tramas')
for entrada in conteo:
print(entrada,": ", conteo[entrada]['cuenta'])
print('frecuencias: ',frecuencia)
print('\nTabulando ---------')
for dispositivo in registros:
print()
print('dispositivo_id: '+str(dispositivo))
print('total muestras: '+str(registros[dispositivo]['conteo']))
print('Promedios RSSI y SNR: ')
print('[canal muestras\t RSSI \t min \t max \t SNR min max DR ]')
print(registros[dispositivo]['resumen'])
registros[dispositivo]['conteo']
De disponer de acceso al servidor MQTT se pueden capturar los mensajes y procesarlos por ejemplo para realizar estadísticas de RSSI y SNR de cada dispositivo respecto al Gateway.
Se requiere una conexión autenticada al servidor MQTT, para caso experimental se recurre a Python con la librería PAHO para acceder como cliente.
# grabando mensajes MQTT
# configurar los parámetros para la creacion de archivo de texto
import paho.mqtt.client as mqtt
# INGRESO
# Parametros para la conexión
servidormqtt = "192.168.xx.xx"
topicolee = "#"
# Para el archivo.txt
cuentamensaje = 0
nombrearchivo = 'unreporte.txt'
archivo = open(nombrearchivo,'w')
archivo.close() # Cierra el archivo
tramabuscada = ['application/1/device/c9c2a03913ac2ec5/event/up',
'application/1/device/b35915f22af620d1/event/up',
'application/1/device/b5a2349b828d97ea/event/up',
'application/1/device/f2f724028c0e996c/event/up']
cuentaDisp = [0,0,0,0]
cuentagtw = 0
# BLOQUE BÁSICO DE PROGRAMA
# Al recibir CONNACK desde el servidor.
def on_connect(client, userdata, flags, rc):
print("Conexión/código de resultado: "+str(rc))
# Inicio o renovación de subscripción
client.subscribe(topicolee)
return()
# el tópico tiene una publicación
def on_message(client, userdata, msg):
global cuentamensaje
print(msg.topic) #+" "+str(msg.payload))
unmensaje = msg.topic+" "+str(msg.payload)
# Archivo en modo añadir 'append'
archivo = open(nombrearchivo,'a')
unalinea = unmensaje + '\n'
archivo.write(unalinea)
if (msg.topic in tramabuscada):
cual = tramabuscada.index(msg.topic)
cuentaDisp[cual] = cuentaDisp[cual] + 1
cuentamensaje = cuentamensaje + 1
print('\n mensajes recibidos: ', cuentamensaje)
print(' cuenta por dispositivos: ', cuentaDisp)
print()
return()
client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.connect(servidormqtt, 1883, 60)
client.loop_forever()
Una forma de registrar los datos que transmite un dispositivo en Home Assistant es mediante la subscripción al mensaje MQTT correspondiente a la trama LoRaWAN.
En la sección anterior se describe la forma de enviar los datos por LoRaWAN. mediante la subscripción al mensaje MQTT desde Home Assistant donde se registra y muestra el dato.
Para añadir el dispositivo a Home Assitant, se añade en el archivo de configuración las características del dispositivo y el mensaje MQTT al que se debe conectar. Esto se realiza editando el archivo de dispositivos con la intrucción:
sudo nano /home/homeassistant/.homeassistant/configuration.yaml
por ejemplo, para registrar valores de RSSI y SNR del paquete transmitido se añaden las siguientes instrucciones:
sensor:
- platform: mqtt
name: 'Dispositivo_LRW01_rssi'
unit_of_measurement: 'dBm'
state_topic: 'application/1/device/c9c2a03913ac2ec5/event/up'
value_template: "{{ value_json.rxInfo[0].rssi}}"
json_attributes_topic: 'application/1/device/c9c2a03913ac2ec5/event/up'
- platform: mqtt
name: 'Dispositivo_LRW01_SNR'
unit_of_measurement: 'dB'
state_topic: 'application/1/device/c9c2a03913ac2ec5/event/up'
value_template: "{{ value_json.rxInfo[0].loRaSNR}}"
Para cada caso particular, se requiere actualizar el identificador de dispositivo además de valores a tomar de la trama en formato json.
nota: el editor de archivo "nano" usa las teclas "Ctrl O" para grabar los cambios y "Ctrl X" para salir del editor.
Una vez actualizado el archivo de configuration.yaml, se reinicia Home Assistant desde la sección de configuración/controles de servidor/reiniciar.
Finalmente se visualiza el dispositivo y sus datos en el historial y en su respectiva posición del tablero.
Referencia: https://www.home-assistant.io/integrations/sensor.mqtt/#json-attributes-topic-configuration
Los datos son enviados como un tren de bits escritos en hexadecimal desde el dispositivo.
Por ejemplo, un mensaje a transmitir conformado por 4 datos numéricos en hexadecimal desde el dispositivo son:
static void prepareTxFrame( uint8_t port ){
appDataSize = 4;//AppDataSize max value is 64
appData[0] = 0x00;
appData[1] = 0x01;
appData[2] = 0x02;
appData[3] = 0x03;
}
Se leen en un mensaje mqtt como:
application/1/device/3532363324003700/event/up {
"applicationID":"1",
"applicationName":"GirniAppPrueba01",
"deviceName":"GirniDispositivo01",
"devEUI":"3532363324003700",
"txInfo":{"frequency":902700000,"dr":0},
"adr":true,
"fCnt":0,
"fPort":2,
"data":"AAECAw=="
}
por lo que el mensaje recibido por el gateway es:
"data":"AAECAw=="
que para leerlo nuevamente en hexadecimal requiere ser traducido desde "base64" por ejemplo en la consola de Rasberry OS como:
pi@GWHTM01:~ $ echo -n AAECAw== | base64 --decode | hexdump -C 00000000 00 01 02 03 |....| 00000004
que es el mensaje original enviado desde el dispositivo
Los datos de sensor/acción se ordenan en una trama LoRa en forma de bytes antes de la transmisión. Se pretende minimizar la cantidad de bytes usados por mensaje, siguiendo la directiva LoRa de mensajes de corta duración.
Un mensaje se puede simplificar, por ejemplo un "ON"/"OFF" presentado en LoRa puede ser 1 "o" 0, que es tan solo un bit. Los mensajes se administran por ChirpStack y los puede traducir nuevamente en mensajes "ON"/"OFF" para ser actualizados en un Broker como Home Assistant.
Un ejemplo básico de instrucciones para un dispositivo genérico permite presentar el uso de librerías LoRaWAN de HELTEC.
Los datos son enviados en una trama como un tren de bits escritos en orden. Por ejemplo, en el dispositivo se tiene los valores de contador1, contador2 y unalectura como datos a ser transmitidos, por lo que se plantea el siguiente esquema:
Las instrucciones en el IDE arduino para el ejemplo de la trama de datos Sigue el mismo orden.
byte contador1 = 0; uint8_t contador2 = 0; int unalectura = 221; static void prepareTxFrame( uint8_t port ){ contador1 = contador1 + 1; contador2 = contador2 - 1; //orden de trama appDataSize = 4 //AppDataSize max value is 64 appData[0] = contador1; //0x01; appData[1] = contador2; appData[2] = highByte(unalectura); appData[3] = lowByte(unalectura); // mostrar en puerto USB Serial.println(contador1); Serial.println(contador2); Serial.println(unalectura); }
En casos particulares hay que ajustar la trama de acuerdo a las lecturas de sensores o información que se requiera transmitir
La trama de datos llega en binario hasta el componente application-server y de forma predeterminada se presenta en formato Base64.
Para leer los datos se puede usar el convertidor en línea o utilizar un decodificador/codificador con instrucciones en JavaScript.
https://v2.cryptii.com/base64/decimal
En ChirpStack en la sección Device-profiles/codec se puede escribir un decodificador para la trama de datos y reconstruirlo en la forma con las variables originales.
para el ejemplo presentado y siguiendo el esquema inicial, las instrucciones en JavaScript son:
function Decode(fPort, bytes, variables) {
var contador1 = bytes[0];
var contador2 = bytes[1];
var unalectura = (bytes[2] << 8) |(bytes[3]);
var appData = {'contador1':contador1, 'contador2':contador2, 'unalectura': unalectura}
return appData;
}
Con lo que en Chirpstack se obtiene para datos del dispositivo de una trama en particular:
Referencia: https://heltec-automation-docs.readthedocs.io/en/latest/general/decode_payload.html
Para capturar los datos en un broker, por ejemplo Home Assistant, se puede usar el mensaje MQTT del application-server. Es posible leer las tramas MQTT de application-server o integrar las plataformas entre si por medio de "integration"
Para observar la estructura del mensaje se realiza una subscripción al mensaje MQTT de la aplicación para un dispositivo:
mosquitto_sub -v -h localhost -p 1883 -t 'application/1/device/#'
y se obtiene los datos decodificados por JavaScript:
Con lo que se puede proceder a incorporar la instrucción en Home-Assistant.
Referencia: TTN - Optimizacion Payload.pdf , https://github.com/akirasan/TTN_LoRaWAN
Para añadir un dispositivo en el Broker ChirpStack, el ejemplo básico envía una trama solo para leer los datos de niveles de señal RSSI y SNR.
El perfil de servicio en la aplicación se asigna un nombre acorde a la aplicación que la usará.
se ingresan los parámetros requeridos, por simplicidad solo se añade un nombre. Ejemplo:
El perfil de dispositivos describe la forma de conexión y autenticación de un grupo de dispositivos.
Por ejemplo, si los dispositivos autentican por medio de OTTA/ABP, si se incluirán dispositivos clase B, clase C y las instrucciones para codificar o decodificar los datos del dispositivo (CODEC).
para conexión OTAA/ABP
soporte de dispositivos clase B
soporte de dispositivos clase C
Para el tema de CODEC se recomienda revisar la sección de LoRaWAN – Mensajes y datos entre componentes
La aplicación agrupa dispositivos que realizan funciones semejantes, mediante el perfil del dispositivo, donde se encuentra también el CODEC de los datos.
Se asigna un nombre y una descripción de lo que va a realizar con los dispositivos:
para finalmente registrar la aplicación
Hay que crear cada dispositivo con identificadores únicos y autenticar en la red.
los primeros datos corresponden a nombre y nombre y descripción.
El identificador único de dispositivo: Device EUI es primordial para la conexión por OTAA. El Device EUI se puede generar con el botón mostrado en la pantalla y se usa en la programación del dispositivo.
El perfil del dispositivo se selecciona desde la pestaña y se despliegan las opciones.
https://heltec-automation-docs.readthedocs.io/en/latest/esp32/lorawan/config_parameter.html
https://www.thethingsnetwork.org/docs/lorawan/addressing.html
los parámetros para OTAA se pueden visualizar, generar en la pestaña correspondiente. Hay un botón para visualizar el que se esté usando, o en otro caso se puede generar para usar en la programación del dispositivo.
Si el dispositivo no ha sido conectado al menos una vez, la pestaña de activación aparece vacía. Para obtener los datos de activación es necesario realizar al menos un intento de conexión, con lo que la ventana llena los datos para completar la programación del dispositivo.
Para observar los datos de activación debe usar el botón de visualización en la parte derecha de la ventana.
Los intentos de conexión se pueden visualizar en la pestaña de LoRaWAN Frames
mientras que los datos recibidos se observan en la pestaña de Device Data
Hay que considerar que la primera vez que se conecta el dispositivo, la autenticación puede llevar algunos intentos, por lo que hay que esperar para que se comiencen a recibir los datos, a pesar que las tramas LoRaWAN si se registran en el programa.
Los datos para la configuración del dispositivo se obtienen en el proceso descrito anteriormente, por lo que es necesario actualizar los datos.
En el caso de dispositivos HELTEC es necesario obtener los datos de licencia que se encuentran al inicio de las instrucciones. La licencia de cada dispositivo se puede revisar en: https://resource.heltec.cn/search/
/* * HelTec Automation(TM) LoRaWAN 1.0.2 OTAA example use OTAA, CLASS A * Solo ESP32+LoRa series boards con licencia http://www.heltec.cn/search/); *https://github.com/HelTecAutomation/ESP32_LoRaWAN */ #include <ESP32_LoRaWAN.h> #include "Arduino.h" /*licencia Heltec ESP32 LoRaWan http://resource.heltec.cn/search uint32_t license[4] = {0xBE21335B, 0xAEC3C5CE, 0xCC0A1CF4, 0xB836F981}; /* OTAA parametros uint8_t DevEui[] = { 0x01, 0x20, 0x08, 0x93, 0xdf, 0x80, 0x37, 0x74 }; uint8_t AppEui[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; uint8_t AppKey[] = { 0x05, 0x8e, 0xeb, 0xff, 0x24, 0xf1, 0x01, 0x84, 0xd0, 0x07, 0xbe, 0xd4, 0x65, 0xe7, 0x6b, 0xb5 }; /* ABP parametros uint32_t DevAddr = ( uint32_t )0x0174b1fd; uint8_t NwkSKey[] = { 0xc1, 0x45, 0x31, 0x28, 0x5f, 0xb2, 0x56, 0x3b, 0x9d, 0x5f, 0x27, 0x15, 0xed, 0x3a, 0x0e, 0xbc}; uint8_t AppSKey[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; //LoraWan channelsmask, default channels 0-7/ uint16_t userChannelsMask[6]={ 0x00FF,0x0000,0x0000,0x0000,0x0000,0x0000 }; DeviceClass_t loraWanClass = CLASS_A; /*Soporte de A and C uint32_t appTxDutyCycle = 15000; /*15000; en [ms] bool overTheAirActivation = true; /*OTAA or ABP bool loraWanAdr = true; /*ADR enable bool isTxConfirmed = true; /*confirmed or unconfirmed messages uint8_t appPort = 2; /* Application port /* reintentos de transmisión, en caso de no recibir ack uint8_t confirmedNbTrials = 8; /* Seleccionado de Arduino IDE tools uint8_t debugLevel = LoRaWAN_DEBUG_LEVEL; LoRaMacRegion_t loraWanRegion = ACTIVE_REGION; // variables de sensor/actuador byte contador1 = 0; uint8_t contador2 = 0; int unalectura = 221; void setup(){ Serial.begin(115200); while (!Serial); SPI.begin(SCK,MISO,MOSI,SS); Mcu.init(SS,RST_LoRa,DIO0,DIO1,license); deviceState = DEVICE_STATE_INIT; } void loop(){ switch( deviceState ) { case DEVICE_STATE_INIT: { LoRaWAN.init(loraWanClass,loraWanRegion); break; } case DEVICE_STATE_JOIN: { LoRaWAN.join(); break; } case DEVICE_STATE_SEND: { prepareTxFrame( appPort ); LoRaWAN.send(loraWanClass); deviceState = DEVICE_STATE_CYCLE; break; } case DEVICE_STATE_CYCLE: { // Schedule next packet transmission txDutyCycleTime = appTxDutyCycle + randr( -APP_TX_DUTYCYCLE_RND, APP_TX_DUTYCYCLE_RND ); LoRaWAN.cycle(txDutyCycleTime); deviceState = DEVICE_STATE_SLEEP; break; } case DEVICE_STATE_SLEEP: { LoRaWAN.sleep(loraWanClass,debugLevel); break; } default: { deviceState = DEVICE_STATE_INIT; break; } } } static void prepareTxFrame( uint8_t port ){ contador1 = contador1 + 1; contador2 = contador2 - 1; //orden de trama appDataSize = 4 //AppDataSize max value is 64 appData[0] = contador1; //0x01; appData[1] = contador2; appData[2] = highByte(unalectura); appData[3] = lowByte(unalectura); // mostrar en puerto USB Serial.println(contador1); Serial.println(contador2); Serial.println(unalectura); }
Los pasos de las secciones anteriores permiten habilitar una página web donde se pueden añadir los gateways que se van a gestionar.
Inicicialmente al abrir la página en la dirección asignada, se mostrarán todos los elementos vacios:
Es necesario añadir una configuración básica para activar y ordenar los gateways.
Se asigna un nombre a la organización o empresa que usa ChirpStack
completar los datos requeridos para la organización y al final usar "update organization"
Use los parámetros indicados en el archivo de configuración de la sección de ChirpStack - network-server
con los parámetros usados en la instalación, complete las casillas:
Para configurar otros parámetros del network-server, revisar el enlace:
https://www.chirpstack.io/network-server/install/config/
Se requiere disponer de un perfil de gateway, se indica los canales que usa el gateway, así como el network-server indicado en la sección anterior.
se muestra un ejemplo de la configuración:
Se pueden añadir usuarios y llaves API, para la configuración básica no se realizará esta actividad.
Es necesario crear un perfil de servicios para los gateways de la organización:
con el que se indica a que network-server se conectará el gateway, y si se añadirán parámetros de estado de la señal.
con lo que se puede añadir un Gateway:
para crear el gateway se requiere el parámetro de "gateway_id" de la sección:
LoRaWAN – Gestor de paquetes / Packet forwarder
siguiente parte de la ventana:
De completar el proceso de registro es posible observar el estado de "last seen" en hace pocos segundos "a few seconds ago".
Terminando así el proceso de añadir un gateway a ChirpStack.
También es posible observar en el tablero principal de la organización:
El siguiente paso es añadir un dispositivo que se conecta al gateway.