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...");
  }
}

45 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
    6. También estoy intentando lo mismo, si alguien ya dio con la solución podrían por favor compartirlo.

      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
    3. Buenas tardes, quisiera hacer algo parecido a fredi, al iniciar que que ejecute el servidor y espere por un tiempo una conexión , si no la hay , que salda e inicie como cliente. no se como hacerlo, desde ya muchas 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
  14. buenas tardes: me pareció excelente este tutorial ya que es exactamente lo que estaba buscando, de paso me suscribí al canal y me ví unos cuantos videos.
    Me gustaría que subas algún video sobre como utilizar los pines rx y tx del ESP8266 01 como entradas/salidas digitales y de ser posible si tenes info de alimentar este mismo ESP con una o dos pilas cr2032. Te pido esto ya que estoy armando un proyecto donde el tamaño reducido es muy importante.
    Muchas gracias y saludos!

    ResponderEliminar
  15. EXCELENTES TUS TUTORIALES, FELICITACIONES AMIGO. SALUDOS DESDE PERU.

    ResponderEliminar
  16. Muy bueno e interesante tu tutorial. Te felicito!!.
    Saludos,
    Pedro de Chile

    ResponderEliminar
  17. Estimado, puedo usar este mismo concepto, para que ademas de el usuario y contraseña del wifi, me guarde una serie de otros valores? Por ejemplo necesito definir temperatura normal, diferencia de temperatura, distancia (en cm) que signifique de debo rellenar agua, pero configurarlos una vez (por el usuario) y que luego los pueda usar dentro del codigo del software.
    Sera posible, hacer esto del softap y el webserver, pero sin boton, sino que detecte si ya estan las configuraciones?

    ResponderEliminar
    Respuestas
    1. Todo lo que decís es posible. Pero te recomiendo usar el SPIFFS. http://www.sinaptec.alomar.com.ar/2017/06/tutorial-8-esp8266-spiffs-json.html

      El botón te recomiendo usarlo,¿sino como vas a hacer para volver a configurarlo?

      Saludos.

      Eliminar
  18. Hola, ¿cómo hago una aplicación de teléfono para conectar el módulo esp a internet?

    ResponderEliminar
  19. 'guardar_conf' was not declared in this scope

    ResponderEliminar
  20. Muy pero muy buen aporte, te agradezco el compartir tus conocimientos, he usado tu ejemplo añadiendo en el código botón la función reset(para no hacerlo manual con el botón del esp o pulsando dos botones como el ejemplo) pero antes uso spiffs para almacenar valores y al reiniciar el esp lea los valores y de este modo sepa el esp de que modo iniciar, quizás suene algo enredado pero es útil simplificar cosas para un usuario final, en fin, un enorme saludo desde México y de nuevo gracias por estos aportes que hasta hoy siguen vigentes, saludos.

    ResponderEliminar
  21. Hola eres un capo... muy bueno el tutorial, tengo una duda...
    cuando entramos el modo configuración, el MCU actúa como un access point verdad, entonces, la ip que nos muestra en el puerto serial se mantiene constante cada vez que entramos al modo configuración o va cambiando? por que si nos tendríamos que conectarnos con USB cada vez que entremos a dicho modo para visualizar la IP, ya que no seria constante, una ayuda en esa parte por favor.

    ResponderEliminar
  22. Buenas tardes, muchas gracias por compartir los conocimientos con nosotros!! y con el mundo.
    Usted sabe que sigo todos los pasos y anda todo muy bien, configuro la red wifi pero luego al reiniciar la placa no logro conectarme, sabe que puede ser?

    ResponderEliminar
  23. hola excelente tutorial soy nuevo programando en arduino estoy haciendo un proyecto con un esp8266 y un receptor RF433MHZ estoy ocupando tu codigo quiero guardar los valores del control que en su ejempo serial 8854245 y quiero agregar una descripcion al control como 22d guardarlos en la eeprom y despues cuando active el control ver si se encuentra guardado y me arroje el valor 22d me podrias apoyar con la duda

    ResponderEliminar
  24. Excelente, muy útil. Con muy poco esfuerzo le agregue para guardar el mqtt broker, port user, password, etc.
    Primero me mori probando con las librerias wifimanager y el uso de spiffs que en mi caso no se justifica porque solamente lo voy a usar para inyectar a todos el mismo codigo y configurarlos una sola vez en toda la vida del producto.
    Si algun principiante lee esto ni lo dude en usar este método.

    ResponderEliminar
  25. Este comentario ha sido eliminado por el autor.

    ResponderEliminar