基于uni-app和OneNET的温室环境监测小程序开发实战

基于uni-app和OneNET的温室环境监测小程序开发实战 简介这是一份基于UniApp与Vue2实现OneNet物联网平台接入的跨端应用工程包面向需要快速搭建APP/小程序/H5物联网前端的中高级开发者。压缩包内共103个文件约48.34MB涵盖3个Vue页面组件、27个JS逻辑文件、27个SVG矢量图、25个PNG图标、6个JSON配置、4个可直接安装的APK包以及HTML/CSS等静态资源便于对照工程代码与打包产物进行学习。项目完整再现了OneNet设备接入、数据上行下行、消息推送、Vuex异步状态管理、网络异常处理与HTTPS安全通信等关键实现同时利用UniApp多端编译特性演示了iOS、Android、微信小程序等平台的适配方式。目前已有452人学习下载对正在从事物联网应用开发或想要了解OneNet平台与uniapp结合实践的读者很有参考价值。 前阵子接了个小需求做一个温室环境监测的小程序要求能实时看温湿度、能远程开关风扇。后端我选了OneNET云平台不自己搭服务器前端用uni-app配Vue2一套代码同时出小程序和H5。整个从零到跑通大概四天中间踩了不少坑尤其是MQTT连接和设备消息格式这两块网上的资料比较碎。这篇就完整记录下这个项目从平台创建到前端联调的关键过程给打算用uniappVue2OneNET这套组合做物联网应用的同学提供一个可参考的路线。1. 项目整体设计与技术选型1.1 这个项目到底要做什么需求本身不复杂一台带温湿度传感器的设备通过WiFi联网定期把数据上报到云端用户通过手机端查看实时数据和历史曲线必要时远程控制继电器开关带动风扇或补光灯。难点不在单个功能而在整个数据链路要闭环——设备到平台、平台到前端、前端再到设备三个环节任一出问题演示的时候都会翻车。我在这个项目里把业务拆成了三块设备接入层、平台数据处理层、用户应用层。设备接入层用ESP8266之类的WiFi模组走MQTT协议上报平台层用OneNET做数据存储和消息转发用户应用层就是今天的重点基于uni-appVue2开发的小程序/H5壳子负责把平台上的数据拉下来展示也负责把用户的操作指令推到平台。1.2 为什么选OneNET而不是自己搭服务器早期我确实考虑过自己搭一个MQTT Broker用EMQX或者Mosquitto然后在后端写接口前端轮询。后来算了一笔账直接放弃需要一台云主机、需要配置安全组、需要处理设备鉴权、还要维护数据库。项目周期才四天根本没时间耗在基础设施上。OneNET这类物联网云平台直接把设备接入、数据存储、消息推送这些事做完了。我只需要在平台上创建产品和设备拿到连接参数设备端和前端按照平台规则接入就行。平台自带的设备管理、数据流可视化、API调用能力对中小型IoT项目来说完全够用。前端做跨端选型我选了uni-app配Vue2原因很直接团队熟悉Vue2生态而且项目需要同时出微信小程序和H5页面uni-app一套代码两端跑比维护两套工程划算得多。2. OneNET平台侧配置2.1 创建产品与设备登录OneNET平台之后第一步是创建产品。这里有几个关键项需要仔细填产品名称按项目填即可比如温室环境监测产品类别选智能家居或智慧农业都行影响不大联网方式选WiFi协议类型一定选MQTT数据格式选OneJSON后面解析数据会省很多事产品创建成功后进入产品详情页添加设备。设备名称我用的是dev_001这种带业务含义的命名规则方便后面多设备扩展时一眼看出是哪台设备。设备创建完平台会生成一个设备ID和对应的鉴权信息这组信息是设备端连接平台的凭证也是前端后续做指令下发时要用的关键参数。注意设备和产品创建后鉴权信息务必自己保存一份。我一般记在项目根目录的config.js里但上线前记得忽略该文件避免把密钥提交到代码仓库。2.2 MQTT接入参数与鉴权规则OneNET的MQTT接入地址和端口在平台文档里都有但不同版本平台的端口有差异。我用的接入参数是Broker地址open.iot.10086.cn端口1883TCP或 8883TLS用户名格式产品ID/设备名称密码设备级鉴权信息即创建设备时生成的keyMQTT连接时客户端ID一般直接用产品ID/设备名称用户名取产品ID/设备名称密码填设备鉴权信息。很多同学第一次连不上就是搞混了这几项的位置——连接里的username不是你的账号而是产品ID加设备名的组合。设备上报数据的Topic和格式也需要注意。OneNET的MQTT报文格式是OneJSON一个典型的上报消息长这样{ id: 123, version: 1.0, params: { temperature: 26.5, humidity: 58.3, relay_state: 1 } }这个params里的字段就是数据流平台会自动解析并存储。前端订阅实时数据时只需要订阅对应的Topic平台会把设备上报的数据转发过来。2.3 平台侧联调验证正式写前端代码之前我强烈建议先用MQTT客户端工具把平台链路验证一遍。我用的工具是MQTTX跨平台、免费填好Broker地址、用户名、密码就能连上。在MQTTX里做两件事一是订阅设备上报Topic看设备端数据有没有正常到达平台二是向后端Topic发一条指令测试下行链路。这两件事都通了再开始写uniapp代码否则前端出了问题你根本判断不了是平台问题还是自己代码问题排查效率极低。3. uni-app工程搭建与MQTT连接封装3.1 用HBuilderX创建项目并安装依赖我用HBuilderX创建了一个默认的uni-app项目Vue版本选Vue2。之所以不用命令行创建是因为HBuilderX对微信小程序和App打包的支持更直接云打包也是它家的能力后面发布省去不少麻烦。项目创建好之后需要安装MQTT依赖。我直接在项目根目录执行npm install mqtt4.3.7 --save注意这里的版本号mqtt.js 5.x版本在部分小程序环境里会有兼容问题实测4.x版本更稳。装完之后在manifest.json里确认一下Vue版本是2.x别装完才发现框架版本不对。提示如果你是纯前端新手对npm不熟也可以在HBuilderX里通过“工具-插件安装”方式处理但npm方式对后续管理依赖更友好建议尽量用npm。3.2 MQTT客户端封装连接MQTT的代码不能散落在每个页面里我单独封装了一个mqtt.js放到utils目录下。封装的时候主要考虑三点连接参数管理、消息分发、断线重连。import mqtt from /node_modules/mqtt/dist/mqtt.min.js const connectOptions { clean: true, connectTimeout: 5000, reconnectPeriod: 0, clientId: 产品ID/设备名称, username: 产品ID/设备名称, password: 设备鉴权信息 } let client null export function connectMqtt(onMessage) { const brokerUrl mqtt://open.iot.10086.cn:1883 client mqtt.connect(brokerUrl, connectOptions) client.on(connect, () { client.subscribe(topic/device/#, { qos: 0 }) }) client.on(message, (topic, payload) { const res JSON.parse(payload.toString()) onMessage onMessage(topic, res) }) client.on(error, (err) { console.error(MQTT连接错误, err) }) } export function publishMessage(topic, data) { if (!client) return client.publish(topic, JSON.stringify(data), { qos: 0 }) } export function disconnectMqtt() { if (!client) return client.end() client null }这段代码的核心是mqtt.connect和subscribe。连接成功后订阅数据主题收到消息就交给回调函数处理页面侧只需要调用connectMqtt传入自己的消息处理函数即可。3.3 在页面里管理MQTT生命周期MQTT连接是全局性的不能每个页面各自建连接否则切页就断。我在App.vue的onLaunch里建立连接在onHide里断开在onShow里重连保证小程序切后台、再回前台时连接状态正确。script import { connectMqtt, disconnectMqtt } from ./utils/mqtt.js export default { onLaunch() { connectMqtt((topic, res) { // 全局消息处理根据topic分发到对应页面 this.globalData.latestData res.params }) }, onShow() { if (!this.globalData.mqttConnected) { connectMqtt() } }, onHide() { disconnectMqtt() } } /script这里有个细节容易被忽略onLaunch和onShow在小程序冷启动和热启动时的执行顺序不同。我第一次写的时候把连接逻辑放在页面里结果小程序切后台超过五分钟再回来MQTT连接已经断了页面却不知道数据一直不动。后来把连接生命周期提到App级别才解决了这个问题。4. 核心功能实现4.1 实时数据展示与下拉刷新的冲突处理实时数据展示页要同时做两件事一是在MQTT消息到达时更新页面数据二是用户下拉时触发一次多平台的数据刷新。这两个功能放在一起会有一个经典冲突——MQTT消息频繁到达时下拉手势的滚动会变得非常卡顿甚至触发页面级的下拉刷新而不是滚动屏内容。这个问题在需求列表里专门有一条实际解决方式是控制数据更新频率并区分滚动与下拉事件。我在数据更新这里用一个简单的节流函数let lastTime 0 function throttleUpdate(data) { const now Date.now() if (now - lastTime 500) return lastTime now this.currentData data }下拉刷新则通过scroll-view的refresherrefresh事件来处理而不是用页面级的onPullDownRefresh。这样用户手动下拉时触发的是滚动区域自己的刷新逻辑不会和MQTT推送的数据更新打架。如果项目对实时性要求不高也可以直接放宽到1秒节流性能更好。4.2 远程指令下发的实现指令下发比数据展示简单但在消息格式上容易踩坑。OneNET的指令下发有两类方式同步下发和异步下发。我在前端用的是设备Topic发布方式——前端MQTT连接到平台后向后端Topic发布一条控制消息平台再将消息推送给在线设备。前端代码如下function sendControl(cmd) { const msg { id: Date.now().toString(), version: 1.0, params: { relay_state: cmd } } publishMessage(topic/device/dev_001/command, msg) }设备端订阅了这个Topic后就能收到指令。注意这里的params字段名要和设备端代码里解析的字段完全一致我一开始用了relay设备端解析的是relay_state结果指令发出去设备毫无反应排查了半天才发现字段对不上。4.3 状态联动与页面刷新控制指令发出去后界面上不能等设备上报才变化这样用户会感觉延迟明显。我的做法是点击按钮时先做本地状态更新乐观更新再发MQTT指令等设备上报的数据回来后再用真实状态校正一次。这样用户的感知是即时响应实际状态也最终保持一致。在页面跳转这里还有一个列表页和详情页的数据同步问题。从列表页跳详情页、再从详情页返回时如果列表页数据没刷新用户会以为控制无效。我通过uni.$emit和uni.$on做跨页通信详情页控制成功后通知列表页刷新数据而不是依赖页面的onShow重新拉取。5. 打包、上架与常见问题5.1 小程序/H5/App打包流程uni-app的打包流程分平台。微信小程序最简单HBuilderX菜单栏“发行-小程序-微信”生成的项目在dist/dev/mp-weixin下用微信开发者工具导入即可。H5打包类似选“发行-H5”就行。内部测试时可以用npm run dev:h5起本地服务在手机上通过局域网IP访问。如果要打包成Android APKHBuilderX有两种方式云打包和离线打包。云打包不需要本地Android环境直接把证书配置好就行离线打包需要下载对应版本的Android SDK改动较大。我建议先用云打包跑通流程后面需要上架应用市场时再准备签名和离线包。App打包时一个容易忽略的点是manifest.json里的权限配置。MQTT连接属于网络通信必须勾选网络权限如果要用定位能力还要在“App模块配置”里开启对应模块。这些配置不做好打完包装到手机上一连就报错排查起来非常头疼。5.2 常见问题速查表结合我自己这个项目的排坑过程整理了下面这份速查表问题现象可能原因排查思路MQTT连不上用户名/密码格式错误确认用户名是“产品ID/设备名”密码是鉴权key连接成功但收不到数据订阅的Topic不对检查Topic前缀是否和平台一致用MQTTX验证数据能收到但页面不刷新数据更新频率过高或事件未触发加节流检查setData或Vue响应式赋值方式下拉时误触发刷新页面级刷新与滚动冲突改用scroll-view的refresherrefreshApp打包后连不上网manifest权限未勾选检查网络权限和模块配置指令发出去设备无反应字段名不一致核对前后端解析的字段名完全一致切后台再回前台数据不同步MQTT连接已断开未重连在App级onShow中做断线重连5.3 两个值得注意的埋点第一个是关于MQTT库版本。我第一次装的是mqtt.js最新版在微信开发者工具里调试没问题一上真机就报regeneratorRuntime is not defined花了大半天。最后退回4.3.7版本并在manifest.json里开启了ES6转ES5问题才解决。如果你是用HBuilderX创建项目记得在运行和发行时都勾选“ES6转ES5”选项。第二个是关于设备离线时的指令处理。OneNET平台在设备离线时收到下发指令默认会丢弃或者缓存取决于产品配置。我项目里的设备偶尔断电用户点开关时如果设备刚好离线界面显示“已开启”但实际没动作。后来我在前端加了一层状态判断——3秒内没有收到设备上报的ack就提示“设备离线请检查网络”避免界面状态和设备真实状态分离。6. 项目总结与个人体会整套流程跑下来我最深的感受是IoT类项目的复杂度不在某一端而在联调。设备端、平台、前端三方各自单独测都正常一旦串起来就各种问题——字段不一致、主题不对、时序错了。所以建议做同类项目的同学一定先把OneNET平台侧的设备和MQTT链路验证通过再写前端代码前端代码先跑通数据展示再加控制功能一层层往上叠。还有一个小技巧调试阶段我会在MQTT消息回调里加上console.log输出完整报文真机调试时把HBuilderX的控制台开着能看到每一条消息的原始数据。这个习惯帮我节省了大量排查时间尤其是在页面渲染和数据上报频率不一致的时候。这套组合用在中小型物联网项目上非常合适尤其是不能自己维护服务器、又要快速上线团队。如果你后面要做多设备管理、历史数据曲线OneNET平台也提供了对应的API前端直接调用HTTP接口拉历史数据画图表不需要在客户端做本地存储。整个项目后续可以扩展的方向也很多比如加设备告警、接入第三方语音控制等等核心链路稳定了这些功能都只是往上面叠。本文还有配套的精品资源点击获取