简介:面向计算机类毕业设计与课程作业的智能家居控制系统(小程序端)完整源码项目,适合正在开展智能家居、物联网或小程序课题的本专科学生参考借鉴。项目基于微信小程序平台,集成物联网通信与人工智能技术,覆盖设备远程控制、场景联动、状态监测等典型功能,并包含需求分析、系统设计、编码实现与测试等毕业设计全流程内容。压缩包共130个文件,以Vue、JavaScript、JSON等逻辑与配置代码为主,辅以WXML/WXSS页面文件、MQTT脚本、字体图标及少量图片素材,整体仅1.58MB,结构清晰便于下载和本地调试。已有128人学习下载。通过研读源码可以深入了解小程序开发框架、MQTT设备通信、传感器数据解析以及AI能力在智能家居中的落地方式,同时学习实际项目中的代码组织、模块划分与编程规范,将课堂知识转化为工程实践,适合作为课程设计或毕业设计的参考模板。
1. 智能家居控制系统(小程序端):课设包里藏着三层技术栈
解压开这份“毕设&课程作业_智能家居控制系统(小程序端)课程设计”压缩包,里面不是预期的几十个源码文件,而是几个不起眼的文件:iconfont.css、mqtt.js、homePic.jpg,还有一个.DS_Store。别急着删。这种结构说明项目里既有小程序前端资源,又有物联网通信层,还隐含了智能控制逻辑,能解决“毕业设计选题智能家居”和“课程作业要可演示”两类需求。适合谁?课设时间紧的计算机类学生,想看懂小程序+MQTT怎么结合、又不想从头啃协议的开发者。下文按文件反推工程结构、把通信层跑起来、补上语音入口和场景联动,最后给一套不翻车的演示方案。
2. 先拆包再动手:从 vendor.js、mqtt.js、iconfont.css 反推工程全貌
上来不要急着导入开发者工具。先建一个目录,把解压内容列一遍,判断手里拿到的是“源码工程”还是“构建产物”。这两个东西的处理方式完全不同,判断错了,后面怎么改都是白费。这一课我踩过一次:文件带vendor.js时大概率不是开发态源码,硬导会报一堆找不到文件的错。
2.1 五个文件各管什么:运行时、协议栈、样式表与静态资源
没写错,这份包结构里能直接看出角色的就是这五类文件。vendor.js是小程序框架编译后的运行时代码,把原生 API、组件系统、生命周期逻辑打包到了一起。它一般出现在“上传/构建”之后,正常开发目录里没有,所以看到它就说明这不是一份可编辑源码包,至少不是你平时写代码的那个目录形态。
mqtt.js是 MQTT 协议的 JavaScript 客户端实现。在小程序里,网络层只能走 HTTP 或 WebSocket,MQTT.js 的作用就是把 MQTT 报文封装成 WebSocket 帧,让业务代码用client.publish和client.on('message')去读写主题。课程设计里,它是设备和小程序之间的“翻译官”。
iconfont.css是图标字体样式表,声明了若干@font-face和对应的 unicode 编码。它配合一个字体文件(通常叫iconfont.woff2或iconfont.ttf)在页面里渲染图标。注意:这份列表里只有.css没有字体文件,直接引用会显示成一排方框,这个坑后面专门说。
homePic.jpg和2.gif是静态图,多半是首页大图和开关按钮的动画占位。.DS_Store是 macOS 文件管理器自动生成的目录元数据,没有任何项目价值,我一般直接删掉,也建议提交 git 前先过滤它,不然答辩拷包时容易带进一堆垃圾。
2.2 判断源码还是构建产物:三条硬线索
判断标准我总结成三条,十分钟以内能确认。第一条,看有没有app.json和pages/目录。任何一个小程序源码工程都具备这两个要素,app.json里注册页面路径,pages/下才是可改的页面逻辑。这份包没有列出它们,那就要按“不完整包/构建产物”处理,而不是当成普通源码打开。
第二条,看vendor.js的样子。用编辑器打开头部,如果是压缩混淆后的一长串,比如!function(e,t){...}这种,那就是产物。源码阶段的业务代码不会长这样。第三条,看资源引用是否能闭环。iconfont.css里如果src:url(iconfont.woff2)指向的文件不存在,说明资源被截断过,或者打包时漏掉了非必须文件。
提示:
.DS_Store和 vendor.js 一起出现,通常意味着这个包是从已完成构建的目录里手工挑选压缩的,不是从版本库里导出的。不要指望还原出百分之百的原版源码,但下面一节的做法能让它重新编译。
2.3 把压缩包还原成可编译工程:改配置与重构建流程
我一般会新建一个原生 JS 小程序工程,然后把包里的东西“塞”进对应位置,而不是反过来强行让工具认这个目录。具体分四步:
# 1. 先清掉无意义文件,再检查剩余结构 rm -f .DS_Store find . -type f | sort这一步先把 macOS 生成的垃圾文件删掉,再列出所有剩余文件,确认手里到底有几个资源、缺哪些目录。
接着在开发者工具里新建一个“JS 基础模板”工程,得到app.js、app.json、pages/骨架。然后把包里的mqtt.js放进utils/mqtt.js,把homePic.jpg放进images/,把iconfont.css放到styles/。这一步是让项目恢复“可编译”的最小结构。
最关键的补一个app.json,pages 数组必须存在,否则工具直接报“app.json 未找到”拒绝编译:
{ "pages": ["pages/index/index"], "window": { "navigationBarTitleText": "智能家居控制台", "backgroundTextStyle": "dark" } }参数说明:pages数组的第一项是启动页,小程序规定至少一页且必须存在;navigationBarTitleText是导航栏标题,课设答辩时能直接看到,值得花一秒写上项目名。页面文件暂时只建一个index,后续把业务代码迁进去,能跑通页面,再去迁其他功能。
再补project.config.json来固定工具行为:
{ "miniprogramRoot": "./", "appid": "touristappid", "compileType": "miniprogram", "setting": { "es6": true, "minified": true, "urlCheck": false } }appid先用touristappid(测试号),开发阶段不需要注册即可编译预览;es6打开保证代码里的const/let和模板字符串能转译为低版本可用;minified在真机预览时会压缩,调试期可以关掉看完整报错。urlCheck: false只在开发调试阶段用,作用是跳过合法域名校验,真机预览白屏问题的根源就在这,后面第 5 章细说。
这一套做完,项目就有了外壳。接下来真正关键的是把mqtt.js接进业务代码,让控制命令能发出去、状态能收回来。
3. 打通 MQTT 接入层:小程序如何与设备网关保持实时对话
控制类系统最核心的不是界面,而是消息能不能稳定到达设备。智能家居课设里,绝大多数同学一开始都在用 HTTP 轮询——每秒调一次wx.request去看设备状态,结果开发工具里跑着挺正常,一上真机就耗电、卡顿、流量超标。换 MQTT 之后这些问题基本消失,但前提是你连对参数、定好 Topic 规则。
3.1 为什么选 MQTT 而不是 HTTP 轮询:流量、延迟与断线语义
HTTP 轮询的问题不是协议本身,而是模型不匹配。设备状态每秒都在变,轮询间隔短了,服务端压力大;间隔长了,界面看到的永远是滞后值。MQTT 用一条长连接承载双向消息:设备往home/xxx/status推状态,小程序订阅同一主题,设备状态一变化,界面几乎是实时收到。反过来控制指令也走唯一连接,不需要为每条指令重新握手。
连接管理上 MQTT 有三个特性是 HTTP 没有的。第一是心跳 keepalive,客户端定时发 PINGREQ,网关能快速感知设备掉线。第二是遗嘱消息 Will,设备异常断线时 broker 会代发一条离线通知。第三是 QoS 分级,0 代表最多一次、1 代表至少一次、2 代表仅一次,课设里状态上报用 0、控制指令用 1 就够。
版本选择上我建议用 MQTT 3.1.1 而不是 5.0。虽然 5.0 多了用户属性和请求响应,但公共 broker 和旧固件兼容性不一定好,课程设计选 3.1.1 省心。这也是mqtt.js里默认的协议版本,几乎不用额外配置。
3.2 手写最小可用封装:mqtt.js 的连接与订阅核心代码
在原生小程序里,最稳的做法是装一个mqtt的 npm 包,再把mqtt.min.js放到工具目录。注意:小程序不能裸连 TCP 的 1883 端口,必须走 WebSocket,所以连接地址用wss://,路径一般是/mqtt,具体以你的 broker 文档为准。
// utils/mqtt-client.js const mqtt = require('mqtt'); const MQTT_CONFIG = { url: 'wss://your-broker-domain.com:8084/mqtt', // WebSocket地址,别用1883 username: 'demo_user', password: 'demo_pass', clientId: 'mini_' + Date.now() + '_' + Math.floor(Math.random() * 10000) }; let client = null; function connect(onMessage) { client = mqtt.connect(MQTT_CONFIG.url, { username: MQTT_CONFIG.username, password: MQTT_CONFIG.password, clientId: MQTT_CONFIG.clientId, reconnectPeriod: 3000, // 断线后每3秒重连一次 connectTimeout: 5000, // 5秒内连不上触发error keepalive: 30, // 心跳周期,单位秒 clean: true // 每次连接重新订阅,免去离线缓存问题 }); client.on('connect', () => { console.log('MQTT connected'); client.subscribe('home/+/status', { qos: 1 }, (err) => { if (err) console.error('订阅失败:', err); }); }); client.on('message', (topic, message) => { try { const data = JSON.parse(message.toString()); onMessage(topic, data); } catch (e) { console.warn('JSON解析失败,原始内容:', message.toString()); } }); client.on('reconnect', () => console.log('正在重连…')); client.on('error', (err) => console.error('连接错误:', err)); } module.exports = { connect };代码逻辑我拆开讲。mqtt.connect(url, options)会根据 url 自动识别 WebSocket 传输层;clientId必须全局唯一,否则两个同名客户端连上同一个 broker 时,后者会把前者踢下线,表现为“连接成功但马上断开”;reconnectPeriod不要小于 2000ms,对公共 broker 来说频繁重连可能被风控。subscribe的回调里一定要判断 err,Topic 写错时会直接暴露在这里。
clean: true的含义是连接断开后 broker 不保留该客户端的会话和未消费消息。课设里简单场景用 true 合适,因为断线重连后重新订阅一次,状态数据靠设备端主动上报,消息不丢失。如果以后要保证离线期间指令也能补发,再改 false 并配合持久会话。
3.3 连接参数与 Topic 设计:broker 地址、QoS、通配符的使用边界
参数直接关系到“能不能连上、连上稳不稳”。我把常用参数列成一张表,照着填可以少走弯路:
| 参数 | 建议值 | 说明 |
|---|---|---|
| broker 地址 | wss://域名:端口/mqtt | 小程序只支持 WebSocket,不要写裸mqtt:// |
| clientId | mini_{时间戳}_{随机数} | 全场唯一,重复会被互相踢下线 |
| QoS(状态上报) | 0 | 状态丢失可接受,下一次上报会补上 |
| QoS(控制指令) | 1 | 保证指令至少到达一次,避免“点了没反应” |
| keepalive | 20~60 秒 | 心跳间隔,必须小于 broker 最大限制 |
| clean session | true | 简单场景用 true,断线后无离线堆积 |
Topic 设计是一套约定,broker 不管其含义,但业务代码必须一致。我习惯这么分:设备状态主题home/{设备ID}/status,控制命令主题home/{设备ID}/cmd,确认消息主题home/{设备ID}/ack。设备上电后向 status 发 JSON,比如{"power":true,"temp":26.5}。小程序订阅home/+/status,这里的+正好匹配一层{设备ID},所以所有设备的状态都能收到。发布控制指令时,主题写具体设备 ID,不要带通配符。
一个很容易翻车的点:+只匹配一层,#匹配剩余所有层。如果设备端上报到home/bedroom/light/status,而小程序订阅的是home/+/status,那么中间那层bedroom和light会把通配范围撑破,消息永远收不到。这类问题最玄学,但查法有迹可循:层级对不上,就收不到。设计 Topic 时先把层级写死,设备、协议文档、小程序三处对齐了再动手编码。
提示:调试阶段可以用桌面端 MQTT 调试工具同时订阅
#,去观察设备端实际发布到哪个 Topic、payload 结构长什么样。工具和小程序共用同一个 broker,就能看到两边消息是否互通。
4. 把“人工智能”落到课设里:语音控制与场景联动的务实方案
很多智能家居课设都在摘要里写“基于人工智能”,答辩时却拿不出 AI 相关的实现,这是最容易被追问出问题的环节。但也不要上纲上线,在课设体量里,“人工智能”的合理落点是小程序端的语义解析与自动化场景规则,而不是去训练一个模型。下面这套路线能过盲审,也能扛住现场追问。
4.1 语音入口的三种实现路线:在线识别 API、本地关键词、规则模拟
先给结论:我建议主线用“文本指令+关键词规则”,把录音识别做成可选扩展,并在文档里说明预留接口。原因有两个,一是录音权限在小程序真机上需要用户授权弹窗,演示环境一旦拒绝,功能当场瘫痪;二是在线识别 API 要申请 key、处理回调,课设时间紧时性价比不高。
// utils/intent-parser.js const INTENT_RULES = [ { keywords: ['打开', '灯'], device: 'living_light', action: 'on' }, { keywords: ['关闭', '灯'], device: 'living_light', action: 'off' }, { keywords: ['空调', '调到'], device: 'aircon', action: 'set_temp' }, { keywords: ['离家'], scene: 'away_mode' }, { keywords: ['回家'], scene: 'home_mode' } ]; function parseIntent(text) { for (const rule of INTENT_RULES) { const matched = rule.keywords.every((keyword) => text.includes(keyword)); if (matched) { if (rule.scene) return { type: 'scene', name: rule.scene }; return { type: 'device', device: rule.device, action: rule.action }; } } return null; } module.exports = { parseIntent };逻辑说明:every意味着规则里所有关键词都必须命中,所以“空调调到 26 度”可以匹配到['空调','调到']这条规则,而“打开客厅灯”能匹配第一条。规则表顺序很重要,越具体的放越前面,比如“关闭空调”如果先命中['打开']就会出错,这里需要再补一条['关闭','空调']规则控制优先级。真机语音输入在 WXML 里用input组件的confirm-type="done"采集文本,再进parseIntent,不影响整个链路演示。
4.2 场景联动规则表:一张表撑起“一键离家”与“回家模式”
所谓智能,在课设里最直观的呈现是“一条指令触发多个设备动作”。我在项目里用一张场景配置表来驱动,把场景名映射到一串控制指令:
// utils/scene-table.js const SCENES = { home_mode: [ { device: 'living_light', cmd: 'set_power', value: true }, { device: 'aircon', cmd: 'set_temp', value: 26 }, { device: 'curtain', cmd: 'set_percent', value: 50 } ], away_mode: [ { device: 'living_light', cmd: 'set_power', value: false }, { device: 'aircon', cmd: 'set_power', value: false }, { device: 'curtain', cmd: 'set_percent', value: 100 } ] }; function getSceneActions(sceneName) { return SCENES[sceneName] || []; } module.exports = { getSceneActions };执行时,把数组里的每条指令逐个通过 MQTT 发布到对应设备的 cmd 主题,注意发布间隔。我习惯每条指令间隔 200~300 毫秒,避免网关串行处理时把并发消息打乱。这里有一个细节:away_mode最后一步窗帘要等灯和空调关完再发,因为有些廉价网关同时处理多条指令会卡死。课设答辩时可以把setTimeout封装成一个简单的publishQueue,照着做就行。
从“人工智能”的角度解释:这张表本质是规则引擎,AI 在完整产品里是把规则表替换成策略决策模块;现在以表驱动,既能演示自动化能力,又能把复杂度控制在一个utils文件里。答辩老师问“自动化和 AI 区别”时,你可以直接回答:自动化是固定规则,AI 是规则学习;本设计实现了可配置的规则层,为后续接入决策算法留了接口——这句话能挡住大多数追问。
4.3 控制链路全流程:按钮点击到设备动作的六步数据流
理解整条链路,比背代码重要。一次“打开客厅灯”传递了六层:页面bindtap捕获点击,调用控制函数;控制函数从场景表或单设备命令里取参数;组装成{cmd, value, timestamp}的 JSON;client.publish到home/living_light/cmd;broker 路由给订阅该主题的网关/设备;设备执行后回发ack,小程序订阅 ack 更新 UI 状态。
// utils/device-controller.js const mqttClient = require('./mqtt-client'); function sendCommand(deviceId, cmd, value) { const topic = `home/${deviceId}/cmd`; const payload = JSON.stringify({ cmd, value, timestamp: Date.now() }); return new Promise((resolve, reject) => { mqttClient.publish(topic, payload, { qos: 1 }, (err) => { if (err) reject(err); else resolve({ topic, payload }); }); }); } module.exports = { sendCommand };参数说明:timestamp字段用来在接收侧去重,因为 QoS 1 的消息可能重复到达,设备端或小程序收到重复指令时,对比时间戳就能丢弃旧指令。这里有个课设里常犯的错误:首页按钮直接调用client.publish,把 Topic 和 payload 写死在 WXML 事件里。这样后面加场景控制时,就得复制一堆代码。正确姿势是统一走sendCommand,页面只传业务参数。
还差一步就完整了:页面层在onShow里要重新订阅状态主题,因为小程序切后台再切回来时,WebSocket 可能已经断开。订阅放哪里、payload 怎么解析,上一章的connect(onMessage)回调里已经有示例,页面堆到onShow里做,才能保证现场演示不出现“切个应用回来界面就冻结”的尴尬。
5. 小程序端避坑与排查:编译失败、连接失联、场景不触发的六个典型问题
这一章全是我自己踩出来的血泪经验。课设包本身不完整、broker 连上又断、真机白屏……每个问题都有固定解法,按现象排查比反复重建工程快得多。
5.1 编译报错“vendor.js 校验失败”或“app.json 未找到”
现象:导入项目后,开发者工具直接弹错 “Error: app.json: 文件未找到”,或者提示 vendor.js 内容与工程不匹配。
原因:包是从构建目录里压缩的,只有编译后的 vendor.js 和资源文件,没有源码层的 app.json、pages 目录。工具把它当源码工程打开,当然找不到入口。
解决:按 2.3 节的方法新建工程再迁文件。另外 vendor.js 这种构建产物不要反混淆修改,它是框架运行时,改了无法维护。我见过有同学尝试删掉 vendor.js 让工具重新生成,结果页面渲染逻辑全乱,最后只能重来一遍。
5.2 MQTT 能连上但收不到消息
现象:控制台能看到MQTT connected,设备端也在发布消息,但小程序 UI 没任何更新。
原因:Topic 层级对不上。对比一下:设备发布home/bedroom/light/status,小程序订阅的是home/+/status。+只匹配一层,而实际路径中间有两层,订阅失败是必然的。
解决:统一 Topic 层级表。设备端上报主题改为home/{deviceId}/status,deviceId 只用一层,比如light_01,这样订阅home/+/status就能收到。这个坑最好在设计阶段就避免,把每条 Topic 写进项目文档,让设备端和小程序同时照表开发。
5.3 真机预览白屏
现象:开发工具模拟器一切正常,用手机扫码预览后白屏,console 提示wss://... not in domain list。
原因:小程序真机环境强制校验 socket 合法域名,开发工具的“不校验合法域名”开关只在开发态有效,真机上必须使用已备案且在后台登记的域名,而且必须走 wss + 合法证书。
解决:临时方案是开发工具里的“本地设置→不校验合法域名”勾选上,继续调试;正式演示前,把 broker 的 WebSocket 域名加到小程序后台的 socket 合法域名列表,等待生效刷新再预览。如果 broker 域名不好备案,备份方案是自己搭一个轻量后端做转发,把 MQTT 封装成 HTTP 接口——但这个改动大,不建议课设答辩前碰。
提示:真机白屏还有一个隐性原因——手机系统时间不准,导致 TLS 证书校验失败。遇到白屏时先检查手机时间,再查域名配置,顺序别弄反。
5.4 设备状态一直离线
现象:设备明明在线,小程序界面显示离线,刷新后恢复正常。
原因:设备或小程序掉线期间,broker 不知道对端已断开,状态缓存一直停留在旧值;也可能小程序切后台把 WebSocket 挂起,重连后没有重新订阅。
解决:设备端连接时设置遗嘱消息,will: { topic: 'home/xxx/status', payload: '{"power":false,"online":false}' },断线时 broker 自动广播离线状态。小程序端重连回调里重新订阅,并在onShow中检查连接是否活跃,不活跃就执行一次重连。
5.5 定时场景不触发
现象:配置了晚上 7 点自动关灯,锁屏几分钟后不执行。
原因:小程序运行在手机系统里,页面切后台后 JS 线程可能被完全挂起,setInterval和setTimeout都不再执行,这是平台限制,不是代码问题。
解决:把定时任务放到服务端或云函数,小程序只负责把“晚上 7 点关灯”这个配置项上报,服务端到点后下发 MQTT 指令。课设里如果不想引入服务端,演示时保持小程序前台运行,定时场景就能正常触发。这个限制要在文档里写清楚,现场演示前把“锁屏后不执行”明确为预期行为,反而显得你对平台特性理解到位。
5.6 iconfont 图标全部显示为方框
现象:真机上原本的开关图标变成了豆腐块,console 没有任何报错。
原因:iconfont.css里@font-face引用的字体文件(woff2/ttf)不在包里,页面渲染时找不到字体资源。这份资源列表里只有.css,没有配套字体,所以十有八九会碰上。
解决:补齐字体文件;如果找不到原文件,去图标字体平台重新生成一份同 class 名的字体文件放回styles/,注意@font-face里的路径要和实际存放位置一致。不想折腾字体的话,可以换 base64 内嵌的图标图片,虽然体积大一点,但不会再出现方框。这个坑在答辩前解决,不然首页一排方框很影响观感。
6. 让答辩演示不翻车:一套无硬件 mock 环境与验收脚本
没有实物设备也能把演示跑完整。我在电脑上跑一个 mock 设备脚本,它用 Node.js 的 mqtt 包连接同一个 broker,模拟上报和接收指令。小程序界面操作时,mock 设备会在终端打印收到的指令,并回发 ack,这样整个闭环肉眼可见。
// mock-device.js 在电脑上用node运行 const mqtt = require('mqtt'); const client = mqtt.connect('wss://your-broker-domain.com:8084/mqtt', { clientId: 'mock_device_01' }); client.on('connect', () => { console.log('mock设备已上线'); setInterval(() => { client.publish('home/mock_01/status', JSON.stringify({ device: 'mock_01', power: true, temp: (22 + Math.random() * 4).toFixed(1) }), { qos: 0 }); }, 3000); client.subscribe('home/mock_01/cmd', { qos: 1 }); }); client.on('message', (topic, payload) => { console.log('收到控制指令:', payload.toString()); client.publish('home/mock_01/ack', JSON.stringify({ ok: true })); });跑起来后,对照这张表验收:连接(有没有 connected 日志)、控制(mock 终端是否打印指令)、状态(界面是否 3 秒内更新)、场景(离家模式是否连发三条指令)、断网(重启 broker 后是否自动重连)。这五关过了,现场基本稳。
我最早做这类课设时,把 broker 地址写死在代码里,现场换了个网络环境就连不上,整场演示在重连等待中草草收场,被主审连问三个问题都答不上来。从那以后,我每次演示前都强制跑一遍 mock 设备加真机预览的流程,手机开热点测一次,再回到演示网络测一次,确认没有域名校验问题才进场。希望帮到你。
本文还有配套的精品资源,点击获取