|
带WiFi功能的控制器如ESP32,都带有一种绝妙的功能就是无线更新固件的能力。这种编程方式称为空中编程(OTA,Over-The-Air)。
ESP32中的OTA编程是什么? OTA编程使您可以通过Wi-Fi更新/上传到ESP32的新程序,而无需通过USB将ESP32连接到计算机。
当没有物理访问ESP模块时,OTA功能就派上用场。此外,它减少了在维护过程中更新每个ESP模块所需的时间。
OTA的一个主要优点是,单个中间位置可以将更新发送给同一网络上的多个ESP。唯一的缺点是,您必须在上传的每个草图中包含一段OTA代码,以便在下一个更新中使用OTA。
在ESP32中实施OTA的方法 在ESP32中实现OTA功能有两种方法。 ● 基本OTA - 使用Arduino IDE提供OTA更新。 ● 网页更新OTA - 更新是通过网页浏览器提供的。 每种方式都有自己的优势,因此您可以选用最适合您项目的其中一种。
3个实现ESP32网页更新OTA的简单步骤 1. 串口上传OTA代码:第一步是上传包含OTA例程的草图。这是一个必要性的步骤,这样您能够通过OTA进行下一次更新/上传。 2. 访问网页浏览器:OTA代码以STA模式创建Web服务器,可以通过网页浏览器访问。登录到网页服务器后,您可以上传新草图。 3. 通过OTA上传新的草图:您可以通过通过网页服务器生成和上传编译的.bin文件将新草图上传到ESP32。
第1步:串口上传OTA代码 ESP32中的出厂镜像中没有OTA升级功能。因此,第一步是通过串口将OTA固件加载到ESP32上。
这是首次更新固件的必要步骤,以便您可以OTA进行下一次更新/上传。
Arduino IDE的ESP32附加组件带有OTA库和OTAWebUpdater示例。您可以通过File > Examples >ArduinoOTA > OTAWebUpdater或通过github访问它。
该默认的OTA Web Updater的UI非常丑陋,因此我们修改了代码以看起来更酷。首先,将ESP32插入计算机,然后下载以下草图。
在上传草图之前,您需要进行一些更改以使其适合您。您需要使用网络凭据修改以下两个变量,以便ESP32可以与现有网络建立连接。 - const char* ssid = "---";
- const char* password = "----";
复制代码完成后,上传草图代码到ESP32。 - #include <WiFi.h>
- #include <WiFiClient.h>
- #include <WebServer.h>
- #include <ESPmDNS.h>
- #include <Update.h>
- const char* host = "esp32";
- const char* ssid = "---";
- const char* password = "----";
- WebServer server(80);
- /* Style */
- String style =
- "<style>#file-input,input{width:100%;height:44px;border-radius:4px;margin:10px auto;font-size:15px}"
- "input{background:#f1f1f1;border:0;padding:0 15px}body{background:#3498db;font-family:sans-serif;font-size:14px;color:#777}"
- "#file-input{padding:0;border:1px solid #ddd;line-height:44px;text-align:left;display:block;cursor:pointer}"
- "#bar,#prgbar{background-color:#f1f1f1;border-radius:10px}#bar{background-color:#3498db;width:0%;height:10px}"
- "form{background:#fff;max-width:258px;margin:75px auto;padding:30px;border-radius:5px;text-align:center}"
- ".btn{background:#3498db;color:#fff;cursor:pointer}</style>";
- /* Login page */
- String loginIndex =
- "<form name=loginForm>"
- "<h1>ESP32 Login</h1>"
- "<input name=userid placeholder='User ID'> "
- "<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')}"
- "}"
- "</script>" + style;
-
- /* Server Index Page */
- 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' id='file' onchange='sub(this)' style=display:none>"
- "<label id='file-input' for='file'> Choose file...</label>"
- "<input type='submit' class=btn value='Update'>"
- "<br><br>"
- "<div id='prg'></div>"
- "<br><div id='prgbar'><div id='bar'></div></div><br></form>"
- "<script>"
- "function sub(obj){"
- "var fileName = obj.value.split('\\\\');"
- "document.getElementById('file-input').innerHTML = ' '+ fileName[fileName.length-1];"
- "};"
- "$('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) + '%');"
- "$('#bar').css('width',Math.round(per*100) + '%');"
- "}"
- "}, false);"
- "return xhr;"
- "},"
- "success:function(d, s) {"
- "console.log('success!') "
- "},"
- "error: function (a, b, c) {"
- "}"
- "});"
- "});"
- "</script>" + style;
- /* 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();
- }
- void loop(void) {
- server.handleClient();
- delay(1);
- }
复制代码
第2步:访问网页服务器 OTA Web Updater草图以STA模式创建Web服务器,该服务器可以通过网页浏览器访问,并用于将新草图上传到您的ESP32。
为了访问网页服务器,请以115200的波特率打开串口监视器。然后按ESP32上的EN按钮。如果一切正常,它将输出从路由器获得的动态IP地址。
接下来,打开浏览器,并将其指向串口显示器上显示的IP地址。ESP32应提供一个网页,要求登录凭据。
输入用户ID和密码: 用户ID:admin 密码:admin
如果您想更改用户ID和密码,请在草图中更改以下代码。 - "if(form.userid.value=='admin' && form.pwd.value=='admin')"
复制代码登录到系统后,您将被重定向到 /ServerIndex页面。此页面使您可以将新草图上传到ESP32。上传的新草图应采用符合的.bin二进制格式。
第3步:上传新草图 您需要在上传的每个草图中添加OTA Web Updater代码。否则,您将失去OTA功能,并且将无法进行OTA下载。因此,建议修改上述代码以包括您的新代码。
例如,我们将在OTA Web Updater代码中包含一个简单的LED闪烁草图。 - #include <WiFi.h>
- #include <WiFiClient.h>
- #include <WebServer.h>
- #include <ESPmDNS.h>
- #include <Update.h>
- const char* host = "esp32";
- const char* ssid = "---";
- const char* password = "----";
- //variabls for blinking an LED with Millis
- const int led = 2; // ESP32 Pin to which onboard LED is connected
- unsigned long previousMillis = 0; // will store last time LED was updated
- const long interval = 1000; // interval at which to blink (milliseconds)
- int ledState = LOW; // ledState used to set the LED
- WebServer server(80);
- /* Style */
- String style =
- "<style>#file-input,input{width:100%;height:44px;border-radius:4px;margin:10px auto;font-size:15px}"
- "input{background:#f1f1f1;border:0;padding:0 15px}body{background:#3498db;font-family:sans-serif;font-size:14px;color:#777}"
- "#file-input{padding:0;border:1px solid #ddd;line-height:44px;text-align:left;display:block;cursor:pointer}"
- "#bar,#prgbar{background-color:#f1f1f1;border-radius:10px}#bar{background-color:#3498db;width:0%;height:10px}"
- "form{background:#fff;max-width:258px;margin:75px auto;padding:30px;border-radius:5px;text-align:center}"
- ".btn{background:#3498db;color:#fff;cursor:pointer}</style>";
- /* Login page */
- String loginIndex =
- "<form name=loginForm>"
- "<h1>ESP32 Login</h1>"
- "<input name=userid placeholder='User ID'> "
- "<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')}"
- "}"
- "</script>" + style;
-
- /* Server Index Page */
- 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' id='file' onchange='sub(this)' style=display:none>"
- "<label id='file-input' for='file'> Choose file...</label>"
- "<input type='submit' class=btn value='Update'>"
- "<br><br>"
- "<div id='prg'></div>"
- "<br><div id='prgbar'><div id='bar'></div></div><br></form>"
- "<script>"
- "function sub(obj){"
- "var fileName = obj.value.split('\\\\');"
- "document.getElementById('file-input').innerHTML = ' '+ fileName[fileName.length-1];"
- "};"
- "$('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) + '%');"
- "$('#bar').css('width',Math.round(per*100) + '%');"
- "}"
- "}, false);"
- "return xhr;"
- "},"
- "success:function(d, s) {"
- "console.log('success!') "
- "},"
- "error: function (a, b, c) {"
- "}"
- "});"
- "});"
- "</script>" + style;
- /* setup function */
- void setup(void) {
- pinMode(led, OUTPUT);
- 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();
- }
- void loop(void) {
- server.handleClient();
- delay(1);
- //loop to blink without delay
- unsigned long currentMillis = millis();
- if (currentMillis - previousMillis >= interval) {
- // save the last time you blinked the LED
- previousMillis = currentMillis;
- // if the LED is off turn it on and vice-versa:
- ledState = not(ledState);
- // set the LED with the ledState of the variable:
- digitalWrite(led, ledState);
- }
- }
复制代码
在Arduino IDE中生成.bin文件 为了将新草图上传到ESP32,我们需要先生成草图的二进制.bin文件。
为此,请转到Sketch > Export compiled Binary
草图成功编译后,就会在草图文件夹中生成.bin文件。 转到Sketch > Show Sketch Folder。
将新的草图通过OTA更新到ESP32 生成.bin文件后,您现在就可以将新草图上传到ESP32。
在浏览器中打开/serverIndex 页面。 单击Choose File… 选择生成的.bin文件,然后单击Update。
在几秒钟内,新草图将上传完成。这时板载的LED灯会开始闪烁。
|