先别急着谈省电,先把数据传上去。很多功耗问题,其实是"根本没跑通"和"跑通了但设计不对"混在一起。下面这段 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,在 开发文档 中查看推送、读取接口的完整说明;需要长期使用可以在 会员中心 用卡密开通月卡或年卡;更多实战内容可以浏览技术博客的其他文章。