GIRNI

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

Autor: Edison Del Rosario

  • Docker Compose - Instalar en Raspberry Pi OS 64 bits

    Instalar en Raspberry Pi OS 64 bits:

    Install using the apt repository

    https://docs.docker.com/engine/install/debian

  • 5. Home Assistant - Docker Compose

    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

     

  • LoRaWan - Pluviometro

    Pluviometro LoRaWan con open hardware y open software

    Componentes de Hardware

    Pluviómetro de cubo basculante

    Pluviometro Part 01

    El componente principal es el colector de lluvia con cubo basculante. El colector adquirir solo o en kit con componentes para velocidad y dirección de viento..

    https://www.dfrobot.com/product-2689.html

    https://www.sparkfun.com/products/15901

    Referencia: https://www.argentdata.com/files/80422_datasheet.pdf

    Sensor de Humedad y temperatura

    Sensor de Humedad y temperatura I2C HDC1080

    Sensor digital de humedad relativa (RH) de baja potencia tipo I2C con precisión del 2%. Compatible con Arduino con la librería

    #include "HDC1080.h"

    https://www.ti.com/product/es-mx/HDC1080

    Sensor de Presión atmosférica y temperatura

    sensor de presión atmosférica y temperatura

    Sensor digital de Presión atmosférica y temperatura BMP180. Comunicación tipo I2C, compatible con Arduino con la librería

    #include "BMP180.h"

     Placa de desarrollo LoRa

    Placa de desarrollo LoRa que incluye controlador ARM y módulo de Radio, conexión USB, puerto para batería y panel solar. Por normativa para Ecuador se usa la versión con US915.

    LoRa development board V2 Heltec cubecell

    El desarrollo es compatible con otros módulos Lora de la serie CubeCell, se ha probado con el modelo "capsule" para la primera versión.

  • LoRaWan - Pluviometro: Chirpstack y HA

    Las instrucciones para interpretar la trama en el gestor de gateways Chirpstack se realizan en JavaScript, siguiendo el mismo orden de bytes transmitido desde el dispositivo.

    Para el caso de valores de temperatura se usan números enteros de 2 bytes con signo con dos decimales, según lo permite el sensor usado. El valor antes de transmitir se multiplica por 100. En la recepción se restaura el formato del número al dividirlo para 100.

    Al final se añade el acumulado del pluviómetro de los últimos 5 minutos como pluviosum para fines generales.

    Con esta parte se habilita la lectura de los parámetros para gestionar los datos en HomeAssistant mediante un mensaje Mqtt.

    ChirpStack

    // 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) {
      var Down_rssi = -1*parseInt(bytes[0]);
      var Down_snr = bytes[1];
      var Down_datarate = bytes[2];
    
      // usando entero sin signo
      var bateria = (bytes[4] << 8) |(bytes[3]);
      bateria = (bateria/1000);
      bateria = +bateria.toFixed(2);
      
      // usando entero con signo
      var unalectura = (bytes[6] << 8) |(bytes[5]); var unsigno = (unalectura>>>15 === 0) ? 1.0 : -1.0;
      var unvalor = unalectura & 0x7fff;
      if (unsigno<0){unvalor = unvalor - 32768;}
      var ht_temp = unvalor/100;
      ht_temp = +ht_temp.toFixed(2);
    
      var ht_humi = (bytes[8] << 8) |(bytes[7]);
      ht_humi = ht_humi/100;
      ht_humi = +ht_humi.toFixed(2);
      
      var unalectura = (bytes[10] << 8) |(bytes[9]); var unsigno = (unalectura>>>15 === 0) ? 1.0 : -1.0;
      var unvalor = unalectura & 0x7fff;
      if (unsigno<0){unvalor = unvalor - 32768;}
      var bar_temp = unvalor/100;
      bar_temp = +bar_temp.toFixed(2);
      
      var unalectura = (bytes[14] << 24) | (bytes[13]<< 16) | (bytes[12] << 8) |(bytes[11]); 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);
      var bar_press = NumReal;
      
      var pluvi0 = bytes[15];
      var pluvi1 = bytes[16];
      var pluvi2 = bytes[17];
      var pluvi3 = bytes[18];
      var pluvi4 = bytes[19];
      var pluviosum = pluvi0 + pluvi1 + pluvi2 + pluvi3 + pluvi4;
      var appData = {'Down_rssi':Down_rssi,'Down_snr':Down_snr,
                     'Down_datarate':Down_datarate,
                     'bateria_V':bateria,
                     'ht_temp':ht_temp,'ht_humi':ht_humi,
                     'bar_temp':bar_temp,'bar_press':bar_press,
                     'pluvi0':pluvi0,'pluvi1':pluvi1,'pluvi2':pluvi2,'pluvi3':pluvi3,'pluvi4':pluvi4,
                     'pluviosum':pluviosum,
                    }
      return appData;
    }
    
    

    Home Assistant

    mqtt:
      sensor:
        - name: "pluviometro01"
          state_topic: "application/4/device/2e4fa4ddf02f06eb/event/up"
          unit_of_measurement: "mm/m2"
          value_template: "{% set valores = value_json.objectJSON |from_json %} {{valores.pluviosum}}"
          #availability:
          #  - topic: "home/sensor1/status"
          payload_available: "online"
          payload_not_available: "offline"
          json_attributes_topic: "application/4/device/2e4fa4ddf02f06eb/event/up"
    
        - name: "Temperatura_pluvi01"
          state_topic: "application/4/device/2e4fa4ddf02f06eb/event/up"
          unit_of_measurement: "°C"
          value_template: "{% set valores = value_json.objectJSON |from_json %} {{valores.ht_temp}}"
    
        - name: "Humedad_pluvi01"
          state_topic: "application/4/device/2e4fa4ddf02f06eb/event/up"
          unit_of_measurement: "%"
          value_template: "{% set valores = value_json.objectJSON |from_json %} {{valores.ht_humi}}"
    
        - name: "Temperatura_pluvi01"
          state_topic: "application/4/device/2e4fa4ddf02f06eb/event/up"
          unit_of_measurement: "°C"
          value_template: "{% set valores = value_json.objectJSON |from_json %} {{valores.bar_temp}}"
    
        - name: "Presion_pluvi01"
          state_topic: "application/4/device/2e4fa4ddf02f06eb/event/up"
          unit_of_measurement: "Pa"
          value_template: "{% set valores = value_json.objectJSON |from_json %} {{valores.bar_press}}"
    

    ..

  • LoRaWan - Pluviometro: Archivo.ino

    Las instrucciones para control del pluviómetro y sensores complementarios se encuentran segmentadas por bloques:

    • Bloque principal
    • Sensores
    • Envío de tramas
    • Recepción de tramas

    [ sensores ] [ envía ] [ recibe ] [ principal ]
    ...


    Sensores

    El bloque de sensores se simplifica por partes de control y comunicación acorde al  tipo de sensor:

    • pulsos: Pluviómetro
    • Analógicos: batería
    • I2C: humedad, temperatura y presión atmosférica

    Pulsos: pluviómetro

    El pluviómetro se centra en medir el vaciado de la "doble cubeta basculante".

    Doble Cubeta Basculante

    Para la detección se usa un interruptor magnético que marca el cambio de estado mediante un pulso (pulse) conectado al pin asignado (pluvioPin). Para que la detección sea independiente del estado del dispositivo se usa una interrupción de instrucciones (pulse_tip)  que cuenta el pulso (pulse) en flanco de subida (RISING)y marca el evento (pulse_flag).

    attachInterrupt(pluvioPin, pulse_tip, RISING);

    El dispositivo cuenta  los pulsos generados durante un minuto  (sample_period) y los almacena en una lista (pulse_list) hasta la transmisión de los datos  hacia el gateway LoRa.

    pulse_tip muestreo

    Analógicos: Batería

    El sensor de estado de batería es un ADC que toma una lectura del estado de la batería antes de cada transmisión LoRa.

    I2C: humedad, temperatura y presión atmosférica

    Los sensores de humedad, temperatura y presión son de tipo I2C que se activan solo al ser leídos en cada ciclo de transmisión mediante el pin Vext, Posterior a la lectura apagan para optimizar el ahorro de energía.

    Antes de cada lectura, se realiza una verificación de estado de conexión al sensor optimizando el tiempo de éste proceso en caso de fallas del sensor expuesto a condiciones ambientales.

    LoRaWan pluviometro sensores bloque
    // sensors block
    // pluviometer pulse counter at interrupt
    void pulse_tip(){
      if (pulse<=255){pulse = pulse + 1;}
      pulse_flag = true;
    }
    void pulse_check(){
      if (pulse_flag==true){
        if (pulse>255){pulse=255;}
        pulse_flag = false;
        tipfree = 0;
        if (serial_msg){
          Serial.print("_|_: ");Serial.println(pulse);
        }
        turnOnRGB(128,200);turnOffRGB();
      }
    }
    
    void minute_check(){
      int i = 0; 
      pulse_list[i_minute] = pulse;
      if (i_minute>=n_minute){
        // pluviometer update
        for (i = 0; i < n_minute; i = i + 1) {
          pluviometer[i] = pulse_list[i];
          pulse_list[i] = 0;
        }
        i_minute = 0;
        loop_0 = false;
        sensor_flag = true;
        tx_done = false;
      }
    
      if (serial_msg){
        Serial.print("i_minute:");Serial.print(i_minute);
        Serial.print(" ; pulse_list:");
        for (i = 0; i < n_minute; i = i + 1) {
          Serial.print(" "); Serial.print(pulse_list[i]);
        }
        Serial.print(" ; pluviometer:");
        for (i = 0; i < n_minute; i = i + 1) {
          Serial.print(" "); Serial.print(pluviometer[i]);
        }
        Serial.print(" ; tipfree "); Serial.println(tipfree);
      }
    }
    
    void read_sensors(){
      batteryVoltage = getBatteryVoltage();
    
      // sensors check i2c on
      byte sensor_error, address;
      digitalWrite(Vext, LOW); delay(300);
      Wire.begin();
      
      humitemp_active = false;
      Wire.beginTransmission(0x40);
      sensor_error = Wire.endTransmission();
      if (sensor_error == 0){humitemp_active = true;}
    
      prestemp_active = false;
      Wire.beginTransmission(0x77);
      sensor_error = Wire.endTransmission();
      if (sensor_error == 0){prestemp_active = true;}
    
      if (humitemp_active){hdc1080.begin(0x40);}
      if (prestemp_active){bmp.begin();}
        
      if (humitemp_active){
        ht_temp = hdc1080.readTemperature();
        ht_humi = hdc1080.readHumidity();
      }
    
      float bar_alti = 0, bar_sea_pres = 0, bar_sea_alt = 0;
      if (prestemp_active){
        bar_temp = bmp.readTemperature();
        bar_pres = bmp.readPressure();
        bar_alti = bmp.readAltitude();
        bar_sea_pres = bmp.readSealevelPressure();
        bar_sea_alt  = bmp.readAltitude(100800);
      }
    
      // sensors I2C off
      Wire.end();
      digitalWrite(Vext, HIGH);
    
      if (serial_msg){
        Serial.print("Battery = ");Serial.print(batteryVoltage);
        
        Serial.print(" ; --- sensor ");
        Serial.print("hum_temp: "); Serial.print(humitemp_active);
        Serial.print(" ; bar_temp: "); Serial.println(prestemp_active);
        
        if (humitemp_active){
          Serial.print("HDC1080 Temperature = ");
          Serial.print(ht_temp); Serial.print(" C ; Humidity = ");
          Serial.print(ht_humi);Serial.println(" %");
        }
        if (prestemp_active){
          Serial.print("BMP180  Temperature = ");
          Serial.print(bar_temp);Serial.println(" C");
          
          Serial.print("  Pressure =      ");
          Serial.print(bar_pres);Serial.print(" Pa ; Altitude = ");
          Serial.print(bar_alti);Serial.println(" meters");
    
          Serial.print("  SeaLevel(calc)= ");
          Serial.print(bar_sea_pres); Serial.print(" Pa ; Altitude = ");
          Serial.print(bar_sea_alt); Serial.println(" meters");
        }
      Serial.println("");
      }
    }
    
    void onSleep(){
      Serial.print("\n  i_minute:");Serial.print(i_minute);
      Serial.printf(" ;Going into lowpower mode, %d ms later wake up.\r\n",timetillwakeup);
      lowpower = 1;
      //timetillwakeup ms later wake up;
      timetillwakeup = sample_period*1000 - (millis()-sample_ti);
      TimerSetValue( &wakeUp, timetillwakeup );
      TimerStart( &wakeUp );
    }
    void onWakeUp(){
      Serial.println("  ... Woke up by time");//, %d ms later into lowpower mode.\r\n",timetillsleep);
    	lowpower = 0;
      sleep_flag = false;
      sleep_done = true;
    	//timetillsleep ms later into lowpower mode;
    	//TimerSetValue( &sleep, timetillsleep );
    	//TimerStart( &sleep );
    }
    

    Adicionalmente existen dos procedimientos usados para el control del modo de ahorro de energía: onSleep() y onWakeup(), que junto a la instrucción lowPowerHandler() permiten un mayor ahorro de energía en los ciclos donde no se han producido pulsos del pluviómetro. Esto minimiza el uso de batería ante los periodos sin lluvia y manteniendo aún el registro por minuto durante largos periodos.

    Básicamente, se usa el modo de ahorro de energía si no han ocurrido eventos de conteo del pluviómetro durante al menos un ciclo de n_minute, caso contrario se mantiene el controlador activo.

    [ sensores ] [ envía ] [ recibe ] [ principal ]
    ...

    LoRaWan - Envia trama

    La trama se configura usando los parámetros obtenidos en el dispositivo para cada sensor.

    • Rssi Downlink (1 byte)
    • Snr Downlink (1 byte)
    • Datarate Downlink (1 byte)
    • Voltaje de batería (2 bytes)
    • sensores (15 bytes)
    LoRaWan pluviometro envia
    /* Prepares the payload of the frame */
    static void prepareTxFrame( uint8_t port ) {
      unsigned char *puc;
      signed char *pucs;
      // trama
      appDataSize = 15 + n_minute; // 15+pluvio_list size
      appData[0] = ack_rssi; //Ack leido en dispositivo
      appData[1] = ack_snr;
      appData[2] = ack_datarate;
      appData[3] = (uint8_t)batteryVoltage;
      appData[4] = (uint8_t)(batteryVoltage>>8);
    
      // convierte float a bytes
      int ht_temp_int = round(ht_temp*100);
      pucs = (signed char *)(&ht_temp_int);
      appData[5] = pucs[0];
      appData[6] = pucs[1];
    
      // convierte float a bytes
      int ht_humi_int = round(ht_humi*100);
      puc = (unsigned char *)(&ht_humi_int);
      appData[7] = puc[0];
      appData[8] = puc[1];
    
      // convierte float a bytes
      int bar_temp_int = round(bar_temp*100);
      pucs = (signed char *)(&bar_temp_int);
      appData[9] = pucs[0];
      appData[10] = pucs[1];
    
      // convierte float a bytes
      puc = (unsigned char *)(&bar_pres);
      appData[11] = puc[0];
      appData[12] = puc[1];
      appData[13] = puc[2];
      appData[14] = puc[3];
    
      for (int i = 0; i < n_minute; i = i + 1) {
        appData[15+i] = pluviometer[i];
      }
    
      // if (serial_msg){
      //   Serial.print("send pluviometer:");
      //   for (int i = 0; i < n_minute; i = i + 1) {
      //     Serial.print(" ");
      //     Serial.print(pluviometer[i]);
      //   }
      //   Serial.println("");
      // }
      for (int i = 0; i < n_minute; i = i + 1) {
        pluviometer[i]=0;
      }
    }
    

    [ sensores ] [ envía ] [ recibe ] [ principal ]
    ...

    LoRaWan - Recibe trama

    Manejo de tramas recibidas por el dispositivo para control o recibo de recibidos (Ack)

    //downlink data handle and downLink Ack Handle functions
    void downLinkDataHandle(McpsIndication_t *mcpsIndication) {
      // revisa parametros
      Serial.print("\nLlegó un mensaje para dispositivo...");
      // Serial.print("Rssi: ");
      // Serial.println(mcpsIndication->Rssi);
    
      // Serial.printf("+REV DATA:%s,RXSIZE %d,PORT %d\r\n",
      //               mcpsIndication->RxSlot ? "RXWIN2" : "RXWIN1",
      //               mcpsIndication->BufferSize, mcpsIndication->Port);
      // Serial.print("+REV DATA:");
      // for (uint8_t i = 0; i < mcpsIndication->BufferSize; i++) {
      //   Serial.printf("%02X", mcpsIndication->Buffer[i]);
      // }
    
      // parametros de recepcion
      ack_rssi = uint8_t(abs(mcpsIndication->Rssi));
      ack_snr  = uint8_t(mcpsIndication->Snr);
      ack_datarate = uint8_t(mcpsIndication->RxDoneDatarate);
      // recibido de trama
      Up_rssi = uint8_t(mcpsIndication->Buffer[0]);
    
      //
      Serial.print("Rx ack_Rssi:-"); Serial.print(ack_rssi);
      Serial.print(", ack_Snr:");Serial.print(ack_snr);
      Serial.print(", ack_Datarate: ");Serial.println(ack_datarate);
      Serial.print(" Up_rssi:");
      Serial.print(-1*Up_rssi);
      Serial.printf(" +REV DATA:%s,RXSIZE %d,PORT %d\r",mcpsIndication->RxSlot?"RXWIN2":"RXWIN1",mcpsIndication->BufferSize,mcpsIndication->Port);
      Serial.println();
    }
    
    void downLinkAckHandle(McpsIndication_t *mcpsIndication){
      ack_rssi = uint8_t(abs(mcpsIndication->Rssi));
      ack_snr  = uint8_t(mcpsIndication->Snr);
      ack_datarate = uint8_t(mcpsIndication->RxDoneDatarate);
      tx_done = true;
      // if (serial_msg){
      //   Serial.println(' ');
      //   Serial.print(" ack received(rssi,snr,datarate): -");
      //   Serial.print(ack_rssi);Serial.print(" ,");
      //   Serial.print(ack_snr);Serial.print(" ,");
      //   Serial.println(ack_datarate);
      // }
    }
    

    [ sensores ] [ envía ] [ recibe ] [ principal ]
    ...

    LoRaWan - Bloque principal

    El bloque principal se basa en el esquema básico de LoRaWan usado para sensores, cambiando la parte de ahorro de energía entre transmisiones DEVICE_STATE_SLEEP a una controlada por periodos de muestreo del pluviómetro con los procedimientos onSleep() y onWakeUp() descritos en la parte de sensores. Se controla las actividades de Lectura de sensores y modos de ahorro de energía por la sucesión de cada evento.

    LoRaWan pluviometro bloque principal

    // LoRaWan Pluviometer, temperature, humidity and barometric pressure
    // 2023 April
    // http://blog.espol.edu.ec/girni/lorawan-pluviometro-ino/
    #include "LoRaWan_APP.h"
    #include "Arduino.h"
    #include <Wire.h>
    #include <HDC1080.h>
    #include <BMP180.h>
    
    /* set LoraWan_RGB to Active,the RGB active in loraWan
     * red   |sending;   purple | joined done;
     * blue  |RxWindow1; yellow | means RxWindow2;
     * green | received done;
     */
    /* LoRaWan: OTAA parameters*/
    uint8_t devEui[] = { 0x2e, 0x4f, 0xa4, 0xdd, 0xf0, 0x2f, 0x06, 0xeb };
    uint8_t appEui[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
    uint8_t appKey[] = { 0x13, 0xb9, 0xd1, 0x66, 0x30, 0x2a, 0xeb, 0x53,
                         0x46, 0x6c, 0x0d, 0x2d, 0xa2, 0x31, 0x6b, 0xf0 };
    /* ABP parameters*/
    uint8_t nwkSKey[] = { 0xe2, 0x28, 0x89, 0xe0, 0x73, 0x22, 0xcb, 0xd1,
                          0xa7, 0x95, 0x64, 0x2e, 0xdb, 0xe5, 0x94, 0x42 };
    uint8_t appSKey[] = { 0x1a, 0xfc, 0x10, 0xc5, 0x6f, 0xb8, 0xba, 0x86,
                          0x0d, 0xf3, 0xcf, 0xc5, 0xd2, 0xdb, 0x44 ,0xb8 };
    uint32_t devAddr =  ( uint32_t )0x01d06174;
    /*LoraWan channelsmask, default channels 0-7*/ 
    uint16_t userChannelsMask[6]={ 0x00FF,0x0000,0x0000,0x0000,0x0000,0x0000 };
    
    /*Select in arduino IDE tools*/
    LoRaMacRegion_t loraWanRegion = ACTIVE_REGION;
    DeviceClass_t loraWanClass = LORAWAN_CLASS;
    bool overTheAirActivation = LORAWAN_NETMODE;
    bool loraWanAdr = LORAWAN_ADR;
    bool keepNet = LORAWAN_NET_RESERVE;
    bool isTxConfirmed = LORAWAN_UPLINKMODE;
    
    // sample period
    uint8_t sample_min = 0; 
    uint8_t sample_seg = 5; 
    uint32_t sample_period = (sample_min*60 + sample_seg);
    
    uint32_t appTxDutyCycle = (sample_min*60 + sample_seg)*1000; // min*seg*ms
    
    uint8_t appPort = 4; /* Application port */
    /* trials to transmit frame, if didn't receive ack.
     * The MAC performs a datarate adaptation,
     * Tx nb|Data Rate
     * -----|----------
     * 1    |DR           * 5    | max(DR-2,0)
     * 2    |DR           * 6    | max(DR-2,0)
     * 3    |max(DR-1,0)  * 7    | max(DR-3,0)
     * 4    |max(DR-1,0)  * 8    | max(DR-3,0)
    */
    uint8_t confirmedNbTrials = 4;
    
    // Ack reception parameters
    uint8_t ack_rssi = 0;
    uint8_t ack_snr = 0;
    uint8_t ack_datarate = 0;
    
    uint8_t Up_rssi = 0;
    
    // serial print messages
    bool serial_msg = true;
    
    // time variables and control check
    unsigned long sample_t0 = 0;
    unsigned long sample_ti = 0;
    unsigned long sample_dt = 0;
    bool pluvio_flag = false;
    bool sensor_flag = false;
    unsigned long tx_t0 = 0;
    bool tx_flag = false;
    bool tx_done = false;
    uint8_t tipfree = 0;
    bool loop_0 = true;
    bool sleep_flag = false;
    bool sleep_done = true;
    
    uint8_t i_minute = 0;
    const uint8_t n_minute = 5;
    uint8_t pulse_list[n_minute];
    uint8_t pluviometer[n_minute];
    uint8_t pulse = 0;
    bool pulse_flag = false;
    const uint8_t pluvioPin = GPIO5;
    
    #define timetillsleep 60*60*1000
    int timetillwakeup = sample_period*1000;
    static TimerEvent_t sleep;
    static TimerEvent_t wakeUp;
    uint8_t lowpower=1;
    
    //sensors libraries
    HDC1080 hdc1080;  // humidity and temperature sensor
    BMP085 bmp; // barometer and temperature sensor
    
    // sensors I2C active check and value variables
    bool humitemp_active = false;
    bool prestemp_active = false;
    float ht_temp = 0, ht_humi = 0;
    float bar_temp = 0, bar_pres = 0;
    
    uint16_t batteryVoltage = 0;
    
    void setup() {
      Serial.begin(115200);
    
    #if(AT_SUPPORT)
    	enableAt();
    #endif
    
      // OLED display status
      //LoRaWAN.displayMcuInit();
    
      pinMode(pluvioPin, INPUT); //OUTPUT_PULLUP);
      attachInterrupt(pluvioPin, pulse_tip, RISING);
      
      for (int i = 0; i < n_minute; i = i + 1) {
        pulse_list[i] = 0;
        pluviometer[i] = 0;
      }
      
      // sensors I2C Vcc pin control
      pinMode(Vext, OUTPUT);
      read_sensors();
    
      // time variables iniciated
      sample_t0 = millis();
      sample_ti = sample_t0;
      tx_t0 = sample_t0;
      
      deviceState = DEVICE_STATE_INIT;
    	LoRaWAN.ifskipjoin(); //if joinned,skip
    
      // lowpower mode
      Radio.Sleep( );
      TimerInit( &sleep, onSleep );
      TimerInit( &wakeUp, onWakeUp );
      onWakeUp();
    }
    
    void loop() {
      if(lowpower){lowPowerHandler();}
    
      pulse_check();
      sample_t0 = millis();
      sample_dt = abs(sample_t0-sample_ti);
      if (sample_dt>=sample_period*1000){
        sample_ti = sample_t0;
        i_minute = i_minute + 1;
        if (pulse==0){ // tipfree counter do not overflow
          if (tipfree<=250){tipfree = tipfree + 1;}
          if (tipfree>250){tipfree = 1;}
        }
        pulse = 0;
        minute_check();
      }
      
      // read I2C sensors at i_minute=0
      if (i_minute==0 && sensor_flag == true){
        Serial.println("  reading sensors ...");
        read_sensors();
        sensor_flag = false;
        }
    
      // tx check
      if (i_minute==0 && tx_flag==false && tx_done == false && loop_0==false){
        tx_t0 = sample_t0;
        deviceState = DEVICE_STATE_SEND;
        tx_flag = true;
        tx_done = false;
        }
      if (i_minute >= n_minute){tx_flag = false;}
      
      // sleep check
      if (i_minute==0 && tipfree>=n_minute && sleep_flag==false && sleep_done==true && tx_done==true){
        sleep_flag = true;
        sleep_done = false;
        lowpower = 1;
        //timetillsleep ms later into lowpower mode;
        TimerSetValue( &sleep, 1 );
        TimerStart( &sleep );
        }
      if (i_minute > 0 && tipfree>=n_minute && sleep_flag==false && sleep_done==true){
        sleep_flag = true;
        sleep_done = false;
        lowpower = 1;
        //timetillsleep ms later into lowpower mode;
        TimerSetValue( &sleep, 1 );
        TimerStart( &sleep );
        }
      
    	switch( deviceState ) {
    		case DEVICE_STATE_INIT: {
    #if(LORAWAN_DEVEUI_AUTO)
    			LoRaWAN.generateDeveuiByChipID();
    #endif
    #if(AT_SUPPORT)
    			getDevParam();
    #endif
    			printDevParam();
    			LoRaWAN.init(loraWanClass,loraWanRegion);
    			deviceState = DEVICE_STATE_JOIN;
    			break;
    		}
    		case DEVICE_STATE_JOIN: {
          //LoRaWAN.displayJoining();
    			LoRaWAN.join();
    			break;
    		}
    		case DEVICE_STATE_SEND:	{
          //LoRaWAN.displaySending();
    			prepareTxFrame( appPort );
    			LoRaWAN.send();
    			deviceState = DEVICE_STATE_CYCLE;
    			break;
    		}
    		case DEVICE_STATE_CYCLE: {
    			// Schedule next packet transmission
    			//txDutyCycleTime = appTxDutyCycle + randr( 0, APP_TX_DUTYCYCLE_RND );
    			//LoRaWAN.cycle(txDutyCycleTime);
    			deviceState = DEVICE_STATE_SLEEP;
    			break;
    		}
    		case DEVICE_STATE_SLEEP: {
          //LoRaWAN.displayAck();
          if (sleep_flag==true && sleep_done==false){ 
          //   if (serial_msg){
          //     Serial.print(" --- Sleep mode ; deviceState : ");
          //     Serial.print(deviceState);
          //     Serial.print(" ; i_minute : "); Serial.println(i_minute);
          //     }
            //LoRaWAN.sleep();
            sleep_done = true;
          }
    			break;
    		}
    		default: {
    			deviceState = DEVICE_STATE_INIT;
    			break;
    		}
    	}
    }
    

    [ sensores ] [ envía ] [ recibe ] [ principal ]

  • LoRaWan - Parámetros Up/Down: Pantalla Chirpstack y HA

    ChirpStack

    DECODER

    function Decode(fPort, bytes, variables) {
      var Down_rssi = -1*parseInt(bytes[0]);
      var Down_snr = bytes[1];
      var Down_datarate = bytes[2];
      // usando entero
      var unalectura = (bytes[4] << 8) |(bytes[3]);
      unalectura = (unalectura/1000);
      unalectura = +unalectura.toFixed(2);
      var appData = {'Down_rssi':Down_rssi,'Down_snr':Down_snr,
                     'Down_datarate':Down_datarate,
                     'bateria_V': unalectura}
      return appData;
    }
    

    ENCODER

    function Encode(fPort, obj, variables) {
      var UP_rssi = obj["UP_rssi"];
      // var Up_snr = obj["UP_snr"];
      var mensaje = [UP_rssi];
      return mensaje;
    }
    

    Home Assistant

    configuration.yaml

    mqtt:
      sensor:
        - name: "rssi_up_cc50"
          unique_id: cc27rssiup
          state_topic: "application/1/device/a53ec615aede3f50/event/up"
          unit_of_measurement: "dBm"
          value_template: "{{ value_json.rxInfo[0].rssi}}"
          #availability:
          #  - topic: "home/sensor1/status"
          payload_available: "online"
          payload_not_available: "offline"
          json_attributes_topic: "application/1/device/a53ec615aede3f50/event/up"

    Automation

    Desencadenante

    platform: mqtt
    topic: application/1/device/a53ec615aede3f50/event/up

    Acción

    service: mqtt.publish
    data:
      qos: 0
      retain: false
      topic: application/1/device/a53ec615aede3f50/command/down
      payload: '{"confirmed":false,"fPort":4,"object":{"UP_rssi":{{(states("sensor.rssi_up_cc50") | int)*(-1)}}}}'
  • LoRaWan - Parámetros Up/Down: Pantalla Archivo.ino

    Pantalla OLED con parametros RSSI UP/DOWN

    Probador de Rssi de subida y bajada

    Hardware: Heltec cubecell AB02, con pantalla OLED incorporada.

    OLED_pantalla.ino

    void displayconectando(){
    	display2.setFont(ArialMT_Plain_16);
    	display2.setTextAlignment(TEXT_ALIGN_CENTER);
    	display2.clear();
    	display2.drawString(58, 22, "Joining...");
    	display2.display();
    }
    void displayConectado(){
    	display2.clear();
    	display2.drawString(64, 22, "Conectado_OK");
    	display2.display();
    	delay(500);
    }
    void displayTransmitiendo(){
    	digitalWrite(Vext,LOW);
    	display2.init();
    	display2.setFont(ArialMT_Plain_16);
    	display2.setTextAlignment(TEXT_ALIGN_CENTER);
    	display2.clear();
    	display2.drawString(58, 22, "Tx...");
    	display2.display();
    	delay(500);
    }
    void displayPaqRecibido(){
      char temp[25];
    	display2.clear();
      display2.setFont(ArialMT_Plain_16);
      display2.setTextAlignment(TEXT_ALIGN_LEFT);
      sprintf(temp,"Rx snr:%d,dr:%d",Down_snr, Down_datarate);
    	display2.drawString(0, 0, temp); //22
    	sprintf(temp,"rssiDw: -%d",Down_rssi);
    	display2.setFont(ArialMT_Plain_24);
    	display2.setTextAlignment(TEXT_ALIGN_RIGHT);
    	display2.drawString(128, 16, temp);
      sprintf(temp,"rssiUp: -%d",Up_rssi);
    	display2.setFont(ArialMT_Plain_24);
    	display2.setTextAlignment(TEXT_ALIGN_RIGHT);
    	display2.drawString(128, 40, temp);
    
      display2.drawString(0,0, temp);
    	if(loraWanClass==CLASS_A)	{
    		display2.setFont(ArialMT_Plain_10);
    		display2.setTextAlignment(TEXT_ALIGN_LEFT);
    		display2.drawString(28, 50, "Into deep sleep in 2S");
    	}
    	display2.display();
      delay(10000);
      display2.clear();
    	if(loraWanClass==CLASS_A){
    		digitalWrite(Vext,HIGH);
    		display2.stop();
    	}
    }
    
    void displayAckRecibido(){
      char temp[25];
    	display2.clear();
      display2.setFont(ArialMT_Plain_16);
      display2.setTextAlignment(TEXT_ALIGN_LEFT);
      sprintf(temp,"ACK snr:%d,dr:%d",confirmaSnr, confirmaDatarate);
    	display2.drawString(0, 0, temp); //22
    	sprintf(temp,"rssiDw: -%d",confirmaRssi);
    	display2.setFont(ArialMT_Plain_24);
    	display2.setTextAlignment(TEXT_ALIGN_RIGHT);
    	display2.drawString(128, 16, temp);
    	if(loraWanClass==CLASS_A)	{
    		display2.setFont(ArialMT_Plain_10);
    		display2.setTextAlignment(TEXT_ALIGN_LEFT);
    		display2.drawString(28, 50, "Into deep sleep in 2S");
    	}
    	display2.display();
      delay(10000);
    	if(loraWanClass==CLASS_A)	{
    		digitalWrite(Vext,HIGH);
    		display2.stop();
    	}
    }
    

    CubeCell_Practicas2023OLED.ino

    // Lectura de Rssi Snr, datarate Up/Downlink
    // Datos Downlink de la trama de confirmación anterior
    // http://blog.espol.edu.ec/girni/lorawan-enlaces-up-down-archivo-ino/
    #include "LoRaWan_APP.h"
    #include "Arduino.h"
    #include <Wire.h>               
    #include "HT_SH1107Wire.h"
    
    SH1107Wire  display2(0x3c, 500000, SDA, SCL ,GEOMETRY_128_64,GPIO10); // addr, freq, sda, scl, resolution, rst
    
    /* set LoraWan_RGB to Active,the RGB active in loraWan
     * red   |sending;   purple | joined done;
     * blue  |RxWindow1; yellow | means RxWindow2;
     * green | received done;
     */
    /* Conexión LoRa: OTAA parametros*/
    uint8_t devEui[] = { 0xa5, 0x3e, 0xc6, 0x15, 0xae, 0xde, 0x3f, 0x50 };
    uint8_t appEui[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
    uint8_t appKey[] = { 0x88, 0xbe, 0x25, 0xca, 0x2c, 0xcf, 0x31, 0x85,
                         0x51, 0x2d, 0xee, 0xe2, 0x80, 0x31, 0x8e, 0x01 };
    /* ABP parametros*/
    uint8_t nwkSKey[] = { 0x15, 0xb1, 0xd0, 0xef, 0xa4, 0x63, 0xdf, 0xbe,
                          0x3d, 0x11, 0x18, 0x1e, 0x1e, 0xc7, 0xda,0x85 };
    uint8_t appSKey[] = { 0x47, 0xdc, 0xac, 0x5f, 0xc2, 0x32, 0x24, 0x31, 
                          0xdf, 0xf1, 0xff, 0xf9, 0x46, 0xe5, 0x2e, 0x17 };
    uint32_t devAddr =  ( uint32_t )0x007bc4150;
    /*LoraWan channelsmask, default channels 0-7*/ 
    uint16_t userChannelsMask[6]={ 0x00FF,0x0000,0x0000,0x0000,0x0000,0x0000 };
    
    /*Select in arduino IDE tools*/
    LoRaMacRegion_t loraWanRegion = ACTIVE_REGION;
    DeviceClass_t  loraWanClass = LORAWAN_CLASS;
    bool overTheAirActivation = LORAWAN_NETMODE;
    bool loraWanAdr = LORAWAN_ADR;
    bool keepNet = LORAWAN_NET_RESERVE;
    bool isTxConfirmed = LORAWAN_UPLINKMODE;
    
    uint8_t duermemin = 0; //15
    uint8_t duermeseg = 300; //0
    
    uint32_t appTxDutyCycle = (duermemin*60 + duermeseg)*1000; // min*seg*ms
    
    uint8_t appPort = 4; /* Application port */
    /* trials to transmit frame, if didn't receive ack.
     * The MAC performs a datarate adaptation,
     * Tx nb|Data Rate
     * -----|----------
     * 1    |DR           * 5    | max(DR-2,0)
     * 2    |DR           * 6    | max(DR-2,0)
     * 3    |max(DR-1,0)  * 7    | max(DR-3,0)
     * 4    |max(DR-1,0)  * 8    | max(DR-3,0)
    */
    uint8_t confirmedNbTrials = 4;
    
    // Ack parametros de recepción
    uint8_t confirmaRssi = 0;
    uint8_t confirmaSnr = 0;
    uint8_t confirmaDatarate = 0;
    
    // Ack parametros de recepción
    uint8_t Down_rssi = 0;
    uint8_t Down_snr = 0;
    uint8_t Down_datarate = 0;
    
    uint8_t Up_rssi = 0;
    
    uint8_t itera = 0;
    uint8_t estado = 0; //0x00, 0x01,"OFF","ON"
    
    void setup() {
    	Serial.begin(115200);
     
    #if(AT_SUPPORT)
    	enableAt();
    #endif
    
      // OLED display status
      //LoRaWAN.displayMcuInit();
      
    	deviceState = DEVICE_STATE_INIT;
    	//LoRaWAN.ifskipjoin(); //if joinned,skip
    
      display2.init();
      display2.setFont(ArialMT_Plain_10);
    }
    
    void loop() {
      Serial.print(".");
      itera = itera + 1;
      if (itera>6){
        itera = 0;
        Serial.println(" ");
      }
    	switch( deviceState ) {
    		case DEVICE_STATE_INIT: {
    #if(LORAWAN_DEVEUI_AUTO)
    			LoRaWAN.generateDeveuiByChipID();
    #endif
    #if(AT_SUPPORT)
    			getDevParam();
    #endif
    			printDevParam();
    			LoRaWAN.init(loraWanClass,loraWanRegion);
    			deviceState = DEVICE_STATE_JOIN;
    			break;
    		}
    		case DEVICE_STATE_JOIN: {
          //LoRaWAN.displayJoining();
          displayconectando();
    			LoRaWAN.join();
    			break;
    		}
    		case DEVICE_STATE_SEND:	{
          //LoRaWAN.displaySending();
          displayTransmitiendo();
    			prepareTxFrame( appPort );
    			LoRaWAN.send();
    			deviceState = DEVICE_STATE_CYCLE;
    			break;
    		}
    		case DEVICE_STATE_CYCLE: {
    			// Schedule next packet transmission
    			txDutyCycleTime = appTxDutyCycle + randr( 0, APP_TX_DUTYCYCLE_RND );
    			LoRaWAN.cycle(txDutyCycleTime);
    			deviceState = DEVICE_STATE_SLEEP;
    			break;
    		}
    		case DEVICE_STATE_SLEEP: {
          //LoRaWAN.displayAck();
    			LoRaWAN.sleep();
    			break;
    		}
    		default: {
    			deviceState = DEVICE_STATE_INIT;
    			break;
    		}
    	}
    }
    

    LoRaWanEnvia.ino

    /* Prepares the payload of the frame */
    static void prepareTxFrame( uint8_t port ) {
      // enciende sensor
      pinMode(Vext, OUTPUT);
      digitalWrite(Vext, LOW);
      //Lectura de Sensor
    
      //apaga sensor
      digitalWrite(Vext, HIGH);
      
      // lectura de bateria  
      uint16_t batteryVoltage = getBatteryVoltage();
      unsigned char *puc;
    
      // trama
      appDataSize = 5;
      appData[0] = confirmaRssi; //Ack leido en dispositivo
      appData[1] = confirmaSnr;
      appData[2] = confirmaDatarate;
      appData[3] = (uint8_t)batteryVoltage;
      appData[4] = (uint8_t)(batteryVoltage>>8);
    
      Serial.print("%, Bateria = ");
      Serial.println(batteryVoltage);
    }
    

    LoRaWanRecibe.ino

    //downlink data handle function example
    void downLinkDataHandle(McpsIndication_t *mcpsIndication){
      // parametros de recepcion
      Down_rssi = uint8_t(abs(mcpsIndication->Rssi));
      Down_snr  = uint8_t(mcpsIndication->Snr);
      Down_datarate = uint8_t(mcpsIndication->RxDoneDatarate);
      // recibido de trama
      Up_rssi = uint8_t(mcpsIndication->Buffer[0]);
    
      //
      Serial.print("Rx Down_rssi:-"); Serial.print(Down_rssi);
      Serial.print(", Down_snr:");Serial.print(Down_snr);
      Serial.print(", Down_datarate: ");Serial.println(Down_datarate);
      Serial.print(" UP_rssi:");
      Serial.print(-1*Up_rssi);
      Serial.printf(" +REV DATA:%s,RXSIZE %d,PORT %d\r",mcpsIndication->RxSlot?"RXWIN2":"RXWIN1",mcpsIndication->BufferSize,mcpsIndication->Port);
      Serial.println();
      
      displayPaqRecibido();
    }
    

    LoRaWanRecibeConfirma.ino

    void downLinkAckHandle(McpsIndication_t *mcpsIndication){
      // ACK parametros de recepcion
      confirmaRssi = uint8_t(abs(mcpsIndication->Rssi));
      confirmaSnr  = uint8_t(mcpsIndication->Snr);
      confirmaDatarate = uint8_t(mcpsIndication->RxDoneDatarate);
    
      Serial.println("");
      Serial.print(" ack received(rssi,snd,datarate): -");
      Serial.print(confirmaRssi);Serial.print(" ,");
      Serial.print(confirmaSnr);Serial.print(" ,");
      Serial.println(confirmaDatarate);
      
      displayAckRecibido();
    }
    
  • 6. Taller LoRa - PracticaLab.ino LoRaWan

    Ejercicio de conexión a LoRaWan con ChirpStack y HomeAssistant

    Bloque principal

    // Lectura de Rssi Snr, datarate Up/Downlink
    // Datos Downlink de la trama de confirmación anterior
    // http://blog.espol.edu.ec/girni/lorawan-enlaces-up-down-archivo-ino/
    #include "LoRaWan_APP.h"
    #include "Arduino.h"
    
    /* set LoraWan_RGB to Active,the RGB active in loraWan
     * red   |sending;   purple | joined done;
     * blue  |RxWindow1; yellow | means RxWindow2;
     * green | received done;
     */
    /* Conexión LoRa: OTAA parametros*/
    uint8_t devEui[] = { 0xa5, 0x3e, 0xc6, 0x15, 0xae, 0xde, 0x3f, 0x00 };
    uint8_t appEui[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
    uint8_t appKey[] = { 0x88, 0xbe, 0x25, 0xca, 0x2c, 0xcf, 0x31, 0x85,
                         0x51, 0x2d, 0xee, 0xe2, 0x80, 0x31, 0x8e, 0x01 };
    /* ABP parametros*/
    uint8_t nwkSKey[] = { 0x15, 0xb1, 0xd0, 0xef, 0xa4, 0x63, 0xdf, 0xbe,
                          0x3d, 0x11, 0x18, 0x1e, 0x1e, 0xc7, 0xda,0x85 };
    uint8_t appSKey[] = { 0x47, 0xdc, 0xac, 0x5f, 0xc2, 0x32, 0x24, 0x31, 
                          0xdf, 0xf1, 0xff, 0xf9, 0x46, 0xe5, 0x2e, 0x17 };
    uint32_t devAddr =  ( uint32_t )0x007bc4200;
    /*LoraWan channelsmask, default channels 0-7*/ 
    uint16_t userChannelsMask[6]={ 0x00FF,0x0000,0x0000,0x0000,0x0000,0x0000 };
    
    /*Select in arduino IDE tools*/
    LoRaMacRegion_t loraWanRegion = ACTIVE_REGION;
    DeviceClass_t  loraWanClass = LORAWAN_CLASS;
    bool overTheAirActivation = LORAWAN_NETMODE;
    bool loraWanAdr = LORAWAN_ADR;
    bool keepNet = LORAWAN_NET_RESERVE;
    bool isTxConfirmed = LORAWAN_UPLINKMODE;
    
    uint8_t duermemin = 0; //15
    uint8_t duermeseg = 30; //0
    
    uint32_t appTxDutyCycle = (duermemin*60 + duermeseg)*1000; // min*seg*ms
    
    uint8_t appPort = 4; /* Application port */
    /* trials to transmit frame, if didn't receive ack.
     * The MAC performs a datarate adaptation,
     * Tx nb|Data Rate
     * -----|----------
     * 1    |DR           * 5    | max(DR-2,0)
     * 2    |DR           * 6    | max(DR-2,0)
     * 3    |max(DR-1,0)  * 7    | max(DR-3,0)
     * 4    |max(DR-1,0)  * 8    | max(DR-3,0)
    */
    uint8_t confirmedNbTrials = 4;
    
    // Ack parametros de recepción
    uint8_t confirmaRssi = 0;
    uint8_t confirmaSnr = 0;
    uint8_t confirmaDatarate = 0;
    
    uint8_t itera = 0;
    uint8_t estado = 0x00; //0x00, 0x01,"OFF","ON"
    
    void setup() {
    	Serial.begin(115200);
     
    #if(AT_SUPPORT)
    	enableAt();
    #endif
    
      // OLED display status
      //LoRaWAN.displayMcuInit();
      
    	deviceState = DEVICE_STATE_INIT;
    	//LoRaWAN.ifskipjoin(); //if joinned,skip
    }
    
    void loop() {
      Serial.print(".");
      itera = itera + 1;
      if (itera>6){
        itera = 0;
        Serial.println(" ");
      }
    	switch( deviceState ) {
    		case DEVICE_STATE_INIT: {
    #if(LORAWAN_DEVEUI_AUTO)
    			LoRaWAN.generateDeveuiByChipID();
    #endif
    #if(AT_SUPPORT)
    			getDevParam();
    #endif
    			printDevParam();
    			LoRaWAN.init(loraWanClass,loraWanRegion);
    			deviceState = DEVICE_STATE_JOIN;
    			break;
    		}
    		case DEVICE_STATE_JOIN: {
          //LoRaWAN.displayJoining();
    			LoRaWAN.join();
    			break;
    		}
    		case DEVICE_STATE_SEND:	{
          //LoRaWAN.displaySending();
    			prepareTxFrame( appPort );
    			LoRaWAN.send();
    			deviceState = DEVICE_STATE_CYCLE;
    			break;
    		}
    		case DEVICE_STATE_CYCLE: {
    			// Schedule next packet transmission
    			txDutyCycleTime = appTxDutyCycle + randr( 0, APP_TX_DUTYCYCLE_RND );
    			LoRaWAN.cycle(txDutyCycleTime);
    			deviceState = DEVICE_STATE_SLEEP;
    			break;
    		}
    		case DEVICE_STATE_SLEEP: {
          //LoRaWAN.displayAck();
    			LoRaWAN.sleep();
    			break;
    		}
    		default: {
    			deviceState = DEVICE_STATE_INIT;
    			break;
    		}
    	}
    }
    
    

    LoraWan transmite

    /* Prepares the payload of the frame */
    static void prepareTxFrame( uint8_t port ) {
      // enciende sensor
      pinMode(Vext, OUTPUT);
      digitalWrite(Vext, LOW);
      
      //Lectura de Sensor
    
      // apaga sensor
      digitalWrite(Vext, HIGH);
      
      // lectura de bateria  
      uint16_t batteryVoltage = getBatteryVoltage();
      unsigned char *puc;
    
      // trama
      appDataSize = 5;
      appData[0] = confirmaRssi; //Ack leido en dispositivo
      appData[1] = confirmaSnr;
      appData[2] = confirmaDatarate;
      appData[3] = (uint8_t)batteryVoltage;
      appData[4] = (uint8_t)(batteryVoltage>>8);
    
      Serial.print("%, Bateria = ");
      Serial.println(batteryVoltage);
    }
    

    LoRWan Recibe

    //downlink data handle function example
    void downLinkDataHandle(McpsIndication_t *mcpsIndication) {
      // revisa parametros
      // Serial.print("\nLlegó un mensaje para dispositivo...");
      // Serial.print("Rssi: ");
      // Serial.println(mcpsIndication->Rssi);
    
      // Serial.printf("+REV DATA:%s,RXSIZE %d,PORT %d\r\n",
      //               mcpsIndication->RxSlot ? "RXWIN2" : "RXWIN1",
      //               mcpsIndication->BufferSize, mcpsIndication->Port);
      // Serial.print("+REV DATA:");
      // for (uint8_t i = 0; i < mcpsIndication->BufferSize; i++) {
      //   Serial.printf("%02X", mcpsIndication->Buffer[i]);
      // }
    
      estado = uint8_t(mcpsIndication->Buffer[0]);
      Serial.print("uplink: rssi = -");
      Serial.println(estado);
      Serial.println();
    //  uint32_t color = mcpsIndication->Buffer[0] << 16 | mcpsIndication->Buffer[1] << 8 | mcpsIndication->Buffer[2];
    //#if (LoraWan_RGB == 1)
    //  turnOnRGB(color, 5000);
    //  turnOffRGB();
    //#endif
    }
    

    LoRaWan recibe confirma (Ack)

    void downLinkAckHandle(McpsIndication_t *mcpsIndication){
      confirmaRssi = uint8_t(abs(mcpsIndication->Rssi));
      confirmaSnr  = uint8_t(mcpsIndication->Snr);
      confirmaDatarate = uint8_t(mcpsIndication->RxDoneDatarate);
      //Serial.println(' ');
      //Serial.print(" ack received(rssi,snd,datarate): -");
      //Serial.print(confirmaRssi);Serial.print(" ,");
      //Serial.print(confirmaSnr);Serial.print(" ,");
      //Serial.println(confirmaDatarate);
    }
    
    
  • 5. Taller LoRa - SemiDuplex.ino con direcciones

    Aunque se reciben todos los mensajes en el radio, se requiere discriminar los mensajes que son dirigidos hacia el dispositivo local.

    Se modifica las intrucciones de recepción añadiendo un bloque para discriminar si el mensaje es para el dispositivo local. Esto implica descomponer la trama enviada en sus partes y convertir al tipo de dato a usar.

    Las operaciones se realizan por caracter o byte con el objetivo de establecer el mecanismo a usar cuando se usa LoRaWan

    Resultados obtenidos en puerto serial

    TX Paquete "312159" , tamano 6 bytes , proximo en 9341 ms 
        RX Paquete "213169" , tamanio 6, Rssi -20 
        Lee mensaje enviado por: 21 valor mensaje: 69
    TX Paquete "312160" , tamano 6 bytes , proximo en 8062 ms 
        RX Paquete "213170" , tamanio 6, Rssi -20 
        Lee mensaje enviado por: 21 valor mensaje: 70
    

    Procedimiento de recepción de paquete LoRa

    void OnRxDone( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr ) {
      turnOnRGB(COLOR_RECEIVED,0);
      Rssi = rssi;     // nivel de recepcion
      rxSize = size;
      memcpy(rxpacket, payload, size );
      rxpacket[size]='\0';  //añade fin de cadena
    
      Radio.Sleep( );
    
      Serial.printf("\r    RX Paquete \"%s\" , tamanio %d, Rssi %d \r\n",
                    rxpacket,rxSize,Rssi);
                    
      //revisa direcciones
      char envia[4];  byte dir_envia;
      char recibe[4]; byte dir_recibe;
      char msj[4];    byte msj_valor;
      envia[0] = rxpacket[0];
      envia[1] = rxpacket[1];
      envia[2] = '\0';  //añade fin de cadena
      recibe[0] = rxpacket[2];
      recibe[1] = rxpacket[3];
      recibe[2] = '\0';  //añade fin de cadena
      msj[0] = rxpacket[4];
      msj[1] = rxpacket[5];
      msj[2] = '\0';  //añade fin de cadena
      
      //convierte a tipo de datos
      dir_envia  = byte(atoi(envia));
      dir_recibe = byte(atoi(recibe));
      msj_valor  = byte(atoi(msj));
      
      // Muestra o discrimina mensaje en Serial-USB/Pantalla
      if (dir_recibe = dir_local){
        Serial.print("    -- Lee mensaje enviado por: ");
        Serial.print(dir_envia);
        Serial.print(" valor mensaje: ");
        Serial.println(msj_valor);
      }
    }
    
  • 4. Taller LoRa - SemiDuplex.ino Cubecell

    Como cada dispositivo contiene solo un radio, la comunicación en dos sentidos puede habilitarse en modo SemiDuplex. Las funciones de transmisión y recepción se alternan.

    Resultados

    TX Paquete "213110" , tamano 6 bytes , proximo en 8118 ms 
        RX Paquete "312139" , tamanio 6, Rssi -22 
    TX Paquete "213111" , tamano 6 bytes , proximo en 8429 ms 
        RX Paquete "312140" , tamanio 6, Rssi -23 
    TX Paquete "213112" , tamano 6 bytes , proximo en 9397 ms 
    

    Instrucciones

    /* LoRa TRANSMITE/RECIBE Semi Duplex / Half-Duplex
     * Referencia: https://github.com/HelTecAutomation/ASR650x-Arduino
    */
    #include "LoRaWan_APP.h"
    #include "Arduino.h"
    
    #ifndef LoraWan_RGB    // LED placa
    #define LoraWan_RGB 0
    #endif
    
    // LoRa Parametros 
    #define RF_FREQUENCY       915E6     // Hz
    #define TX_OUTPUT_POWER    14        // dBm
    #define LORA_BANDWIDTH     0         // [0: 125 kHz, 1: 250 kHz,
                                         //  2: 500 kHz, 3: Reserved]
    #define LORA_SPREADING_FACTOR   7    // [SF7..SF12]
    #define LORA_CODINGRATE         1    // [1: 4/5,  2: 4/6,
                                         //  3: 4/7,  4: 4/8]
    #define LORA_PREAMBLE_LENGTH    8    // Same for Tx and Rx
    #define LORA_SYMBOL_TIMEOUT     0    // Symbols
    #define LORA_FIX_LENGTH_PAYLOAD_ON  false
    #define LORA_IQ_INVERSION_ON        false
    #define RX_TIMEOUT_VALUE            1000
    #define BUFFER_SIZE                 30 // Define the payload size here
    char txpacket[BUFFER_SIZE];          // cadena de caracteres
    char rxpacket[BUFFER_SIZE];
    static RadioEvents_t RadioEvents;
    
    void OnTxDone( void );     // Tx completada
    void OnTxTimeout( void );  // Tx fuera de tiempo
    void OnRxDone( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr );
    
    byte modoOperacion = 0; // 0:RX 1:TX
    bool sleepMode = false;
    int16_t txNumero;
    int16_t Rssi,rxSize;
    
    // Direcciones por dispositivo
    byte dir_local   = 21; // Dispositivo envia
    byte dir_destino = 31; // Dispositivo recibe
    
    // tiempo entre Tx de datos o lecturas de sensor
    long tiempo_antes     = 0;
    long tiempo_intervalo = 7000;
    long tiempo_espera = tiempo_intervalo + random(3000);
    
    void setup() {
      Serial.begin(115200);
      txNumero=10; Rssi=0;
    
      RadioEvents.TxDone = OnTxDone;
      RadioEvents.TxTimeout = OnTxTimeout;
      RadioEvents.RxDone = OnRxDone;
      Radio.Init( &RadioEvents );
    
      Radio.SetChannel( RF_FREQUENCY );
      Radio.SetTxConfig( MODEM_LORA, TX_OUTPUT_POWER, 0, LORA_BANDWIDTH,
                         LORA_SPREADING_FACTOR, LORA_CODINGRATE,
                         LORA_PREAMBLE_LENGTH, LORA_FIX_LENGTH_PAYLOAD_ON,
                         true, 0, 0, LORA_IQ_INVERSION_ON, 3000 );
      Radio.SetRxConfig( MODEM_LORA, LORA_BANDWIDTH, LORA_SPREADING_FACTOR,
                         LORA_CODINGRATE, 0, LORA_PREAMBLE_LENGTH,
                         LORA_SYMBOL_TIMEOUT, LORA_FIX_LENGTH_PAYLOAD_ON,
                         0, true, 0, 0, LORA_IQ_INVERSION_ON, true );
      modoOperacion=0;  // 0:RX 1:TX 2:LOWPOWER
    }
    
    void loop(){
      // Intervalos entre mensajes
      long tiempo_ahora   = millis();
      long t_transcurrido = tiempo_ahora - tiempo_antes;
      if (t_transcurrido >= tiempo_espera){
        tiempo_antes = millis();
        tiempo_espera = tiempo_intervalo + random(3000);
        modoOperacion=1; // 0:RX 1:TX
      }else{
        modoOperacion=0; // 0:RX 1:TX
      }
      
      switch(modoOperacion) { // 0:RX 1:TX 2:LOWPOWER
        case 0:
          Radio.Rx( 0 );   
          modoOperacion=2;  //LOWPOWER;
          break;
        case 1:
          delay(500);
          transmiteMsg();
          modoOperacion=0;
          break;
        case 2:
          lowPowerHandler();
          modoOperacion=0;
          break;
        default:
          break;
      }
      turnOnRGB(0,0); // LED apaga
      delay(100);
      Radio.IrqProcess( );
    }
    

    Procedimiento de transmisión de paquete LoRa

    void transmiteMsg( void ) {
      turnOnRGB(COLOR_SEND,0); // LED de placa
      // Paquete a transmitir
      sprintf(txpacket,"%d",dir_local);
      sprintf(txpacket+strlen(txpacket),"%d",dir_destino);
      sprintf(txpacket+strlen(txpacket),"%d",txNumero); //añade número paquete a txpacket
    
      // Mensaje a pantalla
      Serial.printf("\rTX Paquete \"%s\" , tamano %d bytes\r",
                    txpacket, strlen(txpacket));
      Serial.printf("\r , proximo en %d ms \r\n",
                    tiempo_espera);
      
      // Transmite paquete LoRa
      Radio.Send( (uint8_t *)txpacket, strlen(txpacket) );
    
      txNumero = txNumero + 1;  // cuenta paquete
      if (txNumero>=99){
        txNumero = 0;           // reinicia contador
      }
      modoOperacion=2;//LOWPOWER;
    }
    
    void OnTxDone( void ) {
      turnOnRGB(0,0);
      modoOperacion=0;
    }
    
    void OnTxTimeout( void ) {
      Radio.Sleep( );
      Serial.println("TX Timeout......");
      modoOperacion=1;
    }
    
    

    Procedimiento de recepción de paquete LoRa

    void OnRxDone( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr ) {
      turnOnRGB(COLOR_RECEIVED,0);
      Rssi = rssi;     // nivel de recepcion
      rxSize = size;
      memcpy(rxpacket, payload, size );
      rxpacket[size]='\0';  //añade fin de cadena
    
      Radio.Sleep( );
    
      Serial.printf("\r    RX Paquete \"%s\" , tamanio %d, Rssi %d \r\n",
                    rxpacket,rxSize,Rssi);
    }