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

設備:
安信可NodeMCU-32S #露天拍賣 #蝦皮購物

我們輸入從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' &amp;&amp; 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&amp; 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