blog-eletrogate-logo-desktop blog-eletrogate-logo-mobile
  • Categorias
    • Voltar
    • INICIANTES
    • INTERMEDIÁRIOS
    • AVANÇADOS
    • divide
    • Automação Residencial
    • Componentes Eletrônicos
    • Impressão 3D
    • IoT
    • Modelagem 3D
    • Módulos Wifi
    • Por trás da tecnologia
    • Projetos
    • Raspberry Pi
    • Robótica
    • Sensores
    • Shields
    • Sistemas Operacionais
    • Tipos de Arduino
    • Tutoriais
  • Apostilas
  • Quem Somos
  • Seja um redator
  • Trabalhe Conosco
    • Categorias
      • Voltar
      • INICIANTES
      • INTERMEDIÁRIOS
      • AVANÇADOS
      • divide
      • Automação Residencial
      • Componentes Eletrônicos
      • Impressão 3D
      • IoT
      • Modelagem 3D
      • Módulos Wifi
      • Por trás da tecnologia
      • Projetos
      • Raspberry Pi
      • Robótica
      • Sensores
      • Shields
      • Sistemas Operacionais
      • Tipos de Arduino
      • Tutoriais
    • Apostilas
    • Quem Somos
    • Seja um redator
    • Trabalhe Conosco
Loja Eletrogate
voltar
  • Introdução
  • Materiais Necessários para o Projeto ESP32: Atualizar Firmware via AsyncWebServer
  • Preparando o IDE Arduino
  • Instalando o Notepad++
  • Páginas do WebServer Antes da Atualização OTA
  • Páginas do WebServer Depois da Atualização OTA
  • Hardware do ESP32
  • Software do ESP32
  • Demonstração da Atualização da FLASH
  • Demonstração da Atualização da SPIFFS
  • Conclusão
  • Sobre o Autor
IoT

ESP32: Atualizar Firmware via AsyncWebServer

Eletrogate 8 de dezembro de 2022

Introdução

Neste post, será mostrando como atualizar a SPIFFS e a FLASH do ESP32 por OTA através de um WebServer assíncrono. O ESP, que estará conectado a uma rede WiFi, receberá os Firmwares através do envio por um computador ou celular. O usuário poderá entrar na página HTML de atualização do ESP32 e selecionar um arquivo para a atualização da SPIFFS. Igualmente, o usuário também poderá entrar na página HTML de atualização do ESP32 e selecionar um arquivo para a atualização da FLASH.

Durante a atualização da FLASH ou SPIFFS, na página web, será possível ver a porcentagem em uma barra de progresso de quanto o processo de atualização já percorreu. Ao término da atualização, da FLASH ou SPIFFS, na página web, será mostrada uma mensagem ao usuário de que a atualização ocorreu com sucesso. Além da página de atualização do Firmware, o WebServer também terá a página index, onde, antes da atualização do Firmware via OTA, será mostrado somente os valores da temperatura, pressão atmosférica e altitude. Após a atualização do Firmware via OTA, será mostrado, como demonstração de que o firmware foi atualizado, além dos valores, uma barra circular (será mostrado no post como construí-la utilizando CSS) para indicação da relação entre o valor atual e a faixa de valores usuais da temperatura (-15 ºC a 45 ºC), pressão atmosférica (900 hPa à 1050 hPa) e altitude (-400 m à 8.848 m).


Materiais Necessários para o Projeto ESP32: Atualizar Firmware via AsyncWebServer

  • Módulo WiFi ESP32s Bluetooth 38 pinos
  • Sensor De Pressão e Temperatura BMP280
  • Jumpers – Macho/Macho – 40 Unidades de 10cm
  • Fonte 5V 1A Bivolt
  • Mini Protoboard 170 pontos

Além disso, será utilizado uma Rede sem fio provida por um roteador para conexão do ESP32.


Preparando o IDE Arduino

Para fazer o upload dos arquivos iniciais na SPIFFS, é necessário instalar um plugin no Arduino IDE. Este plugin é o Arduino ESP32 filesystem uploader. Para instala-lo, siga os passos abaixo:

  • acesse a página de lançamento da ferramenta no GitHub no link https://github.com/me-no-dev/arduino-esp32fs-plugin/releases;
  • baixe o arquivo ESP32FS-1.0.zip clicando no nome do arquivo:
  • Após fazer o download do arquivo zipado, navegue no explorador de arquivos de seu computador até a pasta do Sketchbook do Arduino IDE. Caso não saiba onde fica esta pasta, localize-a abrindo o Arduino IDE, clicando em Arquivo ➜ Preferências ➜ Localização do Sketchbook:
  • Na pasta do Sketchbook do Arduino IDE, crie o diretório tools (se ainda não existir):
  • Dentro do diretório tools, criado anteriormente, descompacte o arquivo zipado ESP32FS-1.0.zip:
  • Com o plugin instalado, reinicie o Arduino IDE para verificar se o plugin foi bem instalado, podendo ser acessado através do menu Ferramentas ➜ ESP32 Sketch Data Upload:

Para preparação dos arquivos para serem copiados para a SPIFFS do ESP32 através do plugin, é necessário criar e salvar um novo sketch arduino. Com o novo sketch criado, abra o diretório do sketch (podendo ser aberto também através do atalho Ctrl + K). Dentro do diretório do sketch crie uma nova pasta chamada data.

Os arquivos e pastas dentro da pasta data serão todos copiados para a SPIFFS do ESP32 durante a execução do plugin.


Instalando o Notepad++

Neste post será utilizado como editor HTML o software Notepad++. O Notepad++ é um editor de código fonte gratuito e substituto do Notepad (clássico bloco de notas do Windows), e que suporta vários idiomas.

Para realizar o download do Notepad++, acesse notepad-plus-plus.org/downloads. Na página web do Notepad++, clique na opção da versão mais atual. Em seguida, ao término do download do Notepad++, siga os passos do instalador e finalize a instalação.


Páginas do WebServer Antes da Atualização OTA

Neste tópico, estão explicados os arquivos:

  • Página index:
    • index.html;
    • index.css;
  • Página atualizar-firmware:
    • atualizar-firmware.html;
    • atualizar-firmware.css;
    • atualizar-firmware.js.

Página index

Ao final deste subtópico, a página html index se apresentará da seguinte forma:

Arquivo index.html

Abra o software Notepad++, e com um novo arquivo aberto, salve-o com o nome index.html na pasta data criada no tópico Preparando o IDE Arduino.

No arquivo, insira a declaração <!DOCTYPE html> e as tag’s <head> e a tag <body> dentro da tag <html> (que são a estrutura básica de um documento HTML5):

<!DOCTYPE html>
<html>
    <head>
        
    </head>
    <body>
    
    </body>
</html>

Dentro da tag <head>, insira as meta tag’s  de charset e viewport para, respectivamente, indicar a codificação dos caracteres do documento html e para preparar a visualização responsiva em diferentes dispositivos. Também adicionamos o título do documento através da tag <title>:

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Servidor ESP32 com Atualizador de Firmware - Index</title>
</head>

Dentro da tag <body>, adicione o Título de Cabeçalho, o parágrafo com o link para a página de atualização, o parágrafo com a versão atual do Firmware do ESP32 e os parágrafos com os dados de Pressão, temperatura e altitude do sensor BMP280. Os dados do sensor BMP280 são colocados entre dois símbolos de Porcentagem (%) com um nome de variável, para que a função de processamento de modelos do firmware do ESP32 substitua-as pelos valores reais das devidas variáveis:

<body>
    <h1>Dados do sensor BMP280</h1>
    <p>Deseja atualizar o Firmware? Então <a href="atualizar-firmware.html">clique aqui</a></p>
    <p><strong>Versão do Firmware atual: </strong>%versao_firmware%</p>
    <br>
    <p><strong>Temperatura: </strong>%temperatura% °C</p>
    <p><strong>Pressão Atmosférica: </strong>%pressao% hPa</p>
    <p><strong>Altitude aproximada: </strong>%altitude% m</p>
</body>

O código html final ficará assim:

<!DOCTYPE html>
<html lang="pt-br">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Servidor ESP32 com Atualizador de Firmware - Index</title>
    </head>
    <body>
        <h1>Dados do sensor BMP280</h1>
        <p>Deseja atualizar o Firmware? Então <a href="atualizar-firmware.html">clique aqui</a></p>
        <p><strong>Versão do Firmware atual: </strong>%versao_firmware%</p>
        <br>
        <p><strong>Temperatura: </strong>%temperatura% °C</p>
        <p><strong>Pressão Atmosférica: </strong>%pressao% hPa</p>
        <p><strong>Altitude aproximada: </strong>%altitude% m</p>	
    </body>
</html>

Arquivo index.css

Abra o software Notepad++, e com um novo arquivo aberto, salve-o com o nome index.css na pasta data criada no tópico Preparando a IDE Arduino.

O arquivo index.css não possui nenhum conteúdo em seu interior:

/* Nesta versão, este arquivo é vazio */

Página atualizar-firmware

Ao final deste subtópico a página html atualizar-firmware se apresentará da seguinte forma:

Arquivo atualizar-firmware.html

Abra o software Notepad++, e com um novo arquivo aberto, salve-o com o nome atualizar-firmware.html na pasta data criada no tópico Preparando o IDE Arduino.

No arquivo, insira a declaração <!DOCTYPE html> e as tag’s <head> e a tag <body> dentro da tag <html> (que são a estrutura básica de um documento HTML5):

<!DOCTYPE html>
<html lang="pt-br">
    <head>
        
    </head>
    <body>
    
    </body>
</html>

Dentro da tag <head>, insira as meta tag’s  de charset e viewport para, respectivamente, indicar a codificação dos caracteres do documento html e para preparar a visualização responsiva em diferentes dispositivos. Também adicionamos o título do documento através da tag <title>:

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Servidor ESP32 com Atualizador de Firmware - Atualizar Firmware</title>
</head>

Adicione também dentro da tag <head>, a tag <script> responsável por importar a biblioteca jQuery. A biblioteca jQuery foi desenvolvida para simplificar os scripts Javascript executados no navegador de internet do usuário:

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Servidor ESP32 com Atualizador de Firmware - Atualizar Firmware</title>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
</head>

Ainda dentro da tag <head>, adicione a tag <script> responsável por importar o arquivo Javascript atualizar-firmware.js:

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Servidor ESP32 com Atualizador de Firmware - Atualizar Firmware</title>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
    <script type="text/javascript" src="./atualizar-firmware.js"></script>
</head>

Dentro da tag <head>, adicione a tag <link> responsável por importar o arquivo CSS atualizar-firmware.css:

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Servidor ESP32 com Atualizador de Firmware - Atualizar Firmware</title>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
    <script type="text/javascript" src="./atualizar-firmware.js"></script>
    <link rel="stylesheet" href="./atualizar-firmware.css"></link>
</head>

Agora, dentro da tag <body>, adicione o Título de Cabeçalho (<h1>), o parágrafo com o link (<a>) para a página principal (index), o parágrafo com a versão atual do Firmware do ESP32 (<p>) e a tag <form> que é usada para criar um formulário HTML para entrada do usuário. O dado coletado no formulário é o arquivo de firmware para o ESP32, que o usuário irá realizar o upload para o servidor. A tag <form> possui os atributos:

  • class para fornecer a classe CSS, com o valor "form";
  • id para fornecer um identificador exclusivo, com o valor upload_form;
  • method para informar o método HTTP para enviar o formulário, com o valor POST;
  • action para informar a URL que processará o envio do formulário, com o valor #(não será enviado para nenhum url automaticamente);
  • enctype para informar o tipo MIME envio do formulário, com o valor multipart/form-data(o formulário contém um elemento input do tipo file);
<h1>Atualizar Firmware</h1>
<p>Deseja voltar para Index? Então <a href="index.html">clique aqui</a></p>
<p><strong>Versão do Firmware atual: </strong>%versao_firmware%</p>
<br>
<p>Selecione um arquivo que seja o firmware do ESP32. Em seguida, selecione se o arquivo é para SPIFFS ou FLASH:</p>		
<form class="form" id='upload_form' method='POST' action='#' enctype='multipart/form-data'>
                
</form>

Dentro da tag <form>, adicione a tag <input> do tipo file para permitir ao usuário selecionar um arquivo. Esta tag tem os atributos:

  • id para fornecer um identificador exclusivo, com o valor fileInput;
  • type para informar o tipo do input que é file;
  • name para informar o nome do input no formulário, sendo enviado com o formulário como parte de um par nome/valor, com o valor update;
<input id='fileInput' type='file' name='update'>

Adicione a tag <div>, que é um contêiner genérico que permiti agrupar conteúdo. Dentro da tag <div>, insira a tag <input> do tipo ckeckbox para permitir ao usuário informar se o arquivo é para atualizar a SPIFFS ou a FLASH. Esta tag tem os atributos:

  • id para fornecer um identificador exclusivo, com o valor cb_spiffsOuFlash;
  • type para informar o tipo do input que é checkbox;
  • name para informar o nome do input no formulário, sendo enviado com o formulário como parte de um par nome/valor, com o valor spiffsOuFlash;

Insira também, dentro da tag <div>, a tag <label> que representa uma legenda para o item input checkbox. Esta tag tem o atributo:

  • for para informar do atributo único iddo elemento rotulável checkbox relacionado ao formulário, com o valor cb_spiffsOuFlash;
<div>
    <input type='checkbox' id='cb_spiffsOuFlash' name='spiffsOuFlash'>
    <label for='cb_spiffsOuFlash'>Para SPIFFS?</label>
</div>

Adicione a tag <button>, que é um elemento interativo ativado por um usuário com mouse, teclado, dedo, comando de voz ou outra tecnologia assistiva, que uma vez ativado, ele executa a ação de resetar (voltar aos valores padrão) os dados do formulário. Esta tag tem o atributo:

  • id para fornecer um identificador exclusivo, com o valor buttonCancela;
<button id='buttonCancela'>Cancelar</button>

Adicione a tag <input> do tipo submit para permitir ao usuário enviar o formulário. Esta tag tem os atributos:

  • id para fornecer um identificador exclusivo, com o valor btnSubmit;
  • type para informar o tipo do input que é submit;
  • value para exibir o texto como rótulo do botão, com o valor Atualizar Firmware;
<input id="btnSubmit" type='submit' value='Atualizar Firmware'>

Ainda dentro da tag <form>, adicione a tag <div>. Esta tag será responsável por construir uma barra de progresso. Esta tag possui o id com o valor progressBar.

<div id="progressBar">
</div>

Dentro da tag <div>, insira a tag <label> que representa uma legenda para o item input checkbox. Esta tag tem o atributo for para informar do atributo único iddo elemento rotulável checkbox relacionado ao formulário, com o valor progressbar_exterior.

<div id="progressBar">
    <label for='progressbar_exterior'>Progresso de Upload: </label>
</div>

Insira também outra tag <div>. Esta tag será responsável por construir a barra de progresso externa. Esta tag possui o id com o valor progressbar_exterior.

<div id="progressBar">
    <label for='progressbar_exterior'>Progresso de Upload: </label>
    <div id='progressbar_exterior'>        
    </div>
</div>

Dentro da tag <div> de id com o valor progressbar_exterior, insira outra tag <div>. Esta tag será responsável por construir a barra de progresso interna. Esta tag possui o id com o valor progressbar_interior. Dentro da tag <div> de id com o valor progressbar_interior ficará o valor visível junto com um símbolo de porcentagem na barra de progresso.

<div id="progressBar">
    <label for='progressbar_exterior'>Progresso de Upload: </label>
    <div id='progressbar_exterior'>
        <div id='progressbar_interior'>0%</div>
    </div>
</div>

Note que o símbolo utilizado para a porcentagem na barra de progresso (%) é diferente do símbolo porcentagem comum ‘%’. Isto foi desenvolvido assim, pois como a página atualizar-firmware.html é verificada pela função de processo de modelo do ESP32, se houvesse o símbolo % (símbolo comum de porcentagem) a função de processo de modelo tentaria substituir causando assim erro na página.

Ainda dentro da tag <form>, adicione a tag <div>. Esta tag será responsável por exibir as mensagens de erro ou sucesso ao enviar o firmware ao ESP32. Esta tag possui o id com o valor messageForUser. Dentro da tag <div> de id com o valor messageForUser, terá a mensagem ao usuário.

<div id="messageForUser">Sem mensagem ao usuário</div>

O código html final ficará assim:

<!DOCTYPE html>
<html lang="pt-br">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Servidor ESP32 com Atualizador de Firmware - Atualizar Firmware</title>
        <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
        <script type="text/javascript" src="./atualizar-firmware.js"></script>
        <link rel="stylesheet" href="./atualizar-firmware.css"></link>
    </head>
    <body>
        <h1>Atualizar Firmware</h1>
        <p>Deseja voltar para Index? Então <a href="index.html">clique aqui</a></p>
        <p><strong>Versão do Firmware atual: </strong>%versao_firmware%</p>
        <br>
        <p>Selecione um arquivo que seja o firmware do ESP32. Em seguida, selecione se o arquivo é para SPIFFS ou FLASH:</p>		
        <form class="form" id='upload_form' method='POST' action='#' enctype='multipart/form-data'>
            <input id='fileInput' type='file' name='update'>
            <br>
            <div>
                <input type='checkbox' id='cb_spiffsOuFlash' name='spiffsOuFlash'>
                <label for='cb_spiffsOuFlash'>Para SPIFFS?</label>
            </div>
            <button id='buttonCancela'>Cancelar</button>

            <input id="btnSubmit" type='submit' value='Atualizar Firmware'>
            <br>

            <div id="progressBar">
                <label for='progressbar_exterior'>Progresso de Upload: </label>
                <div id='progressbar_exterior'>
                    <div id='progressbar_interior'>0%</div>
                </div>
            </div>
            
            <div id="messageForUser">
                Sem mensagem ao usuário
            </div>			
        </form>
    </body>
</html>

Arquivo atualizar-firmware.css

Abra o software Notepad++, e com um novo arquivo aberto, salve-o com o nome atualizar-firmware.css na pasta data criada no tópico Preparando o IDE Arduino.

No arquivo, estilizamos os elementos que possuem a classe form. Nesta classe, o elemento terá as seguintes propriedades:

  • a borda no estilo 3D (através da propriedade border-style:outset;);
  • a largura máxima de 300 pixels (através da propriedade max-width: 300px;);
  • um espaço ao redor do conteúdo do elemento de 5 pixels em todas as 4 bordas (através da propriedade padding: 5px;).
.form{
    border-style:outset;
    max-width: 300px;
    padding: 5px;
}

Estilizamos também o elemento que possui o id buttonCancela. O elemento terá a seguinte propriedade:

  • uma margem superior (top) do elemento de 36 pixels atribuído através da propriedade margin-top: 36px;.
#buttonCancela{
    margin-top: 36px;
}

Estilizamos também o elemento que possui o id btnSubmit. O elemento terá a seguinte propriedade:

  • uma margem superior (top) do elemento de 36 pixels atribuído através da propriedade margin-top: 36px;.
#btnSubmit{
    margin-top: 36px;
}

Estilizamos também os elementos que possuem a classe progressbar_exterior. Nesta classe, o elemento terá as seguintes propriedades:

  • a cor de fundo ⬛#cccccc (através da propriedade background-color: #cccccc;);
  • a largura de 100% do elemento que o contém (através da propriedade width: 100%;);
  • um espaço ao redor do conteúdo do elemento de 2 pixels nas bordas superior (top) e inferior (bottom) e de 0 pixel nas bordas esquerda (left) e direita (right) atribuído através da propriedade padding: 2px 0 2px 0;.
#progressbar_exterior {
    background-color: #cccccc;
    width: 100%;
    padding: 2px 0 2px 0;
}

Estilizamos também os elementos que possuem a classe progressbar_interior. Nesta classe, o elemento terá as seguintes propriedades:

  • a cor de fundo ⬛#1aff1a (através da propriedade background-color: #1aff1a;);
  • a largura de 100% do elemento que o contém (através da propriedade width: 100%;);
  • um espaço ao redor do conteúdo do elemento de 2 pixels nas bordas superior (top) e inferior (bottom) e de 0 pixel nas bordas esquerda (left) e direita (right) atribuído através da propriedade padding: 2px 0 2px 0;.
#progressbar_interior {
    background-color: #1aff1a;
    width: 0%;
    height: 20px;
    text-align: center;
}

O código CSS final ficará assim:

.form{
    border-style:outset;
    max-width: 300px;
    padding: 5px;
}

#buttonCancela{
    margin-top: 36px;
}

#btnSubmit{
    margin-top: 36px;
}

#progressbar_exterior {
    background-color: #cccccc;
    width: 100%;
    padding: 2px 0 2px 0;
}

#progressbar_interior {
    background-color: #1aff1a;
    width: 0%;
    height: 20px;
    text-align: center;
}

Arquivo atualizar-firmware.js

Abra o software Notepad++, e com um novo arquivo aberto, salve-o com o nome atualizar-firmware.js na pasta data criada no tópico Preparando o IDE Arduino.

No arquivo, insira o callback do evento ready, de quando o DOM window estiver totalmente carregado:

$(window).ready(function(){

});

Dentro da função de callback ready, insira a função local verificaBtnSubmit(), à qual irá verificar a quantidade de arquivos selecionados pelo usuário no input de id fileInput. Se a quantidade de arquivos for igual à 1, o botão de envio do formulário será habilitado para clique. Caso a quantidade de arquivos for igual à 0, o botão de envio do formulário será desabilitado para clique:

function verificaBtnSubmit(){
    if($("#fileInput").get(0).files.length === 0)
    {
        $("#btnSubmit").prop('disabled', 'true');
    }else
    {
        $("#btnSubmit").prop('disabled','');
    }
}

Insira também a função verificaBtnCancela(), à qual irá verificar se a quantidade de arquivos selecionados pelo usuário no input de id fileInput é maior que zero OU se a caixa de seleção se o arquivo é para SPIFFS ou não está marcado. Se a caixa de seleção estiver marcada ou se houver arquivos selecionados, o botão de cancelar do formulário será habilitado para clique, para que o usuário possa limpar as informações inseridas. Caso contrário, o botão de cancelar do formulário será desabilitado para clique:

function verificaBtnCancela(){
    let comArquivo = $("#fileInput").get(0).files.length > 0;
    let checkedSpiffs = $("#cb_spiffsOuFlash").is(":checked");
    if(checkedSpiffs || comArquivo)
    {
        $("#buttonCancela").prop('disabled',''); // habilita
    }else
    {
        $("#buttonCancela").prop('disabled', 'true'); // desabilita
    }
}

A função verificaBtns(), executa as funções verificaBtnSubmit() e verificaBtnCancela():

function verificaBtns(){
    verificaBtnSubmit();
    verificaBtnCancela();
}

A função setPercentageInProgressBar(value), define a porcentagem da barra de progresso do upload do arquivo para o ESP32. Esta função tem como parâmetro o valor da porcentagem para definir na barra de progresso. Caso o valor informado da porcentagem seja maior que 0 (zero) e menor que 100 (cem), o valor da barra de progresso será definido para o valor informado no parâmetro da função. Caso o valor seja menor que zero, o valor da porcentagem é definido para 0 (zero) e caso o valor seja maior que 100 (cem), o valor da porcentagem é definido para 100 (cem).

function setPercentageInProgressBar(value){
    if(value >=0 && value <=100){
        $('#progressbar_interior').css('width', value+"%");
        $('#progressbar_interior').text(value+"%");
    }else if(value > 100){
        $('#progressbar_interior').css('width', "100%");
        $('#progressbar_interior').text("100%");
    }
    else if(value < 0){
        $('#progressbar_interior').css('width', "0%");
        $('#progressbar_interior').text("0%");
    }
}

Em seguida, ainda dentro da função de callback do evento ready, é executado a função verificaBtns().

verificaBtns();

Também é definido que a caixa de mensagem para o usuário NÃO seja mostrado no início.

$('#messageForUser').css({
    display: 'none'
});

Semelhantemente, também é definido que a barra de progresso de upload do arquivo para o ESP32 NÃO seja mostrado no início.

$('#progressBar').css({
    display: 'none'
});

É anexado ao input de arquivo de firmware do ESP32 um manipulador de evento para executar a função verificaBtns ao ser detectado que o valor (arquivo) do input mudou.

$("#fileInput").on("change",verificaBtns);

É anexado também ao combobox de seleção se o arquivo é ou não para SPIFFS um manipulador de evento para executar a função verificaBtns ao ser detectado que o valor (caixa de seleção) do combobox mudou.

$("#cb_spiffsOuFlash").on("change",verificaBtns);

É anexado também ao botão de cancelar (limpar formulário) um manipulador de evento para executar uma função ao ser detectado que o botão foi clicado. Esta função executa as seguintes instruções:

  • event.preventDefault();: a ação padrão não será executada como normalmente seria (envio do formulário ao clique do botão);
  • $("#cb_spiffsOuFlash").prop('checked','');: define que o combobox, de seleção se o arquivo é ou não SPIFFS, seja tirada a seleção;
  • $("#fileInput").prop('value','');: define que seja removido o arquivo do input de arquivo de firmware para o ESP32;
  • verificaBtns();: executa a função de verificação dos botões cancelar e enviar.
$("#buttonCancela").on('click', function(event) {
    event.preventDefault();
    $("#cb_spiffsOuFlash").prop('checked','');
    $("#fileInput").prop('value','');
    verificaBtns();
});

Em seguida, é anexado ao botão de enviar (submit) um manipulador de evento para executar uma função ao ser detectado que o botão foi clicado:

$('#btnSubmit').on('click', function(event) {

});

Dentro desta função, é executado o seguinte algoritmo:

  • A ação padrão não será executada como normalmente seria (envio do formulário ao clique do botão);
    event.preventDefault();
  • É verificado se há algum arquivo no input de arquivo para firmware do ESP32;
    let comArquivo = $("#fileInput").get(0).files.length > 0;
  • Se houver algum arquivo no input, é executado um algoritmo. Se não houver nenhum arquivo no input, é mostrado uma mensagem de alerta informando que é necessário selecionar um arquivo para realizar o envio para o ESP32;
    if(comArquivo)
    {
    
    }else
    {
        alert("É necessário selecionar um arquivo para envio");
    }
  • Se houver arquivo no input, o seguinte algoritmo é executado:
    • A barra de progresso é mostrada ao usuário tirando o valor none da propriedade CSS display:
      $('#progressBar').css({
          display: ''
      });
    • É armazenado na variável checkedSpiffs o valor booleano da verificação se o combobox (de seleção se o arquivo é ou não para SPIFFS) se está ou não selecionado;
      let checkedSpiffs = $("#cb_spiffsOuFlash").is(":checked");
    • Se o combobox estiver marcado, é definido na variável urlServer o url ao qual o navegador enviará o formulário com o arquivo de atualização de firmware do ESP32. Se o combobox estiver marcado, o url será "/atualizar-spiffs" e se o combobox estiver marcado, o url será "/atualizar-flash";
      let urlServer = checkedSpiffs ? "/atualizar-spiffs" : "/atualizar-flash";
    • É armazenado na variável form o formulário de id upload_form;
      var form = $('#upload_form')[0];
    • É criado uma interface através de FormData para obter os dados do formulário de id upload_form e é armazenada na variável _data;
      var _data = new FormData(form);
    • É obtido o conteúdo dos arquivos do input de id fileInput e adicionados à variável _data;
      jQuery.each(jQuery('#fileInput')[0].files, function(i, file) {
          _data.append('file-'+i, file);
      });
    • É executada uma solicitação HTTP assíncrona através da função $.ajax(). É adicionado nesta função o callback .donepara quw quando a requisição HTTP for bem sucedida, será mostrado a mensagem de sucesso ao usuário. Também é adicionado nesta função o callback .fail para que quando a requisição HTTP obtiver algum erro, será mostrado a mensagem de erro ao usuário.
      $.ajax({
      
      })
      .done(function(data){
          // SUCESSO
          $('#messageForUser').css({
              display: ''
          });
          $('#messageForUser').text('O Firmware foi enviado com sucesso!. Você irá ser redirecionado para a página principal do Servidor em alguns instantes');
      })
      .fail(function(jqXHR, textStatus){
          // Mensagem de Erro
          $('#messageForUser').css({
              display: ''
          });
          $('#messageForUser').text('O envio ao servidor apresentou um erro. Verifique a conexão elétrica do ESP32.');
      });
    • A função $.ajax() possui as seguintes configurações:
      • method: O método HTTP a ser usado para a solicitação (por exemplo “POST”, “GET”, “PUT”, etc.);
      • type: Um aliás para method. Deve-se usar type se estiver usando versões do jQuery anteriores à 1.9.0;
      • url: Uma string contendo a URL para a qual a solicitação será enviada;
      • cache: Se definido como false, forçará as páginas solicitadas a não serem armazenadas em cache pelo navegador;
      • data: Dados a serem enviados ao servidor;
      • contentType: Ao enviar dados para o servidor, será utilizado o tipo de conteúdo definido. O padrão é "application/x-www-form-urlencoded; charset=UTF-8", o que é bom para a maioria dos casos. Pode-se passar false para dizer ao jQuery para não definir nenhum cabeçalho de tipo de conteúdo;
      • processData: Por padrão, os dados passados para a opção data como um objeto (tecnicamente, qualquer coisa que não seja uma string) serão processados e transformados em uma string de consulta, ajustando-se ao tipo de conteúdo padrão "application/x-www-form-urlencoded". Se é desejado enviar outros dados não processados, defina esta opção como false;
      • xhr: Função de callback para criar o objeto XMLHttpRequest.
        method: 'POST',
        type: "POST",
        url: urlServer,
        cache:false,
        data: _data,
        contentType: false,
        processData: false,
        xhr: function(){
            
        }
      • Na função de callback para criar o objeto XMLHttpRequest, o seguinte algoritmo é executado:
        • É criado um objeto do tipo XMLHttpRequest e é armazenado na variável xhr. Também é anexado o evento de callback para o objeto que monitora o progresso de upload do arquivo para quando o evento de progresso é acionado sempre que uma solicitação recebe mais dados. Em seguida, é retornado o objeto xhr para quem o chamou (Função de callback para criar o objeto XMLHttpRequest);
          var xhr = new window.XMLHttpRequest();
          xhr.upload.addEventListener('progress', function(evt) {
              
          });
          
          return xhr;
        • No evento de callback para o objeto que monitora o progresso de upload do arquivo para quando o evento de progresso é acionado sempre que uma solicitação recebe mais dados, é executado o seguinte algoritmo:
          • É verificado se o evento do callback tem um comprimento que pode ser calculado. Caso possa ser calculado, é calculado a porcentagem do progresso de envio do arquivo para o ESP32 através da razão entre o total de bytes do arquivo e o quanto já foi carregado. Através desta porcentagem, é exibida-a para o usuário através da barra de progresso e do indicador de porcentagem na barra de progresso. Caso a porcentagem seja igual à 100%, é exibido ao usuário que o upload foi concluído com sucesso e também é acionado um timer para fazer com que seja navegado até a página index do servidor ESP32 após 2 segundos.
            if (evt.lengthComputable) {
                let per = evt.loaded / evt.total;
                $('#progressbar_interior').css('width', Math.round(per*100) + '%');
                $('#progressbar_interior').html(Math.round(per*100) + '%');
                per = Math.round(per*100);
                setPercentageInProgressBar(per);
                if(per == 100){
                    alert('Upload concluído. Espere algum tempo para que o servidor volte à operar.');
                    var idTime = setTimeout(function(){window.location.href = "/";clearInterval(idTime);},2000);
                }
            }

O código javascript final ficará assim:

$(window).ready(function(){
    
    function verificaBtnSubmit(){
        if($("#fileInput").get(0).files.length === 0)
        {
            $("#btnSubmit").prop('disabled', 'true');
        }else
        {
            $("#btnSubmit").prop('disabled','');
        }
    }

    function verificaBtnCancela(){
        let comArquivo = $("#fileInput").get(0).files.length > 0;
        let checkedSpiffs = $("#cb_spiffsOuFlash").is(":checked");
        if(checkedSpiffs || comArquivo)
        {
            $("#buttonCancela").prop('disabled',''); // habilita
        }else
        {
            $("#buttonCancela").prop('disabled', 'true'); // desabilita
        }
    }
    
    function verificaBtns(){
        verificaBtnSubmit();
        verificaBtnCancela();
    }

    function setPercentageInProgressBar(value){
        if(value >=0 && value <=100){
            $('#progressbar_interior').css('width', value+"%");
            $('#progressbar_interior').text(value+"%");
        }else if(value > 100){
            $('#progressbar_interior').css('width', "100%");
            $('#progressbar_interior').text("100%");
        }
        else if(value < 0){
            $('#progressbar_interior').css('width', "0%");
            $('#progressbar_interior').text("0%");
        }
    }

    verificaBtns();

    $('#messageForUser').css({
        display: 'none'
    });

    $('#progressBar').css({
        display: 'none'
    });

    $("#fileInput").on("change",verificaBtns);

    $("#cb_spiffsOuFlash").on("change",verificaBtns);
    
    $("#buttonCancela").on('click', function(event) {
        event.preventDefault();
        $("#cb_spiffsOuFlash").prop('checked','');
        $("#fileInput").prop('value','');
        verificaBtns();
    });

    $('#btnSubmit').on('click', function(event) {
        event.preventDefault();
        
        let comArquivo = $("#fileInput").get(0).files.length > 0;
        if(comArquivo)
        {
            $('#progressBar').css({
                display: ''
            });
        
            let checkedSpiffs = $("#cb_spiffsOuFlash").is(":checked");
            let urlServer = checkedSpiffs ? "/atualizar-spiffs" : "/atualizar-flash";
            var form = $('#upload_form')[0];
            var _data = new FormData(form);
            jQuery.each(jQuery('#fileInput')[0].files, function(i, file) {
                _data.append('file-'+i, file);
            });

            $.ajax({
                method: 'POST',
                type: "POST",
                url: urlServer,
                cache:false,
                data: _data,
                contentType: false,
                processData: false,
                xhr: function(){
                    var xhr = new window.XMLHttpRequest();
                    xhr.upload.addEventListener('progress', function(evt) {
                        if (evt.lengthComputable) {
                            let per = evt.loaded / evt.total;
                            $('#progressbar_interior').css('width', Math.round(per*100) + '%');
                            $('#progressbar_interior').html(Math.round(per*100) + '%');
                            per = Math.round(per*100);
                            setPercentageInProgressBar(per);
                            if(per == 100){
                                alert('Upload concluído. Espere algum tempo para que o servidor volte à operar.');
                                var idTime = setTimeout(function(){window.location.href = "/";clearInterval(idTime);},2000);
                            }
                        }
                    });

                    return xhr;
                }
            })
            .done(function(data){
                // SUCESSO
                $('#messageForUser').css({
                    display: ''
                });
                $('#messageForUser').text('O Firmware foi enviado com sucesso!. Você irá ser redirecionado para a página principal do Servidor em alguns instantes');
            })
            .fail(function(jqXHR, textStatus){
                // Mensagem de Erro
                $('#messageForUser').css({
                    display: ''
                });
                $('#messageForUser').text('O envio ao servidor apresentou um erro. Verifique a conexão elétrica do ESP32.');
            });
        }else
        {
            alert("É necessário selecionar um arquivo para envio");
        }
    });
});

Páginas do WebServer Depois da Atualização OTA

Para o envio de atualização da SPIFFS, será desenvolvido uma nova versão das páginas index e atualizar-firmware. Para isso, crie uma nova pasta chamada data2.0no mesmo diretório da pasta data.

Neste tópico estão explicados os arquivos:

  • Página index:
    • index.html;
    • index.css;
  • Página atualizar-firmware:
    • atualizar-firmware.html;
    • atualizar-firmware.css;
    • atualizar-firmware.js.

Página index

Ao final deste subtópico a nova página html index se apresentará da seguinte forma:

Arquivo index.html

Abra o software Notepad++, e com um novo arquivo aberto, salve-o com o nome index.html na nova pasta data2.0.

Este arquivo será semelhante ao arquivo Página index, as diferenças consistem na adição de código Javascript dentro da tag  <head> e da adição de três gráficos na tag <body> para visualização dos dados do sensor BMP280.

Adição de código Javascript dentro da tag head

Dentro da tag <head>, insira a tag de código Javacript <script>:

<script type="text/javascript">

</script>

Dentro desta tag, é inserido primeiro a configuração para preenchimento maxBar, que define a medida em grau de cada gráfico circular (Nota: Esta é uma configuração arbitrária e foi definida através de tentativa e erro).

const maxBar = 233;

Em seguida, ainda dentro da tag <script>, é definida a função setPorcentagem(idBar,val,comAnimacao=false). Ela é responsável por definir a porcentagem de uma determinada barra de progresso circular. Esta função possui os argumentos:

  • idBar: o ID da faixa de exibição de determinada Barra de Progresso;
  • val: o valor de porcentagem para definir na Barra de Progresso;
  • comAnimacao: parâmetro com valor padrão de false, que determina se o valor da barra de progresso será atualizado com animação ou não;

    Com Animação

    Sem Animação

function setPorcentagem(idBar,val,comAnimacao=false){

}

Dentro da função setPorcentagem() é executado o seguinte algoritmo:

  • É armazenado na variável $path o elemento da faixa de exibição da Barra de Progresso;
    var $path = $('#'+idBar);
  • É verificado se o valor de porcentagem informado não é um número. Caso não seja um número, é definido que o valor de porcentagem seja igual à 100.
    if (isNaN(val)) {
     val = 100; 
    }
    else{
    	
    }

    Caso o valor de porcentagem informado seja um número, é executado o seguinte algoritmo:

    • se o valor de porcentagem seja menor que 0 (zero), o valor de porcentagem é definido para 0 (zero);
      if (val < 0) { val = 0;}
    • se o valor de porcentagem seja maior que 100 (cem), o valor de porcentagem é definido para 100 (cem);
      if (val > 100) { val = 100;}
    • É criado uma variável local para armazenar até quanto é o valor do array de números na ordem inversa (valor). É criado um array de números de 100 (cem) a 0 (zero), para converter a faixa de valores de 0 a 100 para a ordem inversa (100 a 0) (indiceDecrescente). Em seguida, em um laço de repetição é preenchido o array indiceDecrescente (Isto é necessário, pois a faixa de exibição da barra de progresso trabalha com valores da faixa inversa, ou seja, 100 a 0);
      let valor = 100;
      let indiceDecrescente = [100];
      for (let indice = 0; indice <= 10 0; indice++) {indiceDecrescente[indice] = valor;valor--;}
    • Em seguida, é criada uma variável para armazenar o valor da porcentagem já convertida para a faixa inversa (pct). Em seguida, a variável é convertida para um valor correspondente da faixa de progresso;
      let pct = indiceDecrescente[parseInt(val)];
      pct = (pct/100)*maxBar;
    • É verificado se a atualização da barra de progresso deverá ser executada com animação ou sem. Caso deva ser executada sem animação, é passado o valor da variável pct, já convertido, para a faixa de progresso através da propriedade "stroke-dashoffset", que especifica a distância entre o inicio e o fim do traço.
      if(comAnimacao){
      
      }else{
          $path.css("stroke-dashoffset",pct);
      }

      Caso deva ser executada com animação, é atribuído à faixa de progresso de pouco em pouco à cada 10 milissegundos o valor de pct:

      var indice = maxBar;
      let idInterval = setInterval(function ()
      {
          $path.css("stroke-dashoffset",indice);
          indice--;
          if(indice < pct){
              clearInterval(idInterval);
          }
          
      }
      ,10);

Ainda dentro da tag <script>, é definida a função setText(idBarText,val). Ela é responsável por definir o texto de descrição da barra de progresso circular, o qual este texto está no centro da barra de progresso circular. Esta função possui os argumentos:

  • idBarText: o id da descrição de texto de determinada barra de progresso;
  • val: a descrição de texto para determinada barra de progresso.

Dentro da função setText() é executado o seguinte algoritmo:

  • A propriedade text do elemento de id informado é alterado para o valor val.
    $('#'+idBarText).text(val);

Ainda dentro da tag <script>, é criada a variável primeiraExecucaoTemperatura com o valor true, que irá definir se a barra de progresso está sendo atualizada como primeira vez ou não. Se a barra de progresso referente à temperatura for executada na primeira vez, deverá ser animada, e se for executada de outras vezes, não deverá ser animada. Também é definida a função setTemperature(temperature). Ela é responsável por definir o valor e o texto de descrição da barra de progresso correspondente à temperatura. Esta função possui o argumento:

  • temperature: a temperatura para ser definida na barra de progresso.

Dentro da função setTemperature() é executado o seguinte algoritmo:

  • É definida as constantes com temperatura ambiente mínima e máxima;
    const minTemperature = -10;
    const maxTemperature = 45;
  • É convertido o valor da temperatura informada (temperature) para porcentagem, é definido a porcentagem para barra de progresso circular através de setPorcentagem(), é definido a descrição (temperatura e símbolo Celsius) para barra de progresso circular através de setText(), e é armazenado na variável primeiraExecucaoTemperatura que a barra de progresso referente à temperatura já foi executada uma vez.
    let pct = ((temperature-minTemperature)/(maxTemperature-minTemperature))*100;
    setPorcentagem("bar-temperatura",pct,primeiraExecucaoTemperatura);
    setText("bar-temperatura-text",temperature+" °C");
    primeiraExecucaoTemperatura = false;

Ainda dentro da tag <script>, é criada a variável primeiraExecucaoPressao com o valor true, que irá definir se a barra de progresso está sendo atualizada como primeira vez ou não. Se a barra de progresso referente à pressão for executada na primeira vez, deverá ser animada, e se for executada de outras vezes, não deverá ser animada. Também é definida a função setPressao(pressao). Ela é responsável por definir o valor e o texto de descrição da barra de progresso correspondente à pressão. Esta função possui o argumento:

  • pressao: a pressão para ser definida na barra de progresso.

Dentro da função setPressao() é executado o seguinte algoritmo:

  • É definida as constantes com pressão ambiente mínima e máxima;
    const minPressao = 900;
    const maxPressao = 1050;
  • É convertido o valor da temperatura informada (pressao) para porcentagem, é definido a porcentagem para barra de progresso circular através de setPorcentagem(), é definido a descrição (pressão e símbolo hectopascal) para barra de progresso circular através de setText(), e é armazenado na variável primeiraExecucaoPressao que a barra de progresso referente à pressão já foi executada uma vez.
    let pct = ((pressao-minPressao)/(maxPressao-minPressao))*100;
    setPorcentagem("bar-pressao",pct,primeiraExecucaoPressao);
    setText("bar-pressao-text",pressao+" hPa");
    primeiraExecucaoPressao = false;

Ainda dentro da tag <script>, é criada a variável primeiraExecucaoAltitude com o valor true, que irá definir se a barra de progresso está sendo atualizada como primeira vez ou não. Se a barra de progresso referente à altitude for executada na primeira vez, deverá ser animada, e se for executada de outras vezes, não deverá ser animada. Também é definida a função setAltitude(altitude). Ela é responsável por definir o valor e o texto de descrição da barra de progresso correspondente à altitude. Esta função possui o argumento:

  • altitude: a altitude para ser definida na barra de progresso.

Dentro da função setAltitude() é executado o seguinte algoritmo:

  • É definida as constantes com altitude mínima e máxima;
    const minAltitude = -400;
    const maxPressao = 8848;
  • É convertido o valor da altitude informada (altitude) para porcentagem, é definido a porcentagem para barra de progresso circular através de setPorcentagem(), é definido a descrição (altitude e símbolo metros) para barra de progresso circular através de setText(), e é armazenado na variável primeiraExecucaoAltitude que a barra de progresso referente à altitude já foi executada uma vez.
    let pct = ((altitude-minAltitude)/(maxAltitude-minAltitude))*100;
    setPorcentagem("bar-altitude",pct,primeiraExecucaoAltitude);
    setText("bar-altitude-text",altitude+" m");
    primeiraExecucaoAltitude = false;

Ainda dentro da tag <script>, é definida a função main() que tem a finalidade de executar um algoritmo quando á página do cliente for carregada.

function main(){

}

Dentro desta função, é definido o valor da temperatura, pressão e altitude, cujos valores virão através da função de processamento de modelos do firmware do ESP32:

setTemperature(%temperatura%);
setPressao(%pressao%);
setAltitude(%altitude%);

Assim que o Document Object Model (DOM) da página se tornar seguro para manipulação, é executada a função main():

$(document).ready(main);
Adição de três gráficos na tag body

Dentro da tag <body>, será inserido três gráficos para exibiçãp das barras de progresso correspondentes à temperatura, pressão e altitude. Os três gráficos são semelhantes, mudando apenas os IDs internos e os textos de descrição das barras de progresso, sendo que por este motivo será explicado somente o gráfico referente à temperatura:

Insira a tag de gráficos baseados em vetor em formato XML <svg>. Esta tag possui o atributo viewbox que define a posição e a dimensão, no espaço do usuário, de uma viewport SVG. Este atributo conta com uma lista de quatro números: min-x e min-y para posição, width e height para dimensão:

<svg viewbox="0 0 100 100">

</svg>

Dentro da tag <svg> insira o seguinte algoritmo:

  • É desenhado um elemento gráfico que consiste em texto através da tag <text> para exibir a temperatura e a unidade de medida da temperatura.
    <text class="svg-meio" id="bar-temperatura-text"dominant-baseline="middle" text-anchor="middle" font-size="12">- °C</text>

    Esta tag possui os seguintes atributos:

    • class: atribui um nome de classe ao elemento para estilizar o conteúdo SVG usando CSS;
    • id: atribui um nome exclusivo ao elemento;
    • dominant-baseline: especifica a linha de base dominante, que é a linha de base usada para alinhar o texto da caixa. Possíveis valores: auto, text-bottom, alphabetic, ideographic, middle, central, mathematical, hanging, text-top;
    • text-anchor: é usado para alinhar (alinhamento inicial, intermediário ou final) uma sequência de texto pré-formatado ou texto com quebra automática de linha em que a área de quebra automática é determinada a partir da propriedade relativa a um determinado ponto. Possíveis valores: start, middle, end;
    • font-size: define o tamanho da fonte de linha de base a linha de base.
  • É desenhado um elemento gráfico que consiste em texto através da tag <text> para exibir a legenda da temperatura (nome do parâmetro ambiental).
    <text class="descIndicador" dominant-baseline="middle" text-anchor="middle" font-size="8">Temperatura</text>

    Esta tag possui os seguintes atributos:

    • class: atribui um nome de classe ao elemento para estilizar o conteúdo SVG usando CSS;
    • dominant-baseline: especifica a linha de base dominante, que é a linha de base usada para alinhar o texto da caixa. Possíveis valores: auto, text-bottom, alphabetic, ideographic, middle, central, mathematical, hanging, text-top;
    • text-anchor: é usado para alinhar (alinhamento inicial, intermediário ou final) uma sequência de texto pré-formatado ou texto com quebra automática de linha em que a área de quebra automática é determinada a partir da propriedade relativa a um determinado ponto. Possíveis valores: start, middle, end;
    • font-size: define o tamanho da fonte de linha de base a linha de base.
  • É desenhado um elemento gráfico de fundo da barra de progresso através da tag <path> que é usado para construir qualquer básico elemento genérico (linhas, curvas, arcos e muito mais).
    <path class="fundo-progressbar" d="M40,90 A40,40 0 1,1 60,90"/>

    Esta tag possui os seguintes atributos:

    • class: atribui um nome de classe ao elemento para estilizar o conteúdo SVG usando CSS;
    • d: define a forma do elemento gráfico. Uma definição de caminho é uma lista de comandos em que cada comando é composto por uma letra de comando e números que representam os parâmetros de comando. Todos os comandos são detalhados na documentação. Explicação da lista de comando "M40,90 A40,40 0 1,1 60,90":
      • M40,90(x, y): Move o ponto atual de desenho para a coordenada x 40 e y 90;
      • A40,40 0 1,1 60,90(rx, ry angle large-arc-flag, sweep-flag x, y): Desenha uma curva de arco do ponto atual para a coordenada x 60 e y 90. O centro da elipse usado para desenhar o arco é determinado automaticamente com base nos outros parâmetros do comando:
        • rx e ry: são os dois raios da elipse, sendo rx 40 e ry 40;
        • angle: representa uma rotação (em graus) da elipse em relação ao eixo x, sendo angle 0;
        • large-arc-flage e sweep-flag: permite escolher qual arco deve ser desenhado, pois 4 arcos possíveis podem ser desenhados a partir dos outros parâmetros.
          • large-arc-flag: permite escolher entre arco grande ( 1) ou arco pequeno ( 0), sendo large-arc-flag 1
          • sweep-flag: permite escolher entre o arco de giro no sentido horário ( 1) ou o arco de giro no sentido anti-horário ( 0), sendo sweep-flag 1
        • coordenada x, y: torna-se o novo ponto atual para o próximo comando, sendo x 60 e y 90. Todos os conjuntos subsequentes de parâmetros são considerados comandos de curva de arco absoluta implícita.
  • É desenhado um elemento gráfico de “frente” (que realmente exibirá o a faixa da barra de progresso) da barra de progresso através da tag <path> que é usado para construir qualquer básico elemento genérico (linhas, curvas, arcos e muito mais).
    <path id="bar-temperatura" class="barra-progressbar" d="M40,90 A40,40 0 1,1 60,90" style="stroke:#fca103;"/>

    Esta tag possui os seguintes atributos:

    • class: atribui um nome de classe ao elemento para estilizar o conteúdo SVG usando CSS;
    • d: define a forma do elemento gráfico. Uma definição de caminho é uma lista de comandos em que cada comando é composto por uma letra de comando e números que representam os parâmetros de comando. Todos os comandos são detalhados na documentação. Explicação da lista de comando "M40,90 A40,40 0 1,1 60,90":
      • M40,90(x, y): Move o ponto atual de desenho para a coordenada x 40 e y 90;
      • A40,40 0 1,1 60,90(rx, ry angle large-arc-flag, sweep-flag x, y): Desenha uma curva de arco do ponto atual para a coordenada x 60 e y 90. O centro da elipse usado para desenhar o arco é determinado automaticamente com base nos outros parâmetros do comando:
        • rx e ry: são os dois raios da elipse, sendo rx 40 e ry 40;
        • angle: representa uma rotação (em graus) da elipse em relação ao eixo x, sendo angle 0;
        • large-arc-flage e sweep-flag: permite escolher qual arco deve ser desenhado, pois 4 arcos possíveis podem ser desenhados a partir dos outros parâmetros.
          • large-arc-flag: permite escolher entre arco grande ( 1) ou arco pequeno ( 0), sendo large-arc-flag 1
          • sweep-flag: permite escolher entre o arco de giro no sentido horário ( 1) ou o arco de giro no sentido anti-horário ( 0), sendo sweep-flag 1
        • coordenada x, y: torna-se o novo ponto atual para o próximo comando, sendo x 60 e y 90. Todos os conjuntos subsequentes de parâmetros são considerados comandos de curva de arco absoluta implícita.
  • É desenhado um elemento gráfico que consiste em texto através da tag <text> para exibir a temperatura mínima e a unidade de medida da temperatura mínima.
    <text id="faixaMenorTemperatura" dominant-baseline="middle" text-anchor="middle" font-size="8">-10 °C</text>

    Esta tag possui os seguintes atributos:

    • id: atribui um nome exclusivo ao elemento;
    • dominant-baseline: especifica a linha de base dominante, que é a linha de base usada para alinhar o texto da caixa. Possíveis valores: auto, text-bottom, alphabetic, ideographic, middle, central, mathematical, hanging, text-top;
    • text-anchor: é usado para alinhar (alinhamento inicial, intermediário ou final) uma sequência de texto pré-formatado ou texto com quebra automática de linha em que a área de quebra automática é determinada a partir da propriedade relativa a um determinado ponto. Possíveis valores: start, middle, end;
    • font-size: define o tamanho da fonte de linha de base a linha de base.
  • É desenhado um elemento gráfico que consiste em texto através da tag <text> para exibir a temperatura máxima e a unidade de medida da temperatura máxima.
    <text id="faixaMaiorTemperatura" dominant-baseline="middle" text-anchor="middle" font-size="8">45 °C</text>

    Esta tag possui os seguintes atributos:

    • id: atribui um nome exclusivo ao elemento;
    • dominant-baseline: especifica a linha de base dominante, que é a linha de base usada para alinhar o texto da caixa. Possíveis valores: auto, text-bottom, alphabetic, ideographic, middle, central, mathematical, hanging, text-top;
    • text-anchor: é usado para alinhar (alinhamento inicial, intermediário ou final) uma sequência de texto pré-formatado ou texto com quebra automática de linha em que a área de quebra automática é determinada a partir da propriedade relativa a um determinado ponto. Possíveis valores: start, middle, end;
    • font-size: define o tamanho da fonte de linha de base a linha de base.

O código html final ficará assim:

<!DOCTYPE html>
<html lang="pt-br">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Servidor ESP32 com Atualizador de Firmware - Index</title>
        <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
        <link rel="stylesheet" href="./index.css"></link>
        
        <script type="text/javascript">
            const maxBar = 233;
            
            function setPorcentagem(idBar,val,comAnimacao=false){
              var $path = $('#'+idBar);
      
              if (isNaN(val)) {
               val = 100; 
              }
              else{
              	if (val < 0) { val = 0;}
                if (val > 100) { val = 100;}
                
                let valor = 100;
                let indiceDecrescente = [100];
                for (let indice = 0; indice <= 100; indice++) {indiceDecrescente[indice] = valor;valor--;}
                
                let pct = indiceDecrescente[parseInt(val)];
                pct = (pct/100)*maxBar;

                
                if(comAnimacao){
                    var indice = maxBar;
                    let idInterval = setInterval(function ()
                    {
                        $path.css("stroke-dashoffset",indice);
                        indice--;
                        if(indice < pct){
                            clearInterval(idInterval);
                        }
                        
                    }
                    ,10);
                }else{
                    $path.css("stroke-dashoffset",pct);
                }						    
                
              } 
     		 }

     		 function setText(idBarText,val){
                $('#'+idBarText).text(val);
     		 }

     		 var primeiraExecucaoTemperatura = true;
     		 function setTemperature(temperature){     		 	
     		 	const minTemperature = -10;
     		 	const maxTemperature = 45;
     		 	let pct = ((temperature-minTemperature)/(maxTemperature-minTemperature))*100;
     		 	setPorcentagem("bar-temperatura",pct,primeiraExecucaoTemperatura);
     		 	setText("bar-temperatura-text",temperature+" °C");
     		 	primeiraExecucaoTemperatura = false;
     		 }

     		 var primeiraExecucaoPressao = true;
     		 function setPressao(pressao){
     		 	const minPressao = 900;
     		 	const maxPressao = 1050;
     		 	let pct = ((pressao-minPressao)/(maxPressao-minPressao))*100;
     		 	setPorcentagem("bar-pressao",pct,primeiraExecucaoPressao);
     		 	setText("bar-pressao-text",pressao+" hPa");
     		 	primeiraExecucaoPressao = false;
     		 }

     		 var primeiraExecucaoAltitude = true;
     		 function setAltitude(altitude){
     		 	const minAltitude = -400;
     		 	const maxAltitude = 8848;
     		 	let pct = ((altitude-minAltitude)/(maxAltitude-minAltitude))*100;
     		 	setPorcentagem("bar-altitude",pct,primeiraExecucaoAltitude);
     		 	setText("bar-altitude-text",altitude+" m");
     		 	primeiraExecucaoAltitude = false;
     		 }

     		 function main(){
     		 	setTemperature(%temperatura%);
     		 	setPressao(%pressao%);
     		 	setAltitude(%altitude%);
     		 }
     		 $(document).ready(main);
        </script>
    </head>
    <body>
        <h1>Dados do Tempo do sensor BMP280</h1>
        <p>Deseja atualizar o Firmware? Então <a href="atualizar-firmware.html">clique aqui</a></p>
        <p><strong>Versão do Firmware atual: </strong>%versao_firmware%</p>
        <br>		
        <svg viewbox="0 0 100 100">
            <text class="svg-meio" id="bar-temperatura-text"dominant-baseline="middle" text-anchor="middle" font-size="12">- °C</text>
            <text class="descIndicador" dominant-baseline="middle" text-anchor="middle" font-size="8">Temperatura</text>
            <path class="fundo-progressbar" d="M40,90 A40,40 0 1,1 60,90"/>
            <path id="bar-temperatura" class="barra-progressbar" d="M40,90 A40,40 0 1,1 60,90" style="stroke:#fca103;"/>
            <text id="faixaMenorTemperatura" dominant-baseline="middle" text-anchor="middle" font-size="8">-10 °C</text>
            <text id="faixaMaiorTemperatura" dominant-baseline="middle" text-anchor="middle" font-size="8">45 °C</text>
        </svg>		
        <svg viewbox="0 0 100 100">
            <text class="svg-meio" id="bar-pressao-text"dominant-baseline="middle" text-anchor="middle" font-size="12">- hPa</text>
            <text class="descIndicador" dominant-baseline="middle" text-anchor="middle" font-size="8">Pressão</text>
            <path class="fundo-progressbar" d="M40,90 A40,40 0 1,1 60,90"/>
            <path id="bar-pressao" class="barra-progressbar" d="M40,90 A40,40 0 1,1 60,90" style="stroke:#0380fc;"/>
            <text id="faixaMenorPressao" dominant-baseline="middle" text-anchor="middle" font-size="8">900 hPa</text>
            <text id="faixaMaiorPressao" dominant-baseline="middle" text-anchor="middle" font-size="8">1050 hPa</text>
        </svg>	
        <svg viewbox="0 0 100 100">
            <text class="svg-meio" id="bar-altitude-text"dominant-baseline="middle" text-anchor="middle" font-size="12">- m</text>
            <text class="descIndicador" dominant-baseline="middle" text-anchor="middle" font-size="8">Altitude</text>
            <path class="fundo-progressbar" d="M40,90 A40,40 0 1,1 60,90"/>
            <path id="bar-altitude" class="barra-progressbar" d="M40,90 A40,40 0 1,1 60,90" style="stroke:#03fcdb;"/>
            <text id="faixaMenorAltitude" dominant-baseline="middle" text-anchor="middle" font-size="8">-400 m</text>
            <text id="faixaMaiorAltitude" dominant-baseline="middle" text-anchor="middle" font-size="8">8848 m</text>
        </svg>	
    </body>
</html>

Arquivo index.css

Abra o software Notepad++, e com um novo arquivo aberto, salve-o com o nome index.css na pasta data2.0.

No arquivo, estilizamos o elemento body. Nesta estilização, o elemento terá as seguintes propriedades:

  • a família de fontes Roboto, e como alternativa a família de fontes sans-serif  (através da propriedade font-family: "Roboto",sans-serif;);
  • o alinhamento horizontal como centralizado (através da propriedade text-align: center;).
body{
    font-family: "Roboto",sans-serif;
    text-align: center;
}

Estilizamos também o elemento a. O elemento terá a seguinte propriedade:

  • o valor da cor de seu conteúdo de texto em Azul (⬛#0000d3), atribuído através da propriedade color: blue;.
a{
    color: blue;
}

Estilizamos também o elemento svg. O elemento terá as seguintes propriedades:

  • a altura da área do conteúdo em 200 pixels, atribuído através da propriedade height: 200px;;
  • a área de margem nos quatro lados em 5 pixels, atribuído através da propriedade margin: 5px;.
svg {
    height: 200px;
    margin: 5px;
}

Estilizamos também o elemento path. O elemento terá as seguintes propriedades:

  • a forma a ser usada no final de subcaminhos abertos quando eles são traçados será estendido por meio de um círculo com diâmetro igual à largura do traçado, atribuído através da propriedade stroke-linecap: round;;
  • a largura do traço a ser aplicado à forma será de 7 pixels, atribuído através da propriedade stroke-width: 7px;;
  • o tamanho da fonte será de 12 pixels, atribuído através da propriedade font-size: 12;.
path {
    stroke-linecap: round;
    stroke-width: 7px;
    font-size: 12;
}

Estilizamos também os elementos que possuem a classe fundo-progressbar. Cada elemento terá as seguintes propriedades:

  • a cor usada para pintar o contorno da forma será lightgrey (⬛#d3d3d3), atribuído através da propriedade stroke: lightgrey;;
  • a cor usada para preencher o interior da forma será nenhuma, atribuído através da propriedade fill:none;.
.fundo-progressbar {
    stroke: lightgrey;
    fill:none;
}

Estilizamos também os elementos que possuem a classe barra-progressbar. Cada elemento terá as seguintes propriedades:

  • a cor usada para pintar o contorno da forma será ⬛#0354fc, atribuído através da propriedade stroke: #0354fc;;
  • o padrão de traços e espaços usados ​​para pintar o contorno da forma será de 232.508 (40 * 3,142 * 1,85), atribuído através da propriedade stroke-dasharray: calc(40 * 3.142 * 1.85);;
  • a distância entre o inicio do traço e o fim será de 233 pixels, atribuído através da propriedade stroke-dashoffset: 233;;
  • a cor usada para preencher o interior da forma será nenhuma, atribuído através da propriedade fill:none;.
.barra-progressbar {
    stroke: #0354fc;
    stroke-dasharray: calc(40 * 3.142 * 1.85);
    stroke-dashoffset: 233;
    fill:none;
}

Estilizamos também os elementos que possuem a classe svg-meio. Cada elemento terá a seguinte propriedade:

  • é reposicionado o elemento no plano 2D horizontalmente em 50% e verticalmente em 50% do elemento pai, através da função translateX e translateY do atributo transform, atribuídos através de transform: translateX(50%) translateY(50%);;
.svg-meio{
    transform: translateX(50%) translateY(50%);
}

Estilizamos também os elementos que possuem a classe descIndicador. Cada elemento terá a seguinte propriedade:

  • é reposicionado o elemento no plano 2D horizontalmente em 50% e verticalmente em 60% do elemento pai, através da função translateX e translateY do atributo transform, atribuídos através de transform: translateX(50%) translateY(60%);;
.descIndicador{
    transform: translateX(50%) translateY(60%);
}

Estilizamos também o elemento que possua o id faixaMenorTemperatura. O elemento terá a seguinte propriedade:

  • é reposicionado o elemento no plano 2D horizontalmente em 30% e verticalmente em 98% do elemento pai, através da função translateX e translateY do atributo transform, atribuídos através de transform: translateX(30%) translateY(98%);;
#faixaMenorTemperatura{
    transform: translateX(30%) translateY(98%);
}

Estilizamos também o elemento que possua o id faixaMaiorTemperatura. O elemento terá a seguinte propriedade:

  • é reposicionado o elemento no plano 2D horizontalmente em 67% e verticalmente em 98% do elemento pai, através da função translateX e translateY do atributo transform, atribuídos através de transform: translateX(67%) translateY(98%);;
#faixaMaiorTemperatura{
    transform: translateX(67%) translateY(98%);
}

Estilizamos também o elemento que possua o id faixaMenorPressao. O elemento terá a seguinte propriedade:

  • é reposicionado o elemento no plano 2D horizontalmente em 30% e verticalmente em 98% do elemento pai, através da função translateX e translateY do atributo transform, atribuídos através de transform: translateX(30%) translateY(98%);;
#faixaMenorPressao{
    transform: translateX(30%) translateY(98%);
}

Estilizamos também o elemento que possua o id faixaMaiorPressao. O elemento terá a seguinte propriedade:

  • é reposicionado o elemento no plano 2D horizontalmente em 73% e verticalmente em 98% do elemento pai, através da função translateX e translateY do atributo transform, atribuídos através de transform: translateX(73%) translateY(98%);;
#faixaMaiorPressao{
    transform: translateX(73%) translateY(98%);
}

Estilizamos também o elemento que possua o id faixaMenorAltitude. O elemento terá a seguinte propriedade:

  • é reposicionado o elemento no plano 2D horizontalmente em 30% e verticalmente em 98% do elemento pai, através da função translateX e translateY do atributo transform, atribuídos através de transform: translateX(30%) translateY(98%);;
#faixaMenorAltitude{
    transform: translateX(30%) translateY(98%);
}

Estilizamos também o elemento que possua o id faixaMaiorAltitude. O elemento terá a seguinte propriedade:

  • é reposicionado o elemento no plano 2D horizontalmente em 70% e verticalmente em 98% do elemento pai, através da função translateX e translateY do atributo transform, atribuídos através de transform: translateX(70%) translateY(98%);;
#faixaMaiorAltitude{
    transform: translateX(70%) translateY(98%);
}

Página atualizar-firmware

Ao final deste subtópico a nova página atualizar-firmware.html se apresentará da seguinte forma:

Arquivo atualizar-firmware.html

Abra o software Notepad++, e com um novo arquivo aberto, salve-o com o nome atualizar-firmware.html na nova pasta data2.0.

Este arquivo será idêntico ao arquivo atualizar-firmware.html (versão antes atualizar). O código html final ficará assim:

<!DOCTYPE html>
<html lang="pt-br">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Servidor ESP32 com Atualizador de Firmware - Atualizar Firmware</title>
        <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
        <script type="text/javascript" src="./atualizar-firmware.js"></script>
        <link rel="stylesheet" href="./atualizar-firmware.css"></link>
    </head>
    <body>
        <h1>Atualizar Firmware</h1>
        <p>Deseja voltar para Index? Então <a href="index.html">clique aqui</a></p>
        <p><strong>Versão do Firmware atual: </strong>%versao_firmware%</p>
        <br>
        <p>Selecione um arquivo que seja o firmware do ESP32. Em seguida, selecione se o arquivo é para SPIFFS ou FLASH:</p>
        <form class="form" id='upload_form' method='POST' action='#' enctype='multipart/form-data'>
            <input id='fileInput' type='file' name='update'>
            <br>
            <div>
                <input type='checkbox' id='cb_spiffsOuFlash' name='spiffsOuFlash'>
                <label for='cb_spiffsOuFlash'>Para SPIFFS?</label>
            </div>
            <button id='buttonCancela'>Cancelar</button>

            <input id="btnSubmit" type='submit' value='Atualizar Firmware'>
            <br>
            
            <div id="progressBar">
                <label for='progressbar_exterior'>Progresso de Upload: </label>
                <div id='progressbar_exterior'>
                    <div id='progressbar_interior'>0%</div>
                </div>
            </div>
            
            <div id="messageForUser">
                Sem mensagem ao usuário
            </div>			
        </form>
    </body>
</html>

Arquivo atualizar-firmware.css

Abra o software Notepad++, e com um novo arquivo aberto, salve-o com o nome atualizar-firmware.css na nova pasta data2.0.

Este arquivo será semelhante ao arquivo atualizar-firmware.css (versão antes atualizar), as diferenças consistem na adição de algumas propriedades para a classe form, a adição de estilização do elemento body e a adição de estilização do elemento a.

Adição de algumas propriedades para a classe form

Dentro da classe form, insira as seguintes propriedades:

  • O elemento fluirá com o conteúdo circundante como se fosse uma única caixa em linha, atribuído através de display: inline-block;;
  • o texto do elemento terá é alinhado à borda esquerda da caixa de linha, através da propriedade text-align: initial;;

Todas as propriedades classe form:

.form{
    border-style:outset;
    max-width: 300px;
    padding: 5px;
    display: inline-block;
    text-align: initial;
}
Adição de estilização do elemento body

O elemento body é estilizado com as seguintes propriedades:

  • a família de fontes Roboto, e como alternativa a família de fontes sans-serif  (através da propriedade font-family: "Roboto",sans-serif;);
  • o alinhamento horizontal como centralizado (através da propriedade text-align: center;).

Todas as propriedades classe body:

body{
    font-family: "Roboto",sans-serif;
    text-align: center;
}
Adição de estilização do elemento a

O elemento a é estilizado com a seguinte propriedade:

  • o valor da cor de seu conteúdo de texto em Azul (⬛#0000d3), atribuído através da propriedade color: blue;.
a{
    color: blue;
}

Arquivo atualizar-firmware.js

Abra o software Notepad++, e com um novo arquivo aberto, salve-o com o nome atualizar-firmware.js na nova pasta data2.0.

Este arquivo será idêntico ao arquivo atualizar-firmware.js (versão antes atualizar). O código html final ficará assim:

$(window).ready(function(){
    
    function verificaBtnSubmit(){
        if($("#fileInput").get(0).files.length === 0)
        {
            $("#btnSubmit").prop('disabled', 'true');
        }else
        {
            $("#btnSubmit").prop('disabled','');
        }
    }

    function verificaBtnCancela(){
        let comArquivo = $("#fileInput").get(0).files.length > 0;
        let checkedSpiffs = $("#cb_spiffsOuFlash").is(":checked");
        if(checkedSpiffs || comArquivo)
        {
            $("#buttonCancela").prop('disabled',''); // habilita
        }else
        {
            $("#buttonCancela").prop('disabled', 'true'); // desabilita
        }
    }
    
    function verificaBtns(){
        verificaBtnSubmit();
        verificaBtnCancela();
    }

    function setPercentageInProgressBar(value){
        if(value >=0 && value <=100){
            $('#progressbar_interior').css('width', value+"%");
            $('#progressbar_interior').text(value+"%");
        }else if(value > 100){
            $('#progressbar_interior').css('width', "100%");
            $('#progressbar_interior').text("100%");
        }
        else if(value < 0){
            $('#progressbar_interior').css('width', "0%");
            $('#progressbar_interior').text("0%");
        }
    }

    verificaBtns();

    $('#messageForUser').css({
        display: 'none'
    });

    $('#progressBar').css({
        display: 'none'
    });

    $("#fileInput").on("change",verificaBtns);

    $("#cb_spiffsOuFlash").on("change",verificaBtns);
    
    $("#buttonCancela").on('click', function(event) {
        event.preventDefault();
        $("#cb_spiffsOuFlash").prop('checked','');
        $("#fileInput").prop('value','');
        verificaBtns();
    });

    $('#btnSubmit').on('click', function(event) {
        event.preventDefault();
        
        let comArquivo = $("#fileInput").get(0).files.length > 0;
        if(comArquivo)
        {
            $('#progressBar').css({
                display: ''
            });
        
            let checkedSpiffs = $("#cb_spiffsOuFlash").is(":checked");
            let urlServer = checkedSpiffs ? "/atualizar-spiffs" : "/atualizar-flash";
            var form = $('#upload_form')[0];
            var _data = new FormData(form);
            jQuery.each(jQuery('#fileInput')[0].files, function(i, file) {
                _data.append('file-'+i, file);
            });

            $.ajax({
                method: 'POST',
                type: "POST",
                url: urlServer,
                cache:false,
                data: _data,
                contentType: false,
                processData: false,
                xhr: function(){
                    var xhr = new window.XMLHttpRequest();
                    xhr.upload.addEventListener('progress', function(evt) {
                        if (evt.lengthComputable) {
                            let per = evt.loaded / evt.total;
                            $('#progressbar_interior').css('width', Math.round(per*100) + '%');
                            $('#progressbar_interior').html(Math.round(per*100) + '%');
                            per = Math.round(per*100);
                            setPercentageInProgressBar(per);
                            if(per == 100){
                                alert('Upload concluído. Espere algum tempo para que o servidor volte à operar.');
                                var idTime = setTimeout(function(){window.location.href = "/";clearInterval(idTime);},2000);
                            }
                        }
                    });

                    return xhr;
                }
            })
            .done(function(data){
                // SUCESSO
                $('#messageForUser').css({
                    display: ''
                });
                $('#messageForUser').text('O Firmware foi enviado com sucesso!. Você irá ser redirecionado para a página principal do Servidor em alguns instantes');
            })
            .fail(function(jqXHR, textStatus){
                // Mensagem de Erro
                $('#messageForUser').css({
                    display: ''
                });
                $('#messageForUser').text('O envio ao servidor apresentou um erro. Verifique a conexão elétrica do ESP32.');
            });
        }else
        {
            alert("É necessário selecionar um arquivo para envio");
        }
    });
});

Hardware do ESP32

O Hardware pode ser montado de acordo com o seguinte esquemático:


Software do ESP32

Os arquivos com o software consiste na seguinte estrutura:

Baixe aqui todos os arquivos do software clicando na imagem abaixo:

Se preferir, clique neste link.

Em seguida, após o download, descompacte o arquivo zip.

Configurando sketch e fazendo upload

No arquivo credenciais.h, há duas constantes que armazenam as credenciais de acesso à rede WiFi. Substitua os valores das constantes para as credenciais correspondentes da sua rede à qual queira que o ESP32 se conecte:

const char* ssid = "MEU_SSID"; // SSID da rede WiFi
const char* password = "MINHA_SENHA"; // Senha da rede WiFi

Em seguida, após a configuração das credenciais, faça upload do sketch para a placa ESP32.

Explicação do arquivo ino (sketch)

No sketch, primeiro incluímos as bibliotecas necessárias e também o arquivo credenciais.h:

//Inclusão das bibliotecas
#include <Adafruit_BMP280.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <FS.h>
#include <SPIFFS.h>
#include <Update.h>
#include "credenciais.h" // contém as credenciais de acesso ao WiFi

Em seguida, definimos o endereço I2C do sensor BMP280, que é 0x76. Também definimos a versão atual do firmware arduino desenvolvido ESP32, que neste primeiro momento é v1.0. É definido também o tempo limite, em segundos, para a inicialização do WiFi.

//Definição do endereço I2C do sensor BMP280
#define endereco_bmp280 0x76

//Definição da versão atual do Firmware do ESP32
#define versao_firmware "v1.0"

const unsigned int tempoLimiteConexaoWiFi = 15; // tempo limite da conexao WiFi em segundos

É instanciado os objetos das classes das bibliotecas referentes ao sensor BMP280 e ao servidor assíncrono. Para mais detalhes sobre o BMP280, consulte o artigo Como Utilizar o Sensor BMP280 com Arduino e para mais detalhes sobre o servidor assíncrono ESP32, consulte o artigo WebServer Assíncrono com ESP32, sendo ambos artigos aqui mesmo do blog Eletrogate.

// Instanciação dos objetos das classes das bibliteecas
Adafruit_BMP280 bmp280; // para sensor bmp2280
AsyncWebServer server(80); // para servidor web assíncrono na porta 80

É criada a função processor, que é utilizada para processar conteúdo contendo modelos. Nesta função, caso seja encontrada a expressão %temperatura% no documento processado, seu valor será substituído para a temperatura lida do sensor BMP280. Semelhantemente a altitude, a pressão e a versão do firmware serão substituídos por seus respectivos valores.

// Função que processa modelos (variáveis) nas páginas web especificadas
String processor(const String& var) {
  if (var == "temperatura")
    return (String)bmp280.readTemperature(); // retorna a temperatura lida do sensor bmp280 em graus Celsius
  if (var == "pressao")
    return (String)(bmp280.readPressure() / 100); // retorna a pressão atmosférica lida do sensor bmp280 em hectopascal
  if (var == "altitude")
    return (String)bmp280.readAltitude(); // retorna a altitude aproximada lida do sensor bmp280 em metros
  if (var == "versao_firmware")
    return (String)versao_firmware; // retorna a versão atual do firmware do ESP32
  return String(); // retorna String vazia
}

Também é criada a variável deveReiniciar para controlar se o ESP32 deve ou não reiniciar após o upload de algum firmware no ESP32.

bool deveReiniciar = false; // variável que controla se o ESP32 deve ou não reiniciar

Já dentro do void setup(), é inicializada a comunicação Serial para exibição de informações no Monitor Serial. Também é inicializado o sensor BMP280 em seu endereço I2C informado através da variável endereco_bmp280.

Serial.begin(115200); // configura a taxa de transferência em 115200 bits por segundo (baud rate) para a transmissão serial
Serial.println("Firmware iniciado"); // informa ao usuário que o firmware foi iniciado

if (!bmp280.begin(endereco_bmp280)) // se a inicialização do sensor BMP280 no endereço contido na variável não for bem sucedido, ...
{
  Serial.println("Não foi possível inicializar o sensor BMP280. Verifique as conexões elétricas."); // informa ao usuário que não foi possível inicializar o sensor BMP280
  return; // para o programa
}

delay(200);

Em seguida, nos conectamos ao Ponto de acesso WiFi no modo Estação (WIFI_STA ) com as credenciais obtidas das variáveis ssid e password que estão armazenadas no arquivo credenciais.h.

WiFi.disconnect(); // desconecta de qualquer conexão anterior, esta isntrução evita problemas de conexão
WiFi.mode(WIFI_STA); // configura o WiFi para o modo de estação
WiFi.begin(ssid, password); // Conecta na rede WiFi com as credenciais informadas.

Logo após, e ainda dentro do void setup(), é feita a espera até que o ESP32 tenha uma conexão ao ponto de acesso WiFi. Caso a conexão demore mais que o tempo limite de conexão, é informado ao usuário através do Monitor Serial que não foi possível se conectar à rede WiFi.

Serial.print("Fazendo tentativa de conexão ao WiFi...");
int qtdTentativasConexao = 0;
while (WiFi.status() != WL_CONNECTED) {
  delay(500);
  Serial.print(".");
  qtdTentativasConexao++;

  if (qtdTentativasConexao > tempoLimiteConexaoWiFi * 2) {
    Serial.println("\nTempo limite de Conexão WiFi falhada. Programa finalizado."); // informa ao usuário que não foi possível se conectar ao WiFi
    while (1);
  }
}

É inicializado o sistema de arquivos SPIFFS, dando a mensagem de falha no Monitor Serial caso a inicialização tenha alguma falha.

if (!SPIFFS.begin(true)) // se a montagem do sistema de arquivos SPIFFS (com a opção de formatar ativada em caso de falhas), ...
{
  Serial.println("SPIFFS não inicializada"); // informa ao usuário que a partição SPIFFS não foi bem inicializada
  return; // para o programa
}

É disponibilizado o url “/”, que fará o redirecionamento deste url até o url “/index.html”.

// disponibiliza o url "/"
server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
  request->redirect("/index.html"); // redireciona para página index.html
});

É disponibilizado o url “/index.html”, que irá responder à solicitação do cliente enviando a página index.html da SPIFFS executando o processamento do conteúdo do arquivo contendo modelos.

// disponibiliza o url "/index.html"
server.on("/index.html", HTTP_GET, [](AsyncWebServerRequest * request) {
  request->send(SPIFFS, // o tipo do Filesystem usado é o SPIFFS
                "/index.html", // o caminho do arquivo no diretório do Filesystem SPIFFS é /index.html
                "text/html", // o tipo de mídia da Internet (Media type) é HTML
                false, // o arquivo não vai ser disponibilizado para download direto
                processor // parâmetro para processar conteúdo contendo modelos
               ); // responde à solicitação do cliente enviando-lhe a página index.html.
});

É disponibilizado o url “/atualizar-firmware.html”, que irá responder à solicitação do cliente enviando a página atualizar-firmware.html da SPIFFS executando o processamento do conteúdo do arquivo contendo modelos.

// disponibiliza o url "/atualizar-firmware.html"
server.on("/atualizar-firmware.html", HTTP_GET, [](AsyncWebServerRequest * request) {
  request->send(SPIFFS, "/atualizar-firmware.html", // o caminho do arquivo no diretório do Filesystem SPIFFS é /atualizar-firmware.html
                "text/html", false, processor); // responde à solicitação do cliente enviando-lhe a página atualizar-firmware.html.
});

É disponibilizado o url “/atualizar-firmware.js”, que irá responder à solicitação do cliente enviando a página atualizar-firmware.js da SPIFFS sem executar o processamento do conteúdo do arquivo.

// disponibiliza o url "/atualizar-firmware.js"
server.on("/atualizar-firmware.js", HTTP_GET, [](AsyncWebServerRequest * request) {
  request->send(SPIFFS, "/atualizar-firmware.js", // o caminho do arquivo no diretório do Filesystem SPIFFS é /atualizar-firmware.js
                "text/javascript");// responde à solicitação do cliente, sem processar modelos da página, enviando-lhe a página atualizar-firmware.js
});

É disponibilizado o url “/index.css”, que irá responder à solicitação do cliente enviando a página index.css da SPIFFS sem executar o processamento do conteúdo do arquivo.

// disponibiliza o url "/index.css"
server.on("/index.css", HTTP_GET, [](AsyncWebServerRequest * request) {
  request->send(SPIFFS, "/index.css", // o caminho do arquivo no diretório do Filesystem SPIFFS é /index.css
                "text/css");// responde à solicitação do cliente, sem processar modelos da página, enviando-lhe a index.css
});

É disponibilizado o url “/atualizar-firmware.css”, que irá responder à solicitação do cliente enviando a página atualizar-firmware.css da SPIFFS sem executar o processamento do conteúdo do arquivo.

// disponibiliza o url "/atualizar-firmware.css"
server.on("/atualizar-firmware.css", HTTP_GET, [](AsyncWebServerRequest * request) {
  request->send(SPIFFS, "/atualizar-firmware.css", // o caminho do arquivo no diretório do Filesystem SPIFFS é /atualizar-firmware.css
                "text/css");// responde à solicitação do cliente, sem processar modelos da página, enviando-lhe a página script-atualizar-firmware.css
});

É disponibilizado o url “/atualizar-flash”, que irá permitir receber o firmware para flash do ESP32. Ao término da recepção e gravação do firmware, a variável deveReiniciar recebe o valor booleano contrário da função Update.hasError(), que caso a recepção e gravação do firmware tenha sucesso o retorno será false e caso tenha falha o retorno será true. Também ao término da recepção e gravação do firmware, a conexão da solicitação do cliente é encerrada.

// disponibiliza o url "/atualizar-flash"
server.on("/atualizar-flash", HTTP_POST,
[] (AsyncWebServerRequest * request) {
  deveReiniciar = !Update.hasError();
  AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", deveReiniciar ? "OK" : "FALHO");
  response->addHeader("Connection", "close");
  request->send(response);
},
[](AsyncWebServerRequest * request, String filename, size_t index, uint8_t *data, size_t len, bool final) {

});

Na segunda função lamba de recebimento do firmware, é feito todo o processo de verificação de erros e se o recebimento já terminou. Veja abaixo a explicação:

  • Caso o índice recebido seja diferente de 0 (!index), é inicializado o recebimento do firmware. Caso a inicialização do recebimento do firmware para FLASH tenha dado algum erro, o erro é imprimido no monitor Serial.
    if (!index) {
      Serial.println("Atualização de FLASH iniciada");
      Serial.print("Nome do Arquivo: ");
      Serial.print(filename);
    
      if (!Update.begin(UPDATE_SIZE_UNKNOWN)) {
        Update.printError(Serial);
      }
    }
  • Caso não tenha nenhum erro na actualização do firmware (!Update.hasError()), é feita a escrita do firmware recebido no ESP32 de acordo os dados do arquivo enviado do cliente esteja sendo enviado (dependente da velocidade da rede). Caso haja erro na escrita do firmware no ESP32, o erro é imprimido no Monitor Serial.
    if (!Update.hasError()) {
      /* piscando firmware para ESP32 */
      if (Update.write(data, len) != len) {
        Update.printError(Serial);
      }
    }
  • Caso o final do arquivo enviado do cliente tenha chegado ao final, é finalizado a escrita do firmware no ESP32 através de Update.end(true). Caso a finalização da escrita do firmware no ESP32 tenha algum erro, é mostrada a mensagem de erro no Monitor Serial.
    if (final) {
      if (Update.end(true)) { // true para definir o tamanho para o progresso atual
        Serial.print("Atualização efetuada com sucesso: ");
        Serial.print(index + len);
        Serial.println(" bytes");
        Serial.println("Reiniciando...\n");
      } else {
        Update.printError(Serial);
      }
    }

É disponibilizado o url “/atualizar-spiffs”, que irá permitir receber o firmware para SPIFFS do ESP32. Ao término da recepção e gravação do firmware, a variável deveReiniciar recebe o valor booleano contrário da função Update.hasError(), que caso a recepção e gravação do firmware tenha sucesso o retorno será false e caso tenha falha o retorno será true. Também ao término da recepção e gravação do firmware, a conexão da solicitação do cliente é encerrada.

server.on("/atualizar-spiffs", HTTP_POST,
[] (AsyncWebServerRequest * request) {
  deveReiniciar = !Update.hasError();
  Serial.println("Conexão fechada");
  AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", deveReiniciar ? "OK" : "FALHO");
  response->addHeader("Connection", "close");
  request->send(response);
},
[](AsyncWebServerRequest * request, String filename, size_t index, uint8_t *data, size_t len, bool final) {

});

Na segunda função lambda de recebimento do firmware, é feito todo o processo de verificação de erros e se o recebimento já terminou. Veja abaixo a explicação:

  • Caso o índice recebido seja diferente de 0 (!index), é inicializado o recebimento do firmware. Caso a inicialização do recebimento do firmware para SPIFFS tenha dado algum erro, o erro é imprimido no monitor Serial.
    if (!index) {
      Serial.println("Atualização de FLASH iniciada");
      Serial.print("Nome do Arquivo: ");
      Serial.print(filename);
    
      if (!Update.begin(UPDATE_SIZE_UNKNOWN, U_SPIFFS)) {
        Update.printError(Serial);
      }
    }
  • Caso não tenha nenhum erro na actualização do firmware (!Update.hasError()), é feita a escrita do firmware recebido no ESP32 de acordo os dados do arquivo enviado do cliente esteja sendo enviado (dependente da velocidade da rede). Caso haja erro na escrita do firmware no ESP32, o erro é imprimido no Monitor Serial.
    if (!Update.hasError()) {
      /* piscando firmware para ESP32 */
      if (Update.write(data, len) != len) {
        Update.printError(Serial);
      }
    }
  • Caso o final do arquivo enviado do cliente tenha chegado ao final, é finalizado a escrita do firmware no ESP32 através de Update.end(true). Caso a finalização da escrita do firmware no ESP32 tenha algum erro, é mostrada a mensagem de erro no Monitor Serial.
    if (final) {
      if (Update.end(true)) { // true para definir o tamanho para o progresso atual
        Serial.print("Atualização efetuada com sucesso: ");
        Serial.print(index + len);
        Serial.println(" bytes");
        Serial.println("Reiniciando...\n");
      } else {
        Update.printError(Serial);
      }
    }

Logo após, e ainda dentro do void setup(), é feita a inicialização do servidor para que se possa coemçar ouvir os clientes.

// Servidor começa à ouvir os clientes
server.begin();

Em void loop(), é verificado se a leitura de altitude do sensor BMP280 é um número (diferente de NaN). Caso NÃO seja um número, é mostrado no Monitor Serial a cada 1 segundo que há erro na leitura do sensor BMP280.

if (isnan(bmp280.readAltitude())) {
  Serial.println("Erro na leitura do sensor BMP280");
  delay(1000);
}

Ainda dentro de void loop(), é verificado se deve ou não reiniciar o ESP32 através de  ESP.restart().

if (deveReiniciar) {
  Serial.println("Reiniciando ESP32...");
  delay(100);
  ESP.restart();
}

Por fim, é feito um atraso no programa de 1 milissegundo através de delay(1).

delay(1);

Demonstração da Atualização da FLASH

Para visualizar a atualização do FLASH do ESP32 em ação, siga as instruções abaixo:

  1. No sketch do ESP32 altere o valor da variável versao_firmware (localizado na linha 24 do sketch) de "v1.0" para "v2.0";
  2. Verifique se a exibição das mensagens de saída durante a compilação na IDE Arduino está ativa, navegando em Arquivo ➜ Preferências e na opção Mostrar mensagens de saída. Caso esteja desmarcado, marque o checkbox compilação;
  3. Faça a compilação do sketch modificado clicando em Verificar;
  4. Após o término da compilação, selecione todo o texto da tela de avisos do compilador apertando as teclas de atalho Ctrl + A, tendo o foco do cursor na tela de avisos;
  5. Copie o texto selecionado utilizando as teclas de atalho Ctrl + C;
  6. Em um editor de texto, como o Notepad++, cole o texto utilizando as teclas de atalho Ctrl + V;
  7. Em seguida, abra a ferramenta localizar do Notepad++ clicando Ctrl + F, e procure o termo ino.bin;
  8. Com o termo achado, selecione e guarde o endereço ao qual o termo achado faz parte (no meu caso o endereço encontrado foi C:\Users\cliente\AppData\Local\Temp\arduino_build_891159/Atualizando_a_SPIFFS_e_a_FLASH_do_ESP32_via_OTA_com_WebServer.ino.bin);
  9. Agora abra o navegador e digite o IP do ESP32 na rede (este IP pode ser encontrado no Monitor Serial ao iniciar o ESP32);
  10. Acesse o endereço da página de atualização do firmware (no meu caso como o IP do ESP32 é 192.168.0.107, o endereço será 192.168.0.107/atualizar-firmware.html);
  11. Clique em Escolher arquivo e selecione o arquivo do endereço encontrado no passo 8 (no meu caso foi o arquivo Atualizando_a_SPIFFS_e_a_FLASH_do_ESP32_via_OTA_com_WebServer.ino.bin da pasta C:\Users\cliente\AppData\Local\Temp\arduino_build_891159/;
  12. Deixe o estado do checkbox Para SPIFFS? no estado padrão, que é desselecionado, indicando que o arquivo é para FLASH;
  13. Clique em Atualizar Firmware e aguarde a conclusão e reinicialização do ESP32. Pode ser que aconteça que quando você for redirecionado de página após a conclusão da atualização, a página não esteja atualizada com a nova versão do firmware. Para resolver isto, basta clicar clicar no botão recarregar do navegador. Isto pode acontecer por conta que o navegador pode usar a versão da página web que esteja armazenada no cache do navegador.

Vídeo demonstrando da atualização da FLASH

https://blog.eletrogate.com/wp-content/uploads/2022/09/Exemplo-Atualizacao-de-FLASH.mp4

Demonstração da Atualização da SPIFFS

Para atualizar a SPIFFS do ESP32, siga as instruções abaixo:

  1. Com o sketch do ESP32, clique em Ctrl + K para ir no diretório que se encontra o sketch, a pasta data e a pasta data2.0;
  2. No diretório, altere o nome da pasta data para data1.0 e a pasta data2.0 para data;
  3. Verifique se a exibição das mensagens de saída durante a compilação na IDE Arduino está ativa, navegando em Arquivo ➜ Preferências e na opção Mostrar mensagens de saída. Caso esteja desmarcado, marque o checkbox compilação;
  4. No sketch do ESP32 faça a tentativa de upload dos arquivos da SPIFFS clicando em Feramentas ➜ ESP Sketch Data Upload, o que deve resultar em um erro (SPIFFS Upload failed! );
  5. Após o término da tentativa de upload falha, selecione todo o texto da tela de avisos do compilador apertando as teclas de atalho Ctrl + A, tendo o foco do cursor na tela de avisos;
  6. Copie o texto selecionado utilizando as teclas de atalho Ctrl + C;
  7. Em um editor de texto, como o Notepad++, cole o texto utilizando as teclas de atalho Ctrl + V;
  8. Em seguida, abra a ferramenta localizar do Notepad++ clicando Ctrl + F, e procure o termo spiffs.bin;
  9. Com o termo achado, selecione e guarde o endereço ao qual o termo achado faz parte (no meu caso o endereço encontrado foi C:\Users\cliente\AppData\Local\Temp\arduino_build_891159/Atualizando_a_SPIFFS_e_a_FLASH_do_ESP32_via_OTA_com_WebServer.spiffs.bin);
  10. Agora abra o navegador e digite o IP do ESP32 na rede (este IP pode ser encontrado no Monitor Serial ao iniciar o ESP32);
  11. Acesse o endereço da página de atualização do firmware (no meu caso como o IP do ESP32 é 192.168.0.107, o endereço será 192.168.0.107/atualizar-firmware.html);
  12. Clique em Escolher arquivo e selecione o arquivo do endereço encontrado no passo 9 (no meu caso foi o arquivo Atualizando_a_SPIFFS_e_a_FLASH_do_ESP32_via_OTA_com_WebServer.spiffs.bin da pasta C:\Users\cliente\AppData\Local\Temp\arduino_build_891159/;
  13. Clique para alterar o estado do checkbox Para SPIFFS? para o estado selecionado, indicando que o arquivo é para SPIFFS;
  14. Clique em Atualizar Firmware e aguarde a conclusão e reinicialização do ESP32. Pode ser que aconteça que quando você for redirecionado de página após a conclusão da atualização, a página não esteja atualizada com a nova versão do firmware. Para resolver isto, basta clicar clicar no botão recarregar do navegador. Isto pode acontecer por conta que o navegador pode usar a versão da página web que esteja armazenada no cache do navegador.

Vídeo demonstrando da atualização da SPIFFS

https://blog.eletrogate.com/wp-content/uploads/2022/09/Exemplo-atualizacao-SPIFFS.mp4

Conclusão

Curtiu o post? Avalie e deixe um comentário! Siga-nos também no Instagram e nos marque quando fizer algum projeto nosso: @eletrogate. Até a próxima!


Sobre o Autor


Michel Galvão

Hobbysta em Sistemas Embarcados e IoT. Tem experiência em Automação Residencial e Agrícola.


Eletrogate

8 de dezembro de 2022

A Eletrogate é uma loja virtual de componentes eletrônicos do Brasil e possui diversos produtos relacionados à Arduino, Automação, Robótica e Eletrônica em geral.

Conheça a Metodologia Eletrogate e Lecione um Curso de Robótica nas Escolas da sua Região!

Componentes Eletronicos

Conceitos Básicos sobre Solda Eletrônica

Eletrogate26 de janeiro de 2023

Este post aborda os tipos de ferro de solda, tipos de solda, acessórios para o processo e procedimentos para manutenção do ferro de solda.

Componentes Eletronicos

Conceitos Básicos sobre Solda Eletrônica

Eletrogate26 de janeiro de 2023

Este post aborda os tipos de ferro de solda, tipos de solda, acessórios para o processo e procedimentos para manutenção do ferro de solda.

Sensores

MPU6050 com BluePill e STM32CubeIDE

Eletrogate19 de janeiro de 2023

Neste post, veremos como medir aceleração e velocidade angular utilizando o MPU6050 junto de uma BluePill programada pelo STM32CubeIDE.

Sensores

MPU6050 com BluePill e STM32CubeIDE

Eletrogate19 de janeiro de 2023

Neste post, veremos como medir aceleração e velocidade angular utilizando o MPU6050 junto de uma BluePill programada pelo STM32CubeIDE.

Robótica

Controle de Corrente em Servomotores

Eletrogate12 de janeiro de 2023

Este post trata acerca de um simples sistema que visa ajustar o período do pulso de controle de um servomotor evitando que este permaneça em um estado de alto consumo de corrente.

Robótica

Controle de Corrente em Servomotores

Eletrogate12 de janeiro de 2023

Este post trata acerca de um simples sistema que visa ajustar o período do pulso de controle de um servomotor evitando que este permaneça em um estado de alto consumo de corrente.

Tipos de Arduino

BluePill com STM32CubeIDE

Eletrogate6 de janeiro de 2023 Atualizado em: 09 jan 2023

Neste post, desenvolveremos, utilizando recursos do STM32CubeIDE, um simples piscaLED para a placa BluePill.

Tipos de Arduino

BluePill com STM32CubeIDE

Eletrogate6 de janeiro de 2023 Atualizado em: 09 jan 2023

Neste post, desenvolveremos, utilizando recursos do STM32CubeIDE, um simples piscaLED para a placa BluePill.

Eletrogate Robô

Cadastre-se e fique por
dentro de novidades!

blog-eletrogate-logo-footer

Rua Rio de Janeiro, 441 - Sala 1301
Centro - Belo Horizonte/MG
CEP 30160-041
*Não temos atendimento físico

ANWAR SLEIMAN HACHOUCHE - ME
CNPJ: 18.917.521/0001-73

Atendimento

(31) 3142-3800

contato@eletrogate.com


Seg a Sex - das 8h às 17h

Institucional

  • Apostilas
  • Quem Somos
  • Privacidade
  • Seja um Redator
  • Trabalhe Conosco

Nos acompanhe

Facebook Instagram Youtube

© ELETROGATE 2023 - Todos os direitos reservados. Termos de uso e Política de privacidade.