发布日期:2024年9月30日
一、应用架构设计
本教程将构建一个完整的智能家居控制应用,包含以下核心模块:
- 设备连接:MQTT协议+WebSocket双通道
- 场景联动:可视化规则引擎
- 语音控制:语音识别与合成
- 多端适配:iOS/Android/小程序
- 安全系统:设备认证与数据加密
技术栈:UniApp + Vue3 + uView UI + MQTT.js + WebSocket
二、项目初始化与配置
1. 创建UniApp项目
# 使用Vue3/Vite模板创建项目
npx degit dcloudio/uni-preset-vue#vite my-smart-home
# 安装核心依赖
cd my-smart-home
npm install mqtt socket.io-client uview-ui
npm install -D sass sass-loader
2. 目录结构规划
src/
├── api/ # 接口服务
├── components/ # 公共组件
│ ├── device/ # 设备组件
│ └── scene/ # 场景组件
├── composables/ # 组合式函数
├── pages/ # 页面文件
│ ├── home/ # 首页
│ └── device/ # 设备详情
├── static/ # 静态资源
├── stores/ # Pinia状态
│ ├── device/ # 设备状态
│ └── user/ # 用户状态
├── utils/ # 工具函数
└── uni.scss # 全局样式
三、物联网设备连接
1. 双通道连接服务
// src/composables/useDeviceConnection.ts
import mqtt from 'mqtt'
import { io } from 'socket.io-client'
import { ref, onUnmounted } from 'vue'
export function useDeviceConnection() {
const mqttClient = ref(null)
const socketClient = ref(null)
const isConnected = ref(false)
const connect = (token: string) => {
// MQTT连接
mqttClient.value = mqtt.connect('wss://mqtt.example.com', {
username: 'smart_home',
password: token,
clientId: `client_${Date.now()}`
})
mqttClient.value.on('connect', () => {
console.log('MQTT connected')
isConnected.value = true
})
// WebSocket连接
socketClient.value = io('https://socket.example.com', {
auth: { token },
transports: ['websocket']
})
socketClient.value.on('connect', () => {
console.log('Socket connected')
})
}
const subscribe = (deviceId: string, callback: Function) => {
mqttClient.value?.subscribe(`device/${deviceId}/status`)
mqttClient.value?.on('message', (topic, message) => {
if (topic === `device/${deviceId}/status`) {
callback(JSON.parse(message.toString()))
}
})
socketClient.value?.on(`device:${deviceId}`, (data) => {
callback(data)
})
}
onUnmounted(() => {
mqttClient.value?.end()
socketClient.value?.disconnect()
})
return { connect, subscribe, isConnected }
}
2. 设备控制组件
<template>
<view class="device-card">
<view class="header">
<text>{{ device.name }}</text>
<u-switch
v-model="device.status"
@change="handleStatusChange"
></u-switch>
</view>
<template v-if="device.type === 'light'">
<u-slider
v-model="brightness"
min="0"
max="100"
@change="handleBrightnessChange"
></u-slider>
</template>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useDeviceConnection } from '@/composables/useDeviceConnection'
const props = defineProps<{
device: Device
}>()
const brightness = ref(props.device.brightness || 50)
const { subscribe } = useDeviceConnection()
const handleStatusChange = (val: boolean) => {
sendCommand('power', val ? 'on' : 'off')
}
const handleBrightnessChange = (val: number) => {
sendCommand('brightness', val)
}
const sendCommand = (cmd: string, value: any) => {
uni.request({
url: '/api/device/control',
method: 'POST',
data: {
deviceId: props.device.id,
command: cmd,
value
}
})
}
// 订阅设备状态更新
onMounted(() => {
subscribe(props.device.id, (data: any) => {
if (data.status !== undefined) {
props.device.status = data.status
}
if (data.brightness !== undefined) {
brightness.value = data.brightness
}
})
})
</script>
四、场景联动实现
1. 可视化规则编辑器
<template>
<view class="scene-editor">
<view class="condition" v-for="(cond, index) in conditions" :key="index">
<picker
mode="selector"
:range="devices"
range-key="name"
@change="selectDevice($event, index)"
>
<view>{{ cond.device?.name || '选择设备' }}</view>
</picker>
<picker
mode="selector"
:range="operators"
@change="selectOperator($event, index)"
>
<view>{{ cond.operator || '选择条件' }}</view>
</picker>
</view>
<view class="actions">
<button @click="addCondition">添加条件</button>
<button @click="saveScene">保存场景</button>
</view>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useDeviceStore } from '@/stores/device'
const deviceStore = useDeviceStore()
const conditions = ref([{ device: null, operator: null, value: null }])
const operators = ['=', '>', ' {
conditions.value[index].device = deviceStore.devices[e.detail.value]
}
const addCondition = () => {
conditions.value.push({ device: null, operator: null, value: null })
}
const saveScene = async () => {
await uni.request({
url: '/api/scene',
method: 'POST',
data: { conditions: conditions.value }
})
uni.showToast({ title: '场景保存成功' })
}
</script>
2. 场景执行引擎
// src/utils/sceneEngine.ts
export class SceneEngine {
private deviceStates: Record = {}
constructor(private mqttClient: any) {
this.setupListeners()
}
private setupListeners() {
this.mqttClient.on('message', (topic: string, message: Buffer) => {
const deviceId = topic.split('/')[1]
this.deviceStates[deviceId] = JSON.parse(message.toString())
this.checkScenes()
})
}
private async checkScenes() {
const scenes = await this.fetchActiveScenes()
scenes.forEach(scene => {
const shouldTrigger = scene.conditions.every(cond => {
const state = this.deviceStates[cond.deviceId]
if (!state) return false
switch (cond.operator) {
case '=': return state[cond.property] === cond.value
case '>': return state[cond.property] > cond.value
case '<': return state[cond.property] {
this.mqttClient.publish(
`device/${action.deviceId}/control`,
JSON.stringify({ command: action.command, value: action.value })
)
})
}
}
五、语音控制集成
1. 语音识别组件
<template>
<view class="voice-control">
<button
@touchstart="startListening"
@touchend="stopListening"
:disabled="isListening"
>
{{ isListening ? '识别中...' : '按住说话' }}
</button>
<text v-if="transcript">识别结果: {{ transcript }}</text>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useVoiceCommand } from '@/composables/useVoiceCommand'
const { isListening, transcript, startListening, stopListening } = useVoiceCommand()
watch(transcript, (newText) => {
if (newText.includes('开灯')) {
// 执行开灯命令
} else if (newText.includes('关灯')) {
// 执行关灯命令
}
})
</script>
2. 语音命令处理
// src/composables/useVoiceCommand.ts
import { ref } from 'vue'
export function useVoiceCommand() {
const isListening = ref(false)
const transcript = ref('')
let recognition: any = null
const startListening = () => {
if (typeof plus === 'object') {
// 5+ App环境
recognition = plus.speech.startRecognize({
engine: 'iFly',
lang: 'zh-CN'
}, (res) => {
transcript.value = res
})
} else {
// 小程序/Web环境
recognition = uni.startRecord({
format: 'mp3',
success: (res) => {
uni.uploadFile({
url: '/api/voice/recognize',
filePath: res.tempFilePath,
success: (res) => {
transcript.value = JSON.parse(res.data).text
}
})
}
})
}
isListening.value = true
}
const stopListening = () => {
if (typeof plus === 'object') {
plus.speech.stopRecognize()
} else {
uni.stopRecord()
}
isListening.value = false
}
return { isListening, transcript, startListening, stopListening }
}
六、多端适配方案
1. 平台条件编译
// 设备控制方法适配不同平台
function controlDevice(deviceId: string, command: string, value: any) {
// #ifdef APP-PLUS
// 使用原生能力
const result = plus.device.exec(command, value)
// #endif
// #ifdef MP-WEIXIN
// 小程序使用云函数
wx.cloud.callFunction({
name: 'deviceControl',
data: { deviceId, command, value }
})
// #endif
// #ifdef H5
// Web使用WebSocket
socket.emit('device:control', { deviceId, command, value })
// #endif
}
2. 统一API封装
// src/utils/uniRequest.ts
import { useUserStore } from '@/stores/user'
export const uniRequest = {
async request(options: UniApp.RequestOptions) {
const userStore = useUserStore()
const header = {
'Content-Type': 'application/json',
...options.header
}
if (userStore.token) {
header['Authorization'] = `Bearer ${userStore.token}`
}
return new Promise((resolve, reject) => {
uni.request({
...options,
header,
success: (res) => {
if (res.statusCode >= 200 && res.statusCode reject(err)
})
})
},
get(url: string, params?: object) {
return this.request({ url, method: 'GET', data: params })
},
post(url: string, data: object) {
return this.request({ url, method: 'POST', data })
}
}
七、安全认证系统
1. JWT认证中间件
// src/utils/auth.ts
import { ref } from 'vue'
import { useRouter } from 'vue-router'
export function useAuth() {
const token = ref('')
const router = useRouter()
const login = async (username: string, password: string) => {
try {
const res = await uniRequest.post('/auth/login', { username, password })
token.value = res.token
uni.setStorageSync('token', res.token)
return true
} catch (err) {
return false
}
}
const checkAuth = () => {
const storedToken = uni.getStorageSync('token')
if (storedToken) {
token.value = storedToken
return true
}
return false
}
const logout = () => {
token.value = ''
uni.removeStorageSync('token')
router.replace('/login')
}
return { token, login, checkAuth, logout }
}
2. 设备安全认证
// src/utils/deviceAuth.ts
import CryptoJS from 'crypto-js'
export function generateDeviceSignature(deviceId: string, secret: string) {
const timestamp = Date.now()
const nonce = Math.random().toString(36).substring(2, 10)
const signStr = `${deviceId}|${timestamp}|${nonce}|${secret}`
const signature = CryptoJS.HmacSHA256(signStr, secret).toString()
return {
deviceId,
timestamp,
nonce,
signature
}
}
export function verifyDeviceSignature(
deviceId: string,
timestamp: number,
nonce: string,
signature: string,
secret: string
) {
// 防止重放攻击
if (Date.now() - timestamp > 60000) {
return false
}
const signStr = `${deviceId}|${timestamp}|${nonce}|${secret}`
const calculatedSign = CryptoJS.HmacSHA256(signStr, secret).toString()
return calculatedSign === signature
}
八、总结与扩展
通过本教程,您已经掌握了:
- 物联网设备连接技术
- 场景联动规则引擎
- 多平台语音控制集成
- 跨平台适配方案
- 设备安全认证系统
扩展学习方向:
- AR/VR家居可视化
- 边缘计算设备控制
- AI智能场景推荐
- 能源消耗分析