一、先说我踩过的那个坑:串口全是乱码
那天晚上十一点,我盯着串口监视器上滚动的乱码,心里只有一个念头:这玩意儿是不是坏了。ESP32 开发板刚上电,程序明明烧进去了,可打印出来的中文全是"烫烫烫"和方块,传感器读数也是乱七八糟的符号。
排查了两小时,最后发现根本不是硬件问题,而是我把波特率设成了 9600,代码里却写着 Serial.begin(115200)。改回来那一刻,数据瞬间正常。这个坑听起来很蠢,但新手十有八九都会中招——而且它特别容易把人带偏,让你以为传感器坏了、WiFi 模块坏了,其实只是"两个人说话语速对不上"。
更坑的是第二个问题:数据读出来了,但往服务器传的时候一直超时。后来才搞明白,HTTP 请求发出去之前,WiFi 得先连上、得等 DHCP 分到 IP,我却在 setup() 里连完 WiFi 立刻发请求,压根没给网络栈留时间。这两个坑踩完,我才算真正把"ESP32 通过 HTTP 上传传感器数据"这条链路跑通。下面把整个过程拆开讲。
二、ESP32上传传感器数据到底走的是什么原理
很多人一上来就抄代码,结果出了问题完全不知道从哪查。其实原理就一句话:ESP32 把传感器读到的值,打包成一段 JSON 文本,通过 WiFi 用 HTTP POST 请求发给一个能接收数据的服务器,服务器存下来,你在网页上就能看到。
拆开看是四步:采集(读传感器)、封装(拼成 JSON)、传输(HTTP POST)、落地(服务端存储)。中间任何一步出问题,控制台里就看不到数据。
这里我用的接收端是云递数据,它是一个轻量级云端数据存储与分发平台,不用买服务器和数据库,通过 HTTP/JSON 接口就能存取数据。它只有两个核心接口:推送用 POST http://你的域名/api/push.php,读取用 POST http://你的域名/api/pull.php。对 ESP32 这种内存紧张的设备来说,JSON 请求体小、接口简单,非常合适。
| 环节 | ESP32 做什么 | 服务端做什么 |
|---|---|---|
| 采集 | 读 DHT11/DS18B20 等传感器 | — |
| 封装 | 拼 JSON:api_key、dataset、data | — |
| 传输 | HTTP POST 到 push.php | 接收并校验 api_key |
| 落地 | — | 规范化 + 哈希去重后入库 |
注意最后一步的"哈希去重":如果你连续推送两条内容完全一样的数据,服务端只保留第一条。这本来是个好设计,防止重复数据撑爆库,但如果你没意识到,会以为"我明明推了两条怎么只看到一条"。这个后面 FAQ 里细说。
三、分步实现:从读传感器到推送成功
我按 Arduino 环境来写,用的是 WiFi.h 和 HTTPClient.h。这里用 DHT11 举例,你换成别的传感器把读数那行改掉就行。
第一步,先确保 WiFi 连上再发请求。别在 setup() 里连完立刻发,给个 while 等待循环,这是解决"连接超时"最有效的一招。
#include <WiFi.h>
#include <HTTPClient.h>
#include <DHT.h>
#define DHTPIN 4
#define DHTTYPE DHT11
const char* ssid = "你的WiFi名称";
const char* password = "你的WiFi密码";
const char* serverUrl = "http://你的域名/api/push.php";
const char* apiKey = "你的APIKey";
DHT dht(DHTPIN, DHTTYPE);
void setup() {
Serial.begin(115200);
delay(1000);
dht.begin();
WiFi.begin(ssid, password);
Serial.print("连接 WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("已连接,IP: ");
Serial.println(WiFi.localIP());
}
第二步,读传感器并拼 JSON。这里有个细节:DHT11 读一次温度湿度不能太快,两次之间至少要隔 2 秒,否则读出来是 nan。我用字符串拼接拼 JSON,比引 JSON 库省内存。
void loop() {
float h = dht.readHumidity();
float t = dht.readTemperature();
if (isnan(h) || isnan(t)) {
Serial.println("传感器读取失败,跳过本次");
delay(5000);
return;
}
String payload = "{\"api_key\":\"";
payload += apiKey;
payload += "\",\"dataset\":\"sensor_a\",\"data\":{\"temp\":";
payload += String(t, 2);
payload += ",\"hum\":";
payload += String(h, 2);
payload += "}}";
pushData(payload);
delay(10000); // 10 秒上报一次
}
第三步,发请求。关键点是 http.begin() 之后加 Content-Type: application/json 头,否则服务端可能不认。发完记得 http.end() 释放资源,不然跑几轮就内存不足了。
void pushData(String payload) {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi 断线,跳过");
return;
}
HTTPClient http;
http.begin(serverUrl);
http.addHeader("Content-Type", "application/json");
int code = http.POST(payload);
Serial.print("HTTP 状态码: ");
Serial.println(code);
if (code > 0) {
Serial.println("返回: " + http.getString());
}
http.end();
}
烧进去,打开串口监视器,波特率选 115200。如果一切正常,你会看到 WiFi 连上、状态码 200、返回一段带 success 的 JSON。到这一步,链路就通了。
四、运行效果:控制台里看到数据一条条进来
串口这边的输出大概长这样:
连接 WiFi....
已连接,IP: 192.168.1.23
HTTP 状态码: 200
返回: {"code":0,"msg":"success"}
HTTP 状态码: 200
返回: {"code":0,"msg":"success"}
每 10 秒一条,一小时就是 360 条,一天 8640 条。这时候切到云递数据的控制台,你能看到 sensor_a 这个数据名下面,条数在一条条往上跳,容量、已读状态都清清楚楚。数据还能下载、编辑、清空、删除,想导出成表格离线分析也行。
读取数据用 pull.php,它是按写入顺序(FIFO)读的,取走即标记已读,不会重复下发。也就是说,你写个定时脚本去拉数据做分析,拉过的就不会再来第二遍,不需要自己维护 offset。支持分页、也能按数据名单独读。
curl -X POST "http://你的域名/api/pull.php" \
-H "Content-Type: application/json" \
-d '{"api_key":"你的APIKey","dataset":"sensor_a","limit":20}'
看到数据一条条进来、又能干净地一条条被消费掉,那种感觉比串口乱码时爽太多了。
五、常见坑与注意事项
第一,波特率对不上。 开头那个坑,代码写 115200,监视器选 9600,必乱码。改一致就行。
第二,WiFi 没连上就发请求。 一定要加 while 等待循环,别偷懒。另外 WiFi 断了要能自动重连,或者至少在 pushData 里判断状态再发。
第三,JSON 拼错。 少个引号、多个逗号,服务端直接拒收。建议先用 curl 在电脑上把请求试通,再搬到 ESP32 上,能省一半调试时间。
第四,别把 api_key 硬编码后开源。 密钥泄露了别人能往你的数据名里推垃圾。云递数据支持 X-Api-Key 请求头传密钥,比塞在 URL 里安全一点。
第五,注意去重机制。 内容完全相同的两条数据只留第一条。如果你的传感器读数长时间不变(比如恒温环境),可能推送成功但看不到新条数,这是正常的。
最后提一句部署:云递数据是 Nginx + PHP + MySQL 架构,支持私有化部署到自己的内网或公网服务器,数据自主可控。如果你的项目对数据归属有要求,可以自己搭一套。安全方面它有 API 密钥鉴权、PDO 预处理防注入、CSRF 防护、登录防爆破和接口限流,基本的防护都覆盖了。新用户注册即赠 3 天会员免费试用,正式会员月卡 19 元、年卡 99 元,卡密充值、可叠加顺延,到期后数据保留不删,续费即恢复。
ESP32用HTTP上传传感器数据,为什么一直连接超时?
九成是网络没就绪就发请求。检查三点:WiFi 是否真的连上(用 WiFi.status() 判断)、http.begin() 的 URL 协议和端口对不对、服务端接口地址是否可访问。建议先用电脑 curl 同样的地址试一次,电脑通、ESP32 不通,那就是设备网络或代码时序问题。
推送接口返回成功,但控制台看不到数据是怎么回事?
先看返回的 JSON 里 code 是不是 0。如果返回成功却看不到,最常见原因是内容哈希去重——和上一条完全一样的数据不会重复入库。其次是看错了数据名,推送时的 dataset 和控制台里查看的是不是同一个。最后确认 api_key 是否属于当前账号。
ESP32上传的数据能重复下发吗,会不会重复消费?
不会。云递数据的 pull 接口是按写入顺序 FIFO 读取的,取走即标记已读,不会重复下发,所以不存在同一份数据被消费两次的问题。它支持分页和按数据名读取,你按需拉取即可。
不想买服务器和数据库,ESP32的数据能存到哪里?
这正是云递数据这类平台的用途:无需购买服务器和数据库,通过 HTTP/JSON 接口就能让 ESP32 把数据存到云端,控制台可视化查看条数、容量和已读状态,还能下载、编辑、清空、删除。如果后面有数据归属要求,也可以私有化部署到自己的服务器。
ESP32上传中文或长文本会乱码吗?
只要保证请求体是 UTF-8 编码、Content-Type 正确,中文不会乱码。乱码通常出在两个地方:一是串口监视器波特率不匹配,那是显示问题不是数据问题;二是字符串拼接时用了非 UTF-8 的编码。云递数据的 data 字段支持对象、数组、文本,纯文本请求体也能存一段文本,老程序用 ?api_key=xxx&dataset=yyy 的兼容写法即可。
开始使用
本文的代码示例都可以直接在云递数据上运行:注册后即可获得专属 APIKey,在 开发文档 中查看推送、读取接口的完整说明;需要长期使用可以在 会员中心 用卡密开通月卡或年卡;更多实战内容可以浏览技术博客的其他文章。