醬是創客的ESP32教學主題第11篇,以Ai-Thinker安信可NodeMCU-32S(使用Arduino語言)來實作教學,本篇教學將著重使用OTA Update從網頁Web線上更新軟體Firmware,一般來說嵌入式系統都是驗證到完全無BUG後才上市,但是真的有BUG還是要再更新韌體,這時候總不能再退回原廠慢慢更新吧,所以我們本篇要做一個網頁,可以透過本機的BIN檔上傳更新至ESP32,這樣用戶可以自行去更新軟體(韌體)
以下是我們今天的目標
- 無線網路AP SSID帳密設置: iot/chosemaker
- 如果對Web Server部分還不熟悉,請先參考[ESP32教學#6] Arduino建立Web Server做一個HTML客製化網頁,讓Wifi用戶連入
- css代表我們想要的HTML style
- loginIndex為我們XXX.XXX.XXX.XXX的登入首頁,讓使用者打帳號密碼,請對應server.on(“/”
- serverIndex為我們讓選擇BIN檔案和更新的按鈕,請對應server.on(“/serverIndex”
- 使用mDNS功能,讓用戶不需打IP直接打chosemake.local,就可以連到該台IP
- form.userid.value==’admin’ && form.pwd.value==’admin’ 該段為ESP32的登入帳號密碼,也就是admin/admin,由於這是一個範例教學,不在此處討論安全性,你也可以將參數寫在ROM裡面
我們輸入從AP取得的IP或http://chosemaker.local,即可連到該ESP32

我們選擇一個BIN檔並上傳至ESP32,更新成功後,會自動重新開機

Arduino如何產生客製的BIN檔案呢? 首先點選”草稿碼”>>”匯出已編譯的二進為檔” 產生BIN檔,BIN檔案的位置請點選”草稿碼”>>”顯示草稿碼資料夾”,點下就會跳出一個資料夾的視窗


Arduino 範例程式碼如下,請注意
wordpress的bug,它把&轉換成HTML的&
原本HTTPUpload& upload = server.upload();
請修改HTTPUpload& upload = server.upload();
//醬是創客 開發實作的好夥伴
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>
const char* host = "chosemaker";
const char* ssid = "iot";
const char* password = "chosemaker";
WebServer server(80);
String css =
"<style>#file-input,input{width:100%;height:100px;border-radius:5px;margin:10px auto;font-size:16px}"
"input{background:#fff;border:0;padding:0 15px}body{background:#5398D9;font-family:sans-serif;font-size:14px;color:#4d4d4d}"
"#file-input{padding:0;border:2px solid #ddd;line-height:45px;text-align:left;display:block;cursor:pointer}"
"#bar,#prgbar{background-color:#4d4d4d;border-radius:15px}#bar{background-color:#5398D9;width:0%;height:15px}"
"form{background:#EDF259;max-width:300px;margin:80px auto;padding:30px;border-radius:5px;text-align:center}"
".btn{background:#5398D9;color:#EDF259;cursor:pointer}</style>";
String loginIndex =
"<form name='loginForm'>"
"<h2>Chosemaker ESP32 Login</h2>"
"<input name=userid placeholder='Username'> "
"<input name=pwd placeholder='Password' type=Password> "
"<input type=submit onclick=check(this.form) class=btn value=Login></form>"
"<script>"
"function check(form)"
"{"
"if(form.userid.value=='admin' && form.pwd.value=='admin')"
"{"
"window.open('/serverIndex')"
"}"
"else"
"{"
" alert('Error Password or Username')/*displays error message*/"
"}"
"}"
"</script>" + css;
String serverIndex =
"<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
"<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
"<input type='file' name='update'>"
"<input type='submit' value='Update'>"
"<div id='prg'>0%</div>"
"<div id='prgbar'><div id='bar'></div></div></form>"
"<script>"
"$('form').submit(function(e){"
"e.preventDefault();"
"var form = $('#upload_form')[0];"
"var data = new FormData(form);"
" $.ajax({"
"url: '/update',"
"type: 'POST',"
"data: data,"
"contentType: false,"
"processData:false,"
"xhr: function() {"
"var xhr = new window.XMLHttpRequest();"
"xhr.upload.addEventListener('progress', function(evt) {"
"if (evt.lengthComputable) {"
"var per = evt.loaded / evt.total;"
"$('#prg').html('Running ' + Math.round(per*100) + '%');"
"$('#bar').css('width',Math.round(per*100) + '%');"
"}"
"}, false);"
"return xhr;"
"},"
"success:function(d, s) {"
"console.log('success!')"
"},"
"error: function (a, b, c) {"
"}"
"});"
"});"
"</script>" + css;
void setup(void) {
Serial.begin(115200);
// Connect to WiFi network
WiFi.begin(ssid, password);
Serial.println("");
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
//使用mDNS,網址http://chosemaker.local
if (!MDNS.begin(host)) {
Serial.println("Error setting up MDNS responder!");
while (1) {
delay(1000);
}
}
Serial.println("mDNS responder started");
/*return index page which is stored in serverIndex */
server.on("/", HTTP_GET, []() {
server.sendHeader("Connection", "close");
server.send(200, "text/html", loginIndex);
});
server.on("/serverIndex", HTTP_GET, []() {
server.sendHeader("Connection", "close");
server.send(200, "text/html", serverIndex);
});
/*handling uploading firmware file */
server.on("/update", HTTP_POST, []() {
server.sendHeader("Connection", "close");
server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
ESP.restart();
}, []() {
HTTPUpload& upload = server.upload();
if (upload.status == UPLOAD_FILE_START) {
Serial.printf("Update: %s\n", upload.filename.c_str());
if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size
Update.printError(Serial);
}
} else if (upload.status == UPLOAD_FILE_WRITE) {
/* flashing firmware to ESP*/
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
Update.printError(Serial);
}
} else if (upload.status == UPLOAD_FILE_END) {
if (Update.end(true)) { //true to set the size to the current progress
Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
} else {
Update.printError(Serial);
}
}
});
server.begin();
}
void loop(void) {
server.handleClient();
delay(1);
}
Arduino 序列埠監控視窗 輸出如下
Connected to iot IP address: 192.168.2.105 mDNS responder started Update: sketch.ino.node32s.bin Update Success: 758704 Rebooting... ets Jun 8 2016 00:22:57 rst:0xc (SW_CPU_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) configsip: 0, SPIWP:0xee clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 mode:DIO, clock div:1 load:0x3fff0018,len:4 load:0x3fff001c,len:1044 load:0x40078000,len:8896 load:0x40080400,len:5816 entry 0x400806ac .. Connected to iot IP address: 192.168.2.105 mDNS responder started
