GIRNI

Grupo de Investigación de Redes de iNformación Inalámbricas

Categoría: Dispositivos LoRa

  • 3. LoRaWan - Batería y carga. Gateway-Broker

    El dispositivo envía los datos del sensor de bateria como un entero en dos bytes con valores entre [0,4096]. La conversión del valor a voltaje se realiza a partir del diagrama de circuito Heltec, donde se muestra que el módulo tiene un divisor de voltaje incorporado.

    El divisor de voltaje y su equivalente de la ecuación es:

    \frac{R12}{R10+R12} V_{BAT} = \frac{ADC}{4096}(3.3-0.7) V_{BAT} = \Big[ \frac{ADC}{4096}(3.3-0.7) \Big] \frac{R10+R12}{R12} V_{BAT} = 0.00203125*ADC

    Decodificador de Trama – ChirpStack/Device-Profile

    function Decode(fPort, bytes, variables) {
      
      // usando entero
      var unalectura = (bytes[1] << 8) |(bytes[0]);
      unalectura = unalectura*0.00203125;
      unalectura = +unalectura.toFixed(2);
      var appData = {'Bateria_V': unalectura}
    
      return appData;
    }
    

    En la salida de monitor-Serie se obtiene: Voltaje Batería (V): 2136

    que decodificado en applications de chirpstack_ es: Bateria_V: 4.34

    que es el valor que se envia por Mqtt.

    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: 'Bateria_S02'
        unit_of_measurement: 'V'
        state_topic: 'application/2/device/c5db5e2b64eefcae/event/up'
        value_template: "{% set valores = value_json.objectJSON |from_json %} {{valores.Bateria_V}}"
    

    Referencia: WiFi_Kit_series/SchematicDiagram/WiFi_LoRa_32(V2)/WIFI_LoRa_32_V2(868-915).PDF. https://github.com/Heltec-Aaron-Lee/WiFi_Kit_series/blob/master/SchematicDiagram/WiFi_LoRa_32(V2)/WIFI_LoRa_32_V2(868-915).PDF

    Heltec Wifi LoRa V2 battery management. http://community.heltec.cn/t/heltec-wifi-lora-v2-battery-management/147/36

  • 2. LoRaWan - Bateria y carga. Archivo.ino

    El módulo de desarrollo Heltec Wireless Stick Lite tiene el circuito de sensor y alimentación por baterías. El diagrama de pines muestra que el pin 13 se encuentra conectado como detector de energía, que se incorpora a las instrucciones como PowerDetection. En los nuevos modelos, el detector de energía es el pin 37.

    Preparación de trama

    El valor del sensor ADC para bateria es de tipo entero, entrega la cuantificación en el rango [0, 4096], por lo que un entero de 2 bytes es suficiente para los datos. El proceso de conversión a voltaje se realiza en el decodificador en Application-Server del Broker ChirpStack.

      uint16_t BateriaV = SensorBateria();
      
      appDataSize = 4 ;
      appData[3] = 0 ;
      appData[2] = 0 ;
      appData[1] = highByte(BateriaV);
      appData[0] = lowByte(BateriaV);
    

    Intrucciones Principales

    /* Adaptado a partir de:
     * 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/tree/master/examples/OTAA_Battery_power
    */
    #include <ESP32_LoRaWAN.h>
    #include "Arduino.h"
    
    /*licencia Heltec ESP32 LoRaWan http://resource.heltec.cn/search */
    uint32_t  license[4] = { 0xC254CA22, 0xFB5646A9, 0xA23B184F, 0x8F613844};
    
    /* OTAA parametros*/
    uint8_t DevEui[] = { 0xc5, 0xdb, 0x5e, 0x2b, 0x64, 0xee, 0xfc, 0xae };
    uint8_t AppEui[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
    uint8_t AppKey[] = { 0xf2, 0x1f, 0xff, 0x5c, 0x6f, 0xf2, 0x4c, 0xa0, 0x74, 0x73, 0xf5, 0xef, 0xf7, 0x39, 0x39, 0x10 };
    
    /* ABP parametros*/
    uint8_t NwkSKey[] = { 0x15, 0xb1, 0xd0, 0xef, 0xa4, 0x63, 0xdf, 0xbe, 0x3d, 0x11, 0x18, 0x1e, 0x1e, 0xc7, 0xda,0x85 };
    uint8_t AppSKey[] = { 0xd7, 0x2c, 0x78, 0x75, 0x8c, 0xdc, 0xca, 0xbf, 0x55, 0xee, 0x4a, 0x77, 0x8d, 0x16, 0xef,0x67 };
    uint32_t DevAddr =  ( uint32_t )0x007e6ae1;
    
    /*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;
    
    // Mensajes por Puerto Serial
    volatile boolean serial_msg = true;
    
    // variables de sensor/actuador
    // Sensor de estado de bateria, revisar modelo de módulo
    // PowerDetection 13 en Wireless Stick lite, otros 37
    #define Vext 21
    #define PowerDetection 13
    
    void setup()
    {
      Serial.begin(115200);
      while (!Serial);
      SPI.begin(SCK,MISO,MOSI,SS);
      Mcu.init(SS,RST_LoRa,DIO0,DIO1,license);
    
      //sensor de bateria
      adcAttachPin(PowerDetection);
      analogSetClockDiv(255); // 1338mS
        pinMode(Vext, OUTPUT);
        
      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;
        }
      }
    }
    

    Preparación de trama

    // Trama - integra datos
    static void prepareTxFrame( uint8_t port ){
      
      uint16_t BateriaV = SensorBateria();
      
      appDataSize = 4 ;
      appData[3] = 0 ;
      appData[2] = 0 ;
      appData[1] = highByte(BateriaV);
      appData[0] = lowByte(BateriaV);
    }
    

    Lectura de sensor

    uint16_t SensorBateria(){
      
      // lectura ADC Voltaje de bateria 
      digitalWrite(Vext, LOW);
      delay(10);
      uint16_t ADC_voltage = analogRead(PowerDetection);
      digitalWrite(Vext, HIGH);
    
      Serial.print("Voltaje Batería (V): ");
      Serial.println(ADC_voltage);
    
      return ADC_voltage;
    }
    

    Referencia: Heltec LoRa ESP32, https://github.com/HelTecAutomation/ESP32_LoRaWAN/blob/master/examples/OTAA_Battery_power/OTAA_Battery_power.ino

    Heltec LoRa ESP32 Wireless Stick Lite, diagrama de pintes- https://resource.heltec.cn/download/Wireless_Stick_Lite/Wireless_Stick_Lite.pdf

  • 1. LoRaWan - Batería y carga con panel solar

    una parcela

    Para dispositivos remotos donde es posible solo alimentación de energía mediante baterías es conveniente incluir el estado de la batería entre los datos de sensores.

    Los mensajes LoRa son cortos, poco frecuentes y de bajo consumo de energía, por lo que un pequeño panel solar puede ser suficiente para mantener la batería del dispositivo con suficiente carga.

    En un esquema básico, usando elementos disponibles, se usa un módulo HELTEC LoRa Wireless Stick Lite que dispone de un conector para alimentación con baterías a 3.7 V, además del circuito sensor de estado. Para recarga se conecta un panel solar de 5 a 6V (~1W) con un módulo TP4056 que contiene un circuito de protección se sobre-voltaje y polaridad inversa.

    Esquema del circuito

    Bateria Panel Solar_bb

    El módulo TP4056 regula y controla la carga de la batería, pues el voltaje de carga del panel solar varía según la intensidad de luz solar recibida. El módulo también controla la salida de la batería hacia el módulo LoRa, previene la descarga de la batería por debajo de niveles que la dañen.

    Los módulos de desarrollo para LoRa de Heltec incorporan el circuito y un puerto para conectar la batería de 3.7V. El modelo CubeCell incorpora el circuito de carga y pines de conexión para un panel solar, lo que simplifica aun más el esquema, tener en cuenta el modelo a usar para el caso particular a implementar.

    Referencias: TP 4056, https://www.componentsinfo.com/tp4056-module-pinout-datasheet-details/

    CubeCell Dev-Board Plus, https://heltec.org/project/htcc-ab02/

    Power ESP32/ESP8266 with Solar Panels, https://randomnerdtutorials.com/power-esp32-esp8266-solar-panels-battery-level-monitoring/

     

  • LoRaWan - Temperatura y Humedad: Gateway-HA

    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 - Temperatura y Humedad: 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 - Temperatura y Humedad: Esquema

    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.

    LoraWan Humedad Temperatura 01

    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.

    LoraWan Base DHT_01 bb

    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.

     

     

  • Dispositivos LoRa - índice

    Los dispositivos LoRa se pueden configurar para comunicarse entre ellos (LoRa punto, multipunto) o usando un gateway hacia redes con IP (LoRaWan).

    Multipunto

    Es un ejemplo ampliado de LoRa punto a punto. Implementa el envío de mensajes hacia un dispositivo LoRa que realiza las funciones de "gateway" hacia Wifi, que convierte el mensaje a MQTT hacia un broker Mosquitto ya planteado en los Tutoriales.

    LoRaWan

    LoRa en modalidad LoRaWan requiere un "gateway" más elaborado para envio de mensajes hacia internet. En la sección de Broker/Gestión, se implementa un Gateway a partir del módulo Heltec (HT-M01) y un gestor de Gateways (ChirpStack) de esquema abierto. LoraWan – Gateway esquema abierto

  • 4. LoRa - Temperatura Humedad: MQTT-HA

    Esta es la última sección a realizar, pues se supone que tiene listo el dispositivo, construido y operativas las partes: Dispositivo, Gateway y Broker.

    la visualización de los valores requiere declarar los dispositivos en Home Assistant en el archivo configuration.yaml

    1. Incorporar el dispositivo en Home Assistant

    Se requiere modificar el archivo configuration.yaml en el raspberry.

    Se puede realizar en forma local desde el Raspberry que tiene monitor, teclado y mouse conectado, y editar el archivo que se encuentra en el directorio:

    /home/homeassistant/.homeassistant/configuration.yaml

    las líneas a añadir en la sección sensor:

    sensor: 
      - platform: mqtt
        name: 'inv_D1_temperatura'
        unit_of_measurement: '°C'
        state_topic: 'invernadero/loraD1/temperatura'
    
      - platform: mqtt
        name: 'inv_D1_humedad'
        state_topic: 'invernadero/loraD1/humedad'
        unit_of_measurement: '%'
    
      - platform: mqtt
        name: 'inv_D1_bateria'
        state_topic: 'invernadero/loraD1/voltaje'
        unit_of_measurement: 'volt'
    

    La configuración para añadir una tarjeta en la página es:

    o añadiendo las instrucciones en el editor:

    entities:
      - entity: sensor.inv_d1_temperatura
      - entity: sensor.inv_d1_humedad
      - entity: sensor.inv_d1_bateria
    show_header_toggle: false
    theme: default
    title: Invernadero Dispositivo 1
    type: entities
  • 3. LoRa - Temperatura Humedad: Gateway Archivo.ino

    1. Instrucciones en Arduino IDE

    Para el envío de los mensajes hacia el broker MQTT y Home Assistant se usa un dispositivo configurado como gateway.

    Se usa un dispositivo en lugar de un concentrador en la etapa de prototipo considerando los costos involucrados. La próxima tarea es desarrollar el gateway usando un concentrador, cuyo valor es  más alto.

    /* Gateway LoRa
      Lora/Wifi/MQTT/Home-Assistant
      Envia información por red Lora/Gateway WiFi
      hacia un broker MQTT y gestionar datos en Home-Assistant
      edelros@espol.edu.ec
      http:&#047;&#047;blog.espol.edu.ec/edelros/
      
      Referencia: Aaron.Lee www.heltec.cn
      https://github.com/Heltec-Aaron-Lee/WiFi_Kit_series
    */
    #include "heltec.h"
    #include <WiFi.h>
    #include <PubSubClient.h>
    
    // DISPOSITIVO LORA Banda ISM en Región 915Mhz
    #define BAND  915E6 //433E6,868E6,915E6
    // ranges from 6-12,default 7 see API docs
    byte spread_factor = 8;
    
    // LoRa Mensaje a enviar por direcciones
    String paqueteEnv = "";
    byte dir_local   = 0xC1; // Concentrador 1
    byte dir_destino = 0xD1; // Dispositivo 1
    byte msjContador = 0; // identificador de mensaje
    // tiempo entre lecturas
    long t_anterior = 0;
    int  t_intervalo = 4000;
    
    // LoRa Mensaje Recibido
    byte dir_envio = 0xC1; // Concentrador 1
    int dir_remite = 0xD0; // Inicia Remitente
    String paqueteRcb = "";
    byte   paqrcbID = 0;
    byte   paqrcbEstado = 0;
      // 0:vacio, 1: nuevo, 2:incompleto
      // 3:otro destinatario, 4:Broadcast
    
     // Mensajes por Puerto Serial
    volatile boolean serial_msj = true;
    
    // WIFI: conexión a Router
    char* ssid = "giotirni20";
    char* password = "Anera2020@";
    
    // MQTT: Servidor
    char* MQTT_IP = "192.168.10.50";
    uint16_t MQTT_puerto = 1883;
    char* MQTT_usuario = "usuarioprueba";
    char* MQTT_contrasena = "usuarioclave";
    
    // MQTT: Dispositivo Sensor
    char* MQTT_ID = "LoraGatewayC1";
    char MQTT_TOPIC_T[50] = "invernadero/loraD1/temperatura";
    char MQTT_TOPIC_H[50] = "invernadero/loraD1/humedad";
    char MQTT_TOPIC_V[50] = "invernadero/loraD1/voltaje";
    char MQTT_SensorEstado[10] = "OFF";
    volatile boolean mqtt_desconectado = true;
    // MQTT: Dispositivo Actuador
    char* MQTT_COMMAND = "invernadero/loraD1/cambia";
    char MQTT_ActuadorEstado[10] = "OFF";
    volatile boolean actuador_estado = false;
    volatile boolean actuador_bandera = false;
    char temperatura[10]  = "00.00";
    char humedad[10] = "00.00";
    char voltaje[10] = "00.00";
    
    // Clientes WiFi y MQTT
    WiFiClient wificlient;
    PubSubClient mqttclient(wificlient);
    
    void setup(){
      Heltec.begin(false /*DisplayEnable Enable*/,
        true /*Heltec.Heltec.Heltec.LoRa Disable*/,
        serial_msj /*Serial Enable*/,
        true /*PABOOST Enable*/,
        BAND /*long BAND*/);
      // ranges from 6-12,default 7 see API docs
      LoRa.setSpreadingFactor(spread_factor);
      
      //LoRa.onReceive(cbk);
      LoRa.receive();
      
      // conexión WIFI y MQTT
      inicia_wifi();
      if (WiFi.status() == WL_CONNECTED){
        inicia_mqtt();
        }
    }
    
    void loop(){
      // parametros de recepción
      int rssi_lora = 0;
      int snr_lora = 0;
      
      // Revisa mensajes LoRa entrantes
      int msjRcbLoRa = LoRa.parsePacket();
      if (msjRcbLoRa !=0){
        
        recibirlora(msjRcbLoRa);
        rssi_lora = LoRa.packetRssi();
        snr_lora = LoRa.packetSnr();
           
        if (serial_msj==true){
          Serial.println("remite,msjID,mensaje,estado,Rssi,Snr");
          Serial.print(String(dir_remite, HEX)); Serial.print(",");
          Serial.print(paqrcbID); Serial.print(",");
          Serial.print(paqueteRcb); Serial.print(",");
          Serial.print(paqrcbEstado); Serial.print(",");
          Serial.print(rssi_lora); Serial.print(",");
          Serial.println(snr_lora);
        }
        yield(); // procesa wifi
        
        // LED parpadea Rebibido Lora
        digitalWrite(LED, HIGH); delay(50);
        digitalWrite(LED, LOW); delay(50);
        digitalWrite(LED, HIGH); delay(50);
        digitalWrite(LED, LOW);
        yield(); // procesa wifi
        delay(100);
      }
    
      // Procesa a MQTT mensaje completo
      if (msjRcbLoRa !=0 && paqrcbEstado == 1){
        // Separa parámetros
        String t = paqueteRcb.substring(1,6);
        String h = paqueteRcb.substring(8,10);
        String v = paqueteRcb.substring(12);
        
        // procesa tópico MQTT
        
        // añade dispositivo
        String topico = "invernadero/lora";
        String remite = String(dir_remite, HEX);
        remite.toUpperCase();
        topico = topico + remite;
        
        // procesa topico
        String topicot = topico + "/temperatura";
        String topicoh = topico + "/humedad";
        String topicov = topico + "/voltaje";
        topicot.toCharArray(MQTT_TOPIC_T,topicot.length()+1);
        topicoh.toCharArray(MQTT_TOPIC_H,topicoh.length()+1);
        topicov.toCharArray(MQTT_TOPIC_V,topicov.length()+1);
    
        t.toCharArray(temperatura,t.length()+1);
        h.toCharArray(humedad,h.length()+1);
        v.toCharArray(voltaje,v.length()+1);
        Serial.println(topicov);
    
        
       publica_estado();
      }
      yield(); // procesa wifi
      delay(20);
    
      // reenviar a dispositivo
      if (actuador_bandera == true){
        msjContador = msjContador +1;
        enviarlora(dir_destino, dir_local, 
                   msjContador, paqueteEnv);
        actuador_bandera = false;
      }
      yield(); // procesa wifi
      delay(20);
      
      if (WiFi.status() != WL_CONNECTED){
        inicia_wifi();
      }else{
        if (mqttclient.connected()==false){
          mqtt_desconectado = true;
          inicia_mqtt(); // reintento
        }
        if (mqttclient.connected()==true){
          if (mqtt_desconectado==true){
            publica_estado();
            mqtt_desconectado=false;
          }
          mqttclient.loop();
        }
      }
      yield(); // procesa wifi
    }
    
    
    void enviarlora(byte destino, byte remite,
                    byte paqueteID, String paquete){
      // espera que el radio esté listo
      // para enviar un paquete
      while(LoRa.beginPacket() == 0){
        if (serial_msj==true){
          Serial.println("Esperando radio disponible...");
        }
        yield(); // procesa wifi
        delay(100);
      }
      // envio del mensaje LoRa
      LoRa.beginPacket();
      LoRa.write(destino);
      LoRa.write(remite);
      LoRa.write(paqueteID);
      LoRa.write(paquete.length());
      LoRa.print(paquete);
      LoRa.endPacket();
    }
    
    void recibirlora(int tamano){
      if (tamano == 0){ 
        paqrcbEstado = 0; //vacio
        return;
      }
        
      // lectura de paquete
      paqueteRcb = "";
      dir_envio = LoRa.read();
      dir_remite  = LoRa.read();
      paqrcbID = LoRa.read();
      byte paqrcbTamano = LoRa.read();
      while(LoRa.available()){
        paqueteRcb += (char)LoRa.read();
      }
      
      if (paqrcbTamano != paqueteRcb.length()){
        paqrcbEstado = 2; // Tamaño incompleto
        return;
      }
      if (dir_envio != dir_local){
        paqrcbEstado = 3; // otro destino
        return;
      }
      if (dir_envio == 0xFF) {
        paqrcbEstado = 4; // Broadcast
        return;
      }
      paqrcbEstado = 1;  // mensaje Nuevo
    }
    
    void inicia_mqtt(void){
      int intentosmqtt = 5;
      int cuentamqtt = 0;
      
      if (serial_msj){
        Serial.print(" MQTT Conectando a ");
        Serial.println(MQTT_IP);
        }
    
      mqttclient.setServer(MQTT_IP, MQTT_puerto);
      mqttclient.connect(MQTT_ID, MQTT_usuario, MQTT_contrasena);
      mqttclient.setCallback(recibirmqtt);
      
      while (!mqttclient.connected() && (cuentamqtt<=intentosmqtt)){
        if (serial_msj){
          Serial.print(".");
        }
        cuentamqtt = cuentamqtt + 1;
        // LED Monitor parpadeo MQTT
        digitalWrite(LED, HIGH); delay(200);
        digitalWrite(LED, LOW); delay(200);
      }
      if (mqttclient.connected()){
          publica_estado();
      }
      if (serial_msj){
        //Fin de "...."
        Serial.println();
        Serial.print(" MQTT Conectado: ");
        Serial.print(mqttclient.connected());
        Serial.print("\t MQTT Estado: ");
        Serial.println(mqttclient.state());
      }
    }
    
    void publica_estado() { 
    
      if (mqttclient.connected()==true){
        mqttclient.publish(MQTT_TOPIC_T,temperatura,true);
        mqttclient.publish(MQTT_TOPIC_H,humedad,true);
        mqttclient.publish(MQTT_TOPIC_V,voltaje,true);
        mqttclient.subscribe(MQTT_COMMAND);
      }else{
        mqtt_desconectado = true;
      }
    }
    
    // llega mensaje MQTT, callback mqtt
    void recibirmqtt(char* p_topic, byte* p_payload,
                      unsigned int p_length) {
      Serial.println("un mensaje mqtt");
      Serial.println(p_topic);
      // convierte a texto
      String payload;
      for (uint8_t i = 0; i < p_length; i++) {
        payload.concat((char)p_payload[i]);
        }
      // String dispositivo = p_topic[16] + String(p_topic[17]);
      char dispositivo[3] = "D0";
      dispositivo[1]=p_topic[17];
      dir_destino = (int) strtol(dispositivo,NULL,16);
      paqueteEnv = payload;
      actuador_bandera = true;
    
      if (mqttclient.connected()==true){
        mqttclient.subscribe(MQTT_COMMAND);
      }else{
        mqtt_desconectado = true;
      }
    }
    
    void inicia_wifi(void) {
      int intentoswifi = 10;
      int cuentawifi = 0;
      
      if (serial_msj){
        Serial.print(" WiFi Conectando a ");
        Serial.println(ssid);
        }
      
      WiFi.disconnect(true);
      delay(1000);
      WiFi.mode(WIFI_STA);
      WiFi.setAutoConnect(true);
      WiFi.begin(ssid,password);
      delay(100);
      
      while(WiFi.status() != WL_CONNECTED && 
                 cuentawifi < intentoswifi){
        if (serial_msj){
          Serial.print(".");
          }
        cuentawifi = cuentawifi + 1;
        // Parpadeo de Monitor Wifi
        digitalWrite(LED, HIGH);delay(300);
        digitalWrite(LED, LOW);delay(200);
      }
      if (serial_msj){
        // mensaje a serial
        Serial.println();
        if (WiFi.status() == WL_CONNECTED){
          Serial.print(" Estado: ");
          Serial.println(WiFi.status());
          Serial.print(" MAC: ");
          Serial.println(WiFi.macAddress());
          Serial.print(" IP: ");
          Serial.println(WiFi.localIP());
          Serial.print(" RSSI: ");
          Serial.println(WiFi.RSSI());
          Serial.println();
        }
        if (WiFi.status() != WL_CONNECTED){
            WiFi.printDiag(Serial);
            Serial.println();
        }
      }
    }
    
  • 2. LoRa - Temperatura, Humedad: Dispositivo Archivo.ino

    1. Instrucciones en Arduino IDE

    Para facilitar la programación, se separan en funciones las acciones para enviar y recibir mensajes Lora y las de manejo de sensor/actuador.

    El sensor DTH-11 se conecta al pin 13

    El sensor de bateria se conecta al pin 36 como entrada analógica.

    /*
      Dispositivo Sensor Temperatura y Humedad con DHT11
      Broker: MQTT/Home-Assistant
      Red ruta: LoRa/WiFi/Ethernet
      edelros@espol.edu.ec
      http:&#047;&#047;blog.espol.edu.ec/edelros/
      Referencia: Ejemplos de Aaron.Lee www.heltec.cn
    */
    #include "heltec.h"
    #include "DHT.h"
    
    // Sensor de Temperatura&Humedad
    #define DHTPIN 13
    #define DHTTYPE DHT11 
    DHT dht(DHTPIN, DHTTYPE);
    String temperatura = "";
    String humedad = ""; 
    
    // Sensor de Bateria
    # define BattPIN 36
    String battNivel;
    
    //Banda LoRa 915Mhz - ISM en Región 
    #define BAND  915E6 // 433E6,868E6,915E6
    // ranges from 6-12,default 7 see API docs
    byte spread_factor = 8;
    
    // Mensaje a enviar por direcciones
    byte dir_local   = 0xD1; // Dispositivo  1
    byte dir_destino = 0xC1; // Concentrador 1
    // identificador de mensaje
    byte msjContador = 0;
    // tiempo entre lecturas
    long t_anterior = 0;
    int  t_intervalo = 5000;
    
    // Mensaje Recibido
    byte dir_envio = 0xC1; // Concentrador 1
    int dir_remite = 0xD0; // Inicia Remitente
    String paqueteRcb = "";
    byte   paqrcbvID = 0;
    byte   paqrcbEstado = 0;
      // 0:vacio, 1: nuevo, 2:incompleto
      // 3:otro destinatario, 4:Broadcast
    
     // Mensajes por Puerto Serial
    volatile boolean serial_msj = true;
    
    void setup(){
      Heltec.begin(false /*DisplayEnable Enable*/,
        true /*Heltec.Heltec.Heltec.LoRa Disable*/,
        serial_msj /*Serial Enable*/,
        true /*PABOOST Enable*/,
        BAND /*long BAND*/);
        
      // ranges from 6-12,default 7 see API docs
      LoRa.setSpreadingFactor(spread_factor);
      //LoRa.onReceive(cbk);
      LoRa.receive();
    
      //inicializa sensores
      pinMode(DHTPIN, INPUT);
      dht.begin();
      pinMode(BattPIN, INPUT);
      }
    
    void loop(){
      // Enviar mensajes entre intervalos
      long t_ahora = millis();
      long t_transcurrido = t_ahora - t_anterior;
    
      // parametros de recepción
      int rssi_lora = 0;
      int snr_lora = 0;
      
      if (t_transcurrido >= t_intervalo){
        sensorLeeDHT(); //actualiza estado del sensor
        sensorBateria(); // actualiza estado de bateria
        
        // Construye paquete a enviar
        String paqueteEnv = "";
        paqueteEnv = paqueteEnv + "t" + temperatura;
        paqueteEnv = paqueteEnv + "|";
        paqueteEnv = paqueteEnv + "h" + humedad;
        paqueteEnv = paqueteEnv + "|";
        paqueteEnv = paqueteEnv + "v" + battNivel;
        
        enviarlora(dir_destino, dir_local,
                   msjContador, paqueteEnv);
        
        msjContador = msjContador + 1;
        
        // parametros de recepción
        rssi_lora = LoRa.packetRssi();
        snr_lora = LoRa.packetSnr();
        yield();
        
        // mensaje a serial
        if (serial_msj==true){
          Serial.print(String(dir_destino,HEX));
          Serial.print(",");
          Serial.print(String(dir_local,HEX));
          Serial.print(",");
          Serial.print(msjContador);Serial.print(",");
          Serial.print(paqueteEnv.length());Serial.print(",");
          Serial.print(paqueteEnv);Serial.print(",");
          Serial.print(rssi_lora);Serial.print(",");
          Serial.println(snr_lora);
        }
        
        t_anterior = millis();
        t_intervalo = 3000 + random(2000);
        
        // LED parpadea envio lora
        digitalWrite(LED, HIGH); delay(100);
        digitalWrite(LED, LOW);  delay(100);
        yield(); // procesa wifi
      }
    
      // Revisar mensajes LoRa entrantes
      int msjRcbLoRa = LoRa.parsePacket();
      if (msjRcbLoRa !=0){
        recibirlora(msjRcbLoRa);
        rssi_lora = LoRa.packetRssi();
        snr_lora = LoRa.packetSnr();
        
        if (serial_msj==true){
          if (paqrcbEstado == 1){
            Serial.println("Mensaje: " + paqueteRcb);
            Serial.println("RSSI: " + String(rssi_lora));
            Serial.println("Snr: " + String(snr_lora));
            Serial.println();
          }else{
            Serial.print("Paquete recibido Estado: ");
            Serial.println(paqrcbEstado);
          }
        }
        yield(); // procesa wifi
        
        // LED parpadea Recibido Lora
        digitalWrite(LED, HIGH); delay(50);
        digitalWrite(LED, LOW); delay(50);
        digitalWrite(LED, HIGH); delay(50);
        digitalWrite(LED, LOW);
      }
      delay(100);
      yield(); // procesa wifi
    }
    
    void enviarlora(byte destino, byte remite,
                    byte paqueteID, String paquete){
      // espera que el radio esté listo
      // para enviar un paquete
      while(LoRa.beginPacket() == 0){
        if (serial_msj==true){
          Serial.println("Esperando radio disponible...");
        }
        yield(); // procesa wifi
        delay(100);
      }
      // envio del mensaje LoRa
      LoRa.beginPacket();
      LoRa.write(destino);
      LoRa.write(remite);
      LoRa.write(paqueteID);
      LoRa.write(paquete.length());
      LoRa.print(paquete);
      LoRa.endPacket();
    }
    
    void recibirlora(int tamano){
      if (tamano == 0){ 
        paqrcbEstado = 0; //vacio
        return;
        }
      // lectura de paquete
      paqueteRcb = "";
      dir_envio = LoRa.read();
      dir_remite  = LoRa.read();
      paqrcbvID = LoRa.read();
      byte paqrcbTamano = LoRa.read();
      while(LoRa.available()){
        paqueteRcb += (char)LoRa.read();
      }
      if (paqrcbTamano != paqueteRcb.length()){
        paqrcbEstado = 2; // Tamaño incompleto
        return;
      }
      if (dir_envio != dir_local){
        paqrcbEstado = 3; // otro destino
        return;
      }
      if (dir_envio == 0xFF) {
        paqrcbEstado = 4; // Broadcast
        return;
      }
      paqrcbEstado = 1;  // mensaje Nuevo
    }
    
    // Sensor lecturas
    void sensorLeeDHT(){
      humedad = String(int(dht.readHumidity())).c_str();
      temperatura = String(dht.readTemperature()).c_str();
    }
    
    void sensorBateria(){
      int lectura = analogRead(BattPIN);
      // convierte a equivalente en voltios
      float voltaje = (float(lectura)/4096.0)*4.2*3.0/2.0;
      battNivel = String(voltaje).c_str();
    }