深湾创新
UniApp SDK

UniApp SDK 集成指南

SDK接入示意图

SDK接入示意图

系统要求

HBuilderX版本:3.0.0 及以上

平台支持:App(Android/iOS)、H5、微信小程序

Vue版本:Vue 2.x 或 Vue 3.x

编译模式:自定义组件模式

蓝牙要求:支持低功耗蓝牙(BLE)

SDK集成

1. npm安装

npm install shenwan-brainwave-uniapp --save

2. 手动下载集成

下载SDK包,将文件复制到项目目录:

/common/shenwan-sdk/
  ├── index.js
  ├── device-manager.js
  ├── data-manager.js
  └── utils.js

3. 配置manifest.json

{
  "app-plus": {
    "modules": {
      "Bluetooth": {}
    },
    "permissions": {
      "Bluetooth": {
        "description": "用于连接脑电设备"
      },
      "BluetoothAdmin": {
        "description": "用于管理蓝牙连接"
      }
    }
  },
  "mp-weixin": {
    "permission": {
      "scope.bluetooth": {
        "desc": "用于连接脑电设备"
      }
    }
  }
}

Vue 2.x 基本使用

1. 引入SDK

// main.js
import Vue from 'vue'
import ShenWanSDK from 'shenwan-brainwave-uniapp'

Vue.use(ShenWanSDK)

// 或者在页面中引入
// import { BrainwaveSDK } from '@/common/shenwan-sdk/index.js'

2. 设备连接组件

<template>
  <view class="container">
    <view class="header">
      <text class="title">脑电设备连接</text>
    </view>
    
    <view class="device-section">
      <button @click="startScan" :disabled="isScanning">
        {{ isScanning ? '扫描中...' : '开始扫描' }}
      </button>
      
      <view class="device-list">
        <view 
          v-for="device in deviceList" 
          :key="device.deviceId"
          class="device-item"
          @click="connectDevice(device)"
        >
          <text class="device-name">{{ device.name }}</text>
          <text class="device-id">{{ device.deviceId }}</text>
        </view>
      </view>
    </view>
    
    <view class="status-section">
      <text class="status">状态: {{ connectionStatus }}</text>
    </view>
  </view>
</template>

<script>
import { BrainwaveSDK } from '@/common/shenwan-sdk/index.js'

export default {
  data() {
    return {
      isScanning: false,
      deviceList: [],
      connectionStatus: '未连接',
      sdk: null
    }
  },
  
  onLoad() {
    this.initSDK()
  },
  
  methods: {
    async initSDK() {
      try {
        this.sdk = new BrainwaveSDK()
        await this.sdk.initialize()
        
        // 注册事件监听
        this.sdk.on('deviceFound', this.onDeviceFound)
        this.sdk.on('deviceConnected', this.onDeviceConnected)
        this.sdk.on('deviceDisconnected', this.onDeviceDisconnected)
        this.sdk.on('dataReceived', this.onDataReceived)
        
        console.log('SDK初始化成功')
      } catch (error) {
        console.error('SDK初始化失败:', error)
        uni.showToast({
          title: 'SDK初始化失败',
          icon: 'none'
        })
      }
    },
    
    async startScan() {
      if (this.isScanning) return
      
      try {
        this.isScanning = true
        this.deviceList = []
        
        await this.sdk.startScan({
          timeout: 10000 // 10秒超时
        })
        
        console.log('开始扫描设备')
      } catch (error) {
        console.error('扫描失败:', error)
        uni.showToast({
          title: '扫描失败',
          icon: 'none'
        })
      } finally {
        setTimeout(() => {
          this.isScanning = false
        }, 10000)
      }
    },
    
    async connectDevice(device) {
      try {
        uni.showLoading({
          title: '连接中...'
        })
        
        await this.sdk.connect(device.deviceId)
        
        console.log('设备连接成功:', device.name)
      } catch (error) {
        console.error('连接失败:', error)
        uni.showToast({
          title: '连接失败',
          icon: 'none'
        })
      } finally {
        uni.hideLoading()
      }
    },
    
    onDeviceFound(device) {
      console.log('发现设备:', device)
      this.deviceList.push(device)
    },
    
    onDeviceConnected(device) {
      console.log('设备已连接:', device)
      this.connectionStatus = '已连接'
      
      uni.showToast({
        title: '连接成功',
        icon: 'success'
      })
      
      // 开始数据采集
      this.startDataCollection()
    },
    
    onDeviceDisconnected() {
      console.log('设备已断开')
      this.connectionStatus = '未连接'
      
      uni.showToast({
        title: '设备已断开',
        icon: 'none'
      })
    },
    
    async startDataCollection() {
      try {
        await this.sdk.startDataCollection({
          sampleRate: 250,
          enableProcessing: true,
          algorithms: ['attention', 'relaxation', 'stress']
        })
        
        console.log('开始数据采集')
      } catch (error) {
        console.error('启动数据采集失败:', error)
      }
    },
    
    onDataReceived(data) {
      console.log('接收到数据:', data)
      // 处理接收到的脑电数据
      this.processBrainwaveData(data)
    },
    
    processBrainwaveData(data) {
      // 发送数据到其他页面或组件
      this.$emit('brainwaveData', data)
      
      // 或者使用uni.$emit全局事件
      uni.$emit('brainwaveDataUpdate', data)
    }
  },
  
  onUnload() {
    // 页面卸载时断开连接
    if (this.sdk) {
      this.sdk.disconnect()
    }
  }
}
</script>

3. 数据显示组件

<template>
  <view class="data-display">
    <view class="metrics-grid">
      <view class="metric-card">
        <text class="metric-label">专注度</text>
        <text class="metric-value">{{ attention.toFixed(1) }}</text>
        <progress :percent="attention * 100" stroke-width="8" activeColor="#007AFF" />
      </view>
      
      <view class="metric-card">
        <text class="metric-label">放松度</text>
        <text class="metric-value">{{ relaxation.toFixed(1) }}</text>
        <progress :percent="relaxation * 100" stroke-width="8" activeColor="#34C759" />
      </view>
      
      <view class="metric-card">
        <text class="metric-label">压力值</text>
        <text class="metric-value">{{ stress.toFixed(1) }}</text>
        <progress :percent="stress * 100" stroke-width="8" activeColor="#FF3B30" />
      </view>
    </view>
    
    <view class="chart-section">
      <text class="section-title">实时波形</text>
      <canvas 
        canvas-id="waveformChart" 
        class="chart-canvas"
        @touchstart="onChartTouch"
      ></canvas>
    </view>
    
    <view class="controls">
      <button @click="startRecording" :disabled="isRecording">
        {{ isRecording ? '录制中...' : '开始录制' }}
      </button>
      <button @click="exportData">导出数据</button>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      attention: 0,
      relaxation: 0,
      stress: 0,
      isRecording: false,
      chartData: [],
      chartContext: null
    }
  },
  
  onLoad() {
    this.initChart()
    this.registerDataListener()
  },
  
  methods: {
    initChart() {
      this.chartContext = uni.createCanvasContext('waveformChart', this)
      this.drawChart()
    },
    
    registerDataListener() {
      // 监听全局脑电数据事件
      uni.$on('brainwaveDataUpdate', this.updateData)
    },
    
    updateData(data) {
      // 更新指标值
      this.attention = data.attention || 0
      this.relaxation = data.relaxation || 0
      this.stress = data.stress || 0
      
      // 更新图表数据
      if (data.rawData) {
        this.updateChart(data.rawData)
      }
      
      // 记录数据(如果正在录制)
      if (this.isRecording) {
        this.recordData(data)
      }
    },
    
    updateChart(rawData) {
      // 添加新数据点
      this.chartData.push(...rawData)
      
      // 保持数据长度
      if (this.chartData.length > 500) {
        this.chartData = this.chartData.slice(-500)
      }
      
      // 重绘图表
      this.drawChart()
    },
    
    drawChart() {
      if (!this.chartContext || this.chartData.length === 0) return
      
      const ctx = this.chartContext
      const width = 350
      const height = 200
      
      // 清空画布
      ctx.clearRect(0, 0, width, height)
      
      // 绘制背景
      ctx.setFillStyle('#f8f8f8')
      ctx.fillRect(0, 0, width, height)
      
      // 绘制网格
      ctx.setStrokeStyle('#e0e0e0')
      ctx.setLineWidth(1)
      for (let i = 0; i <= 10; i++) {
        const y = (height / 10) * i
        ctx.moveTo(0, y)
        ctx.lineTo(width, y)
        ctx.stroke()
      }
      
      // 绘制波形
      if (this.chartData.length > 1) {
        ctx.setStrokeStyle('#007AFF')
        ctx.setLineWidth(2)
        ctx.beginPath()
        
        const stepX = width / (this.chartData.length - 1)
        const centerY = height / 2
        const amplitude = height * 0.4
        
        for (let i = 0; i < this.chartData.length; i++) {
          const x = i * stepX
          const y = centerY - (this.chartData[i] * amplitude)
          
          if (i === 0) {
            ctx.moveTo(x, y)
          } else {
            ctx.lineTo(x, y)
          }
        }
        
        ctx.stroke()
      }
      
      ctx.draw()
    },
    
    startRecording() {
      this.isRecording = !this.isRecording
      
      if (this.isRecording) {
        this.recordedData = []
        uni.showToast({
          title: '开始录制',
          icon: 'success'
        })
      } else {
        uni.showToast({
          title: '停止录制',
          icon: 'success'
        })
      }
    },
    
    recordData(data) {
      if (!this.recordedData) {
        this.recordedData = []
      }
      
      this.recordedData.push({
        timestamp: Date.now(),
        ...data
      })
    },
    
    async exportData() {
      if (!this.recordedData || this.recordedData.length === 0) {
        uni.showToast({
          title: '没有数据可导出',
          icon: 'none'
        })
        return
      }
      
      try {
        const jsonData = JSON.stringify(this.recordedData, null, 2)
        
        // 保存到本地文件
        const filePath = `${uni.env.USER_DATA_PATH}/brainwave_data_${Date.now()}.json`
        
        uni.getFileSystemManager().writeFile({
          filePath: filePath,
          data: jsonData,
          encoding: 'utf8',
          success: () => {
            uni.showToast({
              title: '导出成功',
              icon: 'success'
            })
          },
          fail: (error) => {
            console.error('导出失败:', error)
            uni.showToast({
              title: '导出失败',
              icon: 'none'
            })
          }
        })
      } catch (error) {
        console.error('导出数据失败:', error)
      }
    }
  },
  
  onUnload() {
    // 取消事件监听
    uni.$off('brainwaveDataUpdate', this.updateData)
  }
}
</script>

Vue 3.x 组合式API

<template>
  <view class="brainwave-app">
    <DeviceConnection @connected="onDeviceConnected" />
    <DataDisplay v-if="isConnected" :data="brainwaveData" />
  </view>
</template>

<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue'
import { BrainwaveSDK } from '@/common/shenwan-sdk/index.js'
import DeviceConnection from '@/components/DeviceConnection.vue'
import DataDisplay from '@/components/DataDisplay.vue'

// 响应式数据
const isConnected = ref(false)
const brainwaveData = reactive({
  attention: 0,
  relaxation: 0,
  stress: 0,
  rawData: []
})

// SDK实例
let sdk = null

// 初始化SDK
const initSDK = async () => {
  try {
    sdk = new BrainwaveSDK()
    await sdk.initialize()
    
    // 注册事件监听
    sdk.on('dataReceived', onDataReceived)
    sdk.on('deviceDisconnected', onDeviceDisconnected)
    
    console.log('SDK初始化成功')
  } catch (error) {
    console.error('SDK初始化失败:', error)
  }
}

// 设备连接成功
const onDeviceConnected = (device) => {
  isConnected.value = true
  console.log('设备连接成功:', device)
}

// 设备断开连接
const onDeviceDisconnected = () => {
  isConnected.value = false
  console.log('设备已断开连接')
}

// 接收脑电数据
const onDataReceived = (data) => {
  // 更新响应式数据
  Object.assign(brainwaveData, data)
}

// 生命周期
onMounted(() => {
  initSDK()
})

onUnmounted(() => {
  if (sdk) {
    sdk.disconnect()
  }
})
</script>

平台特定功能

1. 微信小程序适配

// 微信小程序蓝牙适配
const WechatBluetooth = {
  async startScan() {
    // 检查蓝牙状态
    const bluetoothState = await this.getBluetoothAdapterState()
    if (!bluetoothState.available) {
      throw new Error('蓝牙不可用')
    }
    
    // 开始扫描
    return new Promise((resolve, reject) => {
      wx.startBluetoothDevicesDiscovery({
        services: ['FFF0'], // 脑电设备服务UUID
        success: resolve,
        fail: reject
      })
    })
  },
  
  async connect(deviceId) {
    return new Promise((resolve, reject) => {
      wx.createBLEConnection({
        deviceId: deviceId,
        success: resolve,
        fail: reject
      })
    })
  },
  
  async getBluetoothAdapterState() {
    return new Promise((resolve, reject) => {
      wx.getBluetoothAdapterState({
        success: resolve,
        fail: reject
      })
    })
  }
}

2. H5平台适配

// H5平台Web Bluetooth适配
const H5Bluetooth = {
  async startScan() {
    if (!navigator.bluetooth) {
      throw new Error('浏览器不支持Web Bluetooth')
    }
    
    try {
      const device = await navigator.bluetooth.requestDevice({
        filters: [{
          services: ['0000fff0-0000-1000-8000-00805f9b34fb']
        }]
      })
      
      return device
    } catch (error) {
      throw new Error('用户取消选择或设备不可用')
    }
  },
  
  async connect(device) {
    const server = await device.gatt.connect()
    const service = await server.getPrimaryService('0000fff0-0000-1000-8000-00805f9b34fb')
    const characteristic = await service.getCharacteristic('0000fff1-0000-1000-8000-00805f9b34fb')
    
    // 启用通知
    await characteristic.startNotifications()
    
    return {
      device,
      server,
      service,
      characteristic
    }
  }
}