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>";

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

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

1、修改CMakeList.txt文件

idf_component_register(SRCS ${main_src} 
                    INCLUDE_DIRS "."
                    EMBED_FILES "upload_script.html" "wifi.html"
                    )

添加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);

通过上述方式,便可以得到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, 
};

其中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;

}

这个函数就是将前文说到的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;
}

当点击了保存并连接按钮后,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;
}

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));
	}

}

代码链接

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

  • 9
    点赞
  • 57
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值