• ESP32实验-自建web服务器配网02


    前言

    上一篇提到了配网的简单方式,采用的json格式传递wifi账户和密码。这种方式优势是可以在esp32端直接用cjson库解析出json数据。但是不好的地方在于,html网页会复杂一点,需要将输入框中的数据转换成为json格式再发送。发送方式为post请求。那么有没有办法直接解析post默认格式数据呢。这一点本文将进行探讨。

    另外一点,上一篇wifi从ap模式切换到station模式采用的是延时。这种方式也是不是很合理,这里进行了优化。

    配网整个流程

    这里对配网的整个流程进行梳理。

    上电->wifi初始化为ap模式->开启http服务器->用户连上esp32wifi->浏览器输入esp网关地址默认是192.168.1.4->在页面上输入要连接的wifi名称和密码->点击页面上的发送按钮->浏览器通过post请求将wifi名称和密码发送到esp32->esp32解析出wifi名称和密码->退出wifiap模式,关闭http服务器->将wifi名称和密码作为参数,将wifi初始化为station模式。 配网完成。

    嵌入html网页方法

    网页文件的内容本质上是很长的字符串。那最简单的方法就是定义一个字符串数组。将数组内容填充为网页内容。

    const char  index_string[] = 
    "<!DOCTYPE html> \
    <head> \
    <meta charset=\"utf-8\"> \
    <title>wifi config</title> \
    </head>";
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    比如采用这样定义,这种方式一般用于给网页的反馈信息,比如404信息等。但是对于复杂一点的网页,这种方式显然就不太方便。要是能直接将用html工具设计生成的.htm格式的文件直接编译那不是更好了。这就是另外一种比较推荐的方式。

    在这里插入图片描述
    这次是直接借用了一个半开源的esp32桌面小电视的配网网页。如图上图。直接编译需要两个步骤。

    1、修改CMakeList.txt文件

    idf_component_register(SRCS ${main_src} 
                        INCLUDE_DIRS "."
                        EMBED_FILES "upload_script.html" "wifi.html"
                        )
    
    • 1
    • 2
    • 3
    • 4

    添加EMBED_FILES

    2、调用编译出来的文件。
    这个wifi.html编译出出来的文本文件怎么使用呢。wifi.html编译出来一般名称是默认的_binary_名称_类型_start。这个指针代编译出来文件的起始地址。_binary_名称_类型_end,代表结束地址。wifi.html的引用方式如下。

        extern const unsigned char script_start[] asm("_binary_wifi_html_start");
        extern const unsigned char script_end[]   asm("_binary_wifi_html_end");
        const size_t script_size = (script_end - script_start);
    
    • 1
    • 2
    • 3

    通过上述方式,便可以得到wifi.html这个大的数组。

    http服务器配置

    上面有了引用wifi.html的方法,但怎么能让其在浏览器中显示呢?这就要用到http服务器。
    在浏览器中输入地址,浏览器会默认使用GET方法向http服务器请求数据。http服务器收到GET命令后,将要在浏览器中显示的数据发送给浏览器,这样浏览器就能显示出网页了。

    在配网中,至少需要定义两个页面,一个是根页面,一个是点击网页上配网的按钮会触发进入的页面。根页面就是访问192.168.4.1进入的网页。具体定义如下:
    定义页面结构体:

    httpd_uri_t index_page = {
        .uri       = "/",    //192.168.1.4
        .method    = HTTP_GET,
        .handler   = index_get_handler,
        /* Let's pass response string in user
         * context to demonstrate it's usage */
        .user_ctx  = NULL, 
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    其中uri为根目录,就是192.168.4.1这个页面。method定义该页面的触发方法,为GET方法。handler定义进入该页面后需要运行的函数。当浏览器访问了192.168.4.1的时候index_get_handler函数就会运行。
    定义index_get_handler

    static esp_err_t index_get_handler(httpd_req_t *req)
    {
        extern const unsigned char upload_script_start[] asm("_binary_wifi_html_start");
        extern const unsigned char upload_script_end[]   asm("_binary_wifi_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;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这个函数就是将前文说到的wifi.html这个文件的内容返回给浏览器进行显示。
    实际页面显示效果如下:
    在这里插入图片描述
    现在再说下当点击了保存并连接需要做的事情。点击这个按钮后,浏览器会调用get方法,将数据发送给http服务器。所以点击保存并连接后需要显示的页面定义及处理方法如下:

    static const httpd_uri_t echo = {
        .uri       = "/",
        .method    = HTTP_POST,
        .handler   = echo_post_handler,
        .user_ctx  = NULL
    };
    static esp_err_t echo_post_handler(httpd_req_t *req)
    {
        char buf[100];
        // char ssid[10];
        // char pswd[10];
        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;
    
            esp_err_t e = httpd_query_key_value(buf,"ssid",wifi_name,sizeof(wifi_name));
            if(e == ESP_OK) {
                printf("ssid = %s\r\n",wifi_name);
            }
            else {
                printf("error = %d\r\n",e);
            }
    
            e = httpd_query_key_value(buf,"password",wifi_password,sizeof(wifi_password));
            if(e == ESP_OK) {
                printf("pswd = %s\r\n",wifi_password);
            }
            else {
                printf("error = %d\r\n",e);
            }
            /* 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);
        if(strcmp(wifi_name ,"\0")!=0 && strcmp(wifi_password,"\0")!=0)
        {
            xSemaphoreGive(ap_sem);
            ESP_LOGI(TAG, "set wifi name and password successfully! goto station mode");
        }
        return ESP_OK;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    当点击了保存并连接按钮后,echo_post_handler函数就会运行。这个函数的主要作用是,将受到的数据返回给浏览器进行显示,并且将post字符串中的wifi名称和密码分别解析出来,赋值给
    char wifi_name[30]={0};
    char wifi_password[30]={0};
    这两个字符串数组。最大的名称和密码长度为29字节。
    当解析出来的wifi_name和wifi_password值都不为空,则释放信号量,给wifi_station的任务,进入station模式。

    当输入名称abc密码123456时,服务端收到的字符串为

    ssid=abc&password=123456&citycode=

    这是一种post方法通过格式的字符串。esp32提供了相应的解析方法。最早就是不知道这个解析方法,所以才会用到json格式发送wifi名称和密码。解析的函数如下:

    esp_err_t e = httpd_query_key_value(buf,“ssid”,wifi_name,sizeof(wifi_name));

    第一个参数为要解析的post字符串。
    第二参数是传入需要解析的“键值对”中的键。
    第三个参数为解析到的数据存储的数组。
    最后一个参数为存储数组的长度
    当解析成功后。e 的值为ESP_OK,通过这个来判断是否解析成功。

    启动http服务器

    启动服务,将上文的两个页面注册到服务器中。这个比较简单。

    httpd_handle_t start_webserver(void)
    {
        httpd_handle_t server = NULL;
        httpd_config_t config = HTTPD_DEFAULT_CONFIG();
        config.lru_purge_enable = true;
    
        // Start the httpd server
        ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
        if (httpd_start(&server, &config) == ESP_OK) {
            // Set URI handlers
            ESP_LOGI(TAG, "Registering URI handlers");
            httpd_register_uri_handler(server, &echo);
            httpd_register_uri_handler(server, &index_page);
            #if CONFIG_EXAMPLE_BASIC_AUTH
            httpd_register_basic_auth(server);
            #endif
            return server;
        }
    
        ESP_LOGI(TAG, "Error starting server!");
        return NULL;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    wifistation

    当收到正确的wifi名称和密码后,esp32要将wifi转成station模式,并进行联网。这一部分是如何实现的?这里用到了一个单独的任务。
    进入station模式前,需要将http服务器关闭,并且wifi重新初始化。**注意下面的这些函数都不能少。否则station模式会工作不正常。**这也是实验了好久才得出的。

    void wifi_station_task(void  * pvParameters)
    {
        uint32_t result =0;
    	while(1)
    	{
            result = xSemaphoreTake(ap_sem,portMAX_DELAY);
            if(result == pdPASS)
            {
                esp_wifi_stop();
                esp_event_handler_unregister(WIFI_EVENT,
                                                            ESP_EVENT_ANY_ID,
                                                            &wifi_event_handler
                                                            );
                esp_netif_destroy_default_wifi(ap_netif);
                esp_event_loop_delete_default();
                esp_wifi_deinit();
                esp_netif_deinit();
                httpd_stop(server);
                printf("hello \r\n");
                ESP_LOGI(TAG,"led on");
                wifi_init_sta(wifi_name,wifi_password);
            }
    
            printf("hello1 \r\n");
    		// vTaskDelay(pdMS_TO_TICKS(1000));
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    代码链接

    具体代码链接如下,开发环境为官方的esp-idf。https://download.csdn.net/download/sinat_36568888/85750520

  • 相关阅读:
    干货 | 独立站运营怎么提高在线聊天客户服务?
    mysql安装配置教程(Linux+Windows)
    Go语言学习笔记——错误处理
    如何生成时间戳和如何生成UUID最新详解(教你怎么用)
    C++作业3:继承派生、多态、文件流
    Kotlin开发者眼中的Java缺少哪些特性?
    凉鞋的 Godot 笔记 105. 第一个通识:编辑-测试 循环
    python爬取每日天气情况
    Kotlin 协程异常全局捕捉
    GO语言开山篇(二):诞生小故事
  • 原文地址:https://blog.csdn.net/sinat_36568888/article/details/125424157