ESP32 HTTP客户端开发实战:TLS加密通信与RESTful API集成方案

ESP32作为物联网网关,除了采集传感器数据,最重要的任务就是把数据安全地传输到云端。在我们沧州虎王科技技术团队的多个项目中,ESP32需要通过HTTP协议与后端服务器通信,调用RESTful API上报数据、接收指令、下载配置。而一旦涉及HTTPS,TLS加密通信就成了绕不过去的技术门槛。

本文从实战角度出发,分享我们在ESP32上实现HTTP客户端的完整方案,涵盖TLS证书配置、RESTful API封装、错误重试机制、连接池管理等工程细节。

一、ESP32的HTTP/TLS技术栈

ESP-IDF提供了 esp_http_client 组件,底层基于mbedTLS实现TLS加密。整体技术栈如下:

复制代码
┌─────────────────────────────────────────┐
│          应用层 (RESTful API封装)         │
│    http_post("/api/telemetry", body)    │
├─────────────────────────────────────────┤
│      esp_http_client (ESP-IDF组件)       │
│   - HTTP请求/响应  - 重定向  - 流式传输   │
├─────────────────────────────────────────┤
│          mbedTLS (TLS/SSL引擎)           │
│   - TLS握手  - 证书验证  - 加密解密       │
├─────────────────────────────────────────┤
│          lwIP (TCP/IP协议栈)             │
│   - TCP连接  - DNS解析  - Socket管理     │
├─────────────────────────────────────────┤
│        WiFi (esp_wifi) / Ethernet       │
└─────────────────────────────────────────┘

理解这个层次结构很重要,因为TLS问题往往出在mbedTLS层,网络超时往往出在lwIP层,HTTP解析问题出在esp_http_client层。调试时需要逐层定位。

二、WiFi连接管理:HTTP通信的前置条件

在做HTTP通信之前,首先要确保WiFi连接稳定。我们封装了一个WiFi连接管理器,处理连接、断线重连、事件通知:

c 复制代码
// wifi_manager.c

#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT      BIT1

static EventGroupHandle_t s_wifi_event_group;
static int s_retry_count = 0;
static bool s_wifi_connected = false;

static void wifi_event_handler(void *arg, esp_event_base_t event_base,
                                int32_t event_id, void *event_data) {
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        s_wifi_connected = false;
        if (s_retry_count < 5) {
            esp_wifi_connect();
            s_retry_count++;
            ESP_LOGW(TAG, "WiFi retry %d/5", s_retry_count);
        } else {
            xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
        }
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
        ESP_LOGI(TAG, "Got IP:" IPSTR, IP2STR(&event->ip_info.ip));
        s_retry_count = 0;
        s_wifi_connected = true;
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

esp_err_t wifi_init(const char *ssid, const char *password) {
    s_wifi_event_group = xEventGroupCreate();
    
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_create_default_wifi_sta();
    
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
    
    esp_event_handler_instance_t instance_any_id;
    esp_event_handler_instance_t instance_got_ip;
    esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, 
                                         &wifi_event_handler, NULL, &instance_any_id);
    esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, 
                                         &wifi_event_handler, NULL, &instance_got_ip);
    
    wifi_config_t wifi_config = {0};
    strcpy((char *)wifi_config.sta.ssid, ssid);
    strcpy((char *)wifi_config.sta.password, password);
    wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
    
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());
    
    // 等待连接
    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
        WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, 
        pdMS_TO_TICKS(15000));
    
    if (bits & WIFI_CONNECTED_BIT) {
        ESP_LOGI(TAG, "WiFi connected to %s", ssid);
        return ESP_OK;
    }
    ESP_LOGE(TAG, "WiFi connection failed");
    return ESP_FAIL;
}

bool wifi_is_connected(void) {
    return s_wifi_connected;
}

三、TLS证书配置:HTTPS的核心

ESP32使用mbedTLS实现TLS,需要配置CA证书用于验证服务器证书。有两种方式:

方式一:嵌入CA证书(适合证书固定的场景)

从浏览器导出服务器CA证书的PEM格式,嵌入到固件中:

c 复制代码
// ca_cert.h
static const char server_cert_pem[] = 
"-----BEGIN CERTIFICATE-----\n"
"MIIDXTCCAkWgAwIBAgIJAKDxYJ5k5g3cMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n"
// ... 完整证书内容 ...
"-----END CERTIFICATE-----\n";

方式二:使用ESP-IDF内置的根证书包

ESP-IDF支持通过 menuconfig 启用mbedTLS根证书包,内置了常见的CA根证书:

复制代码
Component config → mbedTLS → Certificate Bundle → Enable Mozilla root CA bundle

我们沧州虎王科技的物联网平台使用Let's Encrypt证书,所以选择方式二更方便。但如果是内网部署的自签名证书,则必须用方式一。

以下是HTTP客户端的TLS配置代码:

c 复制代码
// http_client.c

#include "esp_http_client.h"
#include "esp_crt_bundle.h"

#define MAX_HTTP_OUTPUT_BUFFER 4096

typedef struct {
    char *response_buf;
    int response_len;
    int response_capacity;
    int status_code;
} http_response_t;

static esp_err_t http_event_handler(esp_http_client_event_t *evt) {
    http_response_t *resp = (http_response_t *)evt->user_data;
    
    switch (evt->event_id) {
    case HTTP_EVENT_ON_DATA:
        if (resp->response_len + evt->data_len < resp->response_capacity) {
            memcpy(resp->response_buf + resp->response_len, evt->data, evt->data_len);
            resp->response_len += evt->data_len;
        } else {
            ESP_LOGW(TAG, "Response buffer overflow, truncating");
        }
        break;
    case HTTP_EVENT_ON_FINISH:
        resp->response_buf[resp->response_len] = '\0';
        break;
    case HTTP_EVENT_ERROR:
        ESP_LOGE(TAG, "HTTP error: %s", esp_err_to_name(evt->error_handle->esp_error_code));
        break;
    default:
        break;
    }
    return ESP_OK;
}

// 创建配置好TLS的HTTP客户端
esp_http_client_handle_t http_client_create(const char *url) {
    esp_http_client_config_t config = {
        .url = url,
        .event_handler = http_event_handler,
        .cert_pem = NULL,               // 使用证书包而非单独证书
        .use_global_ca_store = false,
        .crt_bundle_attach = esp_crt_bundle_attach,  // 启用内置根证书包
        .timeout_ms = 10000,             // 10秒超时
        .buffer_size = 1024,             // 接收缓冲区
        .buffer_size_tx = 1024,          // 发送缓冲区
        .disable_auto_redirect = false,
        .max_redirection_count = 3,
    };
    
    return esp_http_client_init(&config);
}

四、RESTful API封装

基于 esp_http_client,我们封装了一套RESTful API客户端,支持GET/POST/PUT/DELETE,自动处理JSON序列化/反序列化:

c 复制代码
// rest_api.h

typedef struct {
    int status_code;
    char *body;
    int body_len;
} api_response_t;

typedef enum {
    API_OK = 0,
    API_ERR_NETWORK,      // 网络错误
    API_ERR_TIMEOUT,      // 超时
    API_ERR_TLS,          // TLS错误
    API_ERR_HTTP,         // HTTP错误(非2xx状态码)
    API_ERR_JSON,         // JSON解析错误
    API_ERR_MEMORY,       // 内存不足
} api_error_t;

// API配置
typedef struct {
    char base_url[128];          // 如 "https://iot.cztech.cn/api/v1"
    char auth_token[128];        // Bearer Token
    char device_id[64];         // 设备ID
    uint8_t max_retries;        // 最大重试次数
    uint32_t timeout_ms;        // 超时时间
} api_config_t;

static api_config_t s_api_config;
static const char *TAG_API = "REST_API";

void api_init(const api_config_t *config) {
    memcpy(&s_api_config, config, sizeof(api_config_t));
}

// 发送HTTP请求
static api_error_t api_send_request(const char *method_str, 
                                      const char *path,
                                      const char *body, 
                                      int body_len,
                                      const char *content_type,
                                      api_response_t *response) {
    if (!wifi_is_connected()) {
        ESP_LOGW(TAG_API, "WiFi not connected, cannot send request");
        return API_ERR_NETWORK;
    }
    
    // 构建完整URL
    char url[256];
    snprintf(url, sizeof(url), "%s%s", s_api_config.base_url, path);
    
    http_response_t http_resp = {0};
    http_resp.response_buf = malloc(MAX_HTTP_OUTPUT_BUFFER);
    if (!http_resp.response_buf) return API_ERR_MEMORY;
    http_resp.response_capacity = MAX_HTTP_OUTPUT_BUFFER;
    
    esp_http_client_config_t config = {
        .url = url,
        .event_handler = http_event_handler,
        .user_data = &http_resp,
        .crt_bundle_attach = esp_crt_bundle_attach,
        .timeout_ms = s_api_config.timeout_ms,
        .buffer_size = 2048,
        .buffer_size_tx = 2048,
        .is_async = false,
    };
    
    esp_http_client_handle_t client = esp_http_client_init(&config);
    
    // 设置方法
    if (strcmp(method_str, "POST") == 0) {
        esp_http_client_set_method(client, HTTP_METHOD_POST);
    } else if (strcmp(method_str, "PUT") == 0) {
        esp_http_client_set_method(client, HTTP_METHOD_PUT);
    } else if (strcmp(method_str, "DELETE") == 0) {
        esp_http_client_set_method(client, HTTP_METHOD_DELETE);
    }
    
    // 设置认证头
    char auth_header[256];
    snprintf(auth_header, sizeof(auth_header), "Bearer %s", s_api_config.auth_token);
    esp_http_client_set_header(client, "Authorization", auth_header);
    esp_http_client_set_header(client, "Content-Type", content_type);
    esp_http_client_set_header(client, "X-Device-Id", s_api_config.device_id);
    esp_http_client_set_header(client, "User-Agent", "CZTech-ESP32/1.0");
    
    // 设置请求体
    if (body && body_len > 0) {
        esp_http_client_set_post_field(client, body, body_len);
    }
    
    // 发送请求
    esp_err_t err = esp_http_client_perform(client);
    api_error_t result = API_OK;
    
    if (err == ESP_OK) {
        response->status_code = esp_http_client_get_status_code(client);
        response->body = http_resp.response_buf;
        response->body_len = http_resp.response_len;
        
        if (response->status_code >= 200 && response->status_code < 300) {
            ESP_LOGD(TAG_API, "%s %s -> %d (%d bytes)", method_str, path, 
                     response->status_code, response->body_len);
        } else {
            ESP_LOGW(TAG_API, "%s %s -> %d: %.*s", method_str, path,
                     response->status_code, response->body_len, response->body);
            result = API_ERR_HTTP;
        }
    } else {
        if (err == ESP_ERR_HTTP_CONNECT) result = API_ERR_NETWORK;
        else if (err == ESP_ERR_HTTP_FETCH_HEADER || err == ESP_ERR_HTTP_INVALID_TRANSPORT) {
            result = API_ERR_TLS;
        } else {
            result = API_ERR_NETWORK;
        }
        ESP_LOGE(TAG_API, "HTTP request failed: %s", esp_err_to_name(err));
    }
    
    esp_http_client_cleanup(client);
    
    if (result != API_OK) {
        free(http_resp.response_buf);
        response->body = NULL;
    }
    
    return result;
}

// 带重试的请求发送
static api_error_t api_send_with_retry(const char *method,
                                         const char *path,
                                         const char *body,
                                         int body_len,
                                         const char *content_type,
                                         api_response_t *response) {
    api_error_t err;
    int retry_delay_ms = 1000;
    
    for (int i = 0; i <= s_api_config.max_retries; i++) {
        err = api_send_request(method, path, body, body_len, content_type, response);
        
        if (err == API_OK) return err;
        
        // 只对网络/超时错误重试,不对HTTP 4xx错误重试
        if (err == API_ERR_HTTP) return err;
        
        if (i < s_api_config.max_retries) {
            ESP_LOGW(TAG_API, "Retry %d/%d after %dms (error=%d)", 
                     i + 1, s_api_config.max_retries, retry_delay_ms, err);
            vTaskDelay(pdMS_TO_TICKS(retry_delay_ms));
            retry_delay_ms = (retry_delay_ms * 2 < 16000) ? retry_delay_ms * 2 : 16000;
        }
    }
    return err;
}

基于这个底层封装,实现具体的RESTful API调用就非常简洁了:

c 复制代码
// 上报遥测数据
api_error_t api_post_telemetry(const char *json_payload) {
    api_response_t resp = {0};
    return api_send_with_retry("POST", "/telemetry", 
                               json_payload, strlen(json_payload),
                               "application/json", &resp);
}

// 获取设备配置
api_error_t api_get_config(cJSON **config_json) {
    api_response_t resp = {0};
    api_error_t err = api_send_with_retry("GET", "/config", 
                                            NULL, 0, NULL, &resp);
    if (err == API_OK) {
        *config_json = cJSON_Parse(resp.body);
        if (!*config_json) err = API_ERR_JSON;
        free(resp.body);
    }
    return err;
}

// 上报设备状态
api_error_t api_put_status(const char *status_json) {
    api_response_t resp = {0};
    return api_send_with_retry("PUT", "/status",
                               status_json, strlen(status_json),
                               "application/json", &resp);
}

// 下载固件更新包
api_error_t api_download_ota(const char *version, 
                               esp_http_client_handle_t *client) {
    char path[128];
    snprintf(path, sizeof(path), "/ota/%s", version);
    
    // OTA下载需要流式处理,返回client句柄由调用方逐步读取
    // ... 实现省略 ...
    return API_OK;
}

五、MQTT与HTTP的混合使用策略

在实际项目中,HTTP和MQTT各有适用场景。我们的策略是:

  • HTTP:用于设备注册、配置拉取、OTA升级、用户指令下发等低频、请求-响应式通信
  • MQTT:用于遥测数据实时上报、告警事件推送等高频、发布-订阅式通信
c 复制代码
// 混合通信策略示例
void data_report_task(void *arg) {
    sensor_value_t values[8];
    int count;
    int report_counter = 0;
    
    while (1) {
        // 读取传感器数据
        device_read("temp_01", values, 8, &count);
        
        // 构建JSON
        cJSON *root = cJSON_CreateObject();
        cJSON *props = cJSON_CreateObject();
        for (int i = 0; i < count; i++) {
            switch (values[i].type) {
                case SENSOR_TYPE_TEMP:
                    cJSON_AddNumberToObject(props, "temperature", values[i].value.temperature);
                    break;
                case SENSOR_TYPE_HUMIDITY:
                    cJSON_AddNumberToObject(props, "humidity", values[i].value.humidity);
                    break;
            }
        }
        cJSON_AddItemToObject(root, "properties", props);
        cJSON_AddNumberToObject(root, "timestamp", esp_timer_get_time() / 1000);
        
        char *json_str = cJSON_PrintUnformatted(root);
        
        // 高频遥测走MQTT
        mqtt_publish("device/temp_01/telemetry", json_str, 0);
        
        // 每60次(约5分钟)通过HTTP上报一次完整数据快照
        if (++report_counter >= 60) {
            api_post_telemetry(json_str);
            report_counter = 0;
        }
        
        free(json_str);
        cJSON_Delete(root);
        
        vTaskDelay(pdMS_TO_TICKS(5000));  // 5秒间隔
    }
}

六、错误处理与诊断

网络通信中最难处理的不是成功路径,而是各种失败场景。我们建立了一套完整的错误诊断体系:

c 复制代码
// 错误诊断信息
typedef struct {
    api_error_t error;
    const char *description;
    const char *suggestion;
} error_diag_t;

static const error_diag_t s_error_diags[] = {
    {API_OK,           "成功",                 "无需操作"},
    {API_ERR_NETWORK,  "网络连接失败",          "检查WiFi连接、DNS解析、服务器可达性"},
    {API_ERR_TIMEOUT,  "请求超时",             "检查网络延迟、增大超时时间、检查服务器负载"},
    {API_ERR_TLS,      "TLS握手失败",           "检查CA证书有效性、服务器证书是否过期、ESP32时间是否正确"},
    {API_ERR_HTTP,     "HTTP错误响应",          "检查API路径、认证Token、请求体格式"},
    {API_ERR_JSON,     "JSON解析失败",          "检查响应体是否为合法JSON格式"},
    {API_ERR_MEMORY,   "内存不足",             "减少缓冲区大小、检查ESP32内存使用情况"},
};

void api_log_error(api_error_t err) {
    for (int i = 0; i < sizeof(s_error_diags) / sizeof(s_error_diags[0]); i++) {
        if (s_error_diags[i].error == err) {
            ESP_LOGE(TAG_API, "[%d] %s | 建议: %s", err, 
                     s_error_diags[i].description, s_error_diags[i].suggestion);
            return;
        }
    }
}

TLS证书验证失败的排查:这是最常见也最难定位的问题。原因是ESP32的RTC时钟不准,导致证书有效期验证失败。解决方法是在WiFi连接后立即同步NTP时间:

c 复制代码
// NTP时间同步(TLS证书验证的前提)
void ntp_sync_time(void) {
    esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);
    esp_sntp_setservername(0, "ntp.aliyun.com");
    esp_sntp_setservername(1, "pool.ntp.org");
    esp_sntp_init();
    
    // 等待同步
    time_t now = 0;
    struct tm timeinfo = {0};
    int retry = 0;
    do {
        vTaskDelay(pdMS_TO_TICKS(2000));
        time(&now);
        localtime_r(&now, &timeinfo);
        retry++;
    } while (timeinfo.tm_year < (2020 - 1900) && retry < 10);
    
    if (timeinfo.tm_year >= (2020 - 1900)) {
        ESP_LOGI(TAG, "NTP sync: %ld", (long)now);
    } else {
        ESP_LOGW(TAG, "NTP sync failed, TLS may not work properly");
    }
}

七、内存管理与性能优化

ESP32的可用RAM有限(约320KB SRAM),HTTP+TLS的内存消耗不小。以下是我们的优化经验:

优化一:减小mbedTLS内存占用

在menuconfig中调整mbedTLS配置,减少不必要的加密算法支持:

复制代码
Component config → mbedTLS:
  - 取消不用的加密算法(如Camellia, DES等)
  - Maximum SSL fragment size: 4096 → 2048
  - Dynamic fragment size: 启用
  - Dynamic free peer cert: 启用(握手后释放证书内存)

优化二:HTTP客户端复用

避免每次请求都创建和销毁HTTP客户端。对于频繁请求同一服务器的场景,复用client句柄:

c 复制代码
static esp_http_client_handle_t s_persistent_client = NULL;

esp_http_client_handle_t api_get_client(void) {
    if (s_persistent_client) return s_persistent_client;
    
    esp_http_client_config_t config = {
        .url = s_api_config.base_url,
        .crt_bundle_attach = esp_crt_bundle_attach,
        .timeout_ms = s_api_config.timeout_ms,
        .buffer_size = 2048,
    };
    s_persistent_client = esp_http_client_init(&config);
    return s_persistent_client;
}

优化三:响应体按需读取

对于大响应体(如OTA固件下载),不要一次性读入内存,而是分块处理:

c 复制代码
// OTA分块下载
esp_err_t ota_download(const char *url, esp_ota_handle_t *update_handle) {
    esp_http_client_config_t config = {
        .url = url,
        .crt_bundle_attach = esp_crt_bundle_attach,
        .timeout_ms = 30000,
    };
    esp_http_client_handle_t client = esp_http_client_init(&config);
    
    esp_err_t err = esp_http_client_open(client, 0);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
        return err;
    }
    
    int content_length = esp_http_client_fetch_headers(client);
    ESP_LOGI(TAG, "OTA image size: %d bytes", content_length);
    
    // 分块读取并写入OTA分区
    char *buffer = malloc(4096);
    int total_read = 0;
    int read_len;
    
    while ((read_len = esp_http_client_read(client, buffer, 4096)) > 0) {
        err = esp_ota_write(*update_handle, buffer, read_len);
        if (err != ESP_OK) break;
        total_read += read_len;
        
        // 打印进度
        if (total_read % (4096 * 32) == 0) {
            ESP_LOGI(TAG, "OTA progress: %d / %d (%.1f%%)", 
                     total_read, content_length,
                     (float)total_read / content_length * 100);
        }
    }
    
    free(buffer);
    esp_http_client_close(client);
    esp_http_client_cleanup(client);
    
    ESP_LOGI(TAG, "OTA download complete: %d bytes", total_read);
    return (total_read == content_length) ? ESP_OK : ESP_FAIL;
}

八、调试工具与实战技巧

在开发HTTP客户端时,调试网络通信是最耗时的环节。我们团队的ESP32工具箱V2.0集成了网络诊断功能,可以快速执行ping测试、DNS查询、HTTP连通性检查,帮助定位是网络层还是应用层的问题。

在现场调试时,随身WiFi硬件调试工具(hardware.czkree.com)的作用更加明显。它可以通过WiFi连接到ESP32的调试串口,手机浏览器上就能查看ESP32的实时日志。我们经常在工厂车间调试时,用手机看日志确认HTTP请求是否成功,不用专门找一台有串口的电脑。

几个实用的调试技巧:

技巧一:抓取HTTP请求详情

在开发阶段,开启详细的日志级别,打印完整的HTTP请求和响应:

c 复制代码
// 开启详细HTTP日志
esp_log_level_set("HTTP_CLIENT", ESP_LOG_DEBUG);
esp_log_level_set("TRANS_SSL", ESP_LOG_DEBUG);

技巧二:使用HTTP而非HTTPS快速验证

开发阶段先用HTTP(明文)快速验证API逻辑是否正确,再切换到HTTPS排查TLS问题。这样可以把问题分解为"API逻辑"和"TLS配置"两个独立部分:

c 复制代码
// 开发环境使用HTTP
#ifdef CONFIG_DEV_MODE
    #define API_BASE_URL "http://192.168.1.100:8080/api/v1"
#else
    #define API_BASE_URL "https://iot.cztech.cn/api/v1"
#endif

技巧三:服务器端日志比对

当ESP32上报的数据在沧州虎王科技物联网平台上显示异常时,第一步是查看平台后端日志确认数据是否到达、格式是否正确。我们物联网平台提供了请求日志查看功能,可以直接看到ESP32发来的原始HTTP请求体。

九、踩坑经验

坑一:TLS握手内存不足

ESP32默认配置下,TLS握手需要约40KB内存。如果应用本身内存占用高,握手时会因为内存不足而失败,但错误信息可能只是笼统的"connection failed"。解决方案是优化mbedTLS配置,减少fragment size,或者在握手前释放一些不必要的内存。

坑二:HTTP Keep-Alive导致连接复用问题

esp_http_client默认启用Keep-Alive,复用TCP连接。但ESP32的lwIP在连接空闲一段时间后可能被NAT路由器断开,导致下次请求失败。解决方案是设置较短的keep-alive超时,或在请求失败后强制重建连接。

坑三:大JSON响应导致缓冲区溢出

设备配置接口返回的JSON可能很大(包含完整的物模型定义)。我们的做法是在服务器端支持分页和字段过滤,ESP32只请求需要的字段:

c 复制代码
// 只请求必要字段
api_response_t resp = {0};
api_send_with_retry("GET", "/config?fields=model,properties", NULL, 0, NULL, &resp);

十、总结

ESP32的HTTP+TLS通信开发看似简单,实际涉及的环节很多:WiFi连接管理、NTP时间同步、TLS证书配置、HTTP请求封装、错误重试、内存优化。每一个环节出问题都会导致通信失败。

我们沧州虎王科技技术团队总结的这套方案,核心设计原则是:把复杂性封装在底层,对上层暴露简洁的API 。应用代码只需要调用 api_post_telemetry(json_str),不需要关心WiFi是否连接、TLS是否握手成功、是否需要重试。

配合沧州虎王科技物联网平台的API和ESP32工具箱V2.0的调试能力,我们的ESP32设备已经稳定运行在多个项目中,日均处理数十万次HTTP请求。希望这些经验能帮助到正在做ESP32联网开发的同行们。

相关推荐
小僧景贤4 小时前
嵌入式网络协议 | MQTT、CoAP与HTTP协议深度解析(原理、差异、工程选型、代码实操)
mqtt·http·物联网协议·coap·网络协议选型
CDN3606 小时前
360CDN发布HTTP/3(QUIC)弱网加速方案:实测首屏提速40%,彻底终结移动端接口超时与频繁断连
网络·网络协议·http
就叫飞六吧1 天前
Cloudflare 免费 SSL 证书与兼容端口列表科普
网络协议·iphone·ssl
一条泥憨鱼1 天前
【从0开始学习计算机网络】| DNS -记录类型详解
计算机网络·http·github·域名·dns·记录
Data_Journal1 天前
Golang 中解析 HTML 的指南
ios·iphone
游戏开发爱好者81 天前
开心上架是什么,一站式 Apple 开发者工作台总览
android·小程序·https·uni-app·iphone·webview
jike_20261 天前
销售拜访记录APP推荐:客户沟通内容怎么快速整理?
ios·语音识别·iphone
白嫖一茶1 天前
HTTP 状态码总共分为五大类
http
jike_20262 天前
4款会议翻译转写工具对比:多语言、方言和线下会议怎么选?
人工智能·语音识别·iphone