LoRaWan – Sensor Humedad y Temperatura. Gateway y Broker

El dispositivo que envía el mensaje con los datos de Humedad y temperatura debe registrarse en ChirpStack. La trama del mensaje se decodifica en al perfil del dispositivo (Device-profile) que permite al servidor de aplicaciones (Application-Server) integrar el mensaje en Home Assistant.

Decodificador de Trama – ChirpStack/Device-Profile

En el menú «Device-profile» se crea el perfil del dispositivo con datos principales semejantes a los usados en el dispositivo básico. La sección que cambia es la de CODEC que se describe en Java Script.

La decodificación se realiza usando LSB, es decir el orden es byte menos significativo primero. En este caso los datos se reconvierten a número real (float) y se trunca a dos decimales (+NumReal.toFixed(2)).

Con los datos decodificados se integran como un diccionario de resultados.

// Decode decodes an array of bytes into an object.
//  - fPort contains the LoRaWAN fPort number
//  - bytes is an array of bytes, e.g. [225, 230, 255, 0]
//  - variables contains the device variables e.g. {"calibration": "3.5"} (both the key / value are of type string)
// The function must return an object, e.g. {"temperature": 22.5}
//function Decode(fPort, bytes, variables) {
//  return {};
//}
function Decode(fPort, bytes, variables) {

  // usando LSB  (least significant byte first)
  var unalectura = bytes[3]<<24 | bytes[2]<<16 | bytes[1]<<8 | bytes[0]; var unsigno = (unalectura>>>31 === 0) ? 1.0 : -1.0;
  var exponente = unalectura>>>23 & 0xff;
  var mantisa = (exponente === 0) ? (unalectura & 0x7fffff)<<1 : (unalectura & 0x7fffff) | 0x800000;
  var NumReal = unsigno * mantisa * Math.pow(2, exponente - 150);
  NumReal = +NumReal.toFixed(2);
  
  // usando LSB  (least significant byte first)
  var unalectura = bytes[7]<<24 | bytes[6]<<16 | bytes[5]<<8 | bytes[4]; var unsigno = (unalectura>>>31 === 0) ? 1.0 : -1.0;
  var exponente = unalectura>>>23 & 0xff;
  var mantisa = (exponente === 0) ? (unalectura & 0x7fffff)<<1 : (unalectura & 0x7fffff) | 0x800000;
  var NumReal2 = unsigno * mantisa * Math.pow(2, exponente - 150);
  NumReal2 = +NumReal2.toFixed(2);
  
  var appData = {'temperaturaC': NumReal, 'humedad': NumReal2};
  return appData;
}

Configuración en Home-Assistant

La configuración se realiza en el archivo configuration.yaml con la instrucción:

sudo nano /home/homeassistant/.homeassistant/configuration.yaml

al archivo se añade en la sección de sensores las instrucciones de cada lectura.

El tópico a observar se construye tomando los datos de chirpstack para cada dispositivo, observando el número de aplicación y el identificador de dispositivo.

Los valores de los sensores se decodificaron en un diccionario, que se transfirió como un valor tipo texto, por  lo que primero se lo convierte en un diccionario antes de seleccionar el valor a usar. La selección del valor se realiza en value_template.

  - platform: mqtt
    name: 'DHT_S01_temperatura'
    unit_of_measurement: '°C'
    state_topic: 'application/1/device/01200893df803774/event/up'
    value_template: "{% set valores = value_json.objectJSON |from_json %} {{valores.temperaturaC}}"    
    #json_attributes_topic: 'application/1/device/01200893df803774/event/up'

  - platform: mqtt
    name: 'DHT_S01_humedad'
    unit_of_measurement: '%'
    state_topic: 'application/1/device/01200893df803774/event/up'
    value_template: "{% set valores = value_json.objectJSON |from_json %} {{valores.humedad}}"

  - platform: mqtt
    name: 'DHT_S01_rssi'
    unit_of_measurement: 'dB'
    state_topic: 'application/1/device/01200893df803774/event/up'
    value_template: "{{ value_json.rxInfo[0].rssi}}"

  - platform: mqtt
    name: 'DHT_S01_snr'
    unit_of_measurement: 'dB'
    state_topic: 'application/1/device/01200893df803774/event/up'
    value_template: "{{ value_json.rxInfo[0].loRaSNR}}"

El resultado se observa como:

LoRaWan – Sensor Humedad y Temperatura. Archivo.ino

El desarrollo del ejercicio se simplifica al tratarse como una extensión del ejemplo base de comunicación Lora al que se añade el Sensor DHT11.

ChirpStack – Añadir un dispositivo

El archivo de instrucciones se presenta en tres secciones o pestañas. La primera contiene la declaración de variables, la parte de configuración y el lazo principal. Las siguientes dos secciones son procedimientos para preparación de trama y lectura de sensor.

Preparación de trama

El valor de temperatura se almacena en tipo real (float) que usa 4 bytes. En la trama se separan los bytes en orden LSB (Byte menos significativo primero, detalle que hay que recordar para decodificar en ChirpStack.

    unsigned char *puc;
    puc = (unsigned char *)(&temperaturaC);
    appDataSize = 8;//AppDataSize max value is 64
    appData[0] = puc[0];
    appData[1] = puc[1];
    appData[2] = puc[2];
    appData[3] = puc[3];

De utilizar el mismo formato para el valor de Humedad, el formato es muy semejante.

Intrucciones Principales

 /*
 * 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"
#include <DHT.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 = 300000;         /*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
uint8_t contador = 0;  
float humedad = 0;
float temperaturaC = 0;
float temperaturaF = 0;

 / / Mensajes por Puerto Serial
volatile boolean serial_msg = true;

 / / Sensor humedad y temperatura
#define DHTPIN 4
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);

void setup(){
  if (serial_msg){
    Serial.begin(115200);
    while (!Serial);
  }
  SPI.begin(SCK,MISO,MOSI,SS);
  Mcu.init(SS,RST_LoRa,DIO0,DIO1,license);
  deviceState = DEVICE_STATE_INIT;

   / / SENSOR Temperatura&Humedad
  dht.begin();
}
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;
    }
  }
}

Preparación de trama

// Trama - integra datos
static void prepareTxFrame( uint8_t port ){
    contador = contador + 1;
    // Sensor de Temperatura y Humedad
    SensorTempHum();

    if (isnan(humedad) || isnan(temperaturaC) || isnan(temperaturaF)) {
      humedad = 0;
      temperaturaC = 0;
      temperaturaF = 0;
    }
    unsigned char *puc;
    puc = (unsigned char *)(&temperaturaC);
    appDataSize = 8;//AppDataSize max value is 64
    appData[0] = puc[0];
    appData[1] = puc[1];
    appData[2] = puc[2];
    appData[3] = puc[3];

    puc = (unsigned char *)(&humedad);
    appData[4] = puc[0];
    appData[5] = puc[1];
    appData[6] = puc[2];
    appData[7] = puc[3];
    if (serial_msg){
      Serial.print("tx: "+String(temperaturaC)+"  ");
      Serial.print(appData[0]);
      Serial.print(appData[1]);
      Serial.print(appData[2]);
      Serial.println(appData[3]);
      Serial.print("tx: "+String(humedad)+"  ");
      Serial.print(appData[4]);
      Serial.print(appData[5]);
      Serial.print(appData[6]);
      Serial.println(appData[7]);
    }
}

Lectura de sensor

void SensorTempHum(){
  // SENSOR Temperatura&Humedad
  dht.begin(); // regresa de sleep
  delay(2000); // espera minina entre lecturas de sensor
  humedad = dht.readHumidity();
  temperaturaC = dht.readTemperature();
  temperaturaF = dht.readTemperature(true);

  if (isnan(humedad) || isnan(temperaturaC) || isnan(temperaturaF)) {
    if (serial_msg){
      Serial.println(F("Failed to read from DHT sensor!"));
    }
    // espera minina entre lecturas de sensor
    delay(100); 
    return;
  }

  float hif = dht.computeHeatIndex(temperaturaF, humedad);
  float hic = dht.computeHeatIndex(temperaturaC, humedad, false);

  if (serial_msg){
    Serial.print(F(" Humidity: "));
    Serial.print(humedad);
    Serial.print(F("%  \n Temperature: "));
    Serial.print(temperaturaC);
    Serial.print(F("°C "));
    Serial.print(temperaturaF);
    Serial.print(F("°F \n Heat index: "));
    Serial.print(hic);
    Serial.print(F("°C "));
    Serial.print(hif);
    Serial.println(F("°F"));
  }
}

LoRaWan – Sensor Humedad y Temperatura con DHT11

Para disponer de los valores de Humedad y Temperatura de una ubicación mas lejana a la cobertura de Wifi, se propone implementar un dispositivo con LoRaWan y el sensor DHT11. Se lo implementa como primer ejercicio para mostrar la integración de dos redes LoRa y Ethernet, junto a los gateways y brokers.

La propuesta para redes WiFi se realizó con ESP8266 que se describe en el enlace: WiFi Sensor Temperatura-Humedad ESP-07

En LoRaWan, la base del dispositivo es un módulo de desarrollo ESP32-LoRa modelo HELTEC wireless stick-lite al que se añade el sensor DHT11.

Primero se desarrolla la parte básica de captura de datos del sensor, luego se arma la trama de datos, para enviarla por la red LoRa hacia el Gateway controlado por ChirpStack. En ChripStack se acondicionan los datos para una mejor lectura con «Decoder» y se la envía al Broker Home Assistant para mostrarla en una página web.