美文网首页物联网loT从业者物联网相关技术研究
ESP8266学习笔记(20)——HTTP服务器(RTOS SD

ESP8266学习笔记(20)——HTTP服务器(RTOS SD

作者: Leung_ManWah | 来源:发表于2020-04-27 17:09 被阅读0次

    一、背景

    首先手机APP连接智能插座热点(AP)将网关的SSID和密码通过HTTP协议配置到插座,完成配置后智能插座连接网关。

    Post请求和Get请求:

    二、API说明

    以下软件定时器接口位于 esp_http_server/include/esp_http_server.h

    2.1 httpd_start

    2.2 httpd_register_uri_handler

    通过传递类型httpd_uri_t结构的对象来注册URI处理程序,该对象具有包括uri名称,method类型(例如,HTTPD_GET/HTTPD_POST/HTTPD_PUT等等),类型的函数指针和指向用户上下文数据的指针的成员。esp_err_t *handler (httpd_req_t *req) user_ctx

    2.3 httpd_req_get_url_query_len

    2.4 httpd_req_get_url_query_str

    2.5 httpd_req_recv

    2.6 httpd_resp_set_status

    2.7 httpd_resp_send

    三、移植文件

    百度网盘:https://pan.baidu.com/s/1ku_qiKOEuOdLBcNbswL7WQ[https://pan.baidu.com/s/1ku_qiKOEuOdLBcNbswL7WQ] 提取码:490n

    3.1 user_http_server.c

    /*********************************************************************
     * INCLUDES
     */
    #include <sys/param.h>
    
    #include "esp_http_server.h"
    #include "esp_timer.h"
    #include "cJSON.h"
    #include "esp_err.h"
    #include "esp_log.h"
    
    #include "user_http_server.h"
    #include "user_wifi.h"
    #include "common.h"
    
    static esp_err_t handleGetUrlPath(httpd_req_t *pRequest);
    static esp_err_t handlePostUrlPath(httpd_req_t *pRequest);
    static esp_err_t findRequestData(char *pRequestData);
    static void queryClientInfo(void);
    static void configWifi(char *pRequestData);
    static void configCloudServer(char *pRequestData);
    static void configBleInit(char *pRequestData);
    static void sendGetResponse(bool responseOk, char *pResponseData);
    static void sendPostResponse(bool responseOk, char *pResponseData);
    static void jsonPackageResponseData(bool responseOk, char *pSendData);
    static void jsonPackageClientInfoData(bool responseOk, char *pSendData);
    static void startDelayHandleTimer(void);
    static void delayHandleTimerCallback(void *arg);
    
    /*********************************************************************
     * LOCAL VARIABLES
     */
    static httpd_req_t *s_pRequest = NULL;
    
    static httpd_uri_t s_uriClientGet = 
    {
        .uri       = "/client",
        .method    = HTTP_GET,
        .handler   = handleGetUrlPath,
        .user_ctx  = NULL
    };
    
    static httpd_uri_t s_uriConfigPost = 
    {
        .uri       = "/config",
        .method    = HTTP_POST,
        .handler   = handlePostUrlPath,
        .user_ctx  = NULL
    };
    
    // 延迟处理定时器,确保可以回复响应成功
    esp_timer_handle_t s_delayHandleTimer = 0;
    esp_timer_create_args_t s_delayHandleTimerArg = 
    { 
        .callback = &delayHandleTimerCallback,                                              // 设置回调函数
        .arg = NULL,                                                                        // 不携带参数
        .name = "DelayHandleTimer"                                                          // 定时器名字
    };
    
    char s_configSsid[SSID_MAX_LEN] = {0};
    char s_configPassword[PASSWORD_MAX_LEN] = {0};
    
    static const char *TAG = "user_http_server";
    
    /*********************************************************************
     * PUBLIC FUNCTIONS
     */
    /**
     @brief HTTP服务器初始化
     @param 无
     @return 无
    */
    httpd_handle_t HttpServerInit(void)
    {
        httpd_handle_t server = NULL;
        httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    
        // 启动HTTP服务器
        ESP_LOGI(TAG, "Starting HTTP server on port: '%d'", config.server_port);
        if(httpd_start(&server, &config) == ESP_OK)                                              
        {
            // 注册URI处理程序
            httpd_register_uri_handler(server, &s_uriConfigPost);
            httpd_register_uri_handler(server, &s_uriClientGet);
            return server;
        }
    
        ESP_LOGI(TAG, "Error starting server!");
        return NULL;
    }
    
    
    /*********************************************************************
     * LOCAL FUNCTIONS
     */
    /**
     @brief HTTP GET请求URL处理
     @param pRequest -[in] HTTP请求结构体
     @return 错误码
    */
    static esp_err_t handleGetUrlPath(httpd_req_t *pRequest)
    {
        esp_err_t result = ESP_FAIL;
        char *pBuffer;
        size_t bufferLength;
    
        /* Read URL query string length and allocate memory for length + 1,
         * extra byte for null termination */
        bufferLength = httpd_req_get_url_query_len(pRequest) + 1;
        if(bufferLength > 1) 
        {
            pBuffer = malloc(bufferLength);
            if(httpd_req_get_url_query_str(pRequest, pBuffer, bufferLength) == ESP_OK) 
            {
                ESP_LOGI(TAG, "Found URL query => %s", pBuffer);
                s_pRequest = pRequest;
                char requestData[REQUEST_DATA_SIZE] = {0};
                result = findRequestData(requestData);
    
                if(strcmp(pBuffer, "command=info") == 0)
                {
                    queryClientInfo();
                }
            }
            free(pBuffer);
        }
        return result;
    }
    
    /**
     @brief HTTP POST请求URL处理
     @param pRequest -[in] HTTP请求结构体
     @return 错误码
    */
    static esp_err_t handlePostUrlPath(httpd_req_t *pRequest)
    {
        esp_err_t result = ESP_FAIL;
        char *pBuffer;
        size_t bufferLength;
    
        /* Read URL query string length and allocate memory for length + 1,
         * extra byte for null termination */
        bufferLength = httpd_req_get_url_query_len(pRequest) + 1;
        if(bufferLength > 1) 
        {
            pBuffer = malloc(bufferLength);
            if(httpd_req_get_url_query_str(pRequest, pBuffer, bufferLength) == ESP_OK) 
            {
                ESP_LOGI(TAG, "Found URL query => %s", pBuffer);
                s_pRequest = pRequest;
                char requestData[REQUEST_DATA_SIZE] = {0};
                result = findRequestData(requestData);
    
                if(strcmp(pBuffer, "command=wifi") == 0)
                {
                    configWifi(requestData);
                }
                else if(strcmp(pBuffer, "command=cloud_server") == 0)
                {
                    configCloudServer(requestData);                 
                }
                else if(strcmp(pBuffer, "command=switch") == 0)
                {
                    
                }
                else if(strcmp(pBuffer, "command=ble_cmd") == 0)
                {
                    
                }
                else if(strcmp(pBuffer, "command=ble_init") == 0)
                {
                    configBleInit(requestData);     
                }
            }
            free(pBuffer);
        }
        return result;
    }
    
    /**
     @brief 查找请求数据
     @param pRequestData -[out] 请求的数据
     @return 错误码
    */
    static esp_err_t findRequestData(char *pRequestData)
    {
        int result, remaining = s_pRequest->content_len;
    
        while(remaining > 0) 
        {
            // 从HTTP请求读取内容数据
            if((result = httpd_req_recv(s_pRequest, pRequestData, MIN(remaining, REQUEST_DATA_SIZE))) <= 0) 
            {
                if(result == HTTPD_SOCK_ERR_TIMEOUT) 
                {
                    // 发生超时,继续接收
                    continue;
                }
                return ESP_FAIL;
            }
            remaining -= result;
    
            /* Log data received */
            ESP_LOGI(TAG, "=========== RECEIVED DATA ==========");
            ESP_LOGI(TAG, "%.*s", result, pRequestData);
            ESP_LOGI(TAG, "====================================");
        }
        return ESP_OK;
    }
    
    /**
     @brief 查询终端信息接口
     @param pRequestData -[in] 请求的数据
     @return 无
    */
    static void queryClientInfo(void)
    {
        char sendData[RESPONSE_DATA_SIZE] = {0};
        jsonPackageClientInfoData(true, sendData);
        sendGetResponse(true, sendData);
    }
    
    /**
     @brief 配置WIFI接口
     @param pRequestData -[in] 请求的数据
     @return 无
    */
    static void configWifi(char *pRequestData)
    {
        cJSON *pRoot = cJSON_Parse(pRequestData);
        if(!pRoot)
        {
            return ;
        }
       
        char sendData[RESPONSE_DATA_SIZE] = {0};
        cJSON *pRequest = cJSON_GetObjectItem(pRoot, "request");                            // 解析request字段内容
        if(!pRequest)
        {
            sprintf(sendData, "%s", "No request Item");
            sendPostResponse(false, sendData);
            cJSON_Delete(pRoot);
            return ;
        }
    
        cJSON *pStation = cJSON_GetObjectItem(pRequest, "station");                         // 解析request子节点station字段内容
        cJSON *pSoftAp = cJSON_GetObjectItem(pRequest, "softap");                           // 解析request子节点softap字段内容
        if(pStation)
        {
            cJSON *pSsid = cJSON_GetObjectItem(pStation, "ssid");
            cJSON *pPassword = cJSON_GetObjectItem(pStation, "password");
            if(!pSsid)
            {
                sprintf(sendData, "%s", "No ssid Item");
                sendPostResponse(false, sendData);
                cJSON_Delete(pRoot);
                return ;
            }
            if(!pPassword)
            {
                sprintf(sendData, "%s", "No password Item");
                sendPostResponse(false, sendData);
                cJSON_Delete(pRoot);
                return ;
            }
    
            strcpy(s_configSsid, pSsid->valuestring);
            strcpy(s_configPassword, pPassword->valuestring);
    
            sprintf(sendData, "%s", "Config station succeed");
            sendPostResponse(true, sendData);
    
            startDelayHandleTimer();
    
            // SetDhcpClientFlag(0);
            // ConfigStationMode(ssid, password);                                          // 配置连接路由器
    
            // UdpSendDeviceInfo();                                                     // UDP发送设备信息
            // EspPlatformCheckIp();                                                        // ESP平台检查IP
        }
        else if(pSoftAp)
        {
        }
        else
        {
            sprintf(sendData, "%s", "No station or softap Item");
            sendPostResponse(false, sendData);
        }
    
        cJSON_Delete(pRoot);
    }
    
    /**
     @brief 配置云服务器接口
     @param pRequestData -[in] 请求的数据
     @return 无
    */
    static void configCloudServer(char *pRequestData)
    {
        cJSON *pRoot = cJSON_Parse(pRequestData);
        if(!pRoot)
        {
            return ;
        }
    
        char sendData[RESPONSE_DATA_SIZE] = {0};
        cJSON *pRequest = cJSON_GetObjectItem(pRoot, "request");                            // 解析request字段内容
        if(!pRequest)
        {
            sprintf(sendData, "%s", "No request Item");
            sendPostResponse(false, sendData);
            cJSON_Delete(pRoot);
            return ;
        }
    
        cJSON *pTcp = cJSON_GetObjectItem(pRequest, "tcp");                                 // 解析request子节点tcp字段内容
        cJSON *pHttp = cJSON_GetObjectItem(pRequest, "http");                               // 解析request子节点http字段内容
    
        if(pTcp)
        {
            char token[64] = {0};
    
            cJSON *pIpAddr = cJSON_GetObjectItem(pTcp, "ip");
            cJSON *pPort = cJSON_GetObjectItem(pTcp, "port");
            cJSON *pDomain = cJSON_GetObjectItem(pTcp, "domain");
            cJSON *pToken = cJSON_GetObjectItem(pTcp, "token");
            if(pIpAddr)
            {
                char ip[16] = {0};
                strcpy(ip, pIpAddr->valuestring);
                // g_tcpCloudServerUrl.ip.addr = ipaddr_addr(ip);                              // 点分十进制写入IP结构体
            }
            if(pPort)
            {
                // g_tcpCloudServerUrl.port = pPort->valueint;
            }
            if(pDomain)
            {
                char domain[32] = {0};
                strcpy(domain, pDomain->valuestring);
                // memcpy(g_tcpCloudServerUrl.domain, domain, os_strlen(domain));
            }
            if(pToken)
            {
                strcpy(token, pToken->valuestring);
            }
    
            sprintf(sendData, "%s", "Config tcp cloud server succeed");
            sendPostResponse(true, sendData);
    
            // SaveTcpServerParam(&g_tcpCloudServerUrl);                                       // 保存TCP服务器参数到Flash
            // SetFirstConnectEspPlatformFlag(true);
            // EspPlatformCheckIp();                                                           // ESP平台检查IP
        }
        else if(pHttp)
        {
            char ip[16] = {0};
    
            cJSON *pIpAddr = cJSON_GetObjectItem(pHttp, "ip");
            cJSON *pPort = cJSON_GetObjectItem(pHttp, "port");
            cJSON *pDomain = cJSON_GetObjectItem(pHttp, "domain");
            cJSON *pPath = cJSON_GetObjectItem(pHttp, "path");
            if(pIpAddr)
            {
                strcpy(ip, pIpAddr->valuestring);
                // g_httpCloudServerUrl.ip.addr = ipaddr_addr(ip);                          // 点分十进制写入IP结构体
            }
            if(pPort)
            {
                // g_httpCloudServerUrl.port = pPort->valueint;
            }
            if(pDomain)
            {
                // strcpy(g_httpCloudServerUrl.domain, pDomain->valuestring);
            }
            if(pPath)
            {
                // strcpy(g_httpCloudServerUrl.path, pPath->valuestring);
            }
    
            sprintf(sendData, "%s", "Config http cloud server succeed");
            sendPostResponse(true, sendData);
    
            // SaveHttpServerParam(&g_httpCloudServerUrl);                                     // 保存HTTP服务器参数到Flash
            // HttpCloudCheckIp();
        }
        else
        {
            sprintf(sendData, "%s", "No tcp or http Item");
            sendPostResponse(false, sendData);
        }
    
        cJSON_Delete(pRoot);
    }
    
    /**
     @brief 配置BLE初始化接口
     @param pRequestData -[in] 请求的数据
     @return 无
    */
    static void configBleInit(char *pRequestData)
    {
        cJSON *pRoot = cJSON_Parse(pRequestData);
        if(!pRoot)
        {
            return ;
        }
    
        char sendData[RESPONSE_DATA_SIZE] = {0};
        cJSON *pRequest = cJSON_GetObjectItem(pRoot, "request");                            // 解析request字段内容
        if(!pRequest)
        {
            sprintf(sendData, "%s", "No request Item");
            sendPostResponse(false, sendData);
            cJSON_Delete(pRoot);
            return ;
        }
    
        cJSON *pBleInit = cJSON_GetObjectItem(pRequest, "ble_init");                        // 解析request子节点ble_init字段内容
        cJSON *pBleSupervRssi = cJSON_GetObjectItem(pRequest, "ble_superv_rssi");           // 解析request子节点ble_superv_rssi字段内容
        if(pBleInit)
        {
            char buffer[64] = {0};
            cJSON *pBleInitData = cJSON_GetObjectItem(pBleInit, "ble_init_data");
    
            if(pBleInitData)
            {
                strcpy(buffer, pBleInitData->valuestring);
                // UartSendLanBleInit(buffer);
            }
            else
            {
                sprintf(sendData, "%s", "No ble_init_data Item");
                sendPostResponse(false, sendData);
                cJSON_Delete(pRoot);
                return ;
            }
    
            sprintf(sendData, "%s", "Config ble init succeed");
            sendPostResponse(true, sendData);
        }
        else if(pBleSupervRssi)
        {
            cJSON *pRssi = cJSON_GetObjectItem(pBleSupervRssi, "rssi");
    
            if(pRssi)
            {
                // g_bleDeviceRssiStandrdValue = pRssi->valueint;
                // SaveBleDeviceRssiStandrdValue(g_bleDeviceRssiStandrdValue);
            }
            else
            {
                sprintf(sendData, "%s", "No rssi Item");
                sendPostResponse(false, sendData);
                cJSON_Delete(pRoot);
                return ;
            }
    
            sprintf(sendData, "%s", "Config ble superv rssi succeed");
            sendPostResponse(true, sendData);
        }
        else
        {
            sprintf(sendData, "%s", "No ble_init or ble_superv_rssi Item");
            sendPostResponse(false, sendData);
        }
    
        cJSON_Delete(pRoot);
    }
    
    /**
     @brief 发送GET请求HTTP响应
     @param responseOk -[in] 响应是否成功
     @param pResponseData -[in] 响应数据
     @return 无
    */
    static void sendGetResponse(bool responseOk, char *pResponseData)
    {
        if(responseOk)
        {
            httpd_resp_set_status(s_pRequest, HTTPD_200);                                   // 设置响应状态200 OK
        }
        else
        {
            httpd_resp_set_status(s_pRequest, HTTPD_400);                                   // 设置响应状态400 Bad Request
        }
    
        httpd_resp_send(s_pRequest, pResponseData, strlen(pResponseData));                  // 发送响应
    }
    
    /**
     @brief 发送POST请求的HTTP响应
     @param responseOk -[in] 响应是否成功
     @param pResponseData -[in] 响应数据
     @return 无
    */
    static void sendPostResponse(bool responseOk, char *pResponseData)
    {
        jsonPackageResponseData(responseOk, pResponseData);
    
        if(responseOk)
        {
            httpd_resp_set_status(s_pRequest, HTTPD_200);                                   // 设置响应状态200 OK
        }
        else
        {
            httpd_resp_set_status(s_pRequest, HTTPD_400);                                   // 设置响应状态400 Bad Request
        }
    
        httpd_resp_send(s_pRequest, pResponseData, strlen(pResponseData));                  // 发送响应
    }
    
    /**
     @brief JSON格式封装响应数据
     @param responseOk -[in] 响应是否成功
     @param pSendData -[in&out] 要封装的发送数据
     @return 无
    */
    static void jsonPackageResponseData(bool responseOk, char *pSendData)
    {
        cJSON *pRoot = cJSON_CreateObject();
    
        uint16 statusCode;
    
        if(responseOk)
        {
            statusCode = 200;
        }
        else
        {
            statusCode = 400;
        }
    
        cJSON_AddNumberToObject(pRoot, "status", statusCode);
        cJSON_AddStringToObject(pRoot, "message", pSendData);
        char *tempBuffer = cJSON_Print(pRoot);
        sprintf(pSendData, "%s", tempBuffer);
    
        free((void *) tempBuffer);
        cJSON_Delete(pRoot);
    }
    
    /**
     @brief JSON格式封装终端信息数据
     @param responseOk -[in] 响应是否成功
     @param pSendData -[in&out] 要封装的发送数据
     @return 无
    */
    static void jsonPackageClientInfoData(bool responseOk, char *pSendData)
    {
        cJSON *pRoot = cJSON_CreateObject();
    
        uint16 statusCode;
    
        if(responseOk)
        {
            statusCode = 200;
        }
        else
        {
            statusCode = 400;
        }
    
        char wifiMac[23] = {0};
        char bleMac[23] = {0};
        // GetWifiMacStr(wifiMac);
        // GetBleMacStr(bleMac);
    
        cJSON_AddNumberToObject(pRoot, "status", statusCode);
        cJSON_AddStringToObject(pRoot, "wifi_mac", "11:11:11:11:11:11");
        cJSON_AddStringToObject(pRoot, "ble_mac", "11:11:11:11:11:11");
        cJSON_AddStringToObject(pRoot, "ble_name", "a");
        cJSON_AddNumberToObject(pRoot, "ble_superv_rssi", 11);
        char *tempBuffer = cJSON_Print(pRoot);
        sprintf(pSendData, "%s", tempBuffer);
    
        free((void *) tempBuffer);
        cJSON_Delete(pRoot);
    }
    
    /**
     @brief 开启延迟处理定时器
     @param 无
     @return 无
    */
    static void startDelayHandleTimer(void) 
    {
        // 开始创建一个单次周期的定时器并且执行
        ESP_ERROR_CHECK(esp_timer_create(&s_delayHandleTimerArg, &s_delayHandleTimer));
        ESP_ERROR_CHECK(esp_timer_start_once(s_delayHandleTimer, DELAY_HANDLE_PERIOD));     // 1s                    
    }
    
    /**
     @brief 延迟处理定时器的回调函数
     @param 无
     @return 无
    */
    static void delayHandleTimerCallback(void *arg) 
    {
        ESP_LOGI(TAG, "delayHandleTimerCallback");
    
        ConfigStationMode(s_configSsid, s_configPassword);                                  // 配置连接路由器
    
        ESP_ERROR_CHECK(esp_timer_delete(s_delayHandleTimer));                              // 使用完后删除定时器                              
    }
    
    /****************************************************END OF FILE****************************************************/
    

    3.2 user_http_server.h

    #ifndef _USER_HTTP_SERVER_H_
    #define _USER_HTTP_SERVER_H_
    
    /*********************************************************************
     * INCLUDES
     */
    #include <esp_http_server.h>
    
    /*********************************************************************
     * DEFINITIONS
     */
    #define REQUEST_DATA_SIZE           512
    #define RESPONSE_DATA_SIZE          256
    
    #define DELAY_HANDLE_PERIOD         1000 * 1000         // 1s
    
    /*********************************************************************
     * API FUNCTIONS
     */
    httpd_handle_t HttpServerInit(void);
    
    #endif /* _USER_HTTP_SERVER_H_ */
    

    四、使用例子

    cJSON使用部分查看 ESP8266学习笔记(8)——第三方库cJSON使用
    定时器使用部分查看 ESP8266学习笔记(19)——定时器接口使用(RTOS SDK)

    4.1 初始化HTTP服务器

    在main函数中调用HttpServerInit()

    void app_main(void)
    {
        printf("SDK version:%s\n", esp_get_idf_version());
    
        esp_timer_init();
        WifiInit();
        HttpServerInit();
    }
    

    在HttpServerInit()中启动了HTTP服务器,并注册了两种URI处理程序

    httpd_handle_t HttpServerInit(void)
    {
        httpd_handle_t server = NULL;
        httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    
        // 启动HTTP服务器
        ESP_LOGI(TAG, "Starting HTTP server on port: '%d'", config.server_port);
        if(httpd_start(&server, &config) == ESP_OK)                                              
        {
            // 注册URI处理程序
            httpd_register_uri_handler(server, &s_uriConfigPost);
            httpd_register_uri_handler(server, &s_uriClientGet);
            return server;
        }
    
        ESP_LOGI(TAG, "Error starting server!");
        return NULL;
    }
    

    4.2 接收HTTP请求

    使用Postman发送

    POST /config?command=wifi HTTP/1.1
    Host: 192.168.4.1:80
    Cache-Control: no-cache
    
    { "request": { "station": { "ssid":"TP-LINK_576F", "password":"12345678" } } }
    

    由于是Post请求,URI路径为/config,符合已注册好的URI处理程序

    static httpd_uri_t s_uriConfigPost = 
    {
        .uri       = "/config",
        .method    = HTTP_POST,
        .handler   = handlePostUrlPath,
        .user_ctx  = NULL
    };
    

    于是进入handlePostUrlPath()进行处理,在handlePostUrlPath()中查找URL路径,找到command=wifi路径,进入配置WIFI处理函数configWifi()

    static esp_err_t handlePostUrlPath(httpd_req_t *pRequest)
    {
        esp_err_t result = ESP_FAIL;
        char *pBuffer;
        size_t bufferLength;
    
        /* Read URL query string length and allocate memory for length + 1,
         * extra byte for null termination */
        bufferLength = httpd_req_get_url_query_len(pRequest) + 1;
        if(bufferLength > 1) 
        {
            pBuffer = malloc(bufferLength);
            if(httpd_req_get_url_query_str(pRequest, pBuffer, bufferLength) == ESP_OK) 
            {
                ESP_LOGI(TAG, "Found URL query => %s", pBuffer);
                s_pRequest = pRequest;
                char requestData[REQUEST_DATA_SIZE] = {0};
                result = findRequestData(requestData);
    
                if(strcmp(pBuffer, "command=wifi") == 0)
                {
                    configWifi(requestData);
                }
                else if(strcmp(pBuffer, "command=cloud_server") == 0)
                {
                    configCloudServer(requestData);                 
                }
                else if(strcmp(pBuffer, "command=switch") == 0)
                {
                    
                }
                else if(strcmp(pBuffer, "command=ble_cmd") == 0)
                {
                    
                }
                else if(strcmp(pBuffer, "command=ble_init") == 0)
                {
                    configBleInit(requestData);     
                }
            }
            free(pBuffer);
        }
        return result;
    }
    

    在configWifi()函数中对请求数据进行JSON解析,获取SSID和密码。
    调用sendPostResponse进行响应回复,随后开启定时器,1秒后进入配置WIFI连接路由器startDelayHandleTimer()


    • 由 Leung 写于 2020 年 4 月 27 日

    • 参考:ESP-IDF编程指南 - HTTP服务器

    相关文章

      网友评论

        本文标题:ESP8266学习笔记(20)——HTTP服务器(RTOS SD

        本文链接:https://www.haomeiwen.com/subject/dabywhtx.html