ESP32S3(ESP-IDF C)能否通过WebSocket与Cloud Firestore通信?
解决方案:ESP32S3 通过 WebSocket 连接 Firestore 并维持低延迟通信
核心逻辑说明
Firestore 本身无原生 WebSocket 端点,但可以通过 Firebase Realtime Database 的 WebSocket API 实现低延迟数据传输,再配合 Cloud Functions 自动将 Realtime Database 的数据同步到 Firestore——既保留 WebSocket 的低延迟特性,又能复用 Firestore 的结构化数据存储能力。同时需要嵌入 Firebase Auth 的 Token 刷新逻辑,确保长连接的有效性。
步骤1:配置 Firebase 环境
- 在 Firebase 控制台启用 Realtime Database 和 Cloud Functions
- 部署云函数实现 Realtime Database 到 Firestore 的数据同步,示例代码(Node.js):
const functions = require("firebase-functions"); const admin = require("firebase-admin"); admin.initializeApp(); // 监听Realtime Database写入事件,同步到Firestore exports.syncToFirestore = functions.database.ref("/agri-weighing/{dataId}") .onCreate((snapshot, context) => { const rawData = snapshot.val(); return admin.firestore() .collection("agri-weighing") .doc(context.params.dataId) .set(rawData); });
- 配置 Realtime Database 安全规则,仅允许认证用户读写:
{ "rules": { ".read": "auth != null", ".write": "auth != null" } }
步骤2:ESP32S3 端 WebSocket 连接 + Token 刷新实现(ESP-IDF C)
依赖准备
确保 ESP-IDF 环境包含 esp_websocket_client、esp_http_client 和 cJSON 组件(用于解析 Token 响应)。
核心代码示例
#include <stdio.h> #include <string.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_websocket_client.h" #include "esp_http_client.h" #include "esp_log.h" #include "cJSON.h" #define TAG "FIREBASE_WS" #define FIREBASE_RTDB_WS_URL "wss://<你的项目ID>.firebaseio.com/.ws?v=5" #define FIREBASE_AUTH_REFRESH_URL "https://securetoken.googleapis.com/v1/token?key=<你的API密钥>" #define INIT_REFRESH_TOKEN "<初始刷新Token>" #define TOKEN_REFRESH_INTERVAL (55 * 60 * 1000) // 提前5分钟刷新Token static char current_id_token[1024] = {0}; static esp_websocket_client_handle_t ws_client = NULL; static TimerHandle_t token_refresh_timer = NULL; // 刷新Firebase Auth Token static esp_err_t refresh_auth_token(void) { esp_http_client_config_t http_cfg = { .url = FIREBASE_AUTH_REFRESH_URL, .method = HTTP_METHOD_POST, }; esp_http_client_handle_t http_client = esp_http_client_init(&http_cfg); // 构造请求体 char post_data[512]; snprintf(post_data, sizeof(post_data), "grant_type=refresh_token&refresh_token=%s", INIT_REFRESH_TOKEN); esp_http_client_set_post_field(http_client, post_data, strlen(post_data)); esp_http_client_set_header(http_client, "Content-Type", "application/x-www-form-urlencoded"); esp_err_t err = esp_http_client_perform(http_client); if (err == ESP_OK && esp_http_client_get_status_code(http_client) == 200) { char response_buf[2048]; int resp_len = esp_http_client_read_response(http_client, response_buf, sizeof(response_buf)-1); response_buf[resp_len] = '\0'; // 用cJSON解析响应,提取id_token cJSON *root = cJSON_Parse(response_buf); if (root) { cJSON *token_node = cJSON_GetObjectItem(root, "id_token"); if (token_node && cJSON_IsString(token_node)) { strncpy(current_id_token, token_node->valuestring, sizeof(current_id_token)-1); current_id_token[sizeof(current_id_token)-1] = '\0'; ESP_LOGI(TAG, "Token刷新成功"); } cJSON_Delete(root); } } esp_http_client_cleanup(http_client); return err; } // WebSocket事件处理 static void ws_event_handler(void *args, esp_event_base_t base, int32_t event_id, void *event_data) { esp_websocket_event_data_t *ws_data = (esp_websocket_event_data_t *)event_data; switch (event_id) { case WEBSOCKET_EVENT_CONNECTED: ESP_LOGI(TAG, "WebSocket连接成功,发送认证信息"); // 发送认证消息到Realtime Database char auth_msg[256]; snprintf(auth_msg, sizeof(auth_msg), "{\"t\":\"auth\",\"d\":\"%s\"}", current_id_token); esp_websocket_client_send_text(ws_client, auth_msg, strlen(auth_msg), portMAX_DELAY); break; case WEBSOCKET_EVENT_DISCONNECTED: ESP_LOGI(TAG, "WebSocket断开,尝试重连"); esp_websocket_client_start(ws_client); break; case WEBSOCKET_EVENT_DATA: ESP_LOGI(TAG, "收到服务器响应:%.*s", ws_data->data_len, ws_data->data_ptr); break; default: break; } } // Token刷新定时器回调 static void token_refresh_cb(TimerHandle_t xTimer) { ESP_LOGI(TAG, "开始刷新Token"); if (refresh_auth_token() == ESP_OK) { // 重新发送认证信息维持连接 char auth_msg[256]; snprintf(auth_msg, sizeof(auth_msg), "{\"t\":\"auth\",\"d\":\"%s\"}", current_id_token); esp_websocket_client_send_text(ws_client, auth_msg, strlen(auth_msg), portMAX_DELAY); } } void app_main(void) { // 初始化WiFi(请替换为你的现有WiFi初始化代码) // ... // 初始刷新Token refresh_auth_token(); // 配置WebSocket客户端 esp_websocket_client_config_t ws_cfg = { .uri = FIREBASE_RTDB_WS_URL, .disable_auto_reconnect = false, }; ws_client = esp_websocket_client_init(&ws_cfg); esp_websocket_register_events(ws_client, WEBSOCKET_EVENT_ANY, ws_event_handler, NULL); esp_websocket_client_start(ws_client); // 创建Token刷新定时器 token_refresh_timer = xTimerCreate("token_refresh", pdMS_TO_TICKS(TOKEN_REFRESH_INTERVAL), pdTRUE, NULL, token_refresh_cb); xTimerStart(token_refresh_timer, 0); // 模拟高频称重数据发送(替换为你的硬件读取逻辑) while (1) { float weight = (rand() % 2000) / 10.0f; // 模拟0-200kg称重数据 char data_msg[300]; snprintf(data_msg, sizeof(data_msg), "{\"t\":\"put\",\"d\":{\"agri-weighing/%d\":{\"weight\":%.2f,\"timestamp\":%lld}}}", rand(), weight, esp_timer_get_time() / 1000); esp_websocket_client_send_text(ws_client, data_msg, strlen(data_msg), portMAX_DELAY); ESP_LOGI(TAG, "发送称重数据:%.2f kg", weight); vTaskDelay(pdMS_TO_TICKS(100)); // 100ms一次,可根据需求调整频率 } }
关键注意事项
- Token 解析:必须使用
cJSON等 JSON 库解析响应,避免字符串截取的不可靠性 - 连接稳定性:开启 WebSocket 自动重连,配合 Token 刷新机制,确保长连接持续有效
- 延迟优化:WebSocket 建立连接后,单条数据传输延迟可控制在 500ms 以内(取决于网络环境),完全满足农业高频采集需求
- 安全验证:确保 Realtime Database 规则严格限制认证用户访问,避免数据泄露
内容的提问来源于stack exchange,提问作者Adrian Wreyford
相关产品推荐
相关产品推荐

