风筝
发表于: 2022-11-7 20:49:25 | 显示全部楼层

带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。

ESP32-Over-The-Air-OTA-Web-Updater-Working.jpg


第1步:串口上传OTA代码

ESP32中的出厂镜像中没有OTA升级功能。因此,第一步是通过串口将OTA固件加载到ESP32上。


这是首次更新固件的必要步骤,以便您可以OTA进行下一次更新/上传。


Arduino IDE的ESP32附加组件带有OTA库和OTAWebUpdater示例。您可以通过File > Examples >ArduinoOTA > OTAWebUpdater或通过github访问它。


该默认的OTA Web Updater的UI非常丑陋,因此我们修改了代码以看起来更酷。首先,将ESP32插入计算机,然后下载以下草图。


在上传草图之前,您需要进行一些更改以使其适合您。您需要使用网络凭据修改以下两个变量,以便ESP32可以与现有网络建立连接。

  1. const char* ssid = "---";
  2. const char* password = "----";
复制代码

完成后,上传草图代码到ESP32。

  1. #include <WiFi.h>
  2. #include <WiFiClient.h>
  3. #include <WebServer.h>
  4. #include <ESPmDNS.h>
  5. #include <Update.h>

  6. const char* host = "esp32";
  7. const char* ssid = "---";
  8. const char* password = "----";

  9. WebServer server(80);

  10. /* Style */
  11. String style =
  12. "<style>#file-input,input{width:100%;height:44px;border-radius:4px;margin:10px auto;font-size:15px}"
  13. "input{background:#f1f1f1;border:0;padding:0 15px}body{background:#3498db;font-family:sans-serif;font-size:14px;color:#777}"
  14. "#file-input{padding:0;border:1px solid #ddd;line-height:44px;text-align:left;display:block;cursor:pointer}"
  15. "#bar,#prgbar{background-color:#f1f1f1;border-radius:10px}#bar{background-color:#3498db;width:0%;height:10px}"
  16. "form{background:#fff;max-width:258px;margin:75px auto;padding:30px;border-radius:5px;text-align:center}"
  17. ".btn{background:#3498db;color:#fff;cursor:pointer}</style>";

  18. /* Login page */
  19. String loginIndex =
  20. "<form name=loginForm>"
  21. "<h1>ESP32 Login</h1>"
  22. "<input name=userid placeholder='User ID'> "
  23. "<input name=pwd placeholder=Password type=Password> "
  24. "<input type=submit onclick=check(this.form) class=btn value=Login></form>"
  25. "<script>"
  26. "function check(form) {"
  27. "if(form.userid.value=='admin' && form.pwd.value=='admin')"
  28. "{window.open('/serverIndex')}"
  29. "else"
  30. "{alert('Error Password or Username')}"
  31. "}"
  32. "</script>" + style;

  33. /* Server Index Page */
  34. String serverIndex =
  35. "<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
  36. "<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
  37. "<input type='file' name='update' id='file' onchange='sub(this)' style=display:none>"
  38. "<label id='file-input' for='file'>   Choose file...</label>"
  39. "<input type='submit' class=btn value='Update'>"
  40. "<br><br>"
  41. "<div id='prg'></div>"
  42. "<br><div id='prgbar'><div id='bar'></div></div><br></form>"
  43. "<script>"
  44. "function sub(obj){"
  45. "var fileName = obj.value.split('\\\\');"
  46. "document.getElementById('file-input').innerHTML = '   '+ fileName[fileName.length-1];"
  47. "};"
  48. "$('form').submit(function(e){"
  49. "e.preventDefault();"
  50. "var form = $('#upload_form')[0];"
  51. "var data = new FormData(form);"
  52. "$.ajax({"
  53. "url: '/update',"
  54. "type: 'POST',"
  55. "data: data,"
  56. "contentType: false,"
  57. "processData:false,"
  58. "xhr: function() {"
  59. "var xhr = new window.XMLHttpRequest();"
  60. "xhr.upload.addEventListener('progress', function(evt) {"
  61. "if (evt.lengthComputable) {"
  62. "var per = evt.loaded / evt.total;"
  63. "$('#prg').html('progress: ' + Math.round(per*100) + '%');"
  64. "$('#bar').css('width',Math.round(per*100) + '%');"
  65. "}"
  66. "}, false);"
  67. "return xhr;"
  68. "},"
  69. "success:function(d, s) {"
  70. "console.log('success!') "
  71. "},"
  72. "error: function (a, b, c) {"
  73. "}"
  74. "});"
  75. "});"
  76. "</script>" + style;

  77. /* setup function */
  78. void setup(void) {
  79.   Serial.begin(115200);

  80.   // Connect to WiFi network
  81.   WiFi.begin(ssid, password);
  82.   Serial.println("");

  83.   // Wait for connection
  84.   while (WiFi.status() != WL_CONNECTED) {
  85.     delay(500);
  86.     Serial.print(".");
  87.   }
  88.   Serial.println("");
  89.   Serial.print("Connected to ");
  90.   Serial.println(ssid);
  91.   Serial.print("IP address: ");
  92.   Serial.println(WiFi.localIP());

  93.   /*use mdns for host name resolution*/
  94.   if (!MDNS.begin(host)) { //http://esp32.local
  95.     Serial.println("Error setting up MDNS responder!");
  96.     while (1) {
  97.       delay(1000);
  98.     }
  99.   }
  100.   Serial.println("mDNS responder started");
  101.   /*return index page which is stored in serverIndex */
  102.   server.on("/", HTTP_GET, []() {
  103.     server.sendHeader("Connection", "close");
  104.     server.send(200, "text/html", loginIndex);
  105.   });
  106.   server.on("/serverIndex", HTTP_GET, []() {
  107.     server.sendHeader("Connection", "close");
  108.     server.send(200, "text/html", serverIndex);
  109.   });
  110.   /*handling uploading firmware file */
  111.   server.on("/update", HTTP_POST, []() {
  112.     server.sendHeader("Connection", "close");
  113.     server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
  114.     ESP.restart();
  115.   }, []() {
  116.     HTTPUpload& upload = server.upload();
  117.     if (upload.status == UPLOAD_FILE_START) {
  118.       Serial.printf("Update: %s\n", upload.filename.c_str());
  119.       if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size
  120.         Update.printError(Serial);
  121.       }
  122.     } else if (upload.status == UPLOAD_FILE_WRITE) {
  123.       /* flashing firmware to ESP*/
  124.       if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
  125.         Update.printError(Serial);
  126.       }
  127.     } else if (upload.status == UPLOAD_FILE_END) {
  128.       if (Update.end(true)) { //true to set the size to the current progress
  129.         Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
  130.       } else {
  131.         Update.printError(Serial);
  132.       }
  133.     }
  134.   });
  135.   server.begin();
  136. }

  137. void loop(void) {
  138.   server.handleClient();
  139.   delay(1);
  140. }
复制代码

第2步:访问网页服务器

OTA Web Updater草图以STA模式创建Web服务器,该服务器可以通过网页浏览器访问,并用于将新草图上传到您的ESP32。


为了访问网页服务器,请以115200的波特率打开串口监视器。然后按ESP32上的EN按钮。如果一切正常,它将输出从路由器获得的动态IP地址。

Note-Down-IP-Address-Allotted-to-ESP32.jpg


接下来,打开浏览器,并将其指向串口显示器上显示的IP地址。ESP32应提供一个网页,要求登录凭据。

Access-ESP32-OTA-Web-Server.jpg


输入用户ID和密码:

用户ID:admin

密码:admin


如果您想更改用户ID和密码,请在草图中更改以下代码。

  1. "if(form.userid.value=='admin' && form.pwd.value=='admin')"
复制代码

登录到系统后,您将被重定向到 /ServerIndex页面。此页面使您可以将新草图上传到ESP32。上传的新草图应采用符合的.bin二进制格式。


第3步:上传新草图

您需要在上传的每个草图中添加OTA Web Updater代码。否则,您将失去OTA功能,并且将无法进行OTA下载。因此,建议修改上述代码以包括您的新代码。


例如,我们将在OTA Web Updater代码中包含一个简单的LED闪烁草图。

  1. #include <WiFi.h>
  2. #include <WiFiClient.h>
  3. #include <WebServer.h>
  4. #include <ESPmDNS.h>
  5. #include <Update.h>

  6. const char* host = "esp32";
  7. const char* ssid = "---";
  8. const char* password = "----";

  9. //variabls for blinking an LED with Millis
  10. const int led = 2; // ESP32 Pin to which onboard LED is connected
  11. unsigned long previousMillis = 0;  // will store last time LED was updated
  12. const long interval = 1000;  // interval at which to blink (milliseconds)
  13. int ledState = LOW;  // ledState used to set the LED

  14. WebServer server(80);

  15. /* Style */
  16. String style =
  17. "<style>#file-input,input{width:100%;height:44px;border-radius:4px;margin:10px auto;font-size:15px}"
  18. "input{background:#f1f1f1;border:0;padding:0 15px}body{background:#3498db;font-family:sans-serif;font-size:14px;color:#777}"
  19. "#file-input{padding:0;border:1px solid #ddd;line-height:44px;text-align:left;display:block;cursor:pointer}"
  20. "#bar,#prgbar{background-color:#f1f1f1;border-radius:10px}#bar{background-color:#3498db;width:0%;height:10px}"
  21. "form{background:#fff;max-width:258px;margin:75px auto;padding:30px;border-radius:5px;text-align:center}"
  22. ".btn{background:#3498db;color:#fff;cursor:pointer}</style>";

  23. /* Login page */
  24. String loginIndex =
  25. "<form name=loginForm>"
  26. "<h1>ESP32 Login</h1>"
  27. "<input name=userid placeholder='User ID'> "
  28. "<input name=pwd placeholder=Password type=Password> "
  29. "<input type=submit onclick=check(this.form) class=btn value=Login></form>"
  30. "<script>"
  31. "function check(form) {"
  32. "if(form.userid.value=='admin' && form.pwd.value=='admin')"
  33. "{window.open('/serverIndex')}"
  34. "else"
  35. "{alert('Error Password or Username')}"
  36. "}"
  37. "</script>" + style;

  38. /* Server Index Page */
  39. String serverIndex =
  40. "<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
  41. "<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
  42. "<input type='file' name='update' id='file' onchange='sub(this)' style=display:none>"
  43. "<label id='file-input' for='file'>   Choose file...</label>"
  44. "<input type='submit' class=btn value='Update'>"
  45. "<br><br>"
  46. "<div id='prg'></div>"
  47. "<br><div id='prgbar'><div id='bar'></div></div><br></form>"
  48. "<script>"
  49. "function sub(obj){"
  50. "var fileName = obj.value.split('\\\\');"
  51. "document.getElementById('file-input').innerHTML = '   '+ fileName[fileName.length-1];"
  52. "};"
  53. "$('form').submit(function(e){"
  54. "e.preventDefault();"
  55. "var form = $('#upload_form')[0];"
  56. "var data = new FormData(form);"
  57. "$.ajax({"
  58. "url: '/update',"
  59. "type: 'POST',"
  60. "data: data,"
  61. "contentType: false,"
  62. "processData:false,"
  63. "xhr: function() {"
  64. "var xhr = new window.XMLHttpRequest();"
  65. "xhr.upload.addEventListener('progress', function(evt) {"
  66. "if (evt.lengthComputable) {"
  67. "var per = evt.loaded / evt.total;"
  68. "$('#prg').html('progress: ' + Math.round(per*100) + '%');"
  69. "$('#bar').css('width',Math.round(per*100) + '%');"
  70. "}"
  71. "}, false);"
  72. "return xhr;"
  73. "},"
  74. "success:function(d, s) {"
  75. "console.log('success!') "
  76. "},"
  77. "error: function (a, b, c) {"
  78. "}"
  79. "});"
  80. "});"
  81. "</script>" + style;

  82. /* setup function */
  83. void setup(void) {

  84. pinMode(led,  OUTPUT);

  85.   Serial.begin(115200);

  86.   // Connect to WiFi network
  87.   WiFi.begin(ssid, password);
  88.   Serial.println("");

  89.   // Wait for connection
  90.   while (WiFi.status() != WL_CONNECTED) {
  91.     delay(500);
  92.     Serial.print(".");
  93.   }
  94.   Serial.println("");
  95.   Serial.print("Connected to ");
  96.   Serial.println(ssid);
  97.   Serial.print("IP address: ");
  98.   Serial.println(WiFi.localIP());

  99.   /*use mdns for host name resolution*/
  100.   if (!MDNS.begin(host)) { //http://esp32.local
  101.     Serial.println("Error setting up MDNS responder!");
  102.     while (1) {
  103.       delay(1000);
  104.     }
  105.   }
  106.   Serial.println("mDNS responder started");
  107.   /*return index page which is stored in serverIndex */
  108.   server.on("/", HTTP_GET, []() {
  109.     server.sendHeader("Connection", "close");
  110.     server.send(200, "text/html", loginIndex);
  111.   });
  112.   server.on("/serverIndex", HTTP_GET, []() {
  113.     server.sendHeader("Connection", "close");
  114.     server.send(200, "text/html", serverIndex);
  115.   });
  116.   /*handling uploading firmware file */
  117.   server.on("/update", HTTP_POST, []() {
  118.     server.sendHeader("Connection", "close");
  119.     server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
  120.     ESP.restart();
  121.   }, []() {
  122.     HTTPUpload& upload = server.upload();
  123.     if (upload.status == UPLOAD_FILE_START) {
  124.       Serial.printf("Update: %s\n", upload.filename.c_str());
  125.       if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size
  126.         Update.printError(Serial);
  127.       }
  128.     } else if (upload.status == UPLOAD_FILE_WRITE) {
  129.       /* flashing firmware to ESP*/
  130.       if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
  131.         Update.printError(Serial);
  132.       }
  133.     } else if (upload.status == UPLOAD_FILE_END) {
  134.       if (Update.end(true)) { //true to set the size to the current progress
  135.         Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
  136.       } else {
  137.         Update.printError(Serial);
  138.       }
  139.     }
  140.   });
  141.   server.begin();
  142. }

  143. void loop(void) {
  144.   server.handleClient();
  145.   delay(1);

  146. //loop to blink without delay
  147. unsigned long currentMillis = millis();
  148. if (currentMillis - previousMillis >= interval) {
  149. // save the last time you blinked the LED
  150. previousMillis = currentMillis;
  151. // if the LED is off turn it on and vice-versa:
  152. ledState = not(ledState);
  153. // set the LED with the ledState of the variable:
  154. digitalWrite(led,  ledState);
  155. }

  156. }
复制代码

在Arduino IDE中生成.bin文件

为了将新草图上传到ESP32,我们需要先生成草图的二进制.bin文件。


为此,请转到Sketch > Export compiled Binary

Exporting-Compiled-Binary-of-a-Program-In-Arduino-IDE.jpg

草图成功编译后,就会在草图文件夹中生成.bin文件。 转到Sketch > Show Sketch Folder

Open-Sketch-Folder-From-Arduino-IDE.jpg


将新的草图通过OTA更新到ESP32

生成.bin文件后,您现在就可以将新草图上传到ESP32。


在浏览器中打开/serverIndex 页面。 单击Choose File… 选择生成的.bin文件,然后单击Update。

Access-ServerIndex-Page-To-Upload-OTA-Updates.jpg


在几秒钟内,新草图将上传完成。这时板载的LED灯会开始闪烁。


跳转到指定楼层
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

主题 54 | 回复: 107



手机版|

GMT+8, 2024-5-10 03:31 , Processed in 0.046685 second(s), 9 queries , Gzip On, MemCache On. Powered by Discuz! X3.5

YiBoard一板网 © 2015-2022 地址:河北省石家庄市长安区高营大街 ( 冀ICP备18020117号 )

快速回复 返回顶部 返回列表