☰
ESP32+OneNet+MQTT保姆级教程:微信小程序实时显示温湿度
2026/10/5 15:00:55 网站建设 项目流程

帮朋友做的一个小项目需要把温湿度传感器数据显示到手机端,当时在选型上其实纠结了很久。最后定下来用微信小程序 + OneNet + MQTT 这套组合,从硬件通电到小程序看到实时数据,满打满算也就是一顿饭的功夫。这篇文章就是我当时完整流程的复盘,包括平台配置、设备端代码、小程序端请求封装,以及一路上踩过的几个坑。

先说清楚这篇文章适合谁看:你手头有一个 ESP32 或者类似的 WiFi 开发板,想让温湿度数据在手机微信里实时展示,同时希望数据能稳稳地经过云端而不是只在内网玩。文章会覆盖硬件端如何上报、OneNet 平台如何配置、小程序端如何拉取并绘图这三段链路。如果你对 MQTT 协议不熟也没关系,我会用最简单的话把关键点讲明白。

为什么要强调"保姆级"?因为这类教程最坑的地方不是代码,而是平台界面改版、API 版本不一致、鉴权参数填错这种破事。我在文章里会把容易翻车的地方单独拎出来提示,照着做基本不会走弯路。

1. 动手之前,先把这条数据链路在脑子里过一遍

1.1 整条链路长什么样

这个项目的本质是"数据从传感器到手机屏幕"的搬运,所以先别急着写代码,把链路画清楚比什么都重要。

完整链路是这样的:ESP32 开发板通过 GPIO 口读取 DHT11 温湿度传感器数据,然后把数据打包成 JSON,通过 MQTT 协议发布到 OneNet 云平台。OneNet 收到数据后会自动存到数据流里。微信小程序这边不直接连 MQTT,而是通过 OneNet 提供的 HTTP API 去查询数据流里的最新数据点,拿到之后在页面上渲染成数值和趋势折线图。

为什么小程序不直接走 MQTT?这个问题我放到第 5 章细说,你先记住一个结论:在微信小程序里通过 HTTP 轮询 API 是最稳、最快能跑通的方式。设备端用 MQTT 是为了长连接上报,小程序端用 API 是为了简单可靠,两者各司其职。

数据流向可以简化成下面这样:

DHT11传感器 -> ESP32 -> MQTT协议 -> OneNet云平台 -> HTTP API -> 微信小程序

从硬件到云端的这一段,数据是"推"上去的,设备主动上报;从云端到小程序这一段,数据是"拉"下来的,小程序主动查询。搞清楚这个推拉关系,后面很多参数你就能理解为什么这么填了。

1.2 为什么是 OneNet,以及新旧版本那个绕不开的坑

物联网平台其实不少,阿里云 IoT、百度天工、OneNet、EMQX 自建都有。我选 OneNet 的原因很简单:国内访问速度快,免费额度对个人项目足够,而且它同时提供 MQTT 接入和 HTTP 数据查询 API,正好覆盖我们需要的两端。

但 OneNet 这里有个大坑,就是你注册登录之后,平台会引导你进 OneNet Studio 新版界面,而网上大量教程写的是旧版"多协议接入"的流程。这两个版本的产品模型、设备鉴权方式、API 地址完全不一样,很多新手在这里直接迷路。

我帮你把区别整理成一张表:

对比项多协议接入(旧版)OneNet Studio(新版)
产品创建入口开发者中心 -> 创建产品 -> 多协议接入控制台 -> 产品开发 -> OneNET Studio
MQTT 接入认证产品ID、设备ID、APIKey 三元组需要额外生成 token 签名
数据查询 APIapi.heclouds.com,简单直接iot-api.heclouds.com,需要 IAM 认证
上手难度低中高
新注册账号是否还能用部分账号仍可用平台主推

这篇文章以"多协议接入(旧版)"为主线,因为它的 API 最简单,最适合快速演示。如果你的账号进不了旧版入口,也不要慌,思路完全一样,只是第二步的设备连接参数需要换成 Studio 的 token 方式,数据查询换成新版 API。核心逻辑不变。

1.3 材料清单

动手之前把东西备齐,避免做一半卡住。

类别物料说明
硬件ESP32 开发板选带 WiFi 的经典款就行,型号差异不影响代码
硬件DHT11 温湿度传感器DHT22 也行,代码只需要改宏定义
硬件杜邦线若干母对母、母对公各备几根
硬件Micro USB 数据线一定要能传数据的,有的线只能充电
软件Arduino IDE 或 PlatformIO二选一,文章代码两种环境都能用
软件微信开发者工具官方工具,稳定版即可
账号OneNet 账号手机号注册
账号微信小程序账号个人主体即可,主要用于拿 AppID 和配域名

1.4 给"5 分钟"一个准确的预期

标题说 5 分钟,我得说实话:如果你所有环境都装好了,代码模板也有,就改一下 WiFi 密码、APIKey、设备 ID 这几个参数,5 分钟确实够。但从零开始装 IDE、注册账号、申请小程序,整套下来 30 分钟到 1 小时是比较现实的预期。

这篇文章的价值在于让你省掉那些绕弯子的时间。我踩过的坑你直接跳过,速度自然就快了。

2. 硬件端:把传感器数据用 MQTT 喂给 OneNet

2.1 接线和基础准备

DHT11 一共三个引脚(有些模块是四脚,多一个 NC 空脚),和 ESP32 接线非常简单:

DHT11 引脚接到 ESP32
VCC(中间或标 + 的脚)3.3V
GND(标 - 的脚)GND
DATA(标 S 或 out 的脚)GPIO4(可改)

这里有两个小细节要注意。第一,DHT11 的数据引脚最好接一个 4.7k 到 10k 的上拉电阻到 VCC,不过大部分市售 DHT11 模块已经集成上拉电阻了,裸传感器的话建议加一个。第二,GPIO4 不是硬性规定,只要避开 ESP32 的烧录引脚(比如 GPIO0、GPIO2、GPIO12)就行,我习惯用 GPIO4 比较省心。

接线完成后,先把一个最简单的 Blink 程序烧进去,确认开发板本身没问题,再往下走。这一步能帮你把"板子坏了"和"代码有问题"这两个变量隔离开。

2.2 MQTT 连接参数怎么填,为什么要这么填

这是整个项目最容易出错的地方,我单独拿出来讲。

OneNet 旧版多协议接入的 MQTT broker 地址是固定的:183.230.40.96,端口1883。连接的时候需要填三个参数,对应关系如下:

MQTT 参数填写内容说明
Client ID产品 ID(product_id)创建产品时生成的一串数字
Username设备 ID(device_id)创建设备时生成的数字,注意不是设备名称
Password设备 APIKey创建产品或设备时生成的密钥

很多初学者在这里想当然地填了产品名称、设备名称,结果连不上。原因在于 OneNet 的 MQTT 认证体系里,Client ID 和 Username 是用来定位"你这个连接属于哪个产品下的哪个设备"的,必须填 ID 而不是名称。这就好比你去酒店报房号,得说"302 房"而不是"我住的那间"。

2.3 完整可跑通的 ESP32 代码

我用的是 Arduino 框架,在 Arduino IDE 里需要安装两个库:

  • DHT sensor library(Adafruit 出品)
  • PubSubClient(MQTT 客户端库)

安装好之后,新建一个 Arduino 工程,把下面的代码粘进去,改四个参数就能用。

#include <WiFi.h> #include <PubSubClient.h> #include <DHT.h> #define DHTPIN 4 #define DHTTYPE DHT11 const char* ssid = "你的WiFi名称"; const char* password = "你的WiFi密码"; // OneNet 多协议接入参数 const char* mqtt_host = "183.230.40.96"; const int mqtt_port = 1883; const char* product_id = "你的产品ID"; const char* device_id = "你的设备ID"; const char* api_key = "你的APIKey"; DHT dht(DHTPIN, DHTTYPE); WiFiClient espClient; PubSubClient client(espClient); unsigned long lastSendTime = 0; const unsigned long sendInterval = 5000; // 5秒上报一次 void setup() { Serial.begin(115200); dht.begin(); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\nWiFi connected"); client.setServer(mqtt_host, mqtt_port); } void reconnect() { while (!client.connected()) { Serial.print("Attempting MQTT connection..."); if (client.connect(product_id, device_id, api_key)) { Serial.println("connected"); } else { Serial.print("failed, rc="); Serial.print(client.state()); Serial.println(" try again in 2 seconds"); delay(2000); } } } void loop() { if (!client.connected()) { reconnect(); } client.loop(); if (millis() - lastSendTime >= sendInterval) { lastSendTime = millis(); float humidity = dht.readHumidity(); float temperature = dht.readTemperature(); if (isnan(humidity) || isnan(temperature)) { Serial.println("Failed to read from DHT sensor!"); return; } // 注意:OneNet 旧版 MQTT 上报数据点的主题固定是 $dp String payload = "{\"datastreams\":[" "{\"id\":\"temperature\",\"datapoints\":[{\"value\":" + String(temperature) + "}]}," "{\"id\":\"humidity\",\"datapoints\":[{\"value\":" + String(humidity) + "}]}" "]}"; client.publish("$dp", payload.c_str()); Serial.println("Published: " + payload); } }

这段代码里最核心的是这一句:

client.connect(product_id, device_id, api_key)

三个参数我前面已经解释过,就是 MQTT 认证三元组。如果连不上,client.state()会返回一个错误码,比如-2表示网络不通,-4表示用户名或密码不对,5表示未授权。看到错误码再去排查,比瞎猜快得多。

上报数据的 JSON 格式也有讲究,OneNet 规定必须是这样的结构:

{ "datastreams": [ { "id": "temperature", "datapoints": [ { "value": 25.5 } ] }, { "id": "humidity", "datapoints": [ { "value": 60.1 } ] } ] }

id是数据流名字,value是数值。一个 payload 里可以同时上报多个数据流,省流量也省事。我直接把温度和湿度打包在一起发了。

2.4 烧录后怎么验证数据真的上云了

代码烧录之后,打开串口监视器,波特率 115200,你会看到 WiFi 连接日志和 MQTT 连接日志,然后是每 5 秒一条的发布记录。看到Published字样说明设备端已经发出去了,但"发出去了"不等于"平台收到了"。

验证平台是否收到,最快的办法是去 OneNet 控制台看设备状态。如果设备列表里这个设备显示"在线",说明 MQTT 长连接已经建立成功。接着进入设备的"数据流"页面,如果能看到 temperature 和 humidity 两个数据流,并且里面有数据点,那恭喜你,硬件端这条路已经打通了。

这里要特别说明一点:如果数据流页面是空的,不一定是你代码有问题,很可能是数据流模板没有预先创建。这个问题我放在第 3 章讲,因为它是平台侧配置的一部分。

3. OneNet 平台侧:5分钟里最费心的一步

3.1 创建产品和设备,记录三个关键值

登录 OneNet 控制台后,进开发者中心,选择"多协议接入",然后创建一个新产品。创建时需要选协议,这里选 MQTT。产品创建完成后,在产品列表里会看到一个产品 ID,这个就是后面 MQTT 连接里的Client ID。

然后在这个产品下创建设备。设备 ID 是自动生成的,APIKey 也是自动生成的。我强烈建议你现在就把下面三个值单独存到一个文本文件里,后面代码和小程序都要用:

产品ID:XXXXXXXX 设备ID:XXXXXXX APIKey:XXXXXXXXXXXXXXXXXX

为什么要单独存?因为平台页面里这几个值分散在不同的标签页里,你来回切换很容易看花眼。有一次我就是把产品的 APIKey 当成设备的 APIKey 填进去了,排查了半天才发现。

3.2 数据流模板必须手动建,不然数据进不来

这是新手最容易忽略的一步。OneNet 旧版平台虽然会在设备第一次上报数据时尝试自动创建数据流,但这个行为不稳定,我遇到过多次"自动创建失败,数据流列表空空如也"的情况。

正确的做法是在"数据流模板"里提前把两个数据流的名字定义好:temperature和humidity。名字必须和代码里 JSON 的id字段完全一致,一个字母都不能差。

这一步的操作逻辑是:数据流模板相当于给设备定义了一个"数据仓库的货架位",上报的数据来了之后,平台才知道往哪个货架上放。你提前把货架位定义好,数据一进来就能直接入库,查询的时候也不会出现"查不到数据流"的报错。

3.3 用 HTTP API 立刻验证数据点

设备上报正常后,我们先用浏览器或者 Postman 测一下 HTTP API 是否通,这一步非常关键。小程序端后面做的事情,本质上和这个请求一模一样,只是换了个客户端。

打开浏览器,直接访问这个地址:

https://api.heclouds.com/devices/你的设备ID/datastreams/temperature/datapoints?limit=10

注意,浏览器访问的话需要带一个请求头api-key: 你的APIKey。浏览器地址栏没法直接添加请求头,所以我一般用 Postman,或者在 Chrome 里用ModHeader这类插件加请求头。

用 Postman 的话,配置如下:

项值
请求方式GET
URLhttps://api.heclouds.com/devices/{设备ID}/datastreams/temperature/datapoints?limit=10
Headerapi-key: {你的APIKey}

如果一切正常,返回的 JSON 长这样:

{ "errno": 0, "data": { "count": 10, "datapoints": [ { "at": "2025-01-15 10:00:03.000", "value": 25.3 }, { "at": "2025-01-15 10:00:08.000", "value": 25.4 } ] }, "error": "succ" }

errno为 0 表示成功,data.datapoints数组里按时间顺序排列着历史数据点。这个返回结构和微信小程序端要做的事情直接相关,后面封装的代码就是围绕这个结构写的。

3.4 我在这步踩过的坑:版本入口和 APIKey 搞混

这一步我说两个真实踩过的坑,希望你别再踩一次。

第一个坑是新旧版本入口混淆。我一开始用的是一篇讲 OneNet Studio 新版的教程,结果我按旧版平台去操作,导致创建产品时一直找不到对应按钮。后来才意识到,新版和旧版是两个完全不同的产品体系。解决方案是:先确认你用的是哪个版本,然后整个流程都跟着那个版本走,不要新旧混用。

第二个坑是 APIKey 权限范围。旧版里一个产品有一个 APIKey,每个设备也有自己的 APIKey。请求数据的时候,如果你传的是产品 APIKey,可能在某些接口上能用,但在设备数据查询接口上会返回权限错误。我的建议是统一使用设备 APIKey,它是最下钻的粒度,查设备数据一定不会出权限问题。

还有一个关于 HTTP API 的细节:api.heclouds.com的接口地址在微信小程序里是可用的,因为它支持 HTTPS。这一点很重要,微信小程序的所有网络请求强制要求 HTTPS,如果你对接的平台不支持 HTTPS,那就没法在小程序里直接请求了。OneNet 这边问题不大,放心用。

4. 微信小程序:用 API 把云上的数据拉到手机屏幕

4.1 准备工作:账号、开发者工具、域名白名单

小程序端的第一步不是写代码,而是先把账号和工具链准备齐全。

在微信公众平台注册一个小程序账号,个人主体就行,不需要企业资质。注册完你会得到一个 AppID,这个在微信开发者工具里创建项目时要填。如果暂时不想注册,开发者工具也支持使用"测试号",但后续真机预览和域名配置就做不了,所以建议还是注册一下。

开发者工具去官网下载稳定版,安装后用微信扫码登录,新建一个小程序项目,目录选一个空文件夹,AppID 填刚才注册的那个。

接下来是很多人会漏掉的一步:配置服务器域名白名单。在微信公众平台的后台,找到"开发管理 -> 开发设置 -> 服务器域名",点"修改",在request合法域名里添加:

https://api.heclouds.com

这一步不做的话,开发工具里可以用"不校验合法域名"绕过,但真机上一定请求失败。而且域名配置修改后需要几分钟到十几分钟才能生效,别刚配上就去试,等一会儿再刷新。

4.2 封装一个带 API Key 的小程序请求

小程序端我们不直接用原始wx.request到处写一遍,而是封装成一个工具函数。在项目根目录下新建一个utils/request.js,内容如下:

const BASE_URL = 'https://api.heclouds.com'; const DEVICE_ID = '你的设备ID'; const API_KEY = '你的设备APIKey'; function fetchDatapoints(datastreamId, limit = 1) { return new Promise((resolve, reject) => { wx.request({ url: `${BASE_URL}/devices/${DEVICE_ID}/datastreams/${datastreamId}/datapoints`, method: 'GET', data: { limit }, header: { 'api-key': API_KEY }, success(res) { if (res.statusCode === 200 && res.data.errno === 0) { const points = (res.data.data && res.data.data.datapoints) || []; resolve(points); } else { reject(new Error(`API error: ${res.data.errno}`)); } }, fail(err) { reject(err); } }); }); } module.exports = { fetchDatapoints };

这里有几个关键点需要说明。

第一,header里传api-key是 OneNet 鉴权的核心,漏了这个,请求一定会返回 401。第二,我用的 URL 是 HTTPS,这对应前面说的域名白名单。第三,limit参数控制返回多少个数据点,拉最新 1 个用于刷新数值,拉最近 20 个用于画折线图。

第四,也是很多人容易踩的坑:小程序请求返回的res.data是 OneNet 的响应体,不是直接的数据点数组,所以一定要先判断errno === 0,再去取data.datapoints。我看到过不少初学者直接把res.data当成数组去遍历,然后得到undefined的错误。

4.3 首页:数值卡片 + 自动刷新

我们用一个页面来做展示。在index.js里写轮询逻辑:

const { fetchDatapoints } = require('../../utils/request.js'); Page({ data: { temperature: '--', humidity: '--', updateTime: '', tempPoints: [], humiPoints: [] }, onLoad() { this.fetchAll(); this.timer = setInterval(() => { this.fetchAll(); }, 3000); }, onUnload() { if (this.timer) { clearInterval(this.timer); } }, fetchAll() { Promise.all([fetchDatapoints('temperature', 20), fetchDatapoints('humidity', 20)]) .then(([tempPoints, humiPoints]) => { const ops = { temperature: tempPoints.length ? tempPoints[tempPoints.length - 1].value.toFixed(1) : '--', humidity: humiPoints.length ? humiPoints[humiPoints.length - 1].value.toFixed(1) : '--', updateTime: this.formatTime(new Date()), tempPoints, humiPoints }; this.setData(ops); this.drawCharts(humiPoints, 'humiChart', '#3f7fff'); this.drawCharts(tempPoints, 'tempChart', '#ff7f3f'); }) .catch((err) => { console.error('fetch failed:', err); }); }, formatTime(date) { const h = String(date.getHours()).padStart(2, '0'); const m = String(date.getMinutes()).padStart(2, '0'); const s = String(date.getSeconds()).padStart(2, '0'); return `${h}:${m}:${s}`; }, drawCharts(points, canvasId, color) { // 绘图逻辑见 4.4 } });

这里用了Promise.all并行请求温度和湿度两个数据流,比串行请求快一倍。onUnload里清理定时器是必须的,不然页面退出后 setInterval 还在跑,会导致内存泄漏和资源浪费。

对应的index.wxml布局我就不贴完整代码了,结构大致是顶部两个大数字卡片显示温度和湿度,下面两个 canvas 画布显示折线图。核心思路是把数据绑到 data 上,页面用{{temperature}}这样的模板语法渲染。

4.4 用 Canvas 画一张最近一分钟的趋势图

画折线图之前必须先明确一件事:微信小程序的 Canvas 组件有新旧两套 API,旧版是wx.createCanvasContext,新版是 Canvas 2D(通过wx.createSelectorQuery获取节点)。这里我推荐用新版 Canvas 2D,因为旧版接口官方已经停止维护,而且新版性能更好。

在 wxml 里放两个 canvas:

<view class="chart-card"> <view class="chart-title">温度趋势</view> <canvas id="tempChart" type="2d" style="width: 100%; height: 200rpx;"></canvas> </view> <view class="chart-card"> <view class="chart-title">湿度趋势</view> <canvas id="humiChart" type="2d" style="width: 100%; height: 200rpx;"></canvas> </view>

注意type="2d"这个属性,没有它就走不了新版 Canvas 2D 接口。

drawCharts的实现是这样的:

drawCharts(points, canvasId, color) { const query = wx.createSelectorQuery(); query.select(`#${canvasId}`).fields({ node: true, size: true }).exec((res) => { if (!res || !res[0] || !res[0].node) return; const canvas = res[0].node; const ctx = canvas.getContext('2d'); const width = res[0].width; const height = res[0].height; const dpr = wx.getSystemInfoSync().pixelRatio; canvas.width = width * dpr; canvas.height = height * dpr; ctx.scale(dpr, dpr); ctx.clearRect(0, 0, width, height); if (!points || points.length < 2) { ctx.fillStyle = '#999'; ctx.font = '12px sans-serif'; ctx.fillText('等待数据...', 12, height / 2); return; } const values = points.map(p => Number(p.value)); const maxV = Math.max(...values) * 1.1; const minV = Math.min(...values) * 0.9; const range = maxV - minV || 1; const stepX = width / (values.length - 1); ctx.beginPath(); ctx.strokeStyle = color; ctx.lineWidth = 2; ctx.lineJoin = 'round'; values.forEach((v, i) => { const x = i * stepX; const y = height - ((v - minV) / range) * height; if (i === 0) { ctx.moveTo(x, y); } else { ctx.lineTo(x, y); } }); ctx.stroke(); }); }

这段代码的核心逻辑是坐标映射:数据值范围映射到 canvas 的高度范围,数据点索引映射到 canvas 的宽度范围。为什么maxV要乘以 1.1、minV要乘以 0.9?因为如果不留出上下的边距,折线会紧贴画布边缘,看起来很难看,曲线稍微一波动就会被裁掉。

Canvas 2D 还有一个容易踩的坑:canvas 节点的width和height属性默认是 CSS 像素,但 canvas 的实际渲染像素需要乘以设备像素比(devicePixelRatio),否则在 Retina 屏幕上画出来的图是模糊的。所以我在代码里做了canvas.width = width * dpr然后ctx.scale(dpr, dpr)两步操作。

到这里,小程序端已经能从 OneNet 拉数据并展示了。打开开发者工具,模拟器里应该能看到温度和湿度每 3 秒刷新一次,折线图随时间滚动。

5. 进阶:想要真正的"实时推送",轮询之外的路

5.1 轮询方案的瓶颈在哪

我们前面用的方案是"小程序每 3 秒钟主动问一次平台",这叫轮询。轮询的优点是实现简单,缺点也很明显。

第一是延迟。数据变化的真实时间和你看到的时间之间,最坏情况下会差一个轮询周期。3 秒的间隔还能接受,但如果间隔缩短到 1 秒,OneNet 的 API 请求频率会翻好几倍,免费额度可能不够用。

第二是资源浪费。不管数据有没有变化,小程序每 3 秒都要发一次请求,对手机电量、网络流量都是不必要的消耗。第三是小程序本身对setInterval有节流限制,尤其是在页面切到后台之后,定时器会被挂起,回到前台才会补发请求。

5.2 小程序直连 OneNet 的 MQTT 为什么没那么好走

如果你对 MQTT 有一些了解,可能会想到一个更"优雅"的方案:让小程序也走 MQTT 连接 OneNet,平台数据一变,服务器主动推给小程序,这不就实时了吗?

理想很丰满,但现实有两个坎。

第一,微信小程序的wx.connectSocket只允许连接 WSS(WebSocket Secure)协议,也就是加密的 WebSocket。OneNet 的 MQTT 服务对 MQTT over WebSocket 的支持比较有限,而且如果它不支持 WSS 加密,小程序根本就连不上。第二,即使能连上 MQTT broker,小程序端的鉴权流程(尤其是 OneNet Studio 新版需要动态计算 token)会让代码复杂度直线上升,而且api-key会暴露在小程序代码包里,有被反编译窃取的风险。

所以我的建议是:个人项目和演示项目老老实实用 HTTP 轮询。这不是技术上的妥协,而是投入产出比最高的选择。

5.3 一个折中且靠谱的升级路线

如果你确实需要真正的实时推送,可以加一台轻量云服务器做中转,数据链路变成这样:

ESP32 -> MQTT -> OneNet -> Node-RED/云服务器 -> WebSocket -> 微信小程序

Node-RED 可以直接订阅 OneNet 的 MQTT 数据,然后通过 WebSocket 把数据推给小程序。小程序端用wx.connectSocket连接你自己服务器的 WSS 地址。这样做的好处是:你的服务器自己能配 WSS 证书,小程序域名白名单里配置成你自己的域名就行。

这条路线虽然要多花钱买服务器,但实时性和可控性都远优于轮询。如果是生产环境项目,我更推荐这条路线。

6. 我把常见问题按排查顺序整理了一遍

6.1 ESP32 连不上 MQTT,先从这三个地方查

看到failed, rc=这样的日志先别慌,按顺序排查。

第一,确认 WiFi 是真的连上了。ESP32 的串口日志里如果一直打印.没停过,说明 WiFi 配置有问题,先解决网络。第二,确认 MQTT 三元组填对了。我见过太多人把"产品名称"填到 Client ID 里,或者把"设备名称"填到 Username 里。记住,填的是 ID,不是名字。第三,确认平台的设备状态。如果设备编号填错(比如多了一个空格),MQTT 服务器会直接拒绝连接。

如果三个地方都排查了还是不行,用 MQTT X 这个桌面客户端手动连接一次。把 broker 地址、端口、三元组填进去,如果 MQTT X 能连上而你的代码连不上,问题出在代码;如果 MQTT X 也连不上,问题出在平台配置。

6.2 小程序请求报错或者一直在转圈

这个问题的排查顺序是:先看微信开发者工具的控制台报什么错。

如果报request:fail url not in domain list,说明域名白名单没配好,或者配了但还没生效。如果报request:fail但没有详细信息,先试试在开发者工具右上角详情里勾选"不校验合法域名",如果勾选后请求就通了,那问题一定在白名单。如果勾选了还不通,用 Postman 请求同一个接口,看后端是否正常。

还有一种情况比较隐蔽:小程序请求的 URL 是 HTTP 明文,但小程序强制要求 HTTPS。这个在本地开发时容易被忽略,因为开发工具默认允许 HTTP,但真机上一定失败。我建议从一开始就用 HTTPS 的 URL。

6.3 数据在 OneNet 平台能看到,小程序却是空的

出现这种情况,通常不是网络问题,而是请求参数和平台数据对不上。

先检查 APIKey 是不是这个设备的 APIKey。我前面说过,产品和设备的 APIKey 是两码事,搞混了会出现权限错误。再检查数据流名字。代码里查询的是temperature,你平台上的数据流也必须叫temperature,大小写、下划线都要一致。如果数据流模板建的是temp,代码查temperature,必然查不到。

还有一个小技巧:在浏览器的 Postman 里先请求一遍,确认返回errno: 0和data.datapoints数组有值,再去查小程序代码。这样能把问题限定在"平台侧"还是"小程序侧",排查效率会高很多。

6.4 关于"5 分钟"我的真实感受

写这篇文章的时候我重新走了一遍整个流程,包括从零注册一个 OneNet 账号、重建产品、烧录代码、新建小程序项目。最后算下来,不算 IDE 下载和微信开发者工具安装的时间,纯操作大概 15 分钟。如果所有环境都是现成的,参数替换一下,5 分钟确实够。

但我想说的是,"快"不是这个项目最有价值的部分。真正有价值的是这条链路的可扩展性:传感器从 DHT11 换成 SHT30,只需要改几行代码;数据从温湿度换成 PM2.5、光照度、土壤湿度,数据流模板加几个字段就行;展示端从微信小程序换成 Web 页面,API 都不用动。你搭好的是一个"传感器数据上云 + 云端API下发"的通用骨架,后续加设备、加数据维度都是顺水推舟的事。

如果你做完这套流程之后想继续深入,我建议优先研究两件事:一是 MQTT 协议本身,尤其是 QoS 等级和遗嘱消息的作用,这关系到数据上报的可靠性;二是微信小程序的 Canvas 性能优化,当一个页面要画多条实时曲线时,如何避免频繁重绘导致掉帧。这两块吃透了,类似的物联网可视化项目对你来说就没什么难度了。

最后分享一个我个人的小习惯:所有设备端、小程序端的密钥和 ID,我都会在本地维护一份环境配置文件,代码里放占位符,发布前手动替换。这样一来,代码不小心传到 GitHub 上也不会泄露密钥,二来换设备、换平台的时候,只需要改配置,不用动业务逻辑。这个习惯救过我很多次,建议你从一开始就养成。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询