2.3 LoRa Multipunto – Gateway.ino básico con LoRa-ESP32

El prototipo para un gateway simple se inicia con la función de recepción de mensajes que hay que procesar para enviarlos aun servidor MQTT. A partir de donde se gestionan los datos de los sensores.

Para el prototipo se usa placa de desarrollo que contiene: un  módulo LoRa y un SoC ESP32. Si se reutiliza algunos componentes de algoritmos usados para los dispositivos con WiFi, la versión inicial se conecta un router IP via WiFi de donde se envia el mensaje MQTT.

El mensaje MQTT require la descripción de un tópico, por lo que en la conformación del tópico se usa la dirección de envío.

Los valores usados en el mensaje MQTT, son el estado del sensor u otro valor que se requiera. Para facilitar el seguimiento inicial de datos, se publica el identificador de mensaje, que es un contador ascendente que permite observar la secuencia del número de mensaje.

Gateway Multipunto – Recepción Lora y envío MQTT

Para el ejercicio se habilitan dos dispositivos «D1» y «D2», comprobando los mensajes recibidos de varias formas:

– mensajes por puerto serial
– mensajes MQTT en servidor

Instrucciones en Arduino

Los bloques se crean a partir del ejemplo de LoRa Mutipunto para dispositivos, aprovechando que el módulo Heltec LoRa-ESP32 tiene incorporado WiFi. Aunque no se encuentran todos los canales disponibles al mismo tiempo para recibir todas las señales, se aprovecha que si el  canal está libre se puede realizar la transmisión del estado del sensor desde LoRa hacia WiFi para llegar hasta el broker MQTT.

El ejercicio es una prueba de concepto, pues un gateway completo debe estar atento a todos los canales de transmisión y tener la capacida de atención a cada uno de ellos simultaneamente.

Bloque principal

En el bloque principal se adjuntan los parámetros de conexión por WiFi, asi como las instrucciones de inicialización. Desde luego será necesario añadir los procedimientos de WiFi y MQTT.

/* Dispositivo Gateway: LoRa/WiFi/MQTT/Home-Assistant
 * http://blog.espol.edu.ec/girni/lora-multipunto-esquema/
 * Referencia: Aaron.Lee www.heltec.cn, 
 * https://github.com/Heltec-Aaron-Lee/WiFi_Kit_series
*/
#include "heltec.h"
#include <WiFi.h>
#include <PubSubClient.h>

// LoRa Banda ISM en Región 915Mhz
#define BAND 915E6       //433E6,868E6,915E6
byte spread_factor = 8;  // rango 6-12,default 7

// Mensaje LoRa a enviar por direcciones
byte dir_local   = 0xC1; // Dispositivo 1
byte dir_destino = 0xD1; // Dispositivo 2
byte id_msjLoRa  = 0;    // cuenta mensaje
String paqueteEnv = "";  // mensaje

// Mensaje LoRa recibido
byte dir_envio  = 0xC1; // receptor
byte dir_remite = 0xD1; // emisor
String paqueteRcb = "OFF"; // mensaje LoRa
byte   paqRcb_ID  = 0;
byte   paqRcb_Estado = 0;
  // 0:vacio, 1: nuevo,   2:incompleto
  // 3:otro destinatario, 4:Broadcast

// LED interno, ESP01-pin=1, ESP07-pin=2
int LED_pin = LED; //LED interno Heltec

// Mensajes por Puerto Serial
boolean serial_msj = true;

// tiempo entre lecturas
long tiempo_antes     = 0;
int  tiempo_intervalo = 6000;
long tiempo_espera = tiempo_intervalo + random(3000);

// WIFI: conexión a Router
char* ssid     = "miRouter";
char* password = "miRouterclave";

// 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";
String topico_base  = "invernadero/lora";
String topico_valor = "/valor";
char MQTT_TOPIC[50] = ""; // construido en algoritmo
char MQTT_SensorEstado[10] = "OFF";
boolean mqtt_desconectado = true;

// MQTT: Dispositivo Actuador
String topico_accion = "/cambia";
char MQTT_COMMAND[50] = "";
char MQTT_ActuadorEstado[10] = "OFF";
boolean actuador_estado  = false;
boolean actuador_bandera = false;
char* sensor_ON  = "ON";
char* sensor_OFF = "OFF";

// Clientes WiFi y MQTT
WiFiClient   wificlient;
PubSubClient mqttclient(wificlient);

void setup(){
  Heltec.begin(false /*DisplayEnable Enable*/,
    true /*Heltec.LoRa Disable*/,
    serial_msj /*Serial Enable*/,
    true /*PABOOST Enable*/,
    BAND /*long BAND*/);
  LoRa.setSpreadingFactor(spread_factor);
  
  //LoRa.onReceive(cbk);
  LoRa.receive();
  
  inicia_wifi(); // conexión WIFI y MQTT
  if (WiFi.status() == WL_CONNECTED){
    inicia_mqtt();
    }
}

void loop(){

  // LoRa Revisa mensajes de dispositivos
  int msjRcbLoRa = LoRa.parsePacket();
  if (msjRcbLoRa !=0){
    recibe_lora(msjRcbLoRa);
    int rssi_lora = LoRa.packetRssi();
    int snr_lora  = LoRa.packetSnr();
       
    if (serial_msj==true){
      if (paqRcb_Estado == 1){
        Serial.print("Recibido: ");
        Serial.print(String(dir_remite,HEX));Serial.print(",");
        Serial.print(String(dir_envio,HEX)); Serial.print(",");
        Serial.print(paqRcb_ID);  Serial.print(",");
        Serial.print(paqueteRcb); Serial.print(",");
        Serial.print(rssi_lora);  Serial.print(",");
        Serial.print(snr_lora);   Serial.print(",");
        Serial.println();
      }else{
        Serial.print("Paquete recibido Estado: ");
        Serial.println(paqRcb_Estado);
      }
    }
    
    // LED parpadea. Rebibido LoRa 
    digitalWrite(LED, HIGH); delay(50);
    digitalWrite(LED, LOW ); delay(50);
    digitalWrite(LED, HIGH); delay(50);
    digitalWrite(LED, LOW );
  }

  
  // MQTT publica estado recibido desde LoRa
  if (msjRcbLoRa !=0 && paqRcb_Estado == 1){
    
    if (mqttclient.connected()==true){
      publica_mqtt();
    }
  }

  // LoRa Reenvia a dispositivo
  if (actuador_bandera == true){
    id_msjLoRa = id_msjLoRa +1;
    envia_lora(dir_destino, dir_local, 
               id_msjLoRa, paqueteEnv);
    actuador_bandera = false;
  }

  // Revisa estado WiFi y MQTT
  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_mqtt();
        mqtt_desconectado=false;
      }
      mqttclient.loop();
    }
  }
  delay(100);
}

LoRa – envío de mensajes

void envia_lora(byte destino, byte remite,
                byte paqueteID, String paquete){

  // espera radio para enviar un paquete
  while(LoRa.beginPacket() == 0){
    if (serial_msj==true){
      Serial.println("Esperando radio disponible...");
    }
    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();
}

LoRa – Recibe mensajes

void recibe_lora(int tamano){
  if (tamano == 0){ 
    paqRcb_Estado = 0; //vacio
    return;
  }
    
  // lectura de paquete
  paqueteRcb = "";
  dir_envio  = LoRa.read();
  dir_remite = LoRa.read();
  paqRcb_ID   = LoRa.read();
  
  byte paqrcbTamano = LoRa.read();
  while(LoRa.available()){
    paqueteRcb += (char)LoRa.read();
  }
  
  if (paqrcbTamano != paqueteRcb.length()){
    paqRcb_Estado = 2; // tamaño incompleto
    return;
  }
  if (dir_envio != dir_local){
    paqRcb_Estado = 3; // otro destino
    return;
  }
  if (dir_envio == 0xFF) {
    paqRcb_Estado = 4; // Broadcast, difusion
    return;
  }
  paqRcb_Estado = 1;   // mensaje Nuevo
}

MQTT- inicia

void inicia_mqtt(void){
  int esperamqtt = 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(recibe_mqtt);
  
  while (!mqttclient.connected() && (cuentamqtt<=esperamqtt)) {
    cuentamqtt = cuentamqtt + 1;
    if (serial_msj){
      Serial.print(".");
    }
    // LED Monitor parpadeo MQTT
    digitalWrite(LED_pin, HIGH); delay(200);
    digitalWrite(LED_pin, LOW);  delay(200);
  }
  
  if (mqttclient.connected()){
    // publica_mqtt();
    // suscribe a todos los topicos base
  }
  
  if (serial_msj){
    //Fin de "...."
    Serial.print("\n MQTT Conectado: ");
    Serial.print(mqttclient.connected());
    Serial.print("\t Estado: ");
    Serial.println(mqttclient.state());
  }
}

MQTT- publica mensaje

void publica_mqtt() { 
  paqueteRcb.toCharArray(MQTT_SensorEstado,
                         paqueteRcb.length()+1);

  // MQTT Construye tópico por remitente
  String remite = String(dir_remite, HEX);
  remite.toUpperCase();
  String topico = topico_base + remite + topico_valor;
  topico.toCharArray(MQTT_TOPIC,topico.length()+1);
  if (serial_msj==true){
    Serial.print("  ");
    Serial.print(topico); Serial.print("/");
    Serial.println(MQTT_SensorEstado);
  }
  // MQTT Construye tópico por remitente
    String topico_cmd = "";
    topico_cmd += topico_base + remite +"/cambia";
    topico_cmd.toCharArray(MQTT_COMMAND,topico_cmd.length()+1);
    if (serial_msj==true){
      Serial.print("  sucrito: ");
      Serial.println(MQTT_COMMAND);
    }
    
  if (mqttclient.connected()==true){
    mqttclient.publish(MQTT_TOPIC,MQTT_SensorEstado,true);
    mqttclient.subscribe(MQTT_COMMAND);
  }else{
    mqtt_desconectado = true;
  }
}

MQTT- Recibe mensaje

// llega mensaje MQTT, callback mqtt sin confirmación
void recibe_mqtt(char* p_topic, byte* p_payload,
                  unsigned int p_length) {
  if (serial_msj){
    Serial.println("Recibe 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]);
    }
  paqueteEnv = payload;
  
  // direccion destino
  char dispositivo[3] = "D0";
  int desde = topico_base.length();
  dispositivo[0] = p_topic[desde];
  dispositivo[1] = p_topic[desde+1];
  dir_destino = (int) strtol(dispositivo,NULL,16);

  actuador_bandera = true;
  if (mqttclient.connected()==true){
    mqttclient.subscribe(MQTT_COMMAND);
  }else{
    mqtt_desconectado = true;
  }
}

WiFi – inicio

void inicia_wifi(void) {
  int esperawifi = 10;  // >=10 para conectar
  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 < esperawifi)){
    cuentawifi = cuentawifi + 1;
    if (serial_msj){
      Serial.print(".");
    }
    // Parpadeo de Monitor Wifi
    digitalWrite(LED_pin, HIGH); delay(300);
    digitalWrite(LED_pin, LOW);  delay(200);
  }
  
  if (serial_msj){
    // mensaje a serial, Fin de "..."
    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();
    }
  }
}

.