martes, 20 de junio de 2017

Tutorial #7 ESP8266 - Guardar Ssid y Password en la Eeprom

En este tutorial de IOT (internet de las cosas), vamos a ver un ejemplo de cómo guardar (y leer) el ssid y el password en forma no volátil desde la memoria eeprom interna del ESP8266. Para eso nos valemos de un pulsador que usamos para iniciar el micro en el modo SoftAP (acces point) donde montamos un webserver, permitiéndonos configurar todos los datos que necesitamos desde el navegador de un smartphone.


Código Fuente:

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <EEPROM.h>

//-------------------VARIABLES GLOBALES--------------------------
int contconexion = 0;
unsigned long previousMillis = 0;

char ssid[50];      
char pass[50];

const char *ssidConf = "tutorial";
const char *passConf = "12345678";

String mensaje = "";

//-----------CODIGO HTML PAGINA DE CONFIGURACION---------------
String pagina = "<!DOCTYPE html>"
"<html>"
"<head>"
"<title>Tutorial Eeprom</title>"
"<meta charset='UTF-8'>"
"</head>"
"<body>"
"</form>"
"<form action='guardar_conf' method='get'>"
"SSID:<br><br>"
"<input class='input1' name='ssid' type='text'><br>"
"PASSWORD:<br><br>"
"<input class='input1' name='pass' type='password'><br><br>"
"<input class='boton' type='submit' value='GUARDAR'/><br><br>"
"</form>"
"<a href='escanear'><button class='boton'>ESCANEAR</button></a><br><br>";

String paginafin = "</body>"
"</html>";

//------------------------SETUP WIFI-----------------------------
void setup_wifi() {
// Conexión WIFI
  WiFi.mode(WIFI_STA); //para que no inicie el SoftAP en el modo normal
  WiFi.begin(ssid, pass);
  while (WiFi.status() != WL_CONNECTED and contconexion <50) { //Cuenta hasta 50 si no se puede conectar lo cancela
    ++contconexion;
    delay(250);
    Serial.print(".");
    digitalWrite(13, HIGH);
    delay(250);
    digitalWrite(13, LOW);
  }
  if (contconexion <50) {   
      Serial.println("");
      Serial.println("WiFi conectado");
      Serial.println(WiFi.localIP());
      digitalWrite(13, HIGH);  
  }
  else { 
      Serial.println("");
      Serial.println("Error de conexion");
      digitalWrite(13, LOW);
  }
}

//--------------------------------------------------------------
WiFiClient espClient;
ESP8266WebServer server(80);
//--------------------------------------------------------------

//-------------------PAGINA DE CONFIGURACION--------------------
void paginaconf() {
  server.send(200, "text/html", pagina + mensaje + paginafin); 
}

//--------------------MODO_CONFIGURACION------------------------
void modoconf() {
   
  delay(100);
  digitalWrite(13, HIGH);
  delay(100);
  digitalWrite(13, LOW);
  delay(100);
  digitalWrite(13, HIGH);
  delay(100);
  digitalWrite(13, LOW);

  WiFi.softAP(ssidConf, passConf);
  IPAddress myIP = WiFi.softAPIP(); 
  Serial.print("IP del acces point: ");
  Serial.println(myIP);
  Serial.println("WebServer iniciado...");

  server.on("/", paginaconf); //esta es la pagina de configuracion

  server.on("/guardar_conf", guardar_conf); //Graba en la eeprom la configuracion

  server.on("/escanear", escanear); //Escanean las redes wifi disponibles
  
  server.begin();

  while (true) {
      server.handleClient();
  }
}

//---------------------GUARDAR CONFIGURACION-------------------------
void guardar_conf() {
  
  Serial.println(server.arg("ssid"));//Recibimos los valores que envia por GET el formulario web
  grabar(0,server.arg("ssid"));
  Serial.println(server.arg("pass"));
  grabar(50,server.arg("pass"));

  mensaje = "Configuracion Guardada...";
  paginaconf();
}

//----------------Función para grabar en la EEPROM-------------------
void grabar(int addr, String a) {
  int tamano = a.length(); 
  char inchar[50]; 
  a.toCharArray(inchar, tamano+1);
  for (int i = 0; i < tamano; i++) {
    EEPROM.write(addr+i, inchar[i]);
  }
  for (int i = tamano; i < 50; i++) {
    EEPROM.write(addr+i, 255);
  }
  EEPROM.commit();
}

//-----------------Función para leer la EEPROM------------------------
String leer(int addr) {
   byte lectura;
   String strlectura;
   for (int i = addr; i < addr+50; i++) {
      lectura = EEPROM.read(i);
      if (lectura != 255) {
        strlectura += (char)lectura;
      }
   }
   return strlectura;
}

//---------------------------ESCANEAR----------------------------
void escanear() {  
  int n = WiFi.scanNetworks(); //devuelve el número de redes encontradas
  Serial.println("escaneo terminado");
  if (n == 0) { //si no encuentra ninguna red
    Serial.println("no se encontraron redes");
    mensaje = "no se encontraron redes";
  }  
  else
  {
    Serial.print(n);
    Serial.println(" redes encontradas");
    mensaje = "";
    for (int i = 0; i < n; ++i)
    {
      // agrega al STRING "mensaje" la información de las redes encontradas 
      mensaje = (mensaje) + "<p>" + String(i + 1) + ": " + WiFi.SSID(i) + " (" + WiFi.RSSI(i) + ") Ch: " + WiFi.channel(i) + " Enc: " + WiFi.encryptionType(i) + " </p>\r\n";
      //WiFi.encryptionType 5:WEP 2:WPA/PSK 4:WPA2/PSK 7:open network 8:WPA/WPA2/PSK
      delay(10);
    }
    Serial.println(mensaje);
    paginaconf();
  }
}

//------------------------SETUP-----------------------------
void setup() {

  pinMode(13, OUTPUT); // D7 
  
  // Inicia Serial
  Serial.begin(115200);
  Serial.println("");

  EEPROM.begin(512);

  pinMode(14, INPUT);  //D5
  if (digitalRead(14) == 0) {
    modoconf();
  }

  leer(0).toCharArray(ssid, 50);
  leer(50).toCharArray(pass, 50);

  setup_wifi();
}

//--------------------------LOOP--------------------------------
void loop() {

  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= 5000) { //envia la temperatura cada 5 segundos
    previousMillis = currentMillis;
    Serial.println("Funcionado...");
  }
}

26 comentarios:

  1. Genial! esta muy bueno el tutorial.. Es muy util... Te quiero hacer una consulta... A esto le quiero agregar el codigo que uso normalmente para recibir comandos http pero no me permite

    WiFiClient client = server.available();
    if (!client) {
    return;
    }
    Serial.println("new client"); // Wait until the client sends some data
    while(!client.available()){
    delay(1);
    }
    String req = client.readStringUntil('\r'); // Read the first line of the request
    Serial.println(req);
    client.flush();

    if (req.indexOf("/D3/0") != -1) // Match the request
    val1 = 0;
    else if (req.indexOf("/D3/1") != -1)
    val1 = 1;

    ese es el codigo que quiero agregar en el loop... pero me sale el siguiente error...


    C:\Users\xxx\Documents\Arduino\Tecla___config_wifi\Tecla___config_wifi.ino: In function 'void loop()':

    Tecla___config_wifi:260: error: 'class ESP8266WebServer' has no member named 'available'

    WiFiClient client = server.available();

    ^
    exit status 1
    'class ESP8266WebServer' has no member named 'available'

    Podras darme una mano? Gracias

    ResponderEliminar
    Respuestas
    1. Tendría que ver todo el código para estar seguro: pero me parece que te falta declarar: ESP8266WebServer server(80);

      O eliminar la línea: WiFiClient espClient;

      Eliminar
    2. Lo que quiero hacer es agregar al codigo la posibilidad de prender y apagar un led una vez realizada la configuracion. Si llegas a tener idea de como hacerlo te agradeceria muchisimo

      Eliminar
    3. Lo agregas al final de la función: void guardar_conf()
      Te recomiendo el tutorial 8, usar SPIFFS es mejor que usar la EEPROM

      Eliminar
    4. El problema no parece estar en esa parte. Está en la linea 260. Saludos.

      Eliminar
    5. Hola buenas, muy bueno el tutorial, yo estoy intentando hacer lo mismo que el amigo ian carrasco, y tengo el mismo error que el, y quisiera saber si pudieron solucionar el problema? Muchísimas gracias, saludos

      Eliminar
  2. Buenas tardes, super bueno el tutorial, una pregunta porque es mejor spiffs que la eeprom

    ResponderEliminar
  3. Para mi gusto es mejor SPIFFS, fijate que hice un tutorial al respecto. Saludos.

    ResponderEliminar
  4. Buenas tardes, si no se disponer de un interruptor... ¿seria posible disponer del web server que permite introducir el SSID y de loop normal?, seria algo asi como una alternativa puramente software...

    ResponderEliminar
    Respuestas
    1. Si, tenés que iniciar tanto en modo softAP como station es decir: WIFI_AP_STA. Pero no te lo recomiendo, para mi es mejor no usar el modo softAP al menos que se lo necesite, para que sea más estable y consuma menos energía.

      Eliminar
    2. Pero, se puede mantener el server iniciado mientra en el bucle loop normal, se ejecutan otra acciones? por mas que lo pienso no encuentro manera

      Eliminar
    3. Si se puede, eso correr en otro hilo.Tenés que poner dentro del loop:

      server.handleClient();

      Eliminar
  5. Buenas tardes, como hago para agregarle la conexión a un servidor remoto para actualizar datos en una base de datos? He intentado de mil formas pero no lo he logrado.

    Agradecería mucho tu ayuda! Gracias

    ResponderEliminar
    Respuestas
    1. Hice un tutorail al respecto, espero que te sirva. http://www.sinaptec.alomar.com.ar/2017/07/tutorial-9-esp8266-mysql-php-en.html

      Eliminar
  6. Buena tarde, muchas gracias por el código, funciona perfectamente, solo le hice un pequeño cambio para evitar el uso del pulsador, y que mejor revise si está vacía la ssid (si falla la conexión después de los 50 segundos limpia la EEPROM), en este caso se pone automáticamente en modo SoftAP. Pero me queda una duda, habrá forma de reiniciar el dispositivo por código una vez configurada la red?? Saludos!!

    ResponderEliminar
    Respuestas
    1. si entendi bien, respecto a la adaptación a modo automático suprimiendo el pulsador y vaciado de EEPROM, si por alguna razón tenes que apagar el router o el AP al cual se conecta se perdería la configuración del modulo y si tenes varios tendrías que volver a configurar en cada uno el ssid y pass manualmente?

      Eliminar
  7. Muchas gracias! en base a este código y algunas adiciones tengo mi proyecto.
    En modo configuración grabo en la eeprom (Ssid, Pass y le asigno un nombre al host) como mi router le asigna ip por dhcp es dinamica pero siempre puedo acceder al modulo por el nombre.
    Desde la pc le puedo hacer "ping esp8266" y responde bien, desde el navegador "http://esp8266" o simplemente "esp8266/" y muestra el html, pero no asi desde android (solo se puede por la ip) al parecer android no trata los DNS y lo delega directamente a google.
    - Tanto las pc's como el modulo están en la misma red local
    - El modulo es el esp8266-12e y esta como ServerWeb (no AP)
    Ya probe varias opciones entre ellas mDNS pero sigo sin resolverlo. Si a alguien le sucedió lo mismo y pudo solucionarlo se agradece la ayuda! Saludos

    ResponderEliminar
    Respuestas
    1. Este comentario ha sido eliminado por el autor.

      Eliminar
    2. Tengo el mismo problema, pudiste resolverlo? Cómo asigno el nombre de Host al dispositivo?
      Desde ya Gracias.

      Eliminar
  8. Hola Ale, te envidio todo lo que sabes!
    estuve probando esto y es genial, lo unico que no se como hacer es para cambier estas lineas:

    const char *ssidConf = "tutorial";
    const char *passConf = "12345678";

    las cambien 1.000.000 de veces y me sigue tirando tutorial y la pass 12345678.

    me gustaria cambiarlas pero no me da ni pelota, no se si queda grabada en la eeprom o donde?

    reflashee el nodemcu mas de 10 veces y sigue quedando esas lineas

    y el ssdi y password de mi modem guardados.

    si podes ayudarme genial!

    Soy Pablo

    ResponderEliminar
    Respuestas
    1. Hola, proba borrando la eeprom y después volve a cargar tu código.

      #include

      void setup ( ) {
      for (int i = 0 ; i < EEPROM.length() ; i++) {
      EEPROM.write(i, 0);
      }
      }

      void loop ( ) { }

      Eliminar
  9. void WiFiServidor () {
    String User;
    String Pass;
    String message = "{\"mensaje\" = \"";
    message += "Credenciales";
    message += "\"}";
    server.send(200, "text/html",message);
    Serial.println("Configuring connection to server...");
    if (EEPROM.read(100) == '\0') {
    if (server.arg(0).length() != 0 ) {
    User = server.arg(0);
    UserServ = const_cast(User.c_str());
    Pass = server.arg(1);
    PassServ = const_cast(Pass.c_str());
    server.send(200, "text/html",message);
    Serial.println("Leyo credenciales");
    WiFiServerConnect ();
    Serial.println("Despues de WiFiServerConnect");
    } else {
    Serial.println("Esperando credenciales");
    }
    } else {
    Serial.println("entro a leer credenciales guardadas");
    server.send(200, "text/html",message);
    leer(100).toCharArray(UserServ, 50);
    leer(150).toCharArray(PassServ, 50);
    WiFiServerConnect ();
    }
    return;
    }

    void WiFiAP () {
    if (EEPROM.read(100) == '\0') {
    Serial.print("Configuring access point...");
    Serial.println();
    if (EEPROM.read(50) != '\0') {
    leer(50).toCharArray(APpass, 50);
    WiFi.disconnect();
    WiFi.softAP(ssid,APpass);
    Serial.println("wifi AP con contrasena seteado");
    } else {
    WiFi.disconnect();
    WiFi.softAP(ssid);
    RSTButtom = 1;
    Serial.println("wifi AP sin contrasena seteado");
    }
    IPAddress myIP = WiFi.softAPIP();
    Serial.print("Server MAC address: ");
    Serial.println(WiFi.softAPmacAddress());
    Serial.print("AP IP address: ");
    Serial.println(myIP);
    server.begin();
    Serial.println("HTTP server started");
    } else {
    Serial.println("hay algo guardado en user server");
    WiFiServidor ();
    }
    }

    Alguien podria ayudarme, no se porque se va a wdt reset al terminar la funcion WiFiServidor, intente varias maneras pero todas terminaron con el mismo resultado, Ademas de que cuando entra a esta parte leer(100).toCharArray(UserServ, 50);
    leer(150).toCharArray(PassServ, 50); no logra leer correctamente

    ResponderEliminar
  10. Hola buenas tardes, muchas gracias por el tutorial esta perfecto y muy claro, habia estado buscando informacion al respecto pero estoy es lo mas claro que he encontrado.

    Te comento, lo estoy implemmentando con un D1 de WEMOS, en este momento no tengo protoboard y me esta constando acceder al SETUP por el tema del pulsador. ya conecte uno al GPIO14 y a 3.3V, presione y al mismo tiempo reinicie el D1 pero no consigo accesar, tienes alguna idea de como pudeo hacer que funcione con el D1?
    te lo agradecere mucho

    ResponderEliminar
  11. Buenas, gracias por compartir!!

    tengo el mismo problema que "Pescadito".

    al cambiar el valor de las siguientes variables:

    const char *ssidConf = "tutorial";
    const char *passConf = "12345678";

    y cargar el sketch en modo:

    erase flash "Sketch + Wifi Sentinngs"

    Al reiniciar el ESP8266 el nombre del servidor wifi me cambia a

    "ESP_0B0B03" y sin Pass alguno

    He borrado EEPROM, SPiFFS, Y Restaurado de fábrica antes de cargar de nuevo el Sketch, pero no lo consigo.

    Gracias de nuevo

    ResponderEliminar
  12. Hola saludos, alguien ha podido evitar colocar el pulsador para entrar en modo AP. Gracias de antemano por cualquier colaboración para hacer los cambios en el código actual.

    ResponderEliminar
  13. hola Saludos, excelente tutorial!!, me gustaría poder modificar la ip del NODEMCU, desde una pagina web, no sólo por DHCP, muchas gracias.

    ResponderEliminar