You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 03:10:48