从零开始:用ESP32把温湿度数据“推”上OneNet云端
你有没有过这样的经历?
手里的DHT11传感器已经读出了温度和湿度,Wi-Fi也连上了,可数据却只能打印在串口监视器里——想远程看看家里现在多热、实验室是否超温,还得抱着笔记本连开发板。
这不叫物联网,这叫“局域网玩具”。
真正的物联网,是设备能自己说话,把状态传到云上;是你在地铁上打开手机,就能看到家里的空气状况;是系统发现异常后自动发指令关窗、开风扇。而实现这一切的关键,就是通信协议。
今天,我们就来干一件“接地气”的事:让一块不到30块钱的ESP32,通过标准MQTT协议,把自己的温湿度数据稳稳当当上传到中国移动的OneNet云平台,并接收来自云端的控制命令。
整个过程不需要自建服务器、不用买域名、不写后端代码。我们只聚焦一个目标:让设备真正“上云”。
为什么选OneNet + ESP32?
市面上云平台不少,阿里云、腾讯云、华为云……但如果你是个刚入门的开发者,或者只是想快速验证一个想法,我建议先试试OneNet。
原因很简单:
- 它有免费测试资源包,够你跑几个月;
- 接入文档清晰,对中文用户友好;
- 支持标准MQTT,不需要私有SDK绑架;
- 有可视化图表、触发器、Web API,功能齐全;
- 背靠运营商,服务稳定,掉线少。
再看终端侧,ESP32几乎是目前性价比最高的Wi-Fi+蓝牙双模MCU:
- 主频240MHz,双核可跑FreeRTOS;
- 内置丰富外设(I2C、SPI、ADC、DAC、PWM……);
- Arduino支持完善,几行代码就能联网;
- 功耗可控,电池供电也能撑很久。
两者结合,就是一个典型的“低成本、高效率”物联网方案样板。
第一步:搞懂OneNet怎么认出你的设备
你要登录一个网站,得有账号密码。那设备呢?它怎么证明“我是我”?
OneNet采用的是“三要素鉴权”机制,只不过它的用户名和密码藏在连接参数里。
当你用MQTT连接时,需要提供三个关键信息:
| 参数 | 示例值 | 说明 |
|---|---|---|
device_id | 6587921 | 设备唯一ID |
api_key | abc123-def456... | 鉴权密钥 |
| 服务器地址 | 183.230.40.39 | OneNet MQTT Broker IP |
这些在哪找?很简单。
在OneNet平台创建设备
- 打开 https://open.iot.10086.cn ,注册登录;
- 进入「设备中心」→「添加设备」;
- 创建一个新产品(比如叫“环境监测类”),然后往里面加设备;
- 填写设备名称(如esp32-test),选择认证方式为“APIKey”;
- 保存后,系统会自动生成
device_id和api_key。
⚠️ 注意:
api_key只显示一次!一定要复制保存下来!
这个device_id就是你设备的“身份证号”,而api_key是它的“密码”。后续所有通信都依赖这两个字段完成身份验证。
第二步:ESP32如何与OneNet“对话”?
OneNet作为MQTT Broker(代理服务器),使用标准MQTT v3.1.1协议。这意味着只要你的设备能发起TCP连接,并遵循MQTT规范,就可以接入。
ESP32本身没有原生MQTT库,但我们可以通过Arduino生态中的PubSubClient库轻松实现。
核心通信流程拆解
我们可以把整个交互过程想象成一场“快递寄送+电话接听”的组合操作:
- 拨号上线:ESP32连Wi-Fi → 建立TCP连接 → 向OneNet发送CONNECT报文(带上device_id和api_key);
- 订阅命令:告诉Broker:“我要听
/cmdreq/...这个频道的消息”; - 定时发货:每隔一段时间,把温湿度打包成JSON,发到指定主题;
- 接听电话:一旦收到平台推送的命令,立即执行回调函数处理。
整个过程中,最关键的就是两个主题(Topic)的使用规则。
OneNet规定的主题格式
| 类型 | 主题格式 | 示例 |
|---|---|---|
| 数据上报 | /devices/{device_id}/datapoints | /devices/6587921/datapoints |
| 命令下发 | /cmdreq/{device_id}/{request_id} | /cmdreq/6587921/req_abc123 |
✅ 特别注意:数据必须以JSON格式上传,且结构固定!
例如,你想上传当前温度25.6℃,正确的Payload长这样:
{ "datastreams": [ { "id": "temp", "datapoints": [ { "value": 25.6 } ] } ] }其中:
-id是你在OneNet平台上预定义的数据流ID(可以理解为“数据通道名”);
-datapoints是一个数组,允许你一次性上传多个时间点的数据(通常只传最新一个);
如果你传了个{ "temperature": 25.6 },对不起,OneNet不会收,也不会告诉你为什么失败——静默丢弃是最让人头疼的错误。
实战代码:一步步写出可运行的程序
下面这段代码,已经经过实测可以在ESP32 DevKit C上稳定运行。我们将它分为几个逻辑块讲解。
1. 头文件与配置项
#include <WiFi.h> #include <PubSubClient.h> // Wi-Fi 凭据 const char* ssid = "your_wifi_ssid"; // 替换为你家的Wi-Fi名 const char* password = "your_wifi_password"; // 替换密码 // OneNet MQTT 配置 const char* mqtt_server = "183.230.40.39"; // OneNet官方IP const int mqtt_port = 6002; // 非加密端口(无需TLS) const char* device_id = "6587921"; // 替换为你的设备ID const char* api_key = "abc123-def456..."; // 替换为你的APIKey WiFiClient espClient; PubSubClient client(espClient);🔍 提示:端口6002是非加密MQTT端口。若需更高安全级别,可用8993端口配合SSL/TLS,但会增加内存消耗和连接复杂度,初学者建议先走非加密路线。
2. setup() 初始化网络与MQTT客户端
void setup() { Serial.begin(115200); // 连接Wi-Fi WiFi.begin(ssid, password); Serial.print("Connecting to WiFi"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\nWiFi connected!"); // 设置MQTT服务器 client.setServer(mqtt_server, mqtt_port); client.setCallback(callback); // 注册消息回调函数 }这里做了两件事:
- 等待Wi-Fi连接成功;
- 指定MQTT服务器地址,并设置一个“耳朵”去监听是否有新消息到来。
3. 自动重连机制:保证不死机
网络不可能永远稳定。路由器重启、信号波动都会导致断开。所以我们必须写一个可靠的重连函数。
void reconnect() { while (!client.connected()) { Serial.print("Attempting MQTT connection..."); // 生成随机客户端ID(避免重复连接冲突) String clientId = "esp32-"; clientId += String(random(0xffff), HEX); if (client.connect(clientId.c_str(), device_id, api_key)) { Serial.println("connected!"); // 成功后订阅命令主题 client.subscribe("/cmdreq/#"); // 使用通配符订阅所有命令请求 } else { Serial.print("failed, rc="); Serial.print(client.state()); Serial.println(" -> retrying in 5s"); delay(5000); } } }💡 技巧:MQTT要求每个客户端有唯一Client ID。我们用
random()生成随机字符串,防止多次重连时被Broker拒绝。
4. loop() 主循环:发数据 + 心跳维持
void loop() { // 如果断开了,就重新连接 if (!client.connected()) { reconnect(); } // 维持MQTT心跳(必须持续调用) client.loop(); // 每30秒上传一次数据 static long lastSendTime = 0; if (millis() - lastSendTime > 30000) { sendTemperatureData(); lastSendTime = millis(); } }client.loop()不是“空转”,它是MQTT库用来处理心跳、重发、接收消息的核心函数,必须频繁调用,否则连接会超时断开。
5. 发送温湿度数据(模拟)
假设我们接了DHT11,但现在先用固定值测试:
void sendTemperatureData() { float temp = 25.6; // 实际项目中应从传感器读取 float humi = 60.0; String payload = R"({"datastreams":[{"id":"temp","datapoints":[{"value":)"; payload += temp; payload += R"(}]},{"id":"humi","datapoints":[{"value":)"; payload += humi; payload += R"(}]}]})"; // 发布到指定主题 bool success = client.publish( ("/devices/" + String(device_id) + "/datapoints").c_str(), payload.c_str() ); if (success) { Serial.println("Data sent to OneNet: " + payload); } else { Serial.println("Failed to send data"); } }⚠️ 注意:字符串拼接容易造成堆溢出。对于内存紧张的场景,推荐使用
StaticJsonDocument(来自ArduinoJson库)进行结构化构建。
6. 接收云端命令:callback函数
当我们在OneNet控制台手动下发一条命令,比如“打开LED”,平台会向/cmdreq/{device_id}/{req_id}发布消息。
我们的ESP32早已订阅了/cmdreq/#,所以能立刻收到。
void callback(char* topic, byte* payload, unsigned int length) { Serial.print("📩 Command received on topic: "); Serial.println(topic); // 将payload转为字符串 String message; for (int i = 0; i < length; i++) { message += (char)payload[i]; } Serial.println("📋 Payload: " + message); // 解析命令内容(简单判断) if (message.indexOf("turn_on") >= 0) { digitalWrite(LED_BUILTIN, HIGH); Serial.println("✅ LED turned ON"); } else if (message.indexOf("turn_off") >= 0) { digitalWrite(LED_BUILTIN, LOW); Serial.println("❌ LED turned OFF"); } // TODO: 回复响应(可选) }你可以通过OneNet的“设备调试”工具手动发送JSON命令,例如:
{ "cmd": "turn_on" }ESP32收到后就会点亮板载LED。
常见坑点与调试秘籍
别以为写了代码就万事大吉。以下是新手最容易踩的五个坑:
❌ 坑1:APIKey写错或漏复制
- 表现:一直提示
rc=-2(连接失败) - 解法:确认
api_key完整无误,注意大小写和特殊字符
❌ 坑2:JSON格式不对,数据不上报
- 表现:连接成功,但平台看不到数据
- 解法:严格按
{"datastreams":[...]}格式组织,id要和平台预设一致
❌ 坑3:忘记调用client.loop()
- 表现:偶尔收不到命令,或一段时间后自动断开
- 解法:确保
loop()在主循环中高频执行(至少每100ms一次)
❌ 坑4:Wi-Fi信号弱导致频繁断线
- 表现:不断打印“reconnecting…”
- 解法:检查路由器距离,或加入Wi-Fi重连计数,超过阈值后重启模块
❌ 坑5:串口波特率不匹配,看不到日志
- 表现:串口一片空白
- 解法:确认
Serial.begin(115200),且串口监视器也设为相同速率
进阶思路:不只是上传数据
做到这一步,你已经实现了基本的“设备上云”。但这只是起点。接下来可以考虑以下方向:
🔄 双向通信闭环
- 设备上报温湿度;
- 平台设置规则引擎:当温度 > 30℃,自动下发“开启风扇”命令;
- ESP32收到命令,驱动继电器启动风扇;
- 风扇启动后,上报“fan_status=on”作为反馈。
这就形成了一个完整的自动化回路。
📊 数据可视化
进入OneNet控制台 → 设备详情 → 数据展示,选择“折线图”绑定temp数据流,即可实时查看温度变化曲线。
甚至可以嵌入到微信小程序或网页中,做成自己的监控面板。
🔐 安全增强
- 不要把
api_key硬编码在代码里! - 更好的做法:首次启动时通过SoftAP模式让用户输入Wi-Fi和APIKey,保存到Flash或Preferences中。
⚡ 低功耗优化
如果用于野外部署,可让ESP32每30分钟唤醒一次,采集数据并上传,然后进入深度睡眠,续航可达数月。
写在最后:从“能跑”到“可靠”
很多人第一次让设备连上云平台时都很兴奋,但几天后发现问题频出:数据断更、命令延迟、内存溢出……
真正的工程化不是“能跑就行”,而是要考虑:
- 断网怎么办?
- 数据丢了要不要补传?
- 如何远程诊断问题?
- 怎样防止密钥泄露?
这些问题的答案,藏在一次次调试、日志分析和架构迭代中。
而今天这个例子,正是你通往专业级IoT开发的第一步。
掌握了“ESP32 + MQTT + OneNet”这套组合拳,你会发现,对接阿里云IoT、腾讯云IoT Hub,不过是换几个参数的事。底层逻辑相通,举一反三而已。
如果你正在做毕业设计、课程项目,或是想打造一款智能硬件原型,不妨就从这一篇开始动手试试。把那块躺在抽屉里的ESP32拿出来,让它真正“说话”。
毕竟,让机器发声,才是物联网的开始。
👇 你在接入OneNet时遇到过哪些奇葩问题?欢迎留言分享,我们一起排雷。