深湾创新
UniApp H5 SDK

UniApp H5 SDK 集成指南

SDK接入示意图

SDK接入示意图

系统要求

浏览器支持:Chrome 56+, Firefox 52+, Safari 11+

Web Bluetooth:需要HTTPS环境

UniApp版本:HBuilderX 3.0.0+

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

设备要求:支持蓝牙4.0+的设备

SDK集成

1. CDN引入

<script src="https://cdn.jsdelivr.net/npm/shenwan-brainwave-h5@latest/dist/shenwan-sdk.min.js"></script>

2. npm安装

npm install shenwan-brainwave-h5 --save

3. ES6模块引入

import { ShenWanSDK } from 'shenwan-brainwave-h5'

// 或者
import ShenWanSDK from 'shenwan-brainwave-h5'

4. 配置manifest.json(H5平台)

{
  "h5": {
    "template": "index.html",
    "router": {
      "mode": "history",
      "base": "/"
    },
    "optimization": {
      "treeShaking": {
        "enable": true
      }
    },
    "devServer": {
      "https": true,
      "port": 8080
    }
  }
}

基本使用

1. 初始化SDK

<template>
  <div class="brainwave-h5-app">
    <div class="header">
      <h1>脑电H5应用</h1>
      <div class="status">{{ connectionStatus }}</div>
    </div>
    
    <div class="main-content">
      <div class="device-section">
        <button @click="checkBrowserSupport">检查浏览器支持</button>
        <button @click="requestDevice" :disabled="!browserSupported">
          {{ isConnected ? '已连接' : '连接设备' }}
        </button>
        <button @click="disconnect" :disabled="!isConnected">断开连接</button>
      </div>
      
      <div class="data-section" v-if="isConnected">
        <div class="metrics">
          <div class="metric-item">
            <label>专注度</label>
            <div class="progress-bar">
              <div class="progress" :style="{ width: attention * 100 + '%' }"></div>
            </div>
            <span>{{ (attention * 100).toFixed(1) }}%</span>
          </div>
          
          <div class="metric-item">
            <label>放松度</label>
            <div class="progress-bar">
              <div class="progress relaxation" :style="{ width: relaxation * 100 + '%' }"></div>
            </div>
            <span>{{ (relaxation * 100).toFixed(1) }}%</span>
          </div>
          
          <div class="metric-item">
            <label>压力值</label>
            <div class="progress-bar">
              <div class="progress stress" :style="{ width: stress * 100 + '%' }"></div>
            </div>
            <span>{{ (stress * 100).toFixed(1) }}%</span>
          </div>
        </div>
        
        <div class="chart-container">
          <canvas ref="chartCanvas" width="600" height="300"></canvas>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import { ShenWanSDK } from 'shenwan-brainwave-h5'

export default {
  name: 'BrainwaveH5App',
  data() {
    return {
      sdk: null,
      browserSupported: false,
      isConnected: false,
      connectionStatus: '未连接',
      attention: 0,
      relaxation: 0,
      stress: 0,
      chartData: [],
      chartContext: null
    }
  },
  
  mounted() {
    this.initSDK()
    this.initChart()
  },
  
  methods: {
    async initSDK() {
      try {
        this.sdk = new ShenWanSDK({
          platform: 'h5',
          debug: true
        })
        
        // 注册事件监听
        this.sdk.on('connected', this.onDeviceConnected)
        this.sdk.on('disconnected', this.onDeviceDisconnected)
        this.sdk.on('dataReceived', this.onDataReceived)
        this.sdk.on('error', this.onError)
        
        console.log('SDK初始化成功')
      } catch (error) {
        console.error('SDK初始化失败:', error)
        this.$toast('SDK初始化失败')
      }
    },
    
    async checkBrowserSupport() {
      try {
        this.browserSupported = await this.sdk.checkBrowserSupport()
        
        if (this.browserSupported) {
          this.$toast('浏览器支持Web Bluetooth')
        } else {
          this.$toast('浏览器不支持Web Bluetooth,请使用Chrome 56+或其他支持的浏览器')
        }
      } catch (error) {
        console.error('检查浏览器支持失败:', error)
        this.$toast('检查失败')
      }
    },
    
    async requestDevice() {
      if (this.isConnected) return
      
      try {
        this.connectionStatus = '连接中...'
        
        // 请求用户选择设备
        const device = await this.sdk.requestDevice({
          filters: [{
            services: ['0000fff0-0000-1000-8000-00805f9b34fb']
          }],
          optionalServices: ['0000fff1-0000-1000-8000-00805f9b34fb']
        })
        
        console.log('用户选择设备:', device.name)
        
        // 连接设备
        await this.sdk.connect(device)
        
      } catch (error) {
        console.error('连接设备失败:', error)
        this.connectionStatus = '连接失败'
        this.$toast('连接失败: ' + error.message)
      }
    },
    
    async disconnect() {
      try {
        await this.sdk.disconnect()
        console.log('设备已断开连接')
      } catch (error) {
        console.error('断开连接失败:', error)
      }
    },
    
    onDeviceConnected(device) {
      this.isConnected = true
      this.connectionStatus = `已连接: ${device.name}`
      this.$toast('设备连接成功')
      
      // 开始数据采集
      this.startDataCollection()
    },
    
    onDeviceDisconnected() {
      this.isConnected = false
      this.connectionStatus = '未连接'
      this.$toast('设备已断开连接')
    },
    
    async startDataCollection() {
      try {
        await this.sdk.startDataCollection({
          sampleRate: 250,
          enableProcessing: true,
          processingInterval: 100, // 100ms更新一次
          algorithms: {
            attention: { enabled: true },
            relaxation: { enabled: true },
            stress: { enabled: true }
          }
        })
        
        console.log('开始数据采集')
      } catch (error) {
        console.error('启动数据采集失败:', error)
      }
    },
    
    onDataReceived(data) {
      // 更新指标值
      if (data.processed) {
        this.attention = data.processed.attention || 0
        this.relaxation = data.processed.relaxation || 0
        this.stress = data.processed.stress || 0
      }
      
      // 更新图表
      if (data.raw) {
        this.updateChart(data.raw)
      }
    },
    
    onError(error) {
      console.error('SDK错误:', error)
      this.$toast('发生错误: ' + error.message)
    },
    
    initChart() {
      const canvas = this.$refs.chartCanvas
      if (!canvas) return
      
      this.chartContext = canvas.getContext('2d')
      this.drawChart()
    },
    
    updateChart(rawData) {
      // 添加新数据
      this.chartData.push(...rawData)
      
      // 保持数据长度
      if (this.chartData.length > 600) {
        this.chartData = this.chartData.slice(-600)
      }
      
      // 重绘图表
      this.drawChart()
    },
    
    drawChart() {
      if (!this.chartContext || this.chartData.length === 0) return
      
      const ctx = this.chartContext
      const canvas = this.$refs.chartCanvas
      const width = canvas.width
      const height = canvas.height
      
      // 清空画布
      ctx.clearRect(0, 0, width, height)
      
      // 绘制背景
      ctx.fillStyle = '#f8f9fa'
      ctx.fillRect(0, 0, width, height)
      
      // 绘制网格
      ctx.strokeStyle = '#e9ecef'
      ctx.lineWidth = 1
      
      // 垂直网格线
      for (let i = 0; i <= 10; i++) {
        const x = (width / 10) * i
        ctx.beginPath()
        ctx.moveTo(x, 0)
        ctx.lineTo(x, height)
        ctx.stroke()
      }
      
      // 水平网格线
      for (let i = 0; i <= 6; i++) {
        const y = (height / 6) * i
        ctx.beginPath()
        ctx.moveTo(0, y)
        ctx.lineTo(width, y)
        ctx.stroke()
      }
      
      // 绘制波形
      if (this.chartData.length > 1) {
        ctx.strokeStyle = '#007bff'
        ctx.lineWidth = 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()
      }
    },
    
    $toast(message) {
      // 简单的toast实现
      const toast = document.createElement('div')
      toast.textContent = message
      toast.style.cssText = `
        position: fixed;
        top: 20px;
        right: 20px;
        background: #333;
        color: white;
        padding: 10px 20px;
        border-radius: 4px;
        z-index: 1000;
      `
      
      document.body.appendChild(toast)
      
      setTimeout(() => {
        document.body.removeChild(toast)
      }, 3000)
    }
  },
  
  beforeDestroy() {
    if (this.sdk) {
      this.sdk.disconnect()
    }
  }
}
</script>

高级功能

1. 数据录制和回放

// 数据录制功能
class DataRecorder {
  constructor() {
    this.isRecording = false
    this.recordedData = []
    this.startTime = null
  }
  
  startRecording() {
    this.isRecording = true
    this.recordedData = []
    this.startTime = Date.now()
    console.log('开始录制数据')
  }
  
  stopRecording() {
    this.isRecording = false
    console.log('停止录制,共录制', this.recordedData.length, '条数据')
    return this.recordedData
  }
  
  recordData(data) {
    if (!this.isRecording) return
    
    this.recordedData.push({
      timestamp: Date.now() - this.startTime,
      ...data
    })
  }
  
  exportData(format = 'json') {
    const data = {
      metadata: {
        recordingDuration: this.recordedData.length > 0 ? 
          this.recordedData[this.recordedData.length - 1].timestamp : 0,
        sampleCount: this.recordedData.length,
        exportTime: new Date().toISOString()
      },
      data: this.recordedData
    }
    
    if (format === 'json') {
      return JSON.stringify(data, null, 2)
    } else if (format === 'csv') {
      return this.convertToCSV(data)
    }
  }
  
  convertToCSV(data) {
    if (data.data.length === 0) return ''
    
    const headers = Object.keys(data.data[0])
    const csvContent = [
      headers.join(','),
      ...data.data.map(row => 
        headers.map(header => row[header]).join(',')
      )
    ].join('\n')
    
    return csvContent
  }
  
  downloadData(filename, format = 'json') {
    const content = this.exportData(format)
    const blob = new Blob([content], { 
      type: format === 'json' ? 'application/json' : 'text/csv' 
    })
    
    const url = URL.createObjectURL(blob)
    const a = document.createElement('a')
    a.href = url
    a.download = `${filename}.${format}`
    document.body.appendChild(a)
    a.click()
    document.body.removeChild(a)
    URL.revokeObjectURL(url)
  }
}

2. 实时数据分析

// 实时数据分析器
class RealTimeAnalyzer {
  constructor(windowSize = 256) {
    this.windowSize = windowSize
    this.dataBuffer = []
    this.analysisResults = {
      attention: 0,
      relaxation: 0,
      stress: 0,
      meditation: 0
    }
  }
  
  addData(rawData) {
    this.dataBuffer.push(...rawData)
    
    // 保持缓冲区大小
    if (this.dataBuffer.length > this.windowSize * 2) {
      this.dataBuffer = this.dataBuffer.slice(-this.windowSize)
    }
    
    // 如果有足够的数据,进行分析
    if (this.dataBuffer.length >= this.windowSize) {
      this.analyze()
    }
  }
  
  analyze() {
    const data = this.dataBuffer.slice(-this.windowSize)
    
    // 计算频域特征
    const spectrum = this.fft(data)
    const powerBands = this.calculatePowerBands(spectrum)
    
    // 计算各项指标
    this.analysisResults.attention = this.calculateAttention(powerBands)
    this.analysisResults.relaxation = this.calculateRelaxation(powerBands)
    this.analysisResults.stress = this.calculateStress(powerBands)
    this.analysisResults.meditation = this.calculateMeditation(powerBands)
    
    return this.analysisResults
  }
  
  fft(data) {
    // 简化的FFT实现(实际应用中建议使用专业的FFT库)
    const N = data.length
    const spectrum = new Array(N / 2)
    
    for (let k = 0; k < N / 2; k++) {
      let real = 0
      let imag = 0
      
      for (let n = 0; n < N; n++) {
        const angle = -2 * Math.PI * k * n / N
        real += data[n] * Math.cos(angle)
        imag += data[n] * Math.sin(angle)
      }
      
      spectrum[k] = Math.sqrt(real * real + imag * imag)
    }
    
    return spectrum
  }
  
  calculatePowerBands(spectrum) {
    const sampleRate = 250 // Hz
    const freqResolution = sampleRate / spectrum.length / 2
    
    const bands = {
      delta: { min: 0.5, max: 4, power: 0 },
      theta: { min: 4, max: 8, power: 0 },
      alpha: { min: 8, max: 13, power: 0 },
      beta: { min: 13, max: 30, power: 0 },
      gamma: { min: 30, max: 50, power: 0 }
    }
    
    for (let i = 0; i < spectrum.length; i++) {
      const freq = i * freqResolution
      const power = spectrum[i] * spectrum[i]
      
      for (const band in bands) {
        if (freq >= bands[band].min && freq <= bands[band].max) {
          bands[band].power += power
        }
      }
    }
    
    return bands
  }
  
  calculateAttention(powerBands) {
    // 专注度计算:beta波与theta波的比值
    const ratio = powerBands.beta.power / (powerBands.theta.power + 1e-6)
    return Math.min(1, ratio / 10) // 归一化到0-1
  }
  
  calculateRelaxation(powerBands) {
    // 放松度计算:alpha波的相对功率
    const totalPower = Object.values(powerBands).reduce((sum, band) => sum + band.power, 0)
    return powerBands.alpha.power / (totalPower + 1e-6)
  }
  
  calculateStress(powerBands) {
    // 压力值计算:beta波与alpha波的比值
    const ratio = powerBands.beta.power / (powerBands.alpha.power + 1e-6)
    return Math.min(1, ratio / 5) // 归一化到0-1
  }
  
  calculateMeditation(powerBands) {
    // 冥想状态:theta波和alpha波的组合
    const combined = (powerBands.theta.power + powerBands.alpha.power) / 2
    const totalPower = Object.values(powerBands).reduce((sum, band) => sum + band.power, 0)
    return combined / (totalPower + 1e-6)
  }
}

3. 多设备管理

// 多设备管理器
class MultiDeviceManager {
  constructor() {
    this.devices = new Map()
    this.activeDevice = null
  }
  
  async addDevice(deviceId) {
    try {
      const sdk = new ShenWanSDK({
        deviceId: deviceId,
        platform: 'h5'
      })
      
      await sdk.connect()
      
      this.devices.set(deviceId, {
        sdk: sdk,
        status: 'connected',
        lastData: null,
        dataHistory: []
      })
      
      // 设置数据监听
      sdk.on('dataReceived', (data) => {
        this.handleDeviceData(deviceId, data)
      })
      
      console.log(`设备 ${deviceId} 已添加`)
      return true
    } catch (error) {
      console.error(`添加设备 ${deviceId} 失败:`, error)
      return false
    }
  }
  
  removeDevice(deviceId) {
    const device = this.devices.get(deviceId)
    if (device) {
      device.sdk.disconnect()
      this.devices.delete(deviceId)
      
      if (this.activeDevice === deviceId) {
        this.activeDevice = null
      }
      
      console.log(`设备 ${deviceId} 已移除`)
    }
  }
  
  setActiveDevice(deviceId) {
    if (this.devices.has(deviceId)) {
      this.activeDevice = deviceId
      console.log(`切换到设备 ${deviceId}`)
    }
  }
  
  handleDeviceData(deviceId, data) {
    const device = this.devices.get(deviceId)
    if (device) {
      device.lastData = data
      device.dataHistory.push({
        timestamp: Date.now(),
        data: data
      })
      
      // 保持历史数据长度
      if (device.dataHistory.length > 1000) {
        device.dataHistory = device.dataHistory.slice(-1000)
      }
      
      // 如果是活跃设备,触发事件
      if (deviceId === this.activeDevice) {
        this.onActiveDeviceData?.(data)
      }
    }
  }
  
  getDeviceList() {
    return Array.from(this.devices.keys())
  }
  
  getDeviceStatus(deviceId) {
    const device = this.devices.get(deviceId)
    return device ? device.status : 'not_found'
  }
  
  getActiveDeviceData() {
    if (!this.activeDevice) return null
    
    const device = this.devices.get(this.activeDevice)
    return device ? device.lastData : null
  }
  
  async syncAllDevices() {
    const promises = []
    
    for (const [deviceId, device] of this.devices) {
      promises.push(
        device.sdk.sync().catch(error => {
          console.error(`同步设备 ${deviceId} 失败:`, error)
        })
      )
    }
    
    await Promise.all(promises)
    console.log('所有设备同步完成')
  }
}