OTAWebUpdater

はじめに

Webを使ったOTAアップデート(Over The Air: この場合無線でスケッチを書き込むこと)の使い方です。

動作確認は、ESP32 2.0.2のときのものです。

プログラム

定義等

 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
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>

const char* host = "esp32";
const char* ssid = "xxx";
const char* password = "xxxx";

WebServer server(80);

/*
 * Login page
 */

const char* loginIndex =
 "<form name='loginForm'>"
    "<table width='20%' bgcolor='A09F9F' align='center'>"
        "<tr>"
            "<td colspan=2>"
                "<center><font size=4><b>ESP32 Login Page</b></font></center>"
                "<br>"
            "</td>"
            "<br>"
            "<br>"
        "</tr>"
        "<tr>"
             "<td>Username:</td>"
             "<td><input type='text' size=25 name='userid'><br></td>"
        "</tr>"
        "<br>"
        "<br>"
        "<tr>"
            "<td>Password:</td>"
            "<td><input type='Password' size=25 name='pwd'><br></td>"
            "<br>"
            "<br>"
        "</tr>"
        "<tr>"
            "<td><input type='submit' onclick='check(this.form)' value='Login'></td>"
        "</tr>"
    "</table>"
"</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>";

/*
 * Server Index Page
 */

const char* 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'>"
    "</form>"
 "<div id='prg'>progress: 0%</div>"
 "<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('progress: ' + Math.round(per*100) + '%');"
  "}"
  "}, false);"
  "return xhr;"
  "},"
  "success:function(d, s) {"
  "console.log('success!')"
 "},"
 "error: function (a, b, c) {"
 "}"
 "});"
 "});"
 "</script>";

hostは、mDNSで登録するESP32のホスト名です。

ssidとpasswordは、WiFiアクセス用のSSID/パスフレーズです。

WebServer::WebServer()型の変数serverを定義します。ポート番号80で待ち受けます。

loginIndexはログインページで表示するHTMLデータ/JavaScriptプログラム、serverIndexはファイルを選択、送信する際のHTMLデータ/JavaScriptプログラムです。

setup()

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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/*
 * setup function
 */
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());

  /*use mdns for host name resolution*/
  if (!MDNS.begin(host)) { //http://esp32.local
    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();
}

107行目から114行目はWiFiアクセスポイントへの接続です。

WiFi.begin()でアクセスポイントに接続します。WiFi.status()は、現在の接続状態を返却します。アクセスポイントに接続しているときは、WL_CONNECTEDが返ってきます。

MDNS.begin()で、mDNSにホスト名を登録します。

130行目から162行目はHTTPアクセス時のハンドラ定義です。server.on()で、指定したURIにアクセスがあった時に呼び出す関数を登録します。server.sendHeader()でHTTPヘッダを追加し、server.send()で、実際にレスポンスを返します。

130行目は、/にアクセスがあった場合に呼び出される関数として、loginIndex()を登録します。この関数は、ログインページを表示します。下記に、画面を示します。

mDNSの機能を利用しているので、IPアドレス指定ではなく、http://esp32.local/ という指定でESP32にアクセスすることができています。

loginIndexで設定したHTML/JavaScriptプログラムでは、Username、Passwordともadminを入力することで、/serverIndexに遷移することになっています。ただし、セッション等は利用していないので、/serverIndexに直接アクセスすれば、ログインページをスキップできてしまいますが…

134行目は、ログイン後に表示する/serverIndexを登録しています。この関数は、アップロードするプログラムの選択と、アップロード状態を示すページです。以下に、画面を示します。

このページに遷移直後は、ファイルは選択されておらず、ファイルの送信の進捗率も0%です。

139行目から162行目は、/updateに対するPOSTメソッドの定義を行っています。これは、serverIndexのページで、Updateボタンを押したときに呼び出されます。

第3引数の関数(138行目から141行目)はPOSTメソッドでリクエストボディが送信された後に呼び出される関数です。Update.hasError()で、更新にエラーがあるかどうかを判定しエラーがある場合はFAIL、ない場合は、OKをクライアントに返却します。その後、ESP.restart()でESP32を再起動します。

第4引数の関数は、リクエストボディを受信中に実行する関数です。

server.upload()により、リクエストボディ受信中の状態を取得します。

状態が、UPLOAD_FILE_STARTの場合は、Update.begin()により、スケッチの更新を開始します。更新の開始に失敗したときは、Update.printError()により、エラー情報を出力します。

状態が、UPLOAD_FILE_WRITEの場合は、Update.write()を使い、スケッチを書き込みます。

状態が、UPLOAD_FILE_ENDの場合は、Update.end()で、スケッチの更新を終了します。

スケッチの更新中は、以下のように更新状況を表示します(これは、ESP32側の処理ではなく、ブラウザ側の処理です)。

更新が完了すると、progressが100%になって終了します。

最後に、server.begin()で、ウェブサーバを開始します。

loop()

166
167
168
169
void loop(void) {
  server.handleClient();
  delay(1);
}

server.handleClient()で、HTTPリクエストを処理します。

バージョン

Hardware:ESP-WROOM-32
Software:Arduino core for the ESP32 2.0.4

最終更新日

September 4, 2022

inserted by FC2 system