Commit 10c9f727 authored by 李延凯's avatar 李延凯

feat: 添加 WiFi AP 模式代码

parent f2a4f933
......@@ -2,5 +2,7 @@
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
set(EXTRA_COMPONENT_DIRS "./source/http_user" "./source/wifi_user")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(QJ500-7C)
idf_component_register(SRCS "http_service.c"
INCLUDE_DIRS "."
PRIV_REQUIRES esp_http_server app_update
EMBED_FILES "html/favicon.ico" "html/ota_app.html")
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<!-- 页面标题 -->
<meta name="head_meta_name" content="width=device-width, initial-scale=1" charset="utf-8">
<title>应用程序更新</title>
<script>
window.addEventListener("DOMContentLoaded", (event) => {
/* 加载完成后直接触发下拉菜单的点击动作 */
document.getElementById("id_select_partition").dispatchEvent(new Event('click'));
});
</script>
</head>
<body>
<!-- 小标题 -->
<h2>应用程序更新</h2>
<hr>
<!-- 返回首页超链接 -->
<a href="javascript:void(0);" onclick="index_page()" style="font-size: smaller;">[返回首页]</a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a href="javascript:void(0);" onclick="reset_page()" style="font-size: smaller;">[重置页面]</a><br><br>
<!-- 下拉菜单 -->
<label for="lable_partition">更新程序:</label>
<select id="id_select_partition" name="name_select_part" onclick="print_expect_file_name()">
<option selected value="0">-</option>
<option value="1">ESP32-D0WDR2-V3</option>
<option value="2">N32G031C8L7</option>
</select><br><br>
<!-- 选择文本按钮 -->
<label for="label_file">选择文件:</label>
<input id="id_input_file" type="file" accept=".bin" onchange="get_file_info()">
<br><br>
<!-- 上传操作 -->
<label for="label_operation">执行操作:</label>
<button id="id_button_ota_start" type="button" onclick="ota_start()">开始升级</button>
<br><br>
<!-- 进度显示 -->
<label for="lable_ota_percent">进度显示:</label>
<input id="id_input_ota_percent" type="text" value="0.00%" style="width: 75px;border: none;font-size:large;">
<progress id="id_progress_ota" value="0" max="100"></progress>
<br><br>
<hr>
<p id="id_p_select_file_name"></p>
<p id="id_p_select_file_size"></p>
<p id="id_p_ota_start"></p>
<p id="id_p_ota_result"></p>
<script>
function index_page() {
window.location.href = "http://192.168.4.1/";
}
function reset_page() {
location.replace(location.href);
}
function print_expect_file_name() {
var select_partition = document.getElementById("id_select_partition").value;
document.getElementById("id_button_ota_start").disabled = true;
if (select_partition == "0") {
document.getElementById("id_input_file").disabled = true;
}
else {
document.getElementById("id_input_file").disabled = false;
}
}
function get_file_size(size) {
var unit = 1024.00;
if (size < unit) {
return size + "B";
}
if (size < Math.pow(unit, 2)) {
return (size / unit).toFixed(2) + "KB";
}
if (size < Math.pow(unit, 3)) {
return (size / Math.pow(unit, 2)).toFixed(2) + "MB";
}
}
function get_file_info() {
var select_partition = document.getElementById("id_select_partition").value;
var select_file = document.getElementById("id_input_file").files[0];
var select_file_name = select_file.name;
var select_file_size = select_file.size;
var app_size;
var show_info;
if (select_partition == "1") {
app_size = 3072 * 1024;
show_info = 1;
}
else if (select_partition == "2") {
app_size = 64 * 1024;
show_info = 1;
}
else {
app_name = "";
app_size = 0;
show_info = 0;
}
if (show_info == 1) {
if (select_file_size > app_size) {
alert("超出最大限制(" + app_size / 1024 + "KB)\nn请重新选择文件");
document.getElementById("id_button_ota_start").disabled = true;
}
else if (select_file_size == 0) {
alert('空文件\nn请重新选择文件');
document.getElementById("id_button_ota_start").disabled = true;
}
else {
document.getElementById("id_button_ota_start").disabled = false;
}
document.getElementById("id_p_select_file_name").innerHTML = "已加载文件名称:" + select_file_name;
document.getElementById("id_p_select_file_size").innerHTML = "已加载文件大小:" + get_file_size(select_file_size);
}
}
function ota_start() {
var select_partition = document.getElementById("id_select_partition").value;
var file_path = document.getElementById("id_input_file").files[0].name;
var ota_path;
if (select_partition == "1") {
ota_path = "/ota/app/esp/" + file_path;
}
else if (select_partition == "2") {
ota_path = "/ota/app/nation/" + file_path;
}
else
{
}
if (typeof (ota_path) != "undefined") {
document.getElementById("id_p_ota_start").innerHTML = "锁定页面,请务必保持WiFi在线,正在上传...";
document.getElementById("id_input_file").disabled = true;
document.getElementById("id_button_ota_start").disabled = true;
var file = document.getElementById("id_input_file").files[0];
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function () {
if (xhttp.readyState == 4) {
if (xhttp.status == 200) {
document.getElementById("id_p_ota_result").innerHTML = "上传成功!";
} else if (xhttp.status == 0) {
document.getElementById("id_p_ota_result").innerHTML = "服务器断开!<br><br>请检查服务器是否在线,刷新页面后重新上传...";
} else {
alert(xhttp.status + " Error!\n" + xhttp.responseText);
}
}
};
let progress_bar = document.getElementById("id_progress_ota");
xhttp.upload.onprogress = function (e) {
var percent_value = (event.loaded * 100.00) / (event.total * 1.00);
percent_value = percent_value.toFixed(2);
progress_bar.value = Math.round(percent_value);
document.getElementById("id_input_ota_percent").value = percent_value + "%";
}
xhttp.open("POST", ota_path, true);
xhttp.send(file);
}
}
</script>
</body>
</html>
\ No newline at end of file
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/param.h>
#include "esp_log.h"
#include "esp_http_server.h"
#include "esp_ota_ops.h"
#include "http_service.h"
#define MAX_CACHE_SIZE 1024u // HTTP服务接收数据的最大缓存大小
#define MAX_RETRY_COUNT 2 // HTTP服务超时后的最大重试次数
static const char *TAG = "http_service";
httpd_handle_t http_server = NULL;
static void http_resp_favicon_ico(httpd_req_t *req)
{
extern const unsigned char favicon_ico_start[] asm("_binary_favicon_ico_start");
extern const unsigned char favicon_ico_end[] asm("_binary_favicon_ico_end");
const size_t favicon_ico_size = (favicon_ico_end - favicon_ico_start);
ESP_LOGI(TAG, "---- 下载网站图标");
httpd_resp_set_type(req, "image/x-icon");
httpd_resp_send(req, (const char *)favicon_ico_start, favicon_ico_size);
}
static void http_resp_index_html(httpd_req_t *req)
{
extern const unsigned char index_html_start[] asm("_binary_index_html_start");
extern const unsigned char index_html_end[] asm("_binary_index_html_end");
const size_t index_html_size = (index_html_end - index_html_start);
ESP_LOGI(TAG, "---- 固件升级首页");
httpd_resp_set_type(req, "text/HTML");
httpd_resp_send(req, (const char *)index_html_start, index_html_size);
}
static void http_resp_ota_app_html(httpd_req_t *req)
{
extern const unsigned char ota_app_html_start[] asm("_binary_ota_app_html_start");
extern const unsigned char ota_app_html_end[] asm("_binary_ota_app_html_end");
const size_t ota_app_html_size = (ota_app_html_end - ota_app_html_start);
ESP_LOGI(TAG, "---- 进入更新程序页面");
httpd_resp_set_type(req, "text/HTML");
httpd_resp_send(req, (const char *)ota_app_html_start, ota_app_html_size);
}
static esp_err_t http_resp_handler(httpd_req_t *req)
{
const char *uri_get = req->uri;
ESP_LOGI(TAG, "---- request uri: http://192.168.4.1%s", req->uri);
if (strcmp(uri_get, "/") == 0)
{
http_resp_ota_app_html(req);
}
else if (strcmp(uri_get, "/favicon.ico") == 0)
{
http_resp_favicon_ico(req);
}
else if (strcmp(uri_get, "/ota/app") == 0)
{
http_resp_ota_app_html(req);
}
else
{
ESP_LOGI(TAG, "---- unknown uri, back to index page");
http_resp_ota_app_html(req);
}
return ESP_OK;
}
static esp_err_t http_ota_app_handler(httpd_req_t *req)
{
int ret = 0;
int remaining = req->content_len;
int err_timeout_retry_cnt = 0;
ESP_LOGI(TAG, "---- expect ota data length(bytes): %d", remaining);
esp_ota_handle_t app_update_handle = 0;
const esp_partition_t *app_update_partition = esp_ota_get_next_update_partition(NULL);
esp_err_t err = esp_ota_begin(app_update_partition, OTA_WITH_SEQUENTIAL_WRITES, &app_update_handle);
if (err == ESP_OK)
{
char *http_buffer = (char *)malloc(MAX_CACHE_SIZE);
while (remaining > 0)
{
/* Read the data for the request */
if ((ret = httpd_req_recv(req, http_buffer, MIN(remaining, sizeof(http_buffer)))) <= 0)
{
if (ret == HTTPD_SOCK_ERR_TIMEOUT)
{
err_timeout_retry_cnt++;
if (err_timeout_retry_cnt >= MAX_RETRY_COUNT)
{
httpd_resp_send_408(req);
ESP_LOGE(TAG, "---- get ota data timeout, stop ota process, system will reboot after one second.");
break;
}
else
{
/* Retry receiving if timeout occurred */
ESP_LOGW(TAG, "---- get ota data timeout, try it again.");
continue;
}
}
else
{
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to receive file, system will reboot after one second.");
ESP_LOGE(TAG, "---- get ota data error, stop ota process, system will reboot after one second.");
break;
}
}
err_timeout_retry_cnt = 0;
err = esp_ota_write(app_update_handle, http_buffer, ret);
if (err != ESP_OK)
{
ESP_LOGI(TAG, "---- write ota data failed, error message:(%s), system will reboot after one second.", esp_err_to_name(err));
break;
}
else
{
remaining -= ret;
}
}
free(http_buffer);
if (remaining == 0)
{
err = esp_ota_end(app_update_handle);
if (err == ESP_OK)
{
esp_err_t err = esp_ota_set_boot_partition(app_update_partition);
if (err == ESP_OK)
{
if (err == ESP_OK)
{
ESP_LOGI(TAG, "---- ota process: 100.00%%");
ESP_LOGI(TAG, "---- ota success, reboot after one seconds");
}
else
{
ESP_LOGE(TAG, "---- ota success, but error occurred when write firmware info into eeprom.");
}
}
else
{
ESP_LOGE(TAG, "---- set boot partition failed, error message:(%s), system will reboot after one second.", esp_err_to_name(err));
}
}
else
{
ESP_LOGE(TAG, "---- verify program fail, error message:(%s), system will reboot after one second.", esp_err_to_name(err));
}
}
else
{
ESP_LOGE(TAG, "---- ota data lost, stop ota process, system will reboot after one second.");
}
}
else
{
ESP_LOGE(TAG, "---- can not find next app partition");
}
httpd_resp_send_chunk(req, NULL, 0);
vTaskDelay(pdMS_TO_TICKS(1000));
esp_restart();
return ESP_OK;
}
httpd_handle_t start_webserver(void)
{
httpd_handle_t server = NULL;
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.lru_purge_enable = true;
config.uri_match_fn = httpd_uri_match_wildcard;
// Start the httpd server
ESP_LOGI(TAG, "---- httpd server port: '%d'", config.server_port);
if (httpd_start(&server, &config) == ESP_OK)
{
// Set URI handlers
ESP_LOGI(TAG, "---- Set URI handlers");
// 注册客户端请求行为
httpd_uri_t http_resp = {
.uri = "/*",
.method = HTTP_GET,
.handler = http_resp_handler,
.user_ctx = NULL};
httpd_register_uri_handler(server, &http_resp);
// 注册 ota_app
httpd_uri_t http_ota_app = {
.uri = "/ota/app/*",
.method = HTTP_POST,
.handler = http_ota_app_handler,
.user_ctx = NULL};
httpd_register_uri_handler(server, &http_ota_app);
return server;
}
ESP_LOGI(TAG, "---- 服务器启动时发生错误.");
return NULL;
}
esp_err_t stop_webserver(httpd_handle_t server)
{
return httpd_stop(server);
}
void start_webserver_handler(void *arg)
{
httpd_handle_t *server = (httpd_handle_t *)arg;
if (*server == NULL)
{
ESP_LOGI(TAG, "---- 开启HTTP服务器");
*server = start_webserver();
}
}
void stop_webserver_handler(void *arg)
{
httpd_handle_t *server = (httpd_handle_t *)arg;
if (*server)
{
ESP_LOGI(TAG, "---- 关闭HTTP服务器");
if (stop_webserver(*server) == ESP_OK)
{
*server = NULL;
}
else
{
ESP_LOGE(TAG, "Failed to stop http server");
}
}
}
#ifndef _HTTP_SERVICE_H_
#define _HTTP_SERVICE_H_
void start_webserver_handler(void *arg);
void stop_webserver_handler(void *arg);
#endif
idf_component_register(SRCS "wifi_service.c"
INCLUDE_DIRS "."
PRIV_REQUIRES esp_event esp_netif esp_wifi esp_http_server http_user)
#include "esp_log.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "esp_wifi.h"
#include "esp_mac.h"
#include "esp_http_server.h"
#include "wifi_service.h"
#include "http_service.h"
static const char* TAG = "wifi_service";
wifi_service_t g_wifi_service;
esp_netif_t* wifi_ap_netif = NULL;
extern httpd_handle_t http_server;
static void wifi_event_handler(void* event_handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STACONNECTED)
{
wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*)event_data;
ESP_LOGI(TAG, "---- 设备加入网络, MAC地址 :" MACSTR ", 编号(AID) = %d.", MAC2STR(event->mac), event->aid);
start_webserver_handler(event_handler_arg);
// wifi_service_stop_timer_clear();
}
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STADISCONNECTED)
{
wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*)event_data;
ESP_LOGI(TAG, "---- 设备退出网络, MAC地址 :" MACSTR ", 编号(AID) = %d.", MAC2STR(event->mac), event->aid);
stop_webserver_handler(event_handler_arg);
// wifi_service_stop_timer_clear();
}
}
void wifi_ap_start(void)
{
// 创建系统事件循环
ESP_LOGI(TAG, "---- 创建系统事件循环...");
ESP_ERROR_CHECK(esp_event_loop_create_default());
// 将WiFi的所有事件作为实例注册进系统事件循环中
ESP_LOGI(TAG, "---- 注册WiFi事件实例...");
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, wifi_event_handler, &http_server));
// 初始化网卡相关的底层配置
ESP_LOGI(TAG, "---- 网卡初始化...");
ESP_ERROR_CHECK(esp_netif_init());
// 以默认的方式创建一个ap类型的网卡
ESP_LOGI(TAG, "---- 创建AP类型网卡...");
wifi_ap_netif = esp_netif_create_default_wifi_ap();
// 初始化WiFi底层配置
ESP_LOGI(TAG, "---- WiFi初始化...");
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// all configuration will only store in the memory
esp_wifi_set_storage(WIFI_STORAGE_RAM);
// 将WiFi设置成AP模式
ESP_LOGI(TAG, "---- 配置WiFi为AP模式...");
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
// 创建AP配置
ESP_LOGI(TAG, "---- 创建AP配置...");
wifi_config_t wifi_config = {
.ap = {
.ssid = WIFI_AP_SSID,
.password = WIFI_AP_PASSWD,
.ssid_len = strlen(WIFI_AP_SSID),
.channel = 11,
.max_connection = 1,
.authmode = WIFI_AUTH_WPA_WPA2_PSK} };
// 获取当前AP的MAC地址
unsigned char wifi_ap_mac[6];
esp_wifi_get_mac(WIFI_IF_AP, wifi_ap_mac);
// 将MAC地址后两个字节转为字符串
char wifi_ap_temp_str[5];
sprintf(wifi_ap_temp_str, "%02X%02X", wifi_ap_mac[4], wifi_ap_mac[5]);
// 将MAC地址拼接到 ESP_WIFI_SSID 后面
char wifi_ap_ssid[32] = WIFI_AP_SSID;
strcat(wifi_ap_ssid, wifi_ap_temp_str);
// 重新配置 ssid 和 ssid_len
memcpy(wifi_config.ap.ssid, wifi_ap_ssid, sizeof(wifi_config.ap.ssid));
wifi_config.ap.ssid_len = strlen(wifi_ap_ssid);
// 配置AP的属性
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
// 关闭WiFi省电模式
esp_wifi_set_ps(WIFI_PS_NONE);
// 设置WiFi功率为最大 20dBm
esp_wifi_set_max_tx_power(20);
// 开启WiFi
ESP_LOGI(TAG, "---- 开启WiFi, 名称 : %s 密码 : %s", wifi_config.ap.ssid, wifi_config.ap.password);
ESP_ERROR_CHECK(esp_wifi_start());
}
void wifi_ap_stop(void)
{
// 关闭WiFi
ESP_LOGI(TAG, "---- 关闭WiFi...");
ESP_ERROR_CHECK(esp_wifi_stop());
// WiFi反初始化
ESP_LOGI(TAG, "---- WiFi反初始化...");
ESP_ERROR_CHECK(esp_wifi_deinit());
// 销毁网卡实例
ESP_LOGI(TAG, "---- 销毁网卡实例...");
esp_netif_destroy_default_wifi(wifi_ap_netif);
// 网卡反初始化 - 官方未实现的功能
// ESP_LOGI(TAG, "---- 网卡反初始化...");
// ESP_ERROR_CHECK(esp_netif_deinit());
// 注销WiFi事件实例
ESP_LOGI(TAG, "---- 注销WiFi事件实例...");
ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, wifi_event_handler));
// 删除系统事件循环
ESP_LOGI(TAG, "---- 删除系统事件循环...");
ESP_ERROR_CHECK(esp_event_loop_delete_default());
}
unsigned char wifi_turn_on(void)
{
unsigned char ret;
if (g_wifi_service.status == WIFI_SERVICE_STOP)
{
g_wifi_service.status = WIFI_SERVICE_START;
wifi_ap_start();
ret = WIFI_SERVICE_OK;
}
else if (g_wifi_service.status == WIFI_SERVICE_START)
{
ret = WIFI_SERVICE_START;
}
else
{
ret = WIFI_SERVICE_ERR;
}
return ret;
}
unsigned char wifi_turn_off(void)
{
unsigned char ret;
if (g_wifi_service.status == WIFI_SERVICE_START)
{
g_wifi_service.status = WIFI_SERVICE_STOP;
wifi_ap_stop();
ret = WIFI_SERVICE_OK;
}
else if (g_wifi_service.status == WIFI_SERVICE_STOP)
{
ret = WIFI_SERVICE_STOP;
}
else
{
ret = WIFI_SERVICE_ERR;
}
return ret;
}
void wifi_service_status_init()
{
g_wifi_service.status = WIFI_SERVICE_STOP;
}
#ifndef _WIFI_SERVICE_H_
#define _WIFI_SERVICE_H_
#define WIFI_SERVICE_STOP 0 // WiFi服务已关闭
#define WIFI_SERVICE_INIT 1 // WiFi服务初始化
#define WIFI_SERVICE_START 2 // WiFi服务已开启
#define WIFI_SERVICE_DEINIT 3 // WiFi服务反初始化
#define WIFI_SERVICE_ERR 4 // WiFi服务状态错误
#define WIFI_SERVICE_OK 5 // WiFi服务开启或关闭成功
#define NOTHING_TO_DO 0 // 不执行操作
#define ABOUT_TO_STOP 1 // 即将关闭
#define WIFI_AP_STOP 0 // AP已关闭
#define WIFI_AP_INIT 1 // AP初始化
#define WIFI_AP_START 2 // AP已开启
#define WIFI_AP_DEINIT 3 // AP反初始化
#define WIFI_AP_SSID "QJ500-7C-AP-"
#define WIFI_AP_PASSWD "12345677"
typedef struct
{
unsigned char status; // WiFi状态
} wifi_service_t;
/**
* @brief 开启AP
*/
void wifi_ap_start(void);
/**
* @brief 关闭AP
*/
void wifi_ap_stop(void);
/**
* @brief 初始化WiFi的状态
*/
void wifi_service_status_init();
/**
* @brief 立刻开启 WiFi AP
* @note return: WIFI_SERVICE_OK,正在开启
* @note return: WIFI_SERVICE_ERR,WiFi状态不正确,无法开启
* @note return: WIFI_SERVICE_START, WiFi已开启,无需再开启
*/
unsigned char wifi_turn_on(void);
/**
* @brief 立刻关闭 WiFi AP
* @note return: WIFI_SERVICE_OK,正在关闭
* @note return: WIFI_SERVICE_ERR,WiFi状态不正确,无法关闭
* @note return: WIFI_SERVICE_STOP, WiFi已关闭,无需再关闭
*/
unsigned char wifi_turn_off(void);
#endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment