ESP32-WiFi配网(AP和STA模式及配置界面)上

826 阅读2分钟

ESP32-WiFi配网(AP和STA模式及配置界面)上

浏览器显示wifi配置界面;AP模式(ESP32自己是热点);STA模式(ESP32是终端,去连接别人热点);AP&STA共存模式(自己既是热点也可以连接别人热点)

1.配置界面

这一部分主要做浏览器的显示界面,在这个界面里设置ESP32的wifi信息,手机可以连接到ESP32的热点。 (默认的前提是esp32在AP模式,我们手机连上了esp32的热点)

显示界面代码如下:

<!DOCTYPE html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>demo1</title>
 
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        
        body {
            background-color: rgb(235, 235, 235);
        }
        
        form {
            width: 400px;
            height: 200px;
            background-color: white;
            padding: 5px;
            box-sizing: border-box;
            position: absolute;
            left: 50%;
            top: 1%;
            /* 水平居中 */
            transform: translateX(-50%);
            /* transform: translate(-50%, -50%); */
            /* 相对于现在所处位置的位移变化,x便偏移自己宽度的50%,y偏移自己高度的50% */
        }
 
        h2 {
            margin-bottom: 10px;
            text-align: center;
        }
        
        form input {
            width: 100%;
            height: 30px;
            display: block;
            margin-bottom: 8px;
            padding-left: 10px;
            box-sizing: border-box;
        }
        .mya {
            width: 100%;
            height: 30px;
            margin-bottom: 20x;
        }
        
        .mya a:nth-child(1) {
            float: left;
        }
        
        .mya a:nth-child(2) {
            float: right;
        }
        
        button {
            width: 100%;
            height: 40px;
            background-color: rgb(235, 235, 235);
            border: none;
        }
        
        button:active {
            box-shadow: 0 0 3px rgb(173, 172, 172);
            /* x偏移  y偏移  模糊值 颜色 */
        }
    </style>
</head>
 
/*第二部分,主要设置一下传输的数据*/
<body>
 
    <form action="">
        <h2>WiFi 密码配置</h2>
        <input id="wifi" type="text" placeholder="请输入WiFi账号">
        <input id="code" type="text" placeholder="请输入WiFi密码">
        <button id="set_wifi" type="button" onclick="send_wifi()">提交</button>
        <button id="back" type="button" onclick="send_back()">退出</button>
    </form>
 
</body>
 
 
/*调用的函数*/
<script>
function setpath() {
    var default_path = document.getElementById("newfile").files[0].name;
    document.getElementById("filepath").value = default_path;
}
 
function send_wifi() {
    var input_ssid = document.getElementById("wifi").value;
    var input_code = document.getElementById("code").value;
    var xhttp = new XMLHttpRequest();
        xhttp.open("POST", "/wifi_data", true);
        xhttp.onreadystatechange = function() {
            if (xhttp.readyState == 4) {
                if (xhttp.status == 200) {
                    alert("WiFi设置成功!")
                    console.log(xhttp.responseText);
                    location.reload()
                } else if (xhttp.status == 0) {
                    alert("设置失败,请检查网络连接!");
                    location.reload()
                    return
                } else {
                    alert(xhttp.status + " Error!\n" + xhttp.responseText);
                    location.reload()
                    return
                }
            }
        };
    var data = {
        "wifi_name":input_ssid,
        "wifi_code":input_code
    }
    xhttp.send(JSON.stringify(data));
}
 
 
function send_back() {
    var xhttp = new XMLHttpRequest();
        xhttp.open("POST", "/back", true);
        xhttp.onreadystatechange = function() {
            if (xhttp.readyState == 4) {
                if (xhttp.status == 200) {
                    alert("退出设置成功!")
                    console.log(xhttp.responseText);
                    location.reload()
                } else if (xhttp.status == 0) {
                    alert("设置失败,请检查网络连接!");
                    location.reload()
                } else {
                    alert(xhttp.status + " Error!\n" + xhttp.responseText);
                    location.reload()
                }
            }
        };
    var data = {
        "back":"back",
    }
    xhttp.send(JSON.stringify(data));
}
 
</script>

这个代码主要可分三部分看:第一部分是设置页面的尺寸,后面一部分是传输数据,最后一部分是调用的函数。

image.png 在编写之前,看一下官方给的post代码:

/* An HTTP POST handler */
static esp_err_t echo_post_handler(httpd_req_t *req)
{
    char buf[100];
    int ret, remaining = req->content_len;
 
    while (remaining > 0) {
        /* Read the data for the request */
        if ((ret = httpd_req_recv(req, buf,
                        MIN(remaining, sizeof(buf)))) <= 0) {
            if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
                /* Retry receiving if timeout occurred */
                continue;
            }
            return ESP_FAIL;
        }
 
        /* Send back the same data */
        httpd_resp_send_chunk(req, buf, ret);
        remaining -= ret;
 
        /* Log data received */
        ESP_LOGI(TAG, "=========== RECEIVED DATA ==========");
        ESP_LOGI(TAG, "%.*s", ret, buf);
        ESP_LOGI(TAG, "====================================");
    }
 
    // End response
    httpd_resp_send_chunk(req, NULL, 0);
    return ESP_OK;
}
 
static const httpd_uri_t echo = {
    .uri       = "/echo",
    .method    = HTTP_POST,
    .handler   = echo_post_handler,
    .user_ctx  = NULL
};

翻到结构体的定义:

typedef struct httpd_uri {
    const char       *uri;    /*!< The URI to handle */
    httpd_method_t    method; /*!< Method supported by the URI */
 
    /**
     * Handler to call for supported request method. This must
     * return ESP_OK, or else the underlying socket will be closed.
     */
    esp_err_t (*handler)(httpd_req_t *r);
 
    /**
     * Pointer to user context data which will be available to handler
     */
    void *user_ctx;
 
} httpd_uri_t;

url就是我们需要设置的路径,比如在界面中特别标识出来的/wifi_data就是我们设置的url的路径,method是post方法,handler是我们要调用的函数,user_ctx是我们要传递的数据,handler中的函数就是专门处理传递过来的数据。比如刚刚在界面中特别圈出来的两个wifi_name和wifi_code就是会在该函数里面处理。

2.嵌入html网页

我们在上面写好了html网页,但是怎么显示出来呢?第一种最简单的方法就是定义一个数组,内容填充为网页内容,但是对于复杂的网页这个方法不合适。第二种方法是新建一个文件夹,将我们的文件添加进来,文件名是setting.html。然后修改CMakeList.txt文件,把带路径的文件名添加进EMBED_FILES。然后定义结构体,也是利用URL,把这个文件放在根目录这里,让程序一开机就访问这个路径;在定义函数解析文件得到数组,这样界面就能显示出来。

定义结构体代码:

/* URI handler for getting uploaded files */
    httpd_uri_t file_download = {
        .uri       = "/",  // Match all URIs of type /path/to/file
        .method    = HTTP_GET,
        .handler   = download_get_handler,
        .user_ctx  = NULL    
    };

定义函数:

static esp_err_t download_get_handler(httpd_req_t *req)
{
    extern const unsigned char upload_script_start[] asm("_binary_setting_html_start");
    extern const unsigned char upload_script_end[]   asm("_binary_setting_html_end");
    const size_t upload_script_size = (upload_script_end - upload_script_start);
 
    /* Add file upload form and script which on execution sends a POST request to /upload */
    httpd_resp_set_type(req,HTTPD_TYPE_TEXT);
    httpd_resp_send(req, (const char *)upload_script_start, upload_script_size);
    return ESP_OK;
}

3. 获取wifi信息

用json来进行格式解析(注意:ESP32判断字符串完成的标志是‘\0’,所以要在末尾加上)

static esp_err_t send_wifi_handler(httpd_req_t *req)
{
    int total_len = req->content_len;
    int cur_len = 0;
    char *buf = ((struct file_server_data *)(req->user_ctx))->scratch;
    int received = 0;
    if (total_len >= SCRATCH_BUFSIZE) {
        /* Respond with 500 Internal Server Error */
        httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "content too long");
        return ESP_FAIL;
    }
    while (cur_len < total_len) {
        received = httpd_req_recv(req, buf + cur_len, total_len);
        if (received <= 0) {
            /* Respond with 500 Internal Server Error */
            httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to post control value");
            return ESP_FAIL;
        }
        cur_len += received;
    }
 
    buf[total_len] = '\0';
    printf("recived data length is :%d\n",total_len);
    for (int i = 0; i <total_len ; i++){
        putchar(buf[i]);
    }
    printf("\r\nwifi data recived!\r\n");
    cJSON *root = cJSON_Parse(buf);
 
    char *ssid = cJSON_GetObjectItem(root, "wifi_name")->valuestring;
    char *psw = cJSON_GetObjectItem(root, "wifi_code")->valuestring;
    int ssid_len = strlen(ssid);
    int psw_len = strlen(psw);
    set_system_data_wifi_info(ssid,ssid_len,psw ,psw_len);
    print_system_data_wifi_info();
    cJSON_Delete(root);
    httpd_resp_sendstr(req, "Post control value successfully");
    return ESP_OK;
}

编写结构体并注册

httpd_uri_t wifi_data = {
        .uri       = "/wifi_data",   // Match all URIs of type /delete/path/to/file
        .method    = HTTP_POST,
        .handler   = send_wifi_handler,
        .user_ctx  = server_data    // Pass server data as context
    };
    httpd_register_uri_handler(server, &wifi_data);

其中,server_data是这么定义的

struct file_server_data {
    /* Base path of file storage */
    char base_path[ESP_VFS_PATH_MAX + 1];
 
    /* Scratch buffer for temporary storage during file transfer */
    char scratch[SCRATCH_BUFSIZE];
};

4.主函数

/* Function to start the file server */
esp_err_t start_file_server(const char *base_path)
{
    static struct file_server_data *server_data = NULL;
 
    /* Validate file storage base path */
    if (!base_path || strcmp(base_path, "/spiffs") != 0) {
        ESP_LOGE(TAG, "File server presently supports only '/spiffs' as base path");
        return ESP_ERR_INVALID_ARG;
    }
 
    if (server_data) {
        ESP_LOGE(TAG, "File server already started");
        return ESP_ERR_INVALID_STATE;
    }
 
    /* Allocate memory for server data */
    server_data = calloc(1, sizeof(struct file_server_data));
    if (!server_data) {
        ESP_LOGE(TAG, "Failed to allocate memory for server data");
        return ESP_ERR_NO_MEM;
    }
    strlcpy(server_data->base_path, base_path,
            sizeof(server_data->base_path));
 
    httpd_handle_t server = NULL;
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
 
    /* Use the URI wildcard matching function in order to
     * allow the same handler to respond to multiple different
     * target URIs which match the wildcard scheme */
    config.uri_match_fn = httpd_uri_match_wildcard;
 
    ESP_LOGI(TAG, "Starting HTTP Server");
    if (httpd_start(&server, &config) != ESP_OK) {
        ESP_LOGE(TAG, "Failed to start file server!");
        return ESP_FAIL;
    }
 
    /* URI handler for getting uploaded files */
    httpd_uri_t file_download = {
        .uri       = "/*",  // Match all URIs of type /path/to/file
        .method    = HTTP_GET,
        .handler   = download_get_handler,
        .user_ctx  = server_data    // Pass server data as context
    };
    httpd_register_uri_handler(server, &file_download);
 
 
    httpd_uri_t wifi_data = {
        .uri       = "/wifi_data",   // Match all URIs of type /wifi_data/path/to/file
        .method    = HTTP_POST,
        .handler   = send_wifi_handler,
        .user_ctx  = server_data    // Pass server data as context
    };
    httpd_register_uri_handler(server, &wifi_data);
 
    return ESP_OK;
}

至此,我们实现了wifi配置页面的编写及嵌入,可以在手机浏览器上输入192.168.4.1(ESP32默认)