醬是創客的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