Configuración Wifi dinámica en ESP32 con Firebase

por | Mar 27, 2019 | Arduino, ESP32, Firebase | 0 Comentarios

Imagen Miniatura ESP32 Multiwifi

Introducción

Si eres un programador que suele frecuentar los microcontroladores para sus proyectos personales o no tan personales, ni falta que hace mencionarte lo tedioso que es el tema de la configuración wifi dentro de éstos. No obstante, si no estás tan familiarizado puede que te estés preguntando, tedioso ¿por qué?…

Bueno, la configuración de la conexión wifi es algo que se define dentro del programa, y se da por supuesto que durante la ejecución del mismo no se va a modificar. Pero, ¿y si además de necesitar cambiarlo durante la ejecución se da el caso de que no tenemos los medios para realizar el cambio en el código fuente de nuestro programa? Realmente es una gran limitación. De esta forma, supongamos que vamos a diseñar un producto que se conecta a Internet y hace X, ¿venderías dicho producto con una configuración wifi preestablecida e imposible de modificar por el usuario final? No lo creo.

Con estos ejemplos simplemente nos ponemos en situación de las limitaciones que nos afectan al tener una configuración estática. Por ello, en este tutorial haremos un approach a una de las posibles soluciones del problema.

Materiales

Para la realización de este proyecto vamos a necesitar los siguientes materiales:

Requerimientos

Para poder completar todos los pasos necesitaremos:

  • Cuenta Gmail
  • Conocimientos básicos sobre Firebase y su funcionamiento
  • Arduino IDE

Esquema

A continuación deberemos conectar nuestro botón según el esquema proporcionado.

Firebase

Lo primero que haremos será crear un proyecto nuevo de Firebase con nuestra cuenta de Gmail. En él estableceremos la estructura de datos en su Realtime Database. Para ello nos dirigiremos a Página Principal Firebase > Ir a consola > Agregar Proyecto (si no nos sale la opción de la consola es porque debemos iniciar sesión).

Una vez hagamos clic en Agregar proyecto nos pedirá el nombre y aceptar los términos. Cuando se hayan introducido los datos y le demos a Crear proyecto, el proceso durará unos segundos, y cuando finalice, haremos clic en Continuar para llegar a la página principal de nuestro proyecto.
Al lado izquierdo tenemos la barra de navegación, en la que deberemos seleccionar la opción Database y seguidamente hacer clic en Crear base de datos dentro de la sección Realtime Database.
Nos solicitará el modo con el que queremos iniciar nuestra base de datos en tiempo real, e indicaremos que en modo de prueba, para que las reglas de seguridad nos permitan acceder a Firebase sin necesidad de autenticarnos antes, y haremos clic en Habilitar.
La estructura de datos en nuestra Realtime Database deberá quedarnos de la siguiente manera:
Finalmente, lo último que nos quedaría por hacer es recuperar las claves de conexión para el ESP32 con Firebase. Así mismo, el host podemos consultarlo en la pantalla donde se define la estructura de la Realtime Database. Seguidamente, para la clave de autenticación, nos dirigiremos a la ruedita de ajustes al lado de Project Overview > Configuración del proyecto > Cuentas de servicio > Secretos de la base de datos > Mostrar. A continuación os dejo un par de capturas en las cuales se indica dónde hacer clic. Una vez tengamos guardado el host y el auth podremos abrir nuestro IDE de Arduino.

ESP32

Por fin, llegamos a la parte interesante, donde cargaremos el código en nuestro microcontrolador. Una vez esté subido y corriendo podremos juguetear con los valores de nuestra Realtime Database.

Primero deberemos importar las librerías de las que se hará uso.

#include <WiFi.h>
#include <IOXhop_FirebaseESP32.h>
#include <Preferences.h>
#include <ArduinoJson.h>
#include <AceButton.h>
Lo siguiente es definir las constantes y declarar las variables.
#define FIREBASE_HOST "YOUR_HOST"
#define FIREBASE_AUTH "YOUR_AUTH"
#define PREFERENCES_APP "EEPROM-TEST"
#define SETTINGS_NODE "/settings"
#define REBOOT_NODE "/need_reboot"
#define WIFI_NODE "/wifi"
#define SSID_NODE "/wifi/ssid"
#define PASSWORD_NODE "/wifi/password"
#define DEFAULT_SSID "YOUR_SSID"
#define DEFAULT_PASSWORD "YOUR_PASSWORD"
#define RESET_BUTTON_PIN 18

using namespace ace_button;

Preferences preferences;  // Se declara una variable para manejar la EEPROM (memoria permanente)

AceButton resetButton(RESET_BUTTON_PIN);  // Se declara el botón en el pin definido
Ahora definiremos el callback que se asignará al botón, y que será el encargado de controlar en qué estado se encuentra.
// Se define el callback que usará nuestro botón
void handleResetButtonEvent(AceButton* /* button */, uint8_t eventType, uint8_t buttonState) {
  switch (eventType) {
    // Si recibe un evento de larga pulsación resetea el nombre de red y contraseña de la EEPROM y se reinicia
    case AceButton::kEventLongPressed:
      preferences.begin(PREFERENCES_APP, false);
      preferences.clear();
      preferences.end();
      Serial.println("nResetting SSID and PASSWORD");
      ESP.restart();
      break;
  }
}
A continuación definiremos la función setup(). En ella se le establecerá el callback al botón, se recuperará el SSIDPassword y se establecerá la conexión Wifi y con Firebase.
void setup() {
  Serial.begin(115200);

  // Se inicializa el botón de reset con una resistencia interna
  pinMode(RESET_BUTTON_PIN, INPUT_PULLUP);

  // Se inicializa el botón con su configuración y su callback
  ButtonConfig* resetButtonConfig = resetButton.getButtonConfig();
  resetButtonConfig->setEventHandler(handleResetButtonEvent);
  resetButtonConfig->setFeature(ButtonConfig::kFeatureLongPress);
  
  // Se inicializa la biblioteca para controlar la EEPROM
  preferences.begin(PREFERENCES_APP, true);

  // Se recuperan las credenciales guardadas o se establecen unas por defecto
  String strWIFI_SSID = preferences.getString("SSID", DEFAULT_SSID);
  String strWIFI_PASSWORD = preferences.getString("PASSWORD", DEFAULT_PASSWORD);

  // Se muestran las credenciales que se van a usar por consola
  Serial.printf("Current SSID: %sn", strWIFI_SSID.c_str());
  Serial.printf("Current PASSWORD: %sn", strWIFI_PASSWORD.c_str());

  // Se termina la edición de la EEPROM
  preferences.end();

  // Se convierten las credenciales a tipo char
  char charWIFI_SSID[strWIFI_SSID.length()+1];
  char charWIFI_PASSWORD[strWIFI_PASSWORD.length()+1];
  strWIFI_SSID.toCharArray(charWIFI_SSID, strWIFI_SSID.length()+1);
  strWIFI_PASSWORD.toCharArray(charWIFI_PASSWORD, strWIFI_PASSWORD.length()+1);

  // Se inicializa la conexión wifi con las credenciales
  WiFi.begin(charWIFI_SSID, charWIFI_PASSWORD);
  Serial.printf("Connecting to %s", strWIFI_SSID.c_str());
  while (WiFi.status() != WL_CONNECTED) {
    resetButton.check();
    Serial.print(".");
    delay(500);
  }
  Serial.print("nConnected with IP: ");
  Serial.println(WiFi.localIP());

  // Se inicializa la conexión con Firebase
  Firebase.begin(FIREBASE_HOST, FIREBASE_AUTH);
  // Se establece el listener de la conexión con Firebase
  Firebase.stream(SETTINGS_NODE, [](FirebaseStream stream) {
    String eventType = stream.getEvent();
    eventType.toLowerCase();
    
    Serial.printf("event: %sn", eventType.c_str());
    if (eventType == "put") {
      String path = stream.getPath();  // Se obtiene la ruta del cambio

      if (path == WIFI_NODE) {  // Si el cambio se realizó en el nodo entero de las credenciales wifi
        JsonObject& streamData = stream.getData();
        String ssid = streamData["ssid"].as<String>();
        String password = streamData["password"].as<String>();
        Serial.printf("WIFI NODE - SSID: %sn", ssid.c_str());
        Serial.printf("WIFI NODE - PASSWORD: %sn", password.c_str());
        preferences.begin(PREFERENCES_APP, false);
        preferences.putString("SSID", ssid);
        preferences.putString("PASSWORD", password);
        preferences.end();
      }
      else if (path == SSID_NODE) {  // Si el cambio se realizó en el nombre de la red
        String streamData = stream.getDataString();
        Serial.printf("New SSID: %sn", streamData.c_str());
        preferences.begin(PREFERENCES_APP, false);
        preferences.putString("SSID", streamData);
        preferences.end();
      }
      else if (path == PASSWORD_NODE) {  // Si el cambio se realizó en la contraseña
        String streamData = stream.getDataString();
        Serial.printf("New Password: %sn", streamData.c_str());
        preferences.begin(PREFERENCES_APP, false);
        preferences.putString("PASSWORD", streamData);
        preferences.end();
      }
      else if (path == REBOOT_NODE) {  // Si el cambio se realizó en el nodo de reinicio
        bool streamData = stream.getDataBool();
        Serial.printf("Need Reboot?: %sn", streamData ? "true" : "false");
        if (streamData) {  // Se reinicia el dispositivo si se ha indicado que es necesario
          String route = SETTINGS_NODE;
          route = route + REBOOT_NODE;
          Firebase.setBool(route, false);
          ESP.restart();
        }
      }
      
      Serial.printf("Path: %sn", path.c_str());  // Se muestra por consola la ruta donde se realizaron los cambios
    }
  });
}
En cambio, la función loop() únicamente cumplirá la tarea de monitorizar el estado del botón.
void loop() {
  // Se comprueba el estado del botón constantemente
  resetButton.check();
}
De la siguiente manera nos quedará el código completo.
#include <WiFi.h>
#include <IOXhop_FirebaseESP32.h>
#include <Preferences.h>
#include <ArduinoJson.h>
#include <AceButton.h>

#define FIREBASE_HOST "YOUR_HOST"
#define FIREBASE_AUTH "YOUR_AUTH"
#define PREFERENCES_APP "EEPROM-TEST"
#define SETTINGS_NODE "/settings"
#define REBOOT_NODE "/need_reboot"
#define WIFI_NODE "/wifi"
#define SSID_NODE "/wifi/ssid"
#define PASSWORD_NODE "/wifi/password"
#define DEFAULT_SSID "YOUR_SSID"
#define DEFAULT_PASSWORD "YOUR_PASSWORD"
#define RESET_BUTTON_PIN 18

using namespace ace_button;

Preferences preferences;  // Se declara una variable para manejar la EEPROM (memoria permanente)

AceButton resetButton(RESET_BUTTON_PIN);  // Se declara el botón en el pin definido

// Se define el callback que usará nuestro botón
void handleResetButtonEvent(AceButton* /* button */, uint8_t eventType, uint8_t buttonState) {
  switch (eventType) {
    // Si recibe un evento de larga pulsación resetea el nombre de red y contraseña de la EEPROM y se reinicia
    case AceButton::kEventLongPressed:
      preferences.begin(PREFERENCES_APP, false);
      preferences.clear();
      preferences.end();
      Serial.println("nResetting SSID and PASSWORD");
      ESP.restart();
      break;
  }
}

void setup() {
  Serial.begin(115200);

  // Se inicializa el botón de reset con una resistencia interna
  pinMode(RESET_BUTTON_PIN, INPUT_PULLUP);

  // Se inicializa el botón con su configuración y su callback
  ButtonConfig* resetButtonConfig = resetButton.getButtonConfig();
  resetButtonConfig->setEventHandler(handleResetButtonEvent);
  resetButtonConfig->setFeature(ButtonConfig::kFeatureLongPress);
  
  // Se inicializa la biblioteca para controlar la EEPROM
  preferences.begin(PREFERENCES_APP, true);

  // Se recuperan las credenciales guardadas o se establecen unas por defecto
  String strWIFI_SSID = preferences.getString("SSID", DEFAULT_SSID);
  String strWIFI_PASSWORD = preferences.getString("PASSWORD", DEFAULT_PASSWORD);

  // Se muestran las credenciales que se van a usar por consola
  Serial.printf("Current SSID: %sn", strWIFI_SSID.c_str());
  Serial.printf("Current PASSWORD: %sn", strWIFI_PASSWORD.c_str());

  // Se termina la edición de la EEPROM
  preferences.end();

  // Se convierten las credenciales a tipo char
  char charWIFI_SSID[strWIFI_SSID.length()+1];
  char charWIFI_PASSWORD[strWIFI_PASSWORD.length()+1];
  strWIFI_SSID.toCharArray(charWIFI_SSID, strWIFI_SSID.length()+1);
  strWIFI_PASSWORD.toCharArray(charWIFI_PASSWORD, strWIFI_PASSWORD.length()+1);

  // Se inicializa la conexión wifi con las credenciales
  WiFi.begin(charWIFI_SSID, charWIFI_PASSWORD);
  Serial.printf("Connecting to %s", strWIFI_SSID.c_str());
  while (WiFi.status() != WL_CONNECTED) {
    resetButton.check();
    Serial.print(".");
    delay(500);
  }
  Serial.print("nConnected with IP: ");
  Serial.println(WiFi.localIP());

  // Se inicializa la conexión con Firebase
  Firebase.begin(FIREBASE_HOST, FIREBASE_AUTH);
  // Se establece el listener de la conexión con Firebase
  Firebase.stream(SETTINGS_NODE, [](FirebaseStream stream) {
    String eventType = stream.getEvent();
    eventType.toLowerCase();
    
    Serial.printf("event: %sn", eventType.c_str());
    if (eventType == "put") {
      String path = stream.getPath();  // Se obtiene la ruta del cambio

      if (path == WIFI_NODE) {  // Si el cambio se realizó en el nodo entero de las credenciales wifi
        JsonObject& streamData = stream.getData();
        String ssid = streamData["ssid"].as<String>();
        String password = streamData["password"].as<String>();
        Serial.printf("WIFI NODE - SSID: %sn", ssid.c_str());
        Serial.printf("WIFI NODE - PASSWORD: %sn", password.c_str());
        preferences.begin(PREFERENCES_APP, false);
        preferences.putString("SSID", ssid);
        preferences.putString("PASSWORD", password);
        preferences.end();
      }
      else if (path == SSID_NODE) {  // Si el cambio se realizó en el nombre de la red
        String streamData = stream.getDataString();
        Serial.printf("New SSID: %sn", streamData.c_str());
        preferences.begin(PREFERENCES_APP, false);
        preferences.putString("SSID", streamData);
        preferences.end();
      }
      else if (path == PASSWORD_NODE) {  // Si el cambio se realizó en la contraseña
        String streamData = stream.getDataString();
        Serial.printf("New Password: %sn", streamData.c_str());
        preferences.begin(PREFERENCES_APP, false);
        preferences.putString("PASSWORD", streamData);
        preferences.end();
      }
      else if (path == REBOOT_NODE) {  // Si el cambio se realizó en el nodo de reinicio
        bool streamData = stream.getDataBool();
        Serial.printf("Need Reboot?: %sn", streamData ? "true" : "false");
        if (streamData) {  // Se reinicia el dispositivo si se ha indicado que es necesario
          String route = SETTINGS_NODE;
          route = route + REBOOT_NODE;
          Firebase.setBool(route, false);
          ESP.restart();
        }
      }
      
      // Se muestra por consola la ruta donde se realizaron los cambios
      Serial.printf("Path: %sn", path.c_str());
    }
  });
}

void loop() {
  // Se comprueba el estado del botón constantemente
  resetButton.check();
}

Prueba

Una vez tengamos el código subido al ESP32 nos dirigiremos a Firebase para cambiar los valores de nuestra red. Cabe destacar que para esta prueba utilizaré la red Wifi de mi casa y la de mi móvil.

Descarga