先别急着谈省电,先把数据传上去。很多功耗问题,其实是"根本没跑通"和"跑通了但设计不对"混在一起。下面这段 Arduino 代码,是 ESP32 用 HTTP POST 往云递数据推送一条 JSON 的最小示例,能编译、能跑,跑通它再谈优化。

#include <WiFi.h>
#include <HTTPClient.h>

const char* WIFI_SSID = "你的WiFi名";
const char* WIFI_PASS = "你的WiFi密码";
const char* API_URL   = "http://你的域名/api/push.php";
const char* API_KEY   = "你的APIKey";

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) {
    delay(300);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected");

  if (WiFi.status() == WL_CONNECTED) {
    HTTPClient http;
    http.begin(API_URL);
    http.addHeader("Content-Type", "application/json");
    http.addHeader("X-Api-Key", API_KEY);   // 密钥也可以放请求头

    String body = "{\"dataset\":\"sensor\",\"data\":{\"temp\":26.5,\"hum\":58}}";
    int code = http.POST(body);
    Serial.printf("HTTP %d\n", code);
    Serial.println(http.getString());
    http.end();
  }
}

void loop() {
  // 这里先空着,第 3 节再改成低功耗管线
}

注意两点:dataset 是你自己起的数据名,用来分类管理;data 可以是对象、数组,也可以是一段文本。密钥放请求头 X-Api-Key 比塞在 URL 里干净,日志里也不容易漏。跑通这一步,你才有资格讨论"每次上传到底花多少电"。

先跑起来:一个最小可运行的ESP32上传示例

上面那段代码是"直连式"写法:连上 WiFi、发一个请求、结束。它的问题在于把 WiFi 一直挂着,ESP32 的射频模块只要处于连接态,哪怕不发数据,静态电流也在几十毫安级别。电池供电的设备这么干,续航基本按小时算。

所以最小示例只用来验证链路:域名通不通、APIKey 对不对、返回码是不是 200、控制台里能不能看到这条数据。验证完就把它扔掉,别拿它当产品代码。真正的上传管线要围绕"少醒、快传、早睡"来组织,下一节我们先把电费账单算清楚。

ESP32数据上传为什么耗电:唤醒、射频与请求次数的账

把一次上传拆开看,耗电大头有三个:唤醒与启动(从深度睡眠醒来、重新初始化外设)、射频活动(WiFi 扫描、关联、DHCP、TLS/HTTP 往返)、请求次数(每次请求都是一次完整的连接建立与关闭)。深度睡眠本身很省,但"醒来—连网—发一条—睡下"这个循环,如果每 10 秒跑一次,绝大部分电量都花在连网上了。

这笔账的直觉是:单次上传的固定成本(连网)远大于边际成本(多带几十字节数据)。因此优化方向不是"把请求发得更快",而是"把多次上传合并成一次"、"把连网时间压到最短"、"把睡眠时间拉长"。下面这张表把几种常见策略摆在一起对比,方便你选型:

策略唤醒频率单次射频时长适合场景主要代价
常连 WiFi 轮询不睡,持续在线持续需实时下发/调试电流大,电池撑不住
定时唤醒单条上传高(如每 30 秒)每次完整连网数据点少、间隔短连网开销占比高
深度睡眠 + 批量攒包低(如每 10 分钟)每次完整连网传感器周期采样数据有延迟
攒包 + 失败不重试立即睡低最短弱网、偏远部署可能丢一个周期

一句话总结:能批量就批量,能睡就睡,连不上就赶紧睡,下一轮再说。反复重试是最亏的——电花了,数据还不一定上去。

分步实现:从单次上传到低功耗上传管线

第一步,把采样和上传解耦。采样可以很频繁(比如每秒读一次传感器),但只在内存里攒着;攒够 N 条或者到了上传窗口,才醒着去连网。第二步,用深度睡眠定时器控制节奏,醒来后先连网、再一次性把攒的包推上去。第三步,给连网加超时上限,超时就放弃本轮,直接睡。

下面是一个可运行的骨架,用 RTC 内存保存待发数据(深度睡眠期间 RTC 内存不断电),醒来后批量推送:

#include <WiFi.h>
#include <HTTPClient.h>

RTC_DATA_ATTR int bootCount = 0;
RTC_DATA_ATTR char pending[2048] = "";   // 攒包缓冲,放 RTC 内存

const char* API_URL = "http://你的域名/api/push.php";
const char* API_KEY = "你的APIKey";
const uint64_t SLEEP_SEC = 600;          // 10 分钟醒一次

bool wifiConnect(uint32_t timeoutMs) {
  WiFi.mode(WIFI_STA);
  WiFi.begin("你的WiFi名", "你的WiFi密码");
  uint32_t t0 = millis();
  while (WiFi.status() != WL_CONNECTED) {
    if (millis() - t0 > timeoutMs) return false;  // 超时立刻放弃
    delay(100);
  }
  return true;
}

void pushBatch(const String& payload) {
  HTTPClient http;
  http.setTimeout(5000);                 // 请求级超时,别无限等
  http.begin(API_URL);
  http.addHeader("Content-Type", "application/json");
  http.addHeader("X-Api-Key", API_KEY);
  String body = "{\"dataset\":\"sensor\",\"data\":" + payload + "}";
  int code = http.POST(body);
  Serial.printf("POST %d\n", code);
  http.end();
}

void setup() {
  Serial.begin(115200);
  bootCount++;
  Serial.printf("boot #%d, pending=%s\n", bootCount, pending);

  // 模拟采样:把本次读数追加进缓冲
  String cur = "{\"t\":" + String(millis()) + ",\"v\":26.5}";
  if (strlen(pending) == 0) strcpy(pending, ("[" + cur).c_str());
  else strcat(pending, ("," + cur).c_str());
  strcat(pending, "]");

  if (wifiConnect(8000)) {
    pushBatch(String(pending));
    pending[0] = '\0';                   // 成功才清空
  } else {
    Serial.println("WiFi timeout, skip this round");
  }

  esp_sleep_enable_timer_wakeup(SLEEP_SEC * 1000000ULL);
  esp_deep_sleep_start();
}

void loop() {}

这段代码里有几个关键取舍。缓冲用 RTC_DATA_ATTR 存,睡眠不丢;但容量有限,攒满了要主动上传或者丢弃最旧的。连网超时设 8 秒,请求超时设 5 秒,是为了给单轮唤醒设一个明确上限——没有上限的重试,就是电量黑洞。另外,云递数据对内容完全相同的写入会做哈希去重,只保留第一条,所以别指望靠"重复发同一条"来刷存在感,攒包时保证数据有变化才有意义。

如果你数据量更小、更在意延迟,可以把上面的"攒包"换成"每次醒只发一条",但睡眠间隔要拉长到分钟级;反过来,如果采样很密,就把缓冲做大、睡眠拉长。核心公式始终是:单轮唤醒的固定开销 × 唤醒次数,你能压的是后者。

运行效果:如何观察功耗与上传是否正常

功耗侧,最实用的办法是串一个万用表或 USB 电流表看平均电流,配合串口日志看单轮醒着的时长。改造前常连 WiFi 的设备,平均电流往往在几十毫安;改成 10 分钟醒一次、单轮连网 3 秒左右之后,平均电流会掉到毫安甚至微安量级——具体数字取决于你的板子和 WiFi 信号强度,信号差时射频会加大功率,这是变量。

上传侧,登录云递数据控制台就能看到条数、容量、已读状态,数据名(dataset)分好类,一眼能看出哪一轮没上来。也可以用 pull.php 主动读一次验证写入顺序:

curl -X POST http://你的域名/api/pull.php \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: 你的APIKey" \
  -d '{"dataset":"sensor","limit":5}'

注意 pull.php 是队列式消费:按写入顺序 FIFO 读取,取走即标记已读,不会重复下发。所以别拿生产数据随便 pull 着玩,读完就从队列里没了。调试阶段建议单独开一个 dataset,比如 sensor_debug,跟正式数据隔开。

常见坑与注意事项

  • 把 WiFi 当常开服务:任务做完了不 WiFi.disconnect() 也不睡,射频一直挂着,这是最常见的电老虎。
  • 无上限重试:连不上就 while 死等,或者失败后立刻重试十次。正确做法是设总超时,超时就睡,下一轮再试。
  • 缓冲区溢出:RTC 内存不大,strcat 前不判长度,醒来直接跑飞。加长度检查,满了先发或丢弃。
  • 成功判定太宽松:只看"发出了",不看返回码。建议至少看 HTTP 200,再决定清不清缓冲。
  • 密钥硬编码进公开仓库:APIKey 泄露等于别人能用你的账号推数据,正式固件里做隔离。
  • 忽略信号强度:信号弱时射频功耗上升、连网时间变长,部署位置要实测,别只信理论值。

另外提醒一句,云递数据是轻量级存储与分发平台,定位是"不用自己买服务器和数据库",接口就 push 和 pull 两个,别指望它做实时流式下发。你的设备是"推完就睡"的模式,正好契合。

常见问题

ESP32用深度睡眠后,云递数据里的数据会丢吗?

不会。数据一旦通过 push.php 成功写入,就存在云端,跟你设备睡不睡没关系。会丢的是"还没发出去"的数据——那部分在 ESP32 的 RTC 内存里,掉电或复位可能丢,所以缓冲要控制大小、及时上传。会员到期后数据也保留不删除,只是暂停推送和读取,续费即恢复。

上传间隔设多长比较合适?

没有标准答案,看你的数据用途。监测类场景 5 到 15 分钟一次是常见区间,因为单轮连网的固定开销摊到更长的时间上,平均电流才好看。如果你需要更及时的响应,就缩短间隔但接受续航下降;两者不可兼得,先算清楚单轮唤醒耗多少电,再倒推间隔。

数据量很小,还需要批量攒包吗?

需要,但目的不是省流量,是省"连网次数"。一条 50 字节的数据和十条 50 字节的数据,射频建立连接的固定成本几乎一样,所以把十次唤醒合并成一次,省下的是九次连网。数据量小反而更应该攒,因为你的瓶颈从来不是带宽。

WiFi连接失败时反复重试会更费电吗?

会,而且是最费电的一种。每次重试都要重新扫描、关联、等 DHCP,射频全程高功率工作,失败重试十次可能比成功上传一次还耗电。正确做法是给连网设一个总超时(比如 8 秒),超时就放弃本轮直接睡,把重试留给下一个睡眠周期。

会员到期后设备还能继续推送数据吗?

不能。会员到期后数据保留不删除,但推送和读取会暂停,续费后恢复。新用户注册赠送 3 天会员试用,正式会员是月卡 19 元、年卡 99 元,卡密充值,多张可叠加、到期顺延。所以长期部署的设备,记得把续费纳入运维计划,别等数据断了才发现。

开始使用

本文的代码示例都可以直接在云递数据上运行:注册后即可获得专属 APIKey,在 开发文档 中查看推送、读取接口的完整说明;需要长期使用可以在 会员中心 用卡密开通月卡或年卡;更多实战内容可以浏览技术博客的其他文章。