UniApp跨平台智能家居控制App开发:从物联网连接到多端适配全流程实战 | 移动开发进阶

2025-08-19 0 1,009

发布日期: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
}

八、总结与扩展

通过本教程,您已经掌握了:

  1. 物联网设备连接技术
  2. 场景联动规则引擎
  3. 多平台语音控制集成
  4. 跨平台适配方案
  5. 设备安全认证系统

扩展学习方向:

  • AR/VR家居可视化
  • 边缘计算设备控制
  • AI智能场景推荐
  • 能源消耗分析
UniApp跨平台智能家居控制App开发:从物联网连接到多端适配全流程实战 | 移动开发进阶
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

淘吗网 uniapp UniApp跨平台智能家居控制App开发:从物联网连接到多端适配全流程实战 | 移动开发进阶 https://www.taomawang.com/web/uniapp/902.html

常见问题

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务