ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ESP8266+DHT22+MQTT+OneNet+微信小程序:温湿度实时监控实现

ESP8266+DHT22+MQTT+OneNet+微信小程序:温湿度实时监控实现 先说结论这套东西做出来就是一块能在微信里实时看到温湿度的数据卡片。数据从ESP8266上的DHT22读出来用MQTT协议上报到OneNet云平台微信小程序再定时调OneNet API把最新数据拉下来渲染到页面上。标题里说的“5分钟搞定”指的是设备端已经跑起来、平台已经配置完的前提下把小程序这一层接通并显示数据的时间。如果你是从零开始我建议你按一个完整下午的量来准备一边看这篇一边操作多数坑都能提前绕过去。这套方案特别适合三类人一是物联网课程作业不知道怎么做PPT演示的学生二是想给家里的花房、鱼缸、机房做远程监控的个人开发者三是公司里想快速出个Demo给领导看效果的工程师。它不要求你懂底层TCP/IP不要求会写后端服务甚至不用买云服务器只要有一个微信小程序账号、一块ESP8266开发板和一个几块钱的DHT22传感器就够了。下面我把整条链路拆开从平台配置到硬件上报再到小程序展示一步一步来。1. 方案全景一条数据从传感器到微信屏上的完整链路1.1 为什么是OneNet MQTT而不是自己搭服务器先把这个方案的角色搞清楚。MQTT协议在这里干的事情相当于物流公司的运输车传感器把温湿度数据打包成一个小包裹运输车把它送到OneNet这个大型中转仓库微信小程序则是收件人。小程序拿到的不是一个实时连接而是通过OneNet提供的一个取件窗口——HTTP API随时去仓库里问一句“最新的温湿度是什么”仓库把货拿出来给它。有朋友可能会问为什么要绕一圈放到OneNet我直接让ESP8266当个Web服务器小程序直接访问它不行吗答案是不行原因有两个一是ESP8266的WiFi是局域网IP微信小程序在公网环境下根本访问不到你家里的路由器内网设备除非你折腾内网穿透和公网域名二是就算你搞定了网络一个没有公网IP、时不时断流的路由器环境稳定性完全没法保证。OneNet这类云平台的存在就是帮你把“设备接入”和“数据存储”这些脏活累活全接管了。OneNet最吸引我的地方有三个。第一是免费额度对个人开发者足够友好测试阶段完全够用第二是硬件接入生态成熟ESP8266、ESP32、NB模组、4G Cat.1模组都有现成SDK或者教程第三是它的老版本API鉴权特别简单一个API Key丢在Header里就能调接口非常适合微信小程序这种轻客户端直接使用。顺手提醒一句现在OneNet有新旧两个控制台入口进去之后界面完全不一样。做这套方案强烈建议用老版本物联网开放平台下的“多协议接入”因为它的接口简单、文档多、网上踩坑教程也多。新版OneNet Studio功能更强但鉴权要算签名API返回结构也不同后面你会多花很多时间在验证权限上对“快速出效果”这个目标来说是帮倒忙。1.2 微信小程序为什么不直接连MQTT这块我在初学的时候纠结了很久直到被现实教育了才彻底放弃。理论上MQTT是基于WebSocket或者TCP长连接的如果小程序能直接订阅一个温湿度主题那数据就是真正的“实时推送”根本不用轮询。但问题是微信小程序至今没有一个官方的原生MQTT库。有人会说第三方库不是有mqtt.js吗在小程序里用npm装一个mqtt.js再通过WebSocket方式去连Broker不可以吗理论可以但实际操作有两个坎一是微信小程序的网络请求域名有白名单机制你必须在小程序后台把MQTT服务器域名配置成合法socket合法域名而且必须是在公网备案过的域名直接用IP地址或者裸端口基本过不了审核二是OneNet对WebSocket的支持走的是另外一套接入地址和参数配置起来比想象中繁琐。所以对于“展示温湿度”这种低频、低实时性要求的场景最稳妥的方式反而是让小程序每隔10秒调一次HTTP API。微信小程序原生的wx.request就是干这个的不需要额外引库调试方便出问题也容易排查。你牺牲掉的是毫秒级的实时性换来的是一晚上就能跑通全链路。1.3 这套链路涉及的关键角色我习惯在做项目前先把参与方列清楚不然遇到问题都不知道该查谁。角色具体对象职责数据采集端ESP8266 DHT22温湿度传感器读取温度和湿度数值上报协议MQTT over TCP端口6002把数据从设备传到云平台云平台OneNet老版本开放平台存储数据流、提供API查询能力数据通道MQTT数据流ID固定为temp / humi平台侧按ID区分不同种类的数据下行调用HTTPS APIapi.heclouds.com小程序向平台查询最新数据展示端微信小程序原生页面渲染温湿度卡片和更新时间清楚了这张表后面每一步你都知道自己正在给哪个角色配置身份证。2. 环境准备与平台配置注册、建产品、拿API Key2.1 OneNet账号与产品创建的关键字段选择这一步是整个项目的地基我见过太多人卡在“产品创建成功但不知道接下来去哪拿设备ID”的状态。先打开OneNet老版本开放平台注册一个账号然后进入控制台找到“多协议接入”。创建产品时有几个字段必须认真选产品名称随意填比如“SmartHomeTest”行业类别选“智慧城市”或“智能家居”都行不影响功能联网方式选WiFi协议类型这一项务必选MQTT千万别图省事选HTTP。如果你选了HTTP后面设备上报数据的格式就是另外一套API返回的数据流结构也完全不同会导致你网上抄的代码全都对不上号。这里有一个非常容易踩的雷现在的OneNet首页会引导你去新版的“物联网开发平台”那个平台里建的产品跟老版开放平台并不完全互通。如果你误入了新版创建产品时看到的协议选项可能是“MQTT/TCP私有协议”之类的它和老版的“多协议接入-MQTT”在API调用上不是一码事。所以创建前仔细看页面路径老版控制台的网址是open.iot.10086.cn走“多协议接入”入口。产品创建成功之后你会得到一个产品ID在“产品详情”里可以看到。同时你还需要一个API Key。老版的产品详情页里一般会直接展示产品API Key如果没找到可以去用户中心的“API Key管理”里生成一个。这个API Key是后续设备接入的密码也是小程序调用接口的通行证先复制下来存好。2.2 创建设备与数据流数据流ID才是后续API的命脉产品建好之后回到控制台在该产品下点击“添加设备”。设备名称随便填比如“esp8266_room”设备描述可不填但建议写清楚放置位置后面设备多了你就知道这个习惯有多重要。添加成功后系统会自动分配一个设备ID通常是一串数字。这个数字就是设备的唯一标识后面MQTT连要用小程序API的URL里也要用。重点来了在OneNet中设备上报的每一条数据都要归档到“数据流”里。你上报的JSON中datastreams数组里每个元素的id就是数据流ID。比如你上报了下面这个JSON{ datastreams: [ { id: temp, datapoints: [ { value: 26.5 } ] }, { id: humi, datapoints: [ { value: 58.2 } ] } ] }那么平台上会自动创建两个数据流一个叫temp一个叫humi。注意平台是根据上报内容自动生成数据流的不需要你在后台手动创建。很多教程里说“在后台添加数据流”其实指的是在设备详情页里手动先建好再等着收数据这步完全可以跳过。但你必须记住一个规则上报JSON里的数据流IDtemp / humi要和后续查询时用的datastream_id一模一样大小写敏感少一个字母都查不到数据。这个坑我实打实踩过一次当时用了“Temp”上报查询用了“temp”API返回的结果永远是空列表检查了半天才发现大小写不一致。2.3 拿到三样关键凭证productID、deviceID、api-key做完上面的操作你手里应该有下面三样东西。把它们抄在记事本里后面三步都要用。凭证名称获取位置用途productID产品ID产品详情页MQTT连接里的用户名deviceID设备ID设备列表/设备详情页MQTT连接里的ClientId以及API URL路径参数api-keyAPI Key产品详情页/用户中心MQTT连接里的密码以及API请求Header里的鉴权字段需要注意API Key在MQTT设备接入和小程序API调用两个场景下的含义不一样。设备接入时很多SDK要求把密码填成api-key小程序调API时它需要放在Header里。但你不用担心都是用同一个API Key。提示API Key相当于你整个产品的通行证一旦泄露别人就可以通过API读取你设备的所有数据。正式项目里绝对不要把API Key直接写在小程序代码里上一篇提到的云函数代转发方案才是正解。Demo演示阶段无所谓但要有意识后面我会再细说。3. 数据源头ESP8266 DHT22温湿度采集与MQTT上报3.1 硬件接线与Arduino环境准备硬件清单一块ESP8266开发板NodeMCU或Wemos D1 Mini都可以一个DHT22温湿度传感器用DHT11也行但精度稍差几根杜邦线。接线非常简单DHT22一共有三个引脚部分模块是四个引脚但只用到三个DHT22引脚连接到VCC或ESP8266的3.3V或5V引脚GND或-ESP8266的GNDDATA或OUT/SESP8266的D4对应GPIO2手里如果有DHT22的四脚裸传感器通常在DATA和VCC之间需要接一个4.7K到10K的上拉电阻但市面上卖的DHT22模块板大多已经集成了这个电阻直接插线就行。Arduino环境配置这一步如果你以前烧过程序应该轻车熟路先在开发板管理器里安装ESP8266开发板支持包然后把NodeMCU选为当前开发板端口选对串口波特率选115200即可。需要预先安装的库有两个PubSubClientMQTT客户端库用来连接OneNet的BrokerDHT sensor library用来读取DHT11/DHT22的数据如果你用的是DHT22库安装完以后还需要再装一个Adafruit Unified Sensor Lib作为依赖否则编译会报错。3.2 核心代码MQTT连接与JSON数据上报下面是我实际测试可用的一份完整代码你需要把其中的WiFi名称、密码、productID、deviceID、apiKey替换成你自己的。#include ESP8266WiFi.h #include PubSubClient.h #include DHT.h #define DHTPIN 2 // GPIO2也就是D4引脚 #define DHTTYPE DHT22 // 如果用DHT11就改成DHT11 const char* ssid 你的WiFi名; const char* password 你的WiFi密码; const char* mqttServer mqtt.heclouds.com; const int mqttPort 6002; const char* productID 你的产品ID; const char* deviceID 你的设备ID; const char* apiKey 你的APIKey; char pubTopic[64]; char subTopic[64]; DHT dht(DHTPIN, DHTTYPE); WiFiClient espClient; PubSubClient client(espClient); unsigned long lastSendTime 0; const unsigned long sendInterval 10000; // 每10秒上报一次 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(mqttServer, mqttPort); client.setCallback(callback); // OneNet平台的发布与订阅主题 sprintf(pubTopic, dps/publish/%s, deviceID); sprintf(subTopic, dps/device/%s/resp, deviceID); } void callback(char* topic, byte* payload, unsigned int length) { // 平台返回消息简单打印出来 Serial.print(Message arrived [); Serial.print(topic); Serial.print(] ); for (int i 0; i length; i) { Serial.print((char)payload[i]); } Serial.println(); } void reconnect() { while (!client.connected()) { Serial.print(Attempting MQTT connection...); // 关键userName传productIDpassword传apiKeyclientId传deviceID if (client.connect(deviceID, productID, apiKey)) { Serial.println(connected); client.subscribe(subTopic); } else { Serial.print(failed, rc); Serial.print(client.state()); Serial.println( try again in 5 seconds); delay(5000); } } } void sendData() { float h dht.readHumidity(); float t dht.readTemperature(); if (isnan(h) || isnan(t)) { Serial.println(Failed to read from DHT sensor!); return; } // 拼装OneNet要求的JSON格式 char payload[200]; snprintf(payload, sizeof(payload), {\datastreams\:[{\id\:\temp\,\datapoints\:[{\value\:%.1f}]}, {\id\:\humi\,\datapoints\:[{\value\:%.1f}]}]}, t, h); Serial.print(Publish: ); Serial.println(payload); client.publish(pubTopic, payload); } void loop() { if (!client.connected()) { reconnect(); } client.loop(); unsigned long now millis(); if (now - lastSendTime sendInterval) { lastSendTime now; sendData(); } }代码逻辑不复杂我按执行顺序拆开讲一下。setup里先让ESP8266连上WiFi然后配置MQTT服务器地址和端口注册回调函数目前回调里只是把平台返回的消息打出来方便调试。重连函数里那三行是重头戏client.connect的第一个参数是ClientId第二个是用户名第三个是密码对应关系分别是deviceID、productID、apiKey这个顺序千万不能搞错。loop里每10秒读取一次DHT22数据然后用snprintf拼成OneNet需要的JSON格式。这里面有几个细节一是温度用%.1f格式化保留一位小数这样数据流里存的是整活儿过的一位小数二是value直接传数字不要加双引号否则平台会把它解析成字符串类型后面在API返回里拿到的是带引号的“26.5”做前端计算时很容易踩坑。把代码烧录到开发板后打开串口监视器如果一切正常你会看到类似这样的输出WiFi connected Attempting MQTT connection...connected Publish: {datastreams:[{id:temp,datapoints:[{value:26.5}]},{id:humi,datapoints:[{value:58.2}]}]}看到Publish字样就意味着数据已经成功发到OneNet了。3.3 上报不上去的时候先查这几点MQTT连接失败时PubSubClient会在Serial输出rc值常见的状态码有状态码含义排查方向-2网络连接失败WiFi是否正常mqtt.heclouds.com能否ping通-3无法连接服务器端口是否用了6002不要用默认1883-4连接中断检查MQTT参数是否匹配-5用户名或密码错误确认clientId/username/password三个参数有没有填反-6未授权apiKey权限是否正常设备是否被删除我之前调试时踩过最大的一个坑是把apiKey填到了clientId的位置结果串口反复打印“failed, rc-5”。因为OneNet后台的接入日志只会告诉你认证失败不会告诉你哪个字段错了只有把三个参数对应关系重新核对才发现问题。还有一种情况是MQTT显示connected但每次publish之后控制台那台设备的“数据流”里什么都没有。这种情况十有八九是主题名不对。OneNet老版本MQTT的上行主题格式是dps/publish/设备ID注意设备ID前面不要加空格、不要加斜杠拼接多余字符否则平台收不到。实在不行可以先用MQTTX这个桌面客户端手动连一下OneNet主题填dps/publish/你的设备IDpayload填同样的JSON如果能收到数据说明是你的板子代码问题跟平台无关。这个排查思路能帮你快速缩小问题范围。4. 微信小程序读API、解析数据、实时展示4.1 初始化小程序项目与页面结构微信小程序这部分我用的是原生语法没有引第三方框架目的是减少干扰项。在微信开发者工具里新建项目选JavaScript作为开发语言AppID可以用测试号但如果你后面要在真机上访问公网API最好注册一个小程序账号这样才有正规的AppID和后端配置权限。小程序目录我建议就建一个页面叫index。最终目录结构pages/ index/ index.wxml index.wxss index.js index.json页面展示内容很明确温度、湿度、更新时间三块。WXML里先写两个卡片一个温度一个湿度下面放一个更新时间的小字。示例如下view classcontainer view classcard view classmetric温度/view view classvalue{{temp}} ℃/view /view view classcard view classmetric湿度/view view classvalue{{humi}} %/view /view view classupdate-time更新时间{{updateTime}}/view /view样式方面不用复杂圆角卡片加一点阴影就挺好看。注意在index.json里设置一下导航栏标题比如“温湿度监控”。4.2 wx.request拉取温湿度数据与时间戳解析小程序端要调用的OneNet API地址是这样的https://api.heclouds.com/devices/你的设备ID/datapoints?limit1这个接口在加了limit1参数后会返回该设备所有数据流最新的一个数据点不用再分别请求temp和humi一次就能把两个值都拿到。请求的时候要在Header里带上鉴权字段注意字段名是小写的api-key不是Authorization也不是Bearer Token这是无数人踩过的坑。完整代码如下const app getApp() Page({ data: { temp: --, humi: --, updateTime: -- }, deviceId: 你的设备ID, apiKey: 你的APIKey, timer: null, onLoad() { this.loadData() this.timer setInterval(() { this.loadData() }, 10000) }, onUnload() { if (this.timer) { clearInterval(this.timer) } }, loadData() { const that this wx.request({ url: https://api.heclouds.com/devices/${this.deviceId}/datapoints?limit1, method: GET, header: { api-key: this.apiKey }, success(res) { if (res.data res.data.errno 0) { const datastreams res.data.data.datastreams || [] let temp -- let humi -- datastreams.forEach(stream { if (stream.id temp stream.datapoints.length 0) { temp stream.datapoints[0].value } if (stream.id humi stream.datapoints.length 0) { humi stream.datapoints[0].value } }) that.setData({ temp, humi, updateTime: that.formatTime(new Date()) }) } else { console.error(API error:, res.data) } }, fail(err) { console.error(request fail:, err) } }) }, formatTime(date) { const h date.getHours().toString().padStart(2, 0) const m date.getMinutes().toString().padStart(2, 0) const s date.getSeconds().toString().padStart(2, 0) return ${h}:${m}:${s} } })这里有几个关键点。第一api.heclouds.com返回的JSON里data对象下是一个datastreams数组数组里每个元素都有id和datapoints两个属性datapoints里最新的点在数组第一个位置。第二所有字段名必须和平台返回一致大小写错了就解析不出来建议先用微信开发者工具的调试器看一下res.data的完整结构再写解析逻辑。4.3 定时轮询实现“实时”效果很多人问“实时”到底有多实时。我用10秒的setInterval去轮询室温监控这个频率完全够。因为传感器数据本来就是慢慢变化的你手机上看快到1分钟了屋里温度也没变化刷新太快纯粹是浪费流量和平台请求额度。但这里要说明白OneNet平台的数据流存储是有一定缓存延迟的你上报一次数据后平台API立刻去查可能查不到最新点通常要等几秒才会显示。这个问题也会影响小程序的刷新频率。如果发现数据一直没更新先手动在OneNet平台的数据流页面查一下看平台侧的数据到底到没到如果平台侧都没到那就是设备上报的问题。如果你真的追求“秒级”体验比如现场演示时希望数据一上报小程序立刻变那我不建议继续用HTTP轮询方案而是换WebSocket直连Broker。但那是另一套更复杂的工程了。从做Demo、交作业、日常监控这三个场景来评价10秒轮询的实际体验已经非常好而且代码简单到连初中生都能看懂。页面加载时先调一次loadData然后启动定时器。这里有个容易被忽略的小细节小程序页面切到后台时setInterval可能照样执行浪费请求不说还容易被微信后台清理。规范做法是在onShow里启动定时器onHide里清掉定时器而不是用onLoad和onUnload。我这里为了演示简单用了onLoad实际项目里建议改成页面生命周期更稳妥。4.4 换行与样式把数据做得能看WXSS样式我直接给你一份能用的风格是极简卡片适合大多数场景.container { padding: 40rpx; background-color: #f5f6fa; min-height: 100vh; } .card { background: #ffffff; border-radius: 24rpx; padding: 40rpx; margin-bottom: 30rpx; box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05); } .metric { font-size: 28rpx; color: #999999; margin-bottom: 12rpx; } .value { font-size: 72rpx; font-weight: 600; color: #222222; } .update-time { margin-top: 20rpx; font-size: 24rpx; color: #aaaaaa; text-align: right; }编译之后模拟器里如果显示正常你可能会觉得“就这也太简单了”。但对一个IoT数据展示项目来说简单本身就是优点。核心链路已经打通你想加折线图、加历史查询、加多个设备切换都是在现有基础上扩展不会推翻重写。5. 常见问题与排查技巧实录这一节我把调试过程中遇到过的、以及帮粉丝排查过的典型问题整理成一张速查表按“先查硬件、再查平台、最后查小程序”的顺序排查省得你东翻一个教程西看一个文档。5.1 设备在线但没数据 / API返回400的排查顺序现象原因解决办法串口打印MQTT connected但OneNet平台数据流为空上报主题错误检查主题是否为dps/publish/设备ID平台收到数据但API返回的datapoints是空数组上报JSON格式不正确确认value不是字符串数据流ID大小写是否正确API返回errno为18或19参数错误或设备不存在检查URL里的设备ID是否正确limit参数是否写对请求返回401或Invalid API KeyHeader里的鉴权字段错误确认用的是api-key而不是Authorization小程序页面请求报错“不在以下request合法域名列表”小程序后台域名白名单限制开发者工具勾选“不校验合法域名”或在小程序后台配置request合法域名第一个坑说实话是最容易出问题的也是最多人卡住的地方。OneNet的MQTT主题不是通用的topic/xxx这种格式而是固定的dps/publish/设备ID。你如果拿其他MQTT教程里的topic格式套上去平台会显示设备在线但数据全被丢弃查都查不到。API返回400的情况通常是你请求的URL参数少了一个或者字段拼错了。OneNet老版本API对参数要求很严格比如datastream_id多传了一个不存在的值它会直接返回错误这时候把URL里参数一项一项对着官方文档核对基本都能解决。5.2 小程序真机“不在合法域名列表”的解决方法这个问题算是小程序开发入门第一大坑。在开发者工具模拟器里一切正常一点真机预览就觉得毁天灭地——请求全部failConsole里报“不在以下request合法域名列表中”之类的错误。原因不复杂微信小程序出于网络安全考虑要求所有wx.request的请求域名必须事先在小程序后台配置为合法域名。但要注意三点。线上域名必须走HTTPS不能用HTTP明文。api.heclouds.com本身是支持HTTPS的这关能过。第二域名不能带端口如果哪天OneNet突然要求加端口访问那也需要在小程序后台上配置socket合法域名但那属于另一个体系了。第三配置后并不能立即生效微信后台有缓存通常要等几分钟有时候清缓存重进小程序才生效。开发阶段的临时方案是在微信开发者工具的“详情-本地设置”里勾选“不校验合法域名、web-view业务域名、TLS版本以及HTTPS证书”。勾完之后模拟器和手机预览模式下都能正常请求。但要提醒你上线前必须把域名配置好不能依赖这个开关。5.3 其他容易踩的坑API Key放小程序端一定会暴露。在Demo阶段无所谓但如果你要把这个小程序正式发布建议加一层后端代理用云函数或者自己的服务器去请求OneNet再把结果转发给小程序API Key放在服务端环境变量里。这样即使别人抓包看到你的小程序接口也拿不到OneNet的权限。数据刷新太频繁会被限流。OneNet对API的调用频率有策略限制具体阈值我没专门测过但实测5秒刷一次会有概率出现请求失败。10秒一次是安全值15秒更稳。如果你需要高频刷新考虑升级方案用完整体验会好很多。DHT22读取偶发失败。代码里已经加了isnan判断但有时候你会看到串口偶尔打一条“Failed to read from DHT sensor!”这是传感器本身的上电时序问题不是代码错误。对策很简单别管它等下一个10秒周期就好。如果你用的是质量不太好的DHT11失败频率会更高那就要在硬件上做点小改动比如给传感器供电脚加个100nF电容。小程序显示的时间是手机本地时间不是设备上报时间。如果你想显示设备端真实的采集时间需要用OneNet返回的数据点里的at字段那个是ISO格式的时间字符串前端解析后格式化一下。我这里为了省事先用了本地时间严格来说不够严谨但对Demo展示影响不大。结尾几个过来人的建议整套链路我自己搭过至少三次踩坑最多的地方永远不在代码本身而在“你以为对但实际不对”的配置上。MQTT三个参数顺序错、主题多打一个字符、API Key放错Header位置、数据流ID大小写不一致这四件事我全部经历了一遍。所以如果你现在就卡在某一步不要怀疑自己能力认真核对一遍配置项多数问题都能解决。最后分享一个很有用的调试技巧先用MQTTX桌面客户端手动连OneNet并发送固定JSON平台数据流能正常更新、API能查到结果再接ESP8266的代码。这一招把问题隔离得很干净——先证明平台没问题再证明设备侧有没有问题排查范围瞬间缩小一半。如果你后面想让这套东西更完整可以考虑给ESP8266加一个本地OLED屏显示实时数据或者在小程序里用echarts画一个温湿度历史曲线。数据流都有了画曲线只是时间轴的聚合难度不大。等你想从“能看”进化到“能用”的时候再研究告警推送和离线检测整套系统的完成度就上来了。
返回列表