从零构建UniApp蓝牙体重秤小程序:硬件交互与数据解析实战

在智能健康设备普及的今天,蓝牙体重秤已成为家庭健康管理的重要工具。本文将带您深入探索如何基于UniApp框架,开发一个能够与AA系列蓝牙体重秤深度交互的微信小程序。不同于简单的蓝牙连接教程,我们将重点解析硬件协议对接、实时数据流处理以及商业级错误处理机制,提供可直接用于生产环境的代码方案。

1. 开发环境准备与蓝牙基础

1.1 UniApp蓝牙开发环境配置

开发蓝牙相关功能前,需要确保开发环境正确配置。在 manifest.json 中声明蓝牙权限:

{
  "mp-weixin": {
    "appid": "您的APPID",
    "requiredBackgroundModes": ["bluetooth"],
    "permission": {
      "scope.bluetooth": {
        "desc": "需要您的授权才能连接蓝牙设备"
      }
    }
  }
}

关键依赖检查清单:

  • UniApp CLI版本≥2.0
  • 微信开发者工具最新版
  • 真机调试用的Android/iOS设备
  • 硬件厂商提供的协议文档(FE60服务UUID)

提示:开发阶段建议关闭微信小程序的域名校验,在"详情->本地设置"中勾选"不校验合法域名"选项。

1.2 蓝牙协议栈核心概念

理解蓝牙协议栈是开发的基础,特别是GATT层的关键组件:

组件类型 作用描述 体重秤应用示例
Service 设备功能集合 FE60(厂商自定义服务)
Characteristic 服务下的具体数据点 体重数据特征值
Property 特征值属性(read/write/notify等) notify用于接收实时体重数据

蓝牙通信的基本流程可简化为:

  1. 扫描并过滤目标设备
  2. 建立GATT连接
  3. 发现服务与特征值
  4. 启用特征值通知
  5. 处理数据流

2. 设备发现与连接管理

2.1 智能设备扫描策略

针对AA系列体重秤的优化扫描方案:

// 设备过滤函数
function filterDevice(devices) {
  return devices.filter(device => {
    const name = device.name || device.localName || '';
    return name.startsWith('AA') && 
           !this.connectedDevices.has(device.deviceId);
  });
}

// 带缓冲区的设备发现
let discoveryTimer;
function startDiscovery() {
  this.devices = [];
  uni.startBluetoothDevicesDiscovery({
    allowDuplicatesKey: false,
    success: () => {
      discoveryTimer = setInterval(() => {
        uni.getBluetoothDevices({
          success: (res) => {
            this.devices = filterDevice(res.devices);
          }
        });
      }, 1500); // 1.5秒采集周期
    }
  });
}

性能优化要点

  • 设置 allowDuplicatesKey:false 避免重复设备
  • 使用 deviceId 作为设备唯一标识
  • 添加连接状态检查防止重复连接

2.2 稳定连接建立机制

建立连接时需要处理多种异常情况:

async function connectDevice(deviceId) {
  try {
    await this.checkBluetoothState();
    const connection = await new Promise((resolve, reject) => {
      uni.createBLEConnection({
        deviceId,
        timeout: 8000, // 8秒超时
        success: resolve,
        fail: reject
      });
    });
    
    this.registerDisconnectHandler(deviceId);
    return this.discoverServices(deviceId);
  } catch (error) {
    console.error('Connection failed:', error);
    this.handleConnectionError(error);
    throw error;
  }
}

常见连接问题处理方案:

  • 10012超时错误 :增加重试机制,最多3次
  • 10004适配器异常 :引导用户重新开启蓝牙
  • 10009设备已连接 :先断开旧连接再重试

3. 数据通信与协议解析

3.1 体重数据特征值交互

根据FE60服务协议,实现数据收发:

// 启用通知
function enableNotification(deviceId, serviceId, characteristicId) {
  uni.notifyBLECharacteristicValueChange({
    deviceId,
    serviceId,
    characteristicId,
    state: true,
    type: 'notification',
    success: () => {
      this.monitorDataStream();
    }
  });
}

// 数据流监控
function monitorDataStream() {
  uni.onBLECharacteristicValueChange(res => {
    const rawData = this.ab2hex(res.value);
    const weight = this.parseWeightData(rawData);
    this.updateDashboard(weight);
  });
}

3.2 二进制数据解析方案

AA体重秤的典型数据格式示例:

AA 01 62 00 07 D2 00 00 00 00 00 00 BB
  • AA: 帧头
  • 01: 数据长度
  • 62: 数据类型(体重)
  • 07D2: 实际数据(200.2kg)
  • BB: 帧尾

解析函数实现:

function parseWeightData(hexStr) {
  if (!hexStr || hexStr.length < 12) return null;
  
  const parts = hexStr.match(/.{2}/g);
  if (parts[0] !== 'aa' || parts[parts.length-1] !== 'bb') {
    throw new Error('Invalid data format');
  }

  const valueHex = parts[3] + parts[4]; // 大端序
  const value = parseInt(valueHex, 16) / 10;
  
  return {
    value,
    unit: 'kg',
    timestamp: Date.now()
  };
}

注意:不同厂商协议可能使用小端序(LSB),需根据文档确认字节顺序

4. 用户界面与体验优化

4.1 设备连接状态管理

实现可视化连接状态指示:

<template>
  <view class="device-status">
    <view v-if="connecting" class="connecting">
      <text>连接中...</text>
      <progress percent="80" show-info />
    </view>
    <view v-else-if="connected" class="connected">
      <text>已连接: {{ deviceName }}</text>
      <view class="signal" :style="signalStyle"></view>
    </view>
    <view v-else class="disconnected">
      <text>未连接设备</text>
    </view>
  </view>
</template>

<script>
export default {
  computed: {
    signalStyle() {
      return {
        width: `${this.rssi + 100}%`,
        backgroundColor: this.getSignalColor(this.rssi)
      };
    }
  }
}
</script>

4.2 实时数据可视化

使用ECharts实现体重趋势图:

function initChart() {
  this.chart = echarts.init(this.$refs.chart);
  this.chart.setOption({
    xAxis: {
      type: 'time'
    },
    yAxis: {
      name: '体重(kg)'
    },
    series: [{
      type: 'line',
      smooth: true,
      data: this.weightHistory
    }]
  });
}

function updateChart(newData) {
  this.weightHistory.push([
    newData.timestamp,
    newData.value
  ]);
  
  // 保持最近30条记录
  if (this.weightHistory.length > 30) {
    this.weightHistory.shift();
  }
  
  this.chart.setOption({
    series: [{
      data: this.weightHistory
    }]
  });
}

5. 生产环境进阶技巧

5.1 蓝牙通信稳定性优化

提升连接稳定性的关键措施:

  1. 心跳检测机制

    setInterval(() => {
      if (this.lastDataTime && Date.now() - this.lastDataTime > 15000) {
        this.reconnect();
      }
    }, 10000);
    
  2. 数据校验增强

    function validateData(data) {
      const crc = calculateCRC(data.slice(2, -2));
      return crc === data[data.length-2];
    }
    
  3. 自动重连策略

    async function reconnect() {
      if (this.reconnectAttempts >= 3) return;
      
      this.reconnectAttempts++;
      await this.disconnect();
      await new Promise(resolve => setTimeout(resolve, 1000));
      return this.connectDevice(this.lastDeviceId);
    }
    

5.2 多设备管理方案

支持同时管理多个体重秤设备:

class DeviceManager {
  constructor() {
    this.devices = new Map();
  }

  addDevice(device) {
    this.devices.set(device.id, {
      ...device,
      connection: null,
      data: []
    });
  }

  getDevice(id) {
    return this.devices.get(id);
  }

  removeDevice(id) {
    const device = this.devices.get(id);
    if (device && device.connection) {
      device.connection.close();
    }
    this.devices.delete(id);
  }
}

在实际项目中,发现AA系列体重秤在iOS设备上需要额外添加 writeType: 'writeNoResponse' 参数才能稳定通信,这是经过多次真机测试后得出的经验。建议开发者针对不同平台进行差异化配置,可以使用 uni.getSystemInfoSync().platform 获取当前运行平台。

更多推荐