最近客户突然新增一个本地OTA需求,要求能实现让用户通过手机按需对设备进行程序更新,经评估后,考虑直接使用芯片自带的WiFi功能,以实现上电后,5分钟内开启WiFi热点,用于进行OTA升级,具体实现代码如下:
1、WiFi热点配置以及简易网页效果优化(居于esp32 ota demo修改,手机页面效果见下方图片),先贴代码段
wifi_conf.h:
#ifndef _WIFI_CONF_H_
#define _WIFI_CONF_H_
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>
const char* AP_SSID = "FO1_PLUS"; //热点名称
const char* host = "esp32";
const char* ssid = "ssid"; //用户名
const char* password = "password"; //密码
static char web_ack_buf[40];
WebServer server(80);
/*
* Login page
*/
const char* loginIndex =
"<!DOCTYPE html> "
"<html>"
"<head>"
"<title>FO1_PLUS Login</title>"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"> "
"</head>"
"<body>"
"<form name='loginForm'>"
"<div></div>"
"<table width='50%' bgcolor='A09F9F' align='center'>"
"<tr>"
"<td colspan=2>"
"<center><font size=4><b>FO1 PLUS Login Page</b></font></center>"
"<br>"
"</td>"
"<br>"
"<br>"
"</tr>"
"<tr>"
"<td>Username:</td>"
"<td><input type='text' size=15 name='userid'><br></td>"
"<br>"
"<br>"
"</tr>"
"<tr>"
"<td>Password:</td>"
"<td><input type='Password' size=15 name='pwd'><br></td>"
"<br>"
"<br>"
"</tr>"
"<tr>"
"<td colspan=2>"
"<center><input type='submit' size=15 onclick='check(this.form)' value='Login'></center>"
"<br>"
"</td>"
"<br>"
"<br>"
"</tr>"
"</table>"
"</form>"
"</body>"
"</html>"
"<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 =
"<!DOCTYPE html> "
"<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
"<html>"
"<head>"
"<title>FO1_PLUS OTA</title>"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"> "
"</head>"
"<body>"
"<form method='POST' action='/update' enctype='multipart/form-data' id='upload_form'>"
"<div> </div>"
"<table width='50%' bgcolor='A09F9F' align='center'>"
"<tr>"
"<td colspan=2>"
"<center><font size=4><b>FO1 PLUS OTA</b></font></center>"
"<br>"
"</td>"
"<br>"
"<br>"
"</tr>"
"<tr>"
"<td colspan=2>"
"<center><input type='file' size=15 name='update'></center>"
"<br>"
"</td>"
"<br>"
"<br>"
"</tr>"
"<tr>"
"<td colspan=2>"
"<center><input type='submit' size=15 value='Update'></center>"
"<br>"
"</td>"
"<br>"
"<br>"
"</tr>"
"</table>"
"<div id='prg'>progress: 0% 请先选择文件,然后点击“Update”上传更新程序。</div>"
"<div id='prg'><b>注意:</b></div>"
"<div id='prg'>1、升级过程中请勿操作水龙头!</div>"
"<div id='prg'>2、升级过程中请勿操作当前页面,勿息屏,等待文件传输完成!</div>"
"</form>"
"</body>"
"</html>"
"<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>";
#endif /* _WIFI_CONF_H_ */
2、功能实现
2.1、WiFi初始化,开启热点
//wifi热点初始化
void WiFi_OTA_AP_init(){
//WiFi.begin();
//Serial.println("wifi begin");
WiFi.mode(WIFI_AP); //WiFi配置为AP热点模式
WiFi_data.status = 1; //模式状态 0:关闭WiFi 1:AP热点 2:STA终端 3:AP+STA 4:未知
WiFi_data.flag_wifi_symbol = 1;
WiFi_data.flag_ap = 0;
WiFi_data.flag_mdns = 0;
WiFi_data.flag_server = 0;
#if PRINT_U0 != 0 //#endif
Serial.println("WiFi enble by AP");
#endif
}
2.2、关闭热点
//关闭WiFi AP OTA
void closeWiFi() {
WiFi.softAPdisconnect(true);
WiFi.mode(WIFI_OFF);
}
2.3、WiFi热点参数设置与调试代码
//设置wifi热点
uint8_t WiFi_OTA_AP_set(const char* apssid){
if (WiFi.softAP(apssid, ""))
{
//热点开启成功*****************
//WiFi_data.flag_ap = 1; //AP模式下,配网状态 0: 未配置成功 1:配网成功
IPAddress myIP = WiFi.softAPIP();
//打印相关信息
#if PRINT_U0 != 0 //#endif
Serial.print("Soft-AP IP address = ");
Serial.println(myIP);
Serial.println(String("MAC address = ") + WiFi.softAPmacAddress().c_str());
Serial.println("waiting ...");
#endif
return 1;
}else
{
return 0;
}
}
2.4、开启mdns服务
//MDNS服务设置
uint8_t WiFi_MDNS_set(const char* mhost){
/* mdns服务未开启*/
if (!MDNS.begin(mhost))
{ //http://esp32.local
#if PRINT_U0 != 0 //#endif
Serial.println("MDNS begin Error! Try again.");
#endif
return 0;
}
else
{
#if PRINT_U0 != 0 //#endif
Serial.println("mDNS responder started");
#endif
return 1;
}
}
2.5、开启WiFi server,用于对网页操作进行处理,OTA实现的主要代码段:
//Web Server 任务配置
void Server_OTA_set(){
/*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");
if (Update.hasError())
{
//升级出错
OTA_data.status = 3;
OTA_data.cur_ota_time = 10;
server.send(200, "text/plain", "升级失败!");
}
else
{
//升级成功
OTA_data.status = 2;
OTA_data.cur_ota_time = 10;
server.send(200, "text/plain", "升级成功,请关闭页面,等待水龙头重新启动!");
}
//ESP.restart();
},
[]()
{
HTTPUpload &upload = server.upload();
if (upload.status == UPLOAD_FILE_START)
{
//开始升级
OTA_data.status = 1;
OTA_data.cur_ota_time = 100;
#if PRINT_U0 != 0 //#endif
Serial.printf("Update: %s\n", upload.filename.c_str());
#endif
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)
{
OTA_data.status = 3;
OTA_data.cur_ota_time = 10;
Update.printError(Serial);
#if PRINT_U0 != 0 //#endif
Serial.printf("Update writed: %u\n", upload.currentSize);
#endif
}
}
else if (upload.status == UPLOAD_FILE_END)
{
if (Update.end(true))
{ //true to set the size to the current progress
//sprintf(web_ack_buf, "上传成功,传输%u字节,等待重启...", upload.totalSize);
OTA_data.status = 2;
OTA_data.cur_ota_time = 10;
server.send(200, "text/plain", "升级成功,请关闭页面,等待水龙头重新启动!");
#if PRINT_U0 != 0 //#endif
Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
#endif
//ESP.restart();
}
else
{
OTA_data.status = 3;
OTA_data.cur_ota_time = 10;
Update.printError(Serial);
}
}
});
server.begin();
}
2.6、超时处理机制
没有执行ota操作,超时后关闭WiFi;
//WiFi OTA 超时处理
void WiFi_OTA_timeout(){
if (OTA_data.cur_ota_time>0) OTA_data.cur_ota_time--;
if (WiFi_data.ota_time_out>0)
{
WiFi_data.ota_time_out--;
}else
{
WiFi_data.ota_time_out = 0;
}
}
2.7、在核心core0创建一个task任务,让任务跑起来,以及超时结束任务;
//WIFI任务
void Task_WiFi_Sub(void *pvParameters)
{
(void)pvParameters;
// Connect to WiFi network
//WiFi.begin(ssid, password);
WiFi_OTA_AP_init(); //开启WiFi热点,用于OTA升级
for (;;) // A Task shall never return or exit.
{
if (WiFi_data.ota_time_out == 0)
{
if (WiFi_data.status == 1 && OTA_data.status != 1)
{
//wifi开启,且现在没有在执行OTA任务
server.close(); //server.stop(); //关闭WebServer;
MDNS.end();
closeWiFi();
WiFi_data.status = 0;
WiFi_data.flag_wifi_symbol = 0;
#if PRINT_U0 != 0 //#endif
Serial.println("timeout close WiFi");
#endif
vTaskDelete(NULL); // 删除当前任务
}
}
else
{
if (WiFi_data.flag_server == 0)
{
/* web server 未开启 */
if (WiFi_data.flag_mdns == 0)
{
/* mdns 服务未开启*/
if (WiFi_data.flag_ap == 0)
{
/* AP热点未开启成功 */
WiFi_data.flag_ap = WiFi_OTA_AP_set(AP_SSID);
}
else
{
/* AP热点已开启 */
WiFi_data.flag_mdns = WiFi_MDNS_set(host); //开启MDNS
}
}
else
{
/* mdns 服务未开启已开启 */
Server_OTA_set(); //设置并启用OTA Server
WiFi_data.flag_server = 1; //置位标志位
WiFi_data.flag_wifi_symbol = 2;
}
vTaskDelay(1000); //N ms检测一次
}
else
{
/* web server 已开启 */
server.handleClient();
vTaskDelay(10); //N ms检测一次
}
}
}
}
实现功能代码如上,代码中注释信息比较详细,代码段功能就不再多讲了,看下运行效果:
设备上效果演示,小视频(部分涉及客户信息的演示截取掉了):
esp32s3