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).
Além disso, será utilizado uma Rede sem fio provida por um roteador para conexão do ESP32.
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:
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.
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.
Neste tópico, estão explicados os arquivos:
Ao final deste subtópico, a página html index se apresentará da seguinte forma:
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>
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 */
Ao final deste subtópico a página html atualizar-firmware se apresentará da seguinte forma:
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 id
do 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 id
do 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>
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:
border-style:outset;
);max-width: 300px;
);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:
margin-top: 36px;
.#buttonCancela{ margin-top: 36px; }
Estilizamos também o elemento que possui o id btnSubmit
. O elemento terá a seguinte 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:
background-color: #cccccc;
);width: 100%;
);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:
background-color: #1aff1a;
);width: 100%;
);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; }
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:
event.preventDefault();
let comArquivo = $("#fileInput").get(0).files.length > 0;
if(comArquivo) { }else { alert("É necessário selecionar um arquivo para envio"); }
none
da propriedade CSS display
:
$('#progressBar').css({ display: '' });
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");
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";
form
o formulário de id upload_form
;
var form = $('#upload_form')[0];
FormData
para obter os dados do formulário de id upload_form
e é armazenada na variável _data
;
var _data = new FormData(form);
fileInput
e adicionados à variável _data
;
jQuery.each(jQuery('#fileInput')[0].files, function(i, file) { _data.append('file-'+i, file); });
$.ajax()
. É adicionado nesta função o callback .done
para 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.'); });
$.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(){ }
XMLHttpRequest
, o seguinte algoritmo é executado:
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;
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"); } }); });
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.0
no mesmo diretório da pasta data
.
Neste tópico estão explicados os arquivos:
Ao final deste subtópico a nova página html index se apresentará da seguinte forma:
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.
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:
$path
o elemento da faixa de exibição da Barra de Progresso;
var $path = $('#'+idBar);
if (isNaN(val)) { val = 100; } else{ }
Caso o valor de porcentagem informado seja um número, é executado o seguinte algoritmo:
if (val < 0) { val = 0;}
if (val > 100) { val = 100;}
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--;}
pct
). Em seguida, a variável é convertida para um valor correspondente da faixa de progresso;
let pct = indiceDecrescente[parseInt(val)]; pct = (pct/100)*maxBar;
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:
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:
const minTemperature = -10; const maxTemperature = 45;
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:
const minPressao = 900; const maxPressao = 1050;
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:
const minAltitude = -400; const maxPressao = 8848;
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);
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:
<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. <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. <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:
<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:
<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. <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>
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:
font-family: "Roboto",sans-serif;
);text-align: center;
).body{ font-family: "Roboto",sans-serif; text-align: center; }
Estilizamos também o elemento a
. O elemento terá a seguinte propriedade:
color: blue;
.a{ color: blue; }
Estilizamos também o elemento svg
. O elemento terá as seguintes propriedades:
height: 200px;
;margin: 5px;
.svg { height: 200px; margin: 5px; }
Estilizamos também o elemento path
. O elemento terá as seguintes propriedades:
stroke-linecap: round;
;stroke-width: 7px;
;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:
stroke: lightgrey;
;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:
stroke: #0354fc;
;stroke-dasharray: calc(40 * 3.142 * 1.85);
;stroke-dashoffset: 233;
;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:
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:
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:
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:
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:
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:
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:
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:
translateX
e translateY
do atributo transform
, atribuídos através de transform: translateX(70%) translateY(98%);
;#faixaMaiorAltitude{ transform: translateX(70%) translateY(98%); }
Ao final deste subtópico a nova página atualizar-firmware.html se apresentará da seguinte forma:
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>
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
.
Dentro da classe form
, insira as seguintes propriedades:
display: inline-block;
;text-align: initial;
;Todas as propriedades classe form
:
.form{ border-style:outset; max-width: 300px; padding: 5px; display: inline-block; text-align: initial; }
O elemento body
é estilizado com as seguintes propriedades:
font-family: "Roboto",sans-serif;
);text-align: center;
).Todas as propriedades classe body
:
body{ font-family: "Roboto",sans-serif; text-align: center; }
O elemento a
é estilizado com a seguinte propriedade:
color: blue;
.a{ color: blue; }
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"); } }); });
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.
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.
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:
!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); } }
!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); } }
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:
!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); } }
!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); } }
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);
Para visualizar a atualização do FLASH do ESP32 em ação, siga as instruções abaixo:
versao_firmware
(localizado na linha 24 do sketch) de "v1.0"
para "v2.0"
;ino.bin
;C:\Users\cliente\AppData\Local\Temp\arduino_build_891159/Atualizando_a_SPIFFS_e_a_FLASH_do_ESP32_via_OTA_com_WebServer.ino.bin
);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/
;Para atualizar a SPIFFS do ESP32, siga as instruções abaixo:
spiffs.bin
;C:\Users\cliente\AppData\Local\Temp\arduino_build_891159/Atualizando_a_SPIFFS_e_a_FLASH_do_ESP32_via_OTA_com_WebServer.spiffs.bin
);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/
;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!
|
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.
Tenha a Metodologia Eletrogate dentro da sua Escola! Conheça nosso Programa de Robótica nas Escolas!