一、先说我踩过的那个坑:串口全是乱码

那天晚上十一点,我盯着串口监视器上滚动的乱码,心里只有一个念头:这玩意儿是不是坏了。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,在 开发文档 中查看推送、读取接口的完整说明;需要长期使用可以在 会员中心 用卡密开通月卡或年卡;更多实战内容可以浏览技术博客的其他文章。