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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
//醬是創客 開發實作的好夥伴
#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 =
 "<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 序列埠監控視窗 輸出如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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