Neste tutorial vamos mostrar como conectar seu ESP8266 a qualquer rede Wi-Fi de sua escolha sem a necessidade de alterar seu código cada vez que precisar utilizar uma rede diferente. Isso será realizado através da criação de um Ponto de Acesso que exibirá um formulário HTML ao usuário conectado à placa, seja por um computador ou smartphone.
Após a inserção das credenciais de sua rede, a placa ganhará acesso à internet e salvará os dados em sua memória interna para que não haja necessidade de o usuário digitar a senha da rede toda vez que ligar a placa.
Esta função pode ser reaproveitada para tentar uma reconexão à rede caso a placa perca acesso à internet.
Componentes necessários
Para seguir este tutorial, você precisará dos seguintes itens:
A programação do NodeMCU será feita através da IDE do Arduino. Caso não saiba como configurar a IDE para programar módulos baseados no ESP8266, recomendamos primeiro ler o seguinte tutorial:
ESP8266 – Como Programar o NodeMCU Através da Arduino IDE
ESP8266 Como Access Point
Vamos iniciar nosso projeto criando um Ponto de Acesso (AP) para nosso NodeMCU. Isso significa que a placa possuirá uma rede própria à qual o usuário poderá se conectar.
Começaremos com uma chamada à biblioteca de Wi-Fi do ESP8266:
1 | #include <ESP8266WiFi.h> |
Agora vamos definir um nome (SSID) e uma senha para nosso AP. Crie uma variável global para cada dado para facilitar sua alteração caso julgue necessário no futuro:
1 2 | const char *ssid = "ESP8266 Access Point"; const char *password = "smartkitsAP"; |
Os valores das strings acima podem ser alterados para qualquer valor de sua escolha.
Dentro do setup(), vamos inicializar a comunicação serial de nosso NodeMCU com o computador para que possamos imprimir dados da rede no monitor serial. Usaremos um baud rate (velocidade de transmissão de informação) de 115200:
1 | Serial.begin(115200); |
Ainda no setup(), inicialize o AP utilizando as variáveis criadas anteriormente:
1 | WiFi.softAP(ssid, password); |
E imprima o IP de seu AP:
1 | Serial.println(WiFi.softAPIP()); |
É importante saber qual endereço de IP sua placa está usando, pois você se comunicará com a placa através deste endereço. Por padrão, o ESP8266 possui o IP 192.168.4.1, mas é possível alterar este valor através de funções de configuração de AP.
Imagem 1 – IP e nome do AP
ESP8266 Como Web Server
Após criarmos o AP de nosso ESP8266, precisamos que ele seja capaz de receber as credenciais da rede a qual você quer conectar a placa para que ela tenha acesso à internet. Logo, ele passará a agir como um servidor Web que receberá requisições do usuário, realizará um processamento delas e enviará respostas a partir de funções que criaremos em nosso código.
No topo de seu código, adicione a chamada:
1 | #include <ESP8266WebServer.h> |
Esta biblioteca é utilizada para criar servidores HTTP síncronos, onde o usuário vai interagir com uma interface Web através de um navegador, o navegador enviará requisições ao servidor e o servidor vai então responder exibindo no navegador uma nova página ao usuário. Este tipo de servidor suporta um único usuário por vez.
Antes de seguirmos para as configurações do servidor, devemos instanciar um objeto da classe como variável global, passando como argumento a porta HTTP padrão de número 80:
1 | ESP8266WebServer server(80); |
Dentro da função setup(), vamos agora configurar o servidor para receber requisições do usuário e direcioná-las a funções que criaremos mais à frente. Isso é realizado através do método on(URI, handlerFunction) do objeto que acabamos de criar. Este método possui dois parâmetros, sendo o primeiro a URI (parte do endereço de link que ficará após o IP do servidor) e o segundo o nome da função associada ao endereço.
A primeira função que criaremos ficará responsável pela raiz de nosso servidor. Será a primeira operação realizada quando o usuário digitar o IP da placa no navegador. Chamaremos esta função de handleRoot():
1 | server.on("/", handleRoot); |
A segunda função será responsável por conectar a placa a uma rede Wi-Fi a partir dos dados digitados pelo usuário em um formulário que criaremos mais à frente. Vamos chamá-la de handleForm() e sua URI será /action_new_connection:
1 | server.on("/action_new_connection", handleForm); |
Em seguida, teremos uma função para que a placa possa se conectar a partir de informações salvas anteriormente em sua memória interna. Ele se chamará connectEeprom() e sua URI será /action_previous_connection:
1 | server.on("/action_previous_connection", connectEeprom); |
Com todas as funções do servidor declaradas, vamos inicia-lo:
1 | server.begin(); |
Por último, agora dentro da função loop(), devemos chamar o método handleClient() para que seja possível processar as requisições do usuário:
1 | server.handleClient(); |
HTML
Para exibir uma interface de conexão ao usuário, vamos precisar criar uma página HTML. No NodeMCU podemos fazer isso de duas maneiras: a primeira é fazendo upload de um arquivo HTML para a placa (o que exige configuração adicional da IDE do Arduino) e a segunda é criando uma String que receberá todo o código de sua página HTML. Neste tutorial vamos optar pela segunda.
Nossa interface terá apenas um formulário simples através do qual o usuário enviará as credenciais da rede Wi-Fi para o ESP8266. Ele possuirá três estruturas básicas:
- Um <select>, uma espécie de dropdown menu que exibirá todas as redes ao alcance do NodeMCU;
- Um <input> do tipo password, que nada mais é do que uma caixa de texto onde as informações digitadas pelo usuário são convertidas em asteriscos;
- Dois botões, sendo um deles um <input> do tipo button e outro um <button> contendo um link.
Ao clicar no <input> – de nome “Conectar” – será chamada uma função JavaScript que fará a validação dos dados antes de enviá-los para o ESP8266. Caso o usuário deixe um dos campos vazios, será exibida uma mensagem de erro como mostrada na imagem abaixo:
Imagem 2 – Caso um dos campos esteja vazio, é exibida uma mensagem de erro
O segundo botão – uma tag <button> – visto na imagem servirá para conectar o NodeMCU a partir de dados previamente salvos em sua memória interna. Veremos mais à frente como ele funcionará.
Nosso HTML será inserido no código através de uma variável global do tipo vetor de caracteres que será armazenada e lida diretamente da memória de programa (Flash) do NodeMCU em vez da SRAM, onde seria normalmente armazenada. Isso é indicado pelo modificador PROGMEM. Faremos isso para que exista sempre uma cópia inalterada da página disponível no nosso programa sem ocupar o espaço dedicado à sua execução.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | const char MAIN_page[] PROGMEM = R"=====( <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>HTML Form ESP8266 - SmartKits</title> <style> body {color: #434343; font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; font-size: 14px; background-color: #eeeeee; margin-top: 100px;} .container {margin: 0 auto; max-width: 400px; padding: 30px; box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); background-color: #ffffff; border-radius: 10px;} h2 {text-align: center; margin-bottom: 20px; margin-top: 0px; color: #0ee6b1; font-size: 35px;} #titleGreen {color: #00E1AA;} #titleBlack {color: #000000;} h3 {text-align: center; margin-bottom: 40px; margin-top: 0px; color: #336859; font-size: 35px;} form .field-group {box-sizing: border-box; clear: both; padding: 4px 0; position: relative; margin: 1px 0; width: 100%;} .text-field {font-size: 15px; margin-bottom: 4%; -webkit-appearance: none; display: block; background: #fafafa; color: #636363; width: 100%; padding: 15px 0px 15px 0px; text-indent: 10px; border-radius: 5px; border: 1px solid #e6e6e6; background-color: transparent;} .text-field:focus {border-color: #00bcd4; outline: 0;} .button-container {box-sizing: border-box; clear: both; margin: 1px 0 0; padding: 4px 0; position: relative; width: 100%;} .button {background: #00E1AA; border: none; border-radius: 5px; color: #ffffff; cursor: pointer; display: block; font-weight: bold; font-size: 16px; padding: 15px 0; text-align: center; text-transform: uppercase; width: 100%; -webkit-transition: background 250ms ease; -moz-transition: background 250ms ease; -o-transition: background 250ms ease; transition: background 250ms ease;} p {text-align: center; text-decoration: none; color: #87c1d3; font-size: 18px;} a {text-decoration: none; color: #ffffff; margin-top: 0%;} #status {text-align: center; text-decoration: none; color: #336859; font-size: 14px;} </style> <script> function validateForm() { var ssid = document.forms["myForm"]["ssid"].value; var password = document.forms["myForm"]["password"].value; var status = document.getElementById("statusDiv"); if (ssid == "" && password == "") { status.innerHTML = "<p id='status' style='color:red;'>Insira SSID e senha.</p>"; return false; } else if (ssid == "") { status.innerHTML = "<p id='status' style='color:red;'>Insira o SSID.</p>"; return false; } else if (password == "") { status.innerHTML = "<p id='status' style='color:red;'>Insira a senha.</p>"; return false; } else { status.innerHTML = "<p id='status'>Conectando...</p>"; return true; } } </script> </head> <body> <div class="container"> <h2><span id="titleGreen">smart</span><span id="titleBlack">kits</span></h2> <h3>Conexão ESP8266</h3> <form name="myForm" action="/action_new_connection" onsubmit="return validateForm()" method="post"> <div class="field-group"> <select class='text-field' name='ssid'></select> </div> <br> <div class="field-group"> <input class="text-field" type="password" name="password" length=64 placeholder="Password"> </div> <br> <div id="statusDiv"> <br><br> </div> <div class="button-container"> <input class="button" type="submit" value="Conectar"> </div> </form> <p>OU</p> <div class="button-container"> <button class="button" type="button" onclick="window.location.href='/action_previous_connection'">Conectar à última rede utilizada</button> </div> </div> </body> </html> )====="; |
Note que nosso código CSS está embutido na tag <head> de nosso HTML para melhorar a aparência da página.
Processando Requisições no ESP8266
Vamos agora criar as funções handleRoot() e handleForm() que definimos anteriormente para o nosso web server.
O propósito desta primeira será apenas exibir nossa interface de conexão para o usuário. Porém, antes de fazer isso devemos buscar por todas as redes Wi-Fi ao alcance do NodeMCU para que nosso <select> não fique vazio no HTML. Será então criada uma função que chamaremos de listSSID() para realizar essa tarefa.
Listando Redes Wi-Fi Próximas
Começaremos recuperando nosso HTML da memória Flash, pois vamos precisar edita-lo:
1 | String index = (const __FlashStringHelper*) MAIN_page; |
Em seguida, chamaremos a função WiFi.scanNetworks() que nos retornará a quantidade de redes disponíveis. Vamos armazenar este valor em uma variável do tipo int:
1 | int n = WiFi.scanNetworks(); |
Esta operação também armazena dentro do objeto WiFi informações como SSID e potência de sinal de todas as redes encontradas.
Agora que temos a quantidade de redes próximas e suas informações, podemos chamar o método WiFi.SSID() dentro de um laço de repetição para adicionar o nome de todas as redes ao nosso HTML. Este método recebe como argumento o índice de cada uma das redes dentro de uma lista criada dentro do objeto.
Aplicaremos o método replace() na String que contém nosso HTML. Para preencher o <select> com os SSIDs das redes.
Nossa função listSSID() ficará assim:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | String listSSID() { String index = (const __FlashStringHelper*) MAIN_page; //Lê a página HTML String networks = ""; int n = WiFi.scanNetworks(); Serial.println("Scan done."); if (n == 0) { Serial.println("Nenhuma rede foi encontrada."); index.replace("<select class='text-field' name='ssid'></select>", "<select class='text-field' name='ssid'><option value='' disabled selected>Nenhuma rede encontrada</option></select>"); index.replace("<br><br>", "<p id='status' style='color:red;'>Rede não encontrada.</p>"); return index; } else { Serial.printf("%d networks found.\n", n); networks += "<select class='text-field' name='ssid'><option value='' disabled selected>SSID</option>"; // Pega o SSID de cada rede encontrada for (int i = 0; i < n; ++i) { networks += "<option value='" + WiFi.SSID(i) + "'>" + WiFi.SSID(i) + "</option>"; } networks += "</select>"; } index.replace("<select class='text-field' name='ssid'></select>", networks); return index; } |
Dentro da função handleRoot(), faremos uma chamada à listSSID() armazenando seu resultado em uma String que será então enviada para o usuário. Para isso, é usado o método send() de nosso servidor. Ele possui três parâmetros: status message, tipo de conteúdo e por último o conteúdo em si – no caso, nossa String.
1 2 3 4 | void handleRoot() { String index = listSSID(); //Recebe página HTML editada server.send(200, "text/html", index); //Envia página web ao usuário } |
Recebendo Dados do Formulário
Agora vamos criar a função handleForm() que processará os dados informados pelo usuário.
Voltando ao nosso HTML, vemos que tanto nosso <select> quanto nosso <input> do tipo “password” possuem atributos chamados “name” (“nome” em inglês).
1 | <select class='text-field' name='ssid'></select> |
1 | <input class="text-field" type="password" name="password" length=64 placeholder="Password"> |
Estes “nomes” são utilizados para identificar cada campo do formulário para que seus valores possam ser acessados pelo NodeMCU através do método arg() de nosso servidor. Vamos armazenar estes valores em variáveis, nos certificar de que os campos não estão vazios e então enviar os dados para uma nova função que chamaremos de connectToWiFi().
1 2 3 4 5 6 7 8 | void handleForm() { String ssidWifi = server.arg("ssid"); String passwordWifi = server.arg("password"); if(!ssidWifi.equals("") && !passwordWifi.equals("")) { connectToWiFi(ssidWifi, passwordWifi); } } |
Antes de seguirmos para esta função, vamos criar no topo do código – abaixo da inclusão de bibliotecas – uma macro que corresponderá ao LED embutido na placa, conectado ao pino D4:
1 | #define ONBOARD_LED D4 |
Conectando à Rede Wi-Fi
Dentro de connectToWiFi(), passaremos as credenciais da rede para o método WiFi.begin(), que é responsável por conectar o NodeMCU à rede Wi-Fi.
Vamos então criar um laço de repetição que vai esperar pelo resultado desta operação, verificado pelo método WiFi.status(). Caso a conexão ocorra normalmente, vamos acender o LED, exibir o endereço IP do dispositivo no monitor serial e enviar uma nova página ao usuário contendo uma mensagem de sucesso. Caso contrário, será enviada uma página com uma mensagem de erro.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | void connectToWiFi(String ssidWifi, String passwordWifi) { int count = 0; WiFi.begin(ssidWifi.c_str(), passwordWifi.c_str()); //Conecta ao seu roteador Wi-Fi // Espera pela conexão while ( count < 15 ) { delay(500); if (WiFi.status() == WL_CONNECTED) { salvarEeprom(ssidWifi, passwordWifi); //Se a conexão acontecer, mostre o endereço IP no monitor serial Serial.println("Conectado ao WiFi"); Serial.print("Endereço IP: "); Serial.println(WiFi.localIP()); //Endereço IP atribuído a seu ESP digitalWrite(ONBOARD_LED, LOW); //Ativa o LED da placa String responsePage = (const __FlashStringHelper*) MAIN_page; //Lê conteúdo do HTML responsePage.replace("<br><br>", "<p id='status'>Conectado!</p>"); //Aciona mensagem de sucesso server.send(200, "text/html", responsePage); //Envia a página web return; } else if (WiFi.status() == WL_CONNECT_FAILED) { String responsePage = (const __FlashStringHelper*) MAIN_page; //Lê conteúdo do HTML responsePage.replace("<br><br>", "<p id='status' style='color:red;'>Falha na conexão.</p>"); //Aciona mensagem de erro server.send(200, "text/html", responsePage); //Envia a página web } count++; } //Caso ocorra algum erro não detectado dentro do while String responsePage = (const __FlashStringHelper*) MAIN_page; //Lê conteúdo do HTML responsePage.replace("<br><br>", "<p id='status' style='color:red;'>Erro.</p>"); server.send(200, "text/html", responsePage); return; } |
Imagem 3 – Mensagem de sucesso
Imagem 4 – Mensagem de erro
Caso tenha problemas para receber o resultado da operação (timeout), teste diminuir o número de iterações do laço de repetição.
EEPROM do ESP8266
A EEPROM (Electrically-Erasable Programmable Read-Only Memory) é um tipo de memória não-volátil utilizada para armazenar pequenas quantidades de dados que não são perdidos após o desligamento do dispositivo. Ela é muito útil para salvar dados de configuração, credenciamento de usuários ou leituras de sensores por exemplo. Neste projeto, vamos usá-la para salvar a última rede a qual o NodeMCU se conectou para que não seja necessário o usuário inserir novamente o nome da rede e sua senha toda vez que ligar o dispositivo. A reconexão poderá ser realizada através de um simples clique de botão.
O ESP8266 possui uma biblioteca padrão utilizada para a manipulação da EEPROM. Para iniciá-la, basta adicionar a seguinte chamada ao topo de seu código:
1 | #include <EEPROM.h> |
Salvando Dados
Para salvar as credenciais da rede Wi-Fi, vamos criar uma função chamada salvarEeprom() que terá como parâmetros o nome e a senha da rede:
1 | void salvarEeprom(String ssidWifi, String passwordWifi) {} |
Diferente das placas Arduino, o ESP8266 não possui uma memória EEPROM real. Ela é emulada utilizando uma sessão de sua memória Flash. Por isso, no ESP8266 é necessário informar durante a inicialização da EEPROM a quantidade de bytes que serão utilizados. Isso acontece através da função EEPROM.begin(size), que aceita valores de 4 a 4096 bytes. Para este tutorial, vamos usar 98 bytes:
1 | EEPROM.begin(98); |
A EEPROM pode ser interpretada como um array (vetor) de bytes, com cada posição dela sendo 1 byte. Isso significa que em cada posição de nossa EEPROM poderemos armazenar uma única variável do tipo char (caractere), totalizando 98 caracteres quando ocupada por completo.
Sabemos que 1 único byte também pode representar valores numéricos de 0 a 255, logo, vamos reservar as duas primeiras posições da EEPROM para armazenar dois valores numéricos que indicarão os tamanhos (quantidade de caracteres) do SSID e da senha. Estes valores serão usados para dizer ao programa quantas posições cada uma destas informações está ocupando na memória e possibilitar a leitura dos dados de forma independente.
Para salvar dados na EEPROM, precisamos chamar a função EEPROM.write(address, value), onde address é o índice (de 0 a 97) e value será nossa variável de tamanho. Como estamos usando objetos do tipo String para representar o SSID e a senha, podemos usar o método length() da classe String para pegar os seus tamanhos. Sendo assim, temos:
1 2 | EEPROM.write(0, ssidWifi.length()); EEPROM.write(1, passwordWifi.length()); |
Em seguida, criaremos dois laços de repetição para armazenar as credenciais na memória:
1 2 3 4 5 6 7 8 9 | for(int i = 2; i < 2+ssidWifi.length(); i++) { Serial.print(ssidWifi.charAt(i-2)); EEPROM.write(i, ssidWifi.charAt(i-2)); } for(int j = 2+ssidWifi.length(); j < 2+ssidWifi.length()+passwordWifi.length(); j++) { Serial.print(passwordWifi.charAt(j-2-ssidWifi.length())); EEPROM.write(j, passwordWifi.charAt(j-2-ssidWifi.length())); } |
Ao final, é necessário chamar a função EEPROM.commit() para assegurar que as mudanças sejam salvas na memória Flash e a função EEPROM.end() para encerrar as operações na EEPROM.
Com a biblioteca EEPROM padrão do ESP8266, a memória Flash é reprogramada toda vez que os dados da EEPROM são gravados, o que pode desgasta-la rapidamente. Para evitar operações desnecessárias na Flash, vamos adicionar uma condição para que os dados sejam salvos: a operação só será realizada caso os dados informados pelo usuário sejam diferentes daqueles já presentes na memória. Isso será verificado por uma função que chamaremos de compareEeprom().
Nossa função salvarEeprom() ficará assim:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | void salvarEeprom(String ssidWifi, String passwordWifi) { EEPROM.begin(98); // Tamanho da FLASH reservado para EEPROM. Pode ser de 4 a 4096 bytes if(!compareEeprom(ssidWifi, passwordWifi)) { EEPROM.write(0, ssidWifi.length()); for(int i = 2; i < 2+ssidWifi.length(); i++) { EEPROM.write(i, ssidWifi.charAt(i-2)); } EEPROM.write(1, passwordWifi.length()); for(int j = 2+ssidWifi.length(); j < 2+ssidWifi.length()+passwordWifi.length(); j++) { EEPROM.write(j, passwordWifi.charAt(j-2-ssidWifi.length())); } EEPROM.commit(); // Salva alterações na FLASH } EEPROM.end(); // Apaga a cópia da EEPROM salva na RAM } |
Comparando Dados da Memória EEPROM
Em nossa função compareEeprom() serão realizadas operações apenas de leitura, o que não prejudicará a integridade da Flash. Caso o valor lido seja igual ao recebido pelo usuário, a função retornará true e não salvará nada na EEPROM. Caso o valor lido seja diferente do recebido, a função retornará false e os dados presentes na EEPROM serão substituídos.
Para ler uma informação da memória, usamos a função EEPROM.read(address), onde address é a posição a ser lida. Lembrando que as duas primeiras posições são valores numéricos que não fazem parte das credenciais do Wi-Fi, a função compareEeprom() ficará assim:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | boolean compareEeprom(String ssidWifi, String passwordWifi) { int idLength = int(EEPROM.read(0)); // Tamanho do SSID armazenado (número de bytes) int passLength = int(EEPROM.read(1)); // Tamanho do Password armazenado (número de bytes) String id = ""; String pass = ""; for(int i = 2; i < 2+idLength; i++) { id = id + char(EEPROM.read(i)); } for(int j = 2+idLength; j < 2+idLength+passLength; j++) { pass = pass + char(EEPROM.read(j)); } if(id.equals(ssidWifi) && pass.equals(passwordWifi)) { // Se dados já presentes na memória. return true; } else { return false; } } |
Conexão Através da EEPROM
Nossa última função de acesso à EEPROM será a que realiza a conexão Wi-Fi a partir dos dados armazenados na memória, sendo chamada pelo usuário ao clicar no botão “Conectar à Última Rede Utilizada.” Ela vai se assemelhar bastante à função compareEeprom(), mas ao invés de retornarmos um valor booleano, ao final da função vamos fazer uma chamada à função connectToWiFi() passando como parâmetros os valores de SSID e senha lidos da EEPROM. Chamaremos essa função de connectEeprom():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | void connectEeprom() { EEPROM.begin(98); // Tamanho da FLASH reservado para EEPROM. Pode ser de 4 a 4096 bytes int ssidSize = (int)EEPROM.read(0); // Tamanho do SSID armazenado (número de bytes) int passwordSize = (int)EEPROM.read(1); // Tamanho do Password armazenado (número de bytes) String ssidWifi = ""; String passwordWifi = ""; for(int i = 2; i < 2+ssidSize; i++) { ssidWifi.concat(char(EEPROM.read(i))); } for(int j = 2+ssidSize; j < 2+ssidSize+passwordSize; j++) { passwordWifi.concat(char(EEPROM.read(j))); } EEPROM.end(); // Apaga a cópia da EEPROM salva na RAM connectToWiFi(ssidWifi, passwordWifi); } |
Código do ESP8266
Aqui está o código completo pronto para ser carregado para sua placa:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 | #include <ESP8266WiFi.h> #include <ESP8266WebServer.h> #include <EEPROM.h> #define ONBOARD_LED D4 //Led embutido const char MAIN_page[] PROGMEM = R"=====( <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>HTML Form ESP8266 - SmartKits</title> <style> body {color: #434343; font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; font-size: 14px; background-color: #eeeeee; margin-top: 100px;} .container {margin: 0 auto; max-width: 400px; padding: 30px; box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); background-color: #ffffff; border-radius: 10px;} h2 {text-align: center; margin-bottom: 20px; margin-top: 0px; color: #0ee6b1; font-size: 35px;} #titleGreen {color: #00E1AA;} #titleBlack {color: #000000;} h3 {text-align: center; margin-bottom: 40px; margin-top: 0px; color: #336859; font-size: 35px;} form .field-group {box-sizing: border-box; clear: both; padding: 4px 0; position: relative; margin: 1px 0; width: 100%;} .text-field {font-size: 15px; margin-bottom: 4%; -webkit-appearance: none; display: block; background: #fafafa; color: #636363; width: 100%; padding: 15px 0px 15px 0px; text-indent: 10px; border-radius: 5px; border: 1px solid #e6e6e6; background-color: transparent;} .text-field:focus {border-color: #00bcd4; outline: 0;} .button-container {box-sizing: border-box; clear: both; margin: 1px 0 0; padding: 4px 0; position: relative; width: 100%;} .button {background: #00E1AA; border: none; border-radius: 5px; color: #ffffff; cursor: pointer; display: block; font-weight: bold; font-size: 16px; padding: 15px 0; text-align: center; text-transform: uppercase; width: 100%; -webkit-transition: background 250ms ease; -moz-transition: background 250ms ease; -o-transition: background 250ms ease; transition: background 250ms ease;} p {text-align: center; text-decoration: none; color: #87c1d3; font-size: 18px;} a {text-decoration: none; color: #ffffff; margin-top: 0%;} #status {text-align: center; text-decoration: none; color: #336859; font-size: 14px;} </style> <script> function validateForm() { var ssid = document.forms["myForm"]["ssid"].value; var password = document.forms["myForm"]["password"].value; var status = document.getElementById("statusDiv"); if (ssid == "" && password == "") { status.innerHTML = "<p id='status' style='color:red;'>Insira SSID e senha.</p>"; return false; } else if (ssid == "") { status.innerHTML = "<p id='status' style='color:red;'>Insira SSID.</p>"; return false; } else if (password == "") { status.innerHTML = "<p id='status' style='color:red;'>Insira senha.</p>"; return false; } else { status.innerHTML = "<p id='status'>Conectando...</p>"; return true; } } </script> </head> <body> <div class="container"> <h2><span id="titleGreen">smart</span><span id="titleBlack">kits</span></h2> <h3>Conexão ESP8266</h3> <form name="myForm" action="/action_new_connection" onsubmit="return validateForm()" method="post"> <div class="field-group"> <select class='text-field' name='ssid'></select> </div> <br> <div class="field-group"> <input class="text-field" type="password" name="password" length=64 placeholder="Password"> </div> <br> <div id="statusDiv"> <br><br> </div> <div class="button-container"> <input class="button" type="submit" value="Conectar"> </div> </form> <p>OU</p> <div class="button-container"> <button class="button" type="button" onclick="window.location.href='/action_previous_connection'">Conectar à última rede utilizada</button> </div> </div> </body> </html> )====="; const char *ssid = "ESP8266 Access Point"; // Nome da rede WiFi que será criada const char *password = "smartkitsAP"; // Senha para se conectar nesta rede ESP8266WebServer server(80); //Server utiliza a porta 80 void setup() { pinMode(ONBOARD_LED, OUTPUT); //LED embutido Serial.begin(115200); WiFi.softAP(ssid, password); Serial.print("Access Point \""); Serial.print(ssid); Serial.println("\" iniciado"); Serial.print("IP address:\t"); Serial.println(WiFi.softAPIP()); //Tratamento de rotas server.on("/", handleRoot); server.on("/action_new_connection", handleForm); server.on("/action_previous_connection", connectEeprom); server.begin(); Serial.println("Servidor HTTP iniciado"); } void loop() { server.handleClient(); //Trata requisições de clientes if(WiFi.status() != WL_CONNECTED) { digitalWrite(ONBOARD_LED, HIGH); //Desative o LED } } void handleRoot() { String index = listSSID(); //Leia o conteúdo HTML server.send(200, "text/html", index); //Enviar pagina Web } void handleForm() { String ssidWifi = server.arg("ssid"); String passwordWifi = server.arg("password"); Serial.printf("SSID: %s\n", ssidWifi); Serial.printf("Password: %s\n", passwordWifi); if(!ssidWifi.equals("") && !passwordWifi.equals("")) { connectToWiFi(ssidWifi, passwordWifi); } } void connectToWiFi(String ssidWifi, String passwordWifi) { int count = 0; WiFi.begin(ssidWifi.c_str(), passwordWifi.c_str()); //Conecta com seu roteador Serial.println(""); //Espera por uma conexão while ( count < 15 ) { delay(500); Serial.print("."); if (WiFi.status() == WL_CONNECTED) { Serial.println(""); salvarEeprom(ssidWifi, passwordWifi); Serial.println(""); //Se a conexão ocorrer com sucesso, mostre o endereço IP no monitor serial Serial.println("Conectado ao WiFi"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); //Endereço IP do ESP8266 digitalWrite(ONBOARD_LED, LOW); //Acende o LED String responsePage = (const __FlashStringHelper*) MAIN_page; //Leia o conteúdo HTML responsePage.replace("<br><br>", "<p id='status'>Conectado!</p>"); server.send(200, "text/html", responsePage); return; } else if (WiFi.status() == WL_CONNECT_FAILED) { String responsePage = (const __FlashStringHelper*) MAIN_page; responsePage.replace("<br><br>", "<p id='status' style='color:red;'>Falha na conexão.</p>"); server.send(200, "text/html", responsePage); } count++; } Serial.println(); Serial.println("Timed out."); String responsePage = (const __FlashStringHelper*) MAIN_page; responsePage.replace("<br><br>", "<p id='status' style='color:red;'>Erro.</p>"); server.send(200, "text/html", responsePage); return; } String listSSID() { String index = (const __FlashStringHelper*) MAIN_page; //Leia o conteúdo HTML String networks = ""; int n = WiFi.scanNetworks(); Serial.println("Scan done."); if (n == 0) { Serial.println("Nenhuma rede encontrada."); index.replace("<select class='text-field' name='ssid'></select>", "<select class='text-field' name='ssid'><option value='' disabled selected>Nenhuma rede encontrada</option></select>"); index.replace("<br><br>", "<p id='status' style='color:red;'>Rede não encontrada.</p>"); return index; } else { Serial.printf("%d networks found.\n", n); networks += "<select class='text-field' name='ssid'><option value='' disabled selected>SSID</option>"; for (int i = 0; i < n; ++i) { // Imprime o SSID de cada rede encontrada networks += "<option value='" + WiFi.SSID(i) + "'>" + WiFi.SSID(i) + "</option>"; } networks += "</select>"; } index.replace("<select class='text-field' name='ssid'></select>", networks); return index; } void salvarEeprom(String ssidWifi, String passwordWifi) { EEPROM.begin(98); // Tamanho da FLASH reservado para EEPROM. Pode ser de 4 a 4096 bytes if(!compareEeprom(ssidWifi, passwordWifi)) { Serial.println("Salvando:"); EEPROM.write(0, ssidWifi.length()); Serial.println(ssidWifi.length()); for(int i = 2; i < 2+ssidWifi.length(); i++) { Serial.print(ssidWifi.charAt(i-2)); EEPROM.write(i, ssidWifi.charAt(i-2)); } Serial.println(""); Serial.println("Salvando:"); EEPROM.write(1, passwordWifi.length()); Serial.println(passwordWifi.length()); for(int j = 2+ssidWifi.length(); j < 2+ssidWifi.length()+passwordWifi.length(); j++) { Serial.print(passwordWifi.charAt(j-2-ssidWifi.length())); EEPROM.write(j, passwordWifi.charAt(j-2-ssidWifi.length())); } Serial.println(""); EEPROM.commit(); // Salva alterações na FLASH } EEPROM.end(); // Apaga a cópia da EEPROM salva na RAM } boolean compareEeprom(String ssidWifi, String passwordWifi) { int idLength = int(EEPROM.read(0)); // Tamanho do SSID armazenado (número de bytes) int passLength = int(EEPROM.read(1)); // Tamanho do Password armazenado (número de bytes) String id = ""; String pass = ""; Serial.println("Lendo SSID:"); Serial.print("Tamanho:"); Serial.println(idLength); for(int i = 2; i < 2+idLength; i++) { Serial.print("Posição "); Serial.print(i); Serial.print(": "); id = id + char(EEPROM.read(i)); Serial.println(id[i-2]); } Serial.println(""); Serial.println("Lendo senha:"); Serial.print("Tamanho:"); Serial.println(passLength); for(int j = 2+idLength; j < 2+idLength+passLength; j++) { Serial.print("Posição "); Serial.print(j); Serial.print(": "); pass = pass + char(EEPROM.read(j)); Serial.println(pass[j-2-idLength]); Serial.println(pass); } Serial.println(""); Serial.print("SSID é igual: "); Serial.println(id.equals(ssidWifi)); Serial.print("Senha é igual: "); Serial.println(pass.equals(passwordWifi)); if(id.equals(ssidWifi) && pass.equals(passwordWifi)) { Serial.println("Dados já presentes na memória."); return true; } else { return false; } } void connectEeprom() { EEPROM.begin(98); // Tamanho da FLASH reservado para EEPROM. Pode ser de 4 a 4096 bytes int ssidSize = (int)EEPROM.read(0); // Tamanho do SSID armazenado (número de bytes) int passwordSize = (int)EEPROM.read(1); // Tamanho do Password armazenado (número de bytes) String ssidWifi = ""; String passwordWifi = ""; Serial.println("Lendo:"); for(int i = 2; i < 2+ssidSize; i++) { Serial.print(char(EEPROM.read(i))); ssidWifi.concat(char(EEPROM.read(i))); } Serial.println(""); Serial.println("Lendo:"); for(int j = 2+ssidSize; j < 2+ssidSize+passwordSize; j++) { Serial.print(char(EEPROM.read(j))); passwordWifi.concat(char(EEPROM.read(j))); } Serial.println(""); EEPROM.end(); // Apaga a cópia da EEPROM salva na RAM Serial.println("Leu:"); Serial.println(ssidWifi); Serial.println(passwordWifi); connectToWiFi(ssidWifi, passwordWifi); } |
Gostou? Deixe seu comentário logo abaixo, não deixe de conferir outras postagens do nosso blog. Confira também a nossa loja virtual e encontre todos os componentes utilizados no projeto no post.
Gabriel,
estava a um tempo procurando uma solução para esse problema, de ter as credenciais da rede wifi no código, hoje estava desenvolvendo algo para gravar e ler na eprom, até que encontrei esse seu link.
Fou sucesso! Resolveu meu provlema 100%. Agora posso até criar um produto comercial, onde posso entregar o embarcado para o cliente configurar.
Parabéns!
Bom dia.
Segui todos os parametros indicados (ESP8266 NodeMCU 12), gravei o sketch.
Funcionou como o indicado porem não está gravando na EEPROM.
Ao dar RESET ele volta com 192.168.4.1 e não acessa WiFi local.
Qualquer dica será bem vinda.
Abraços.
INFORMAÇÕES:
IDE Arduino 1.8.16
Placa: NodeMCU 1.0 (ESP-12E Module)
Flash Size:4MB(FS:2MB OTA:~1019KB)
VTables:”Flash”
Stack Protection: Disable
Erase Flash:”Only Sketch”
SSL Support: All SSL ciphers (most compatible)
MMU: 32KB cache+32KB RAM(balanced)
Non-32-Bit Access