第34集:uni-app 蓝牙功能

核心知识点

1. 蓝牙 API

uni-app 提供了完整的蓝牙 API,用于扫描、连接和控制蓝牙设备。主要包括以下几类:

  • 蓝牙适配器管理uni.openBluetoothAdapter()uni.closeBluetoothAdapter()uni.getBluetoothAdapterState()
  • 设备搜索uni.startBluetoothDevicesDiscovery()uni.stopBluetoothDevicesDiscovery()uni.getBluetoothDevices()uni.onBluetoothDeviceFound()
  • 设备连接uni.createBLEConnection()uni.closeBLEConnection()uni.getConnectedBluetoothDevices()
  • 服务与特征值uni.getBLEDeviceServices()uni.getBLEDeviceCharacteristics()
  • 数据传输uni.readBLECharacteristicValue()uni.writeBLECharacteristicValue()uni.notifyBLECharacteristicValueChange()uni.onBLECharacteristicValueChange()

2. 蓝牙设备连接流程

使用 uni-app 连接蓝牙设备的基本流程如下:

  1. 初始化蓝牙适配器:调用 uni.openBluetoothAdapter() 打开蓝牙适配器
  2. 开始搜索设备:调用 uni.startBluetoothDevicesDiscovery() 开始搜索附近的蓝牙设备
  3. 监听设备发现:通过 uni.onBluetoothDeviceFound() 监听发现的蓝牙设备
  4. 连接设备:调用 uni.createBLEConnection() 连接目标蓝牙设备
  5. 获取服务:调用 uni.getBLEDeviceServices() 获取设备的服务
  6. 获取特征值:调用 uni.getBLEDeviceCharacteristics() 获取服务的特征值
  7. 读写数据:通过 uni.readBLECharacteristicValue()uni.writeBLECharacteristicValue() 读写数据
  8. 监听数据变化:通过 uni.notifyBLECharacteristicValueChange() 启用通知,然后通过 uni.onBLECharacteristicValueChange() 监听数据变化
  9. 断开连接:调用 uni.closeBLEConnection() 断开连接

3. 蓝牙数据传输

蓝牙低功耗(BLE)设备的数据传输需要注意以下几点:

  • 数据格式:蓝牙传输的数据是 ArrayBuffer 格式,需要进行转换
  • 数据长度:单次写入的数据长度有限制,通常为 20 字节
  • 数据分包:对于大于限制长度的数据,需要进行分包传输
  • 数据编码:根据设备的要求,可能需要进行特定的编码和解码

实用案例分析

案例1:实现蓝牙设备控制

功能需求

实现一个蓝牙设备控制应用,包括以下功能:

  1. 扫描并显示附近的蓝牙设备
  2. 连接指定的蓝牙设备
  3. 读写设备的特征值
  4. 控制设备的开关状态

代码实现

<template>
  <view class="container">
    <view class="header">
      <text class="title">蓝牙设备控制器</text>
      <button @click="initBluetooth" type="primary" size="mini">初始化蓝牙</button>
    </view>
    
    <view class="status-bar">
      <text>蓝牙状态: {{ bluetoothState }}</text>
    </view>
    
    <view class="controls">
      <button @click="startScan" type="primary" :disabled="isScanning">开始扫描</button>
      <button @click="stopScan" type="default" :disabled="!isScanning">停止扫描</button>
    </view>
    
    <view class="device-list">
      <text class="section-title">附近的设备</text>
      <view v-for="(device, index) in deviceList" :key="index" class="device-item">
        <view class="device-info">
          <text class="device-name">{{ device.name || '未知设备' }}</text>
          <text class="device-id">{{ device.deviceId }}</text>
          <text class="device-rssi">信号强度: {{ device.RSSI }} dBm</text>
        </view>
        <button @click="connectDevice(device.deviceId)" type="primary" size="mini">连接</button>
      </view>
    </view>
    
    <view v-if="connectedDevice" class="connected-device">
      <text class="section-title">已连接设备</text>
      <text class="device-name">{{ connectedDevice.name || '未知设备' }}</text>
      <text class="device-id">{{ connectedDevice.deviceId }}</text>
      
      <view class="device-controls">
        <text class="control-title">设备控制</text>
        <view class="switch-control">
          <text>设备开关</text>
          <switch :checked="deviceStatus" @change="toggleDevice"></switch>
        </view>
        <button @click="disconnectDevice" type="warn">断开连接</button>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      bluetoothState: '未初始化',
      isScanning: false,
      deviceList: [],
      connectedDevice: null,
      deviceStatus: false,
      serviceId: '',
      characteristicId: ''
    };
  },
  onLoad() {
    // 监听蓝牙适配器状态变化
    uni.onBluetoothAdapterStateChange((res) => {
      this.updateBluetoothState(res);
    });
    
    // 监听蓝牙设备发现
    uni.onBluetoothDeviceFound((res) => {
      this.handleDeviceFound(res);
    });
    
    // 监听蓝牙特征值变化
    uni.onBLECharacteristicValueChange((res) => {
      this.handleCharacteristicValueChange(res);
    });
  },
  methods: {
    // 初始化蓝牙
    initBluetooth() {
      uni.openBluetoothAdapter({
        success: (res) => {
          console.log('蓝牙适配器初始化成功:', res);
          this.bluetoothState = '已初始化';
          uni.showToast({ title: '蓝牙初始化成功', icon: 'success' });
        },
        fail: (err) => {
          console.error('蓝牙适配器初始化失败:', err);
          this.bluetoothState = '初始化失败';
          uni.showToast({ title: '蓝牙初始化失败', icon: 'none' });
        }
      });
    },
    
    // 更新蓝牙状态
    updateBluetoothState(res) {
      if (res.available) {
        this.bluetoothState = res.discovering ? '搜索中' : '就绪';
      } else {
        this.bluetoothState = '不可用';
      }
    },
    
    // 开始扫描设备
    startScan() {
      this.deviceList = [];
      this.isScanning = true;
      
      uni.startBluetoothDevicesDiscovery({
        services: [], // 空数组表示搜索所有服务
        success: (res) => {
          console.log('开始搜索设备:', res);
          uni.showToast({ title: '开始搜索设备', icon: 'success' });
        },
        fail: (err) => {
          console.error('开始搜索设备失败:', err);
          this.isScanning = false;
          uni.showToast({ title: '搜索失败', icon: 'none' });
        }
      });
    },
    
    // 停止扫描设备
    stopScan() {
      uni.stopBluetoothDevicesDiscovery({
        success: (res) => {
          console.log('停止搜索设备:', res);
          this.isScanning = false;
          uni.showToast({ title: '已停止搜索', icon: 'success' });
        },
        fail: (err) => {
          console.error('停止搜索设备失败:', err);
          uni.showToast({ title: '停止搜索失败', icon: 'none' });
        }
      });
    },
    
    // 处理发现的设备
    handleDeviceFound(res) {
      const devices = res.devices;
      devices.forEach(device => {
        // 过滤掉重复设备和无名称的设备
        if (device.name && !this.deviceList.some(d => d.deviceId === device.deviceId)) {
          this.deviceList.push(device);
        }
      });
    },
    
    // 连接设备
    connectDevice(deviceId) {
      uni.createBLEConnection({
        deviceId: deviceId,
        success: (res) => {
          console.log('连接设备成功:', res);
          
          // 获取设备信息
          const device = this.deviceList.find(d => d.deviceId === deviceId);
          this.connectedDevice = device;
          
          // 停止搜索
          this.stopScan();
          
          // 获取设备服务
          this.getDeviceServices(deviceId);
          
          uni.showToast({ title: '连接成功', icon: 'success' });
        },
        fail: (err) => {
          console.error('连接设备失败:', err);
          uni.showToast({ title: '连接失败', icon: 'none' });
        }
      });
    },
    
    // 获取设备服务
    getDeviceServices(deviceId) {
      uni.getBLEDeviceServices({
        deviceId: deviceId,
        success: (res) => {
          console.log('获取设备服务成功:', res.services);
          
          // 假设我们使用第一个服务
          if (res.services.length > 0) {
            this.serviceId = res.services[0].uuid;
            this.getDeviceCharacteristics(deviceId, this.serviceId);
          }
        },
        fail: (err) => {
          console.error('获取设备服务失败:', err);
        }
      });
    },
    
    // 获取设备特征值
    getDeviceCharacteristics(deviceId, serviceId) {
      uni.getBLEDeviceCharacteristics({
        deviceId: deviceId,
        serviceId: serviceId,
        success: (res) => {
          console.log('获取设备特征值成功:', res.characteristics);
          
          // 找到可写的特征值
          const writeChar = res.characteristics.find(c => c.properties.write);
          if (writeChar) {
            this.characteristicId = writeChar.uuid;
            
            // 启用通知
            uni.notifyBLECharacteristicValueChange({
              deviceId: deviceId,
              serviceId: serviceId,
              characteristicId: writeChar.uuid,
              state: true,
              success: (res) => {
                console.log('启用通知成功:', res);
              },
              fail: (err) => {
                console.error('启用通知失败:', err);
              }
            });
          }
        },
        fail: (err) => {
          console.error('获取设备特征值失败:', err);
        }
      });
    },
    
    // 控制设备开关
    toggleDevice(e) {
      const newStatus = e.detail.value;
      this.deviceStatus = newStatus;
      
      if (!this.connectedDevice || !this.serviceId || !this.characteristicId) {
        uni.showToast({ title: '设备未就绪', icon: 'none' });
        return;
      }
      
      // 准备发送的数据
      const data = new ArrayBuffer(1);
      const dataView = new DataView(data);
      dataView.setUint8(0, newStatus ? 1 : 0);
      
      // 发送数据
      uni.writeBLECharacteristicValue({
        deviceId: this.connectedDevice.deviceId,
        serviceId: this.serviceId,
        characteristicId: this.characteristicId,
        value: data,
        success: (res) => {
          console.log('发送数据成功:', res);
          uni.showToast({ title: `设备已${newStatus ? '开启' : '关闭'}`, icon: 'success' });
        },
        fail: (err) => {
          console.error('发送数据失败:', err);
          uni.showToast({ title: '控制失败', icon: 'none' });
        }
      });
    },
    
    // 处理特征值变化
    handleCharacteristicValueChange(res) {
      console.log('特征值变化:', res);
      
      // 解析数据
      const value = res.value;
      const dataView = new DataView(value);
      const status = dataView.getUint8(0) === 1;
      
      // 更新设备状态
      this.deviceStatus = status;
    },
    
    // 断开连接
    disconnectDevice() {
      if (!this.connectedDevice) {
        return;
      }
      
      uni.closeBLEConnection({
        deviceId: this.connectedDevice.deviceId,
        success: (res) => {
          console.log('断开连接成功:', res);
          this.connectedDevice = null;
          this.deviceStatus = false;
          this.serviceId = '';
          this.characteristicId = '';
          uni.showToast({ title: '已断开连接', icon: 'success' });
        },
        fail: (err) => {
          console.error('断开连接失败:', err);
          uni.showToast({ title: '断开连接失败', icon: 'none' });
        }
      });
    }
  }
};
</script>

<style scoped>
.container {
  padding: 20rpx;
}

.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20rpx;
}

.title {
  font-size: 32rpx;
  font-weight: bold;
}

.status-bar {
  padding: 15rpx;
  background-color: #f5f5f5;
  border-radius: 8rpx;
  margin-bottom: 20rpx;
  font-size: 28rpx;
}

.controls {
  display: flex;
  justify-content: space-between;
  margin-bottom: 30rpx;
}

.controls button {
  flex: 1;
  margin: 0 10rpx;
}

.device-list {
  margin-bottom: 30rpx;
}

.section-title {
  font-size: 28rpx;
  font-weight: bold;
  margin-bottom: 15rpx;
  display: block;
}

.device-item {
  padding: 20rpx;
  border-bottom: 1rpx solid #eaeaea;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.device-info {
  flex: 1;
}

.device-name {
  font-size: 30rpx;
  margin-bottom: 5rpx;
  display: block;
}

.device-id {
  font-size: 24rpx;
  color: #666;
  margin-bottom: 5rpx;
  display: block;
}

.device-rssi {
  font-size: 22rpx;
  color: #999;
  display: block;
}

.connected-device {
  padding: 20rpx;
  background-color: #f5f5f5;
  border-radius: 8rpx;
}

.connected-device .device-name {
  font-size: 32rpx;
  font-weight: bold;
  margin-top: 10rpx;
}

.device-controls {
  margin-top: 20rpx;
  padding-top: 20rpx;
  border-top: 1rpx solid #eaeaea;
}

.control-title {
  font-size: 26rpx;
  font-weight: bold;
  margin-bottom: 15rpx;
  display: block;
}

.switch-control {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20rpx;
  font-size: 28rpx;
}
</style>

案例2:实现蓝牙心率监测

功能需求

实现一个蓝牙心率监测应用,包括以下功能:

  1. 扫描并连接心率监测设备
  2. 实时显示心率数据
  3. 记录心率历史数据
  4. 显示心率变化趋势

代码实现

<template>
  <view class="container">
    <view class="header">
      <text class="title">蓝牙心率监测</text>
      <button @click="initBluetooth" type="primary" size="mini">初始化蓝牙</button>
    </view>
    
    <view class="status-bar">
      <text>蓝牙状态: {{ bluetoothState }}</text>
    </view>
    
    <view class="controls">
      <button @click="startScan" type="primary" :disabled="isScanning">开始扫描</button>
      <button @click="stopScan" type="default" :disabled="!isScanning">停止扫描</button>
    </view>
    
    <view class="device-list">
      <text class="section-title">附近的设备</text>
      <view v-for="(device, index) in deviceList" :key="index" class="device-item">
        <view class="device-info">
          <text class="device-name">{{ device.name || '未知设备' }}</text>
          <text class="device-id">{{ device.deviceId }}</text>
          <text class="device-rssi">信号强度: {{ device.RSSI }} dBm</text>
        </view>
        <button @click="connectDevice(device.deviceId)" type="primary" size="mini">连接</button>
      </view>
    </view>
    
    <view v-if="connectedDevice" class="heart-rate-monitor">
      <text class="section-title">心率监测</text>
      <text class="device-name">{{ connectedDevice.name || '未知设备' }}</text>
      
      <view class="heart-rate-display">
        <text class="heart-rate-value">{{ currentHeartRate }}</text>
        <text class="heart-rate-unit">BPM</text>
      </view>
      
      <view class="heart-rate-chart">
        <canvas canvas-id="heartRateChart" style="width: 100%; height: 300rpx;"></canvas>
      </view>
      
      <view class="heart-rate-stats">
        <view class="stat-item">
          <text class="stat-label">平均心率</text>
          <text class="stat-value">{{ averageHeartRate }} BPM</text>
        </view>
        <view class="stat-item">
          <text class="stat-label">最高心率</text>
          <text class="stat-value">{{ maxHeartRate }} BPM</text>
        </view>
        <view class="stat-item">
          <text class="stat-label">最低心率</text>
          <text class="stat-value">{{ minHeartRate }} BPM</text>
        </view>
      </view>
      
      <button @click="disconnectDevice" type="warn">断开连接</button>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      bluetoothState: '未初始化',
      isScanning: false,
      deviceList: [],
      connectedDevice: null,
      currentHeartRate: 0,
      heartRateHistory: [],
      averageHeartRate: 0,
      maxHeartRate: 0,
      minHeartRate: 0,
      serviceId: '',
      characteristicId: '',
      chartContext: null
    };
  },
  onLoad() {
    // 初始化画布
    this.chartContext = uni.createCanvasContext('heartRateChart');
    
    // 监听蓝牙适配器状态变化
    uni.onBluetoothAdapterStateChange((res) => {
      this.updateBluetoothState(res);
    });
    
    // 监听蓝牙设备发现
    uni.onBluetoothDeviceFound((res) => {
      this.handleDeviceFound(res);
    });
    
    // 监听蓝牙特征值变化
    uni.onBLECharacteristicValueChange((res) => {
      this.handleHeartRateData(res);
    });
  },
  methods: {
    // 初始化蓝牙
    initBluetooth() {
      uni.openBluetoothAdapter({
        success: (res) => {
          console.log('蓝牙适配器初始化成功:', res);
          this.bluetoothState = '已初始化';
          uni.showToast({ title: '蓝牙初始化成功', icon: 'success' });
        },
        fail: (err) => {
          console.error('蓝牙适配器初始化失败:', err);
          this.bluetoothState = '初始化失败';
          uni.showToast({ title: '蓝牙初始化失败', icon: 'none' });
        }
      });
    },
    
    // 更新蓝牙状态
    updateBluetoothState(res) {
      if (res.available) {
        this.bluetoothState = res.discovering ? '搜索中' : '就绪';
      } else {
        this.bluetoothState = '不可用';
      }
    },
    
    // 开始扫描设备
    startScan() {
      this.deviceList = [];
      this.isScanning = true;
      
      uni.startBluetoothDevicesDiscovery({
        services: ['180D'], // 心率服务 UUID
        success: (res) => {
          console.log('开始搜索设备:', res);
          uni.showToast({ title: '开始搜索设备', icon: 'success' });
        },
        fail: (err) => {
          console.error('开始搜索设备失败:', err);
          this.isScanning = false;
          uni.showToast({ title: '搜索失败', icon: 'none' });
        }
      });
    },
    
    // 停止扫描设备
    stopScan() {
      uni.stopBluetoothDevicesDiscovery({
        success: (res) => {
          console.log('停止搜索设备:', res);
          this.isScanning = false;
          uni.showToast({ title: '已停止搜索', icon: 'success' });
        },
        fail: (err) => {
          console.error('停止搜索设备失败:', err);
          uni.showToast({ title: '停止搜索失败', icon: 'none' });
        }
      });
    },
    
    // 处理发现的设备
    handleDeviceFound(res) {
      const devices = res.devices;
      devices.forEach(device => {
        // 过滤掉重复设备和无名称的设备
        if (device.name && !this.deviceList.some(d => d.deviceId === device.deviceId)) {
          this.deviceList.push(device);
        }
      });
    },
    
    // 连接设备
    connectDevice(deviceId) {
      uni.createBLEConnection({
        deviceId: deviceId,
        success: (res) => {
          console.log('连接设备成功:', res);
          
          // 获取设备信息
          const device = this.deviceList.find(d => d.deviceId === deviceId);
          this.connectedDevice = device;
          
          // 停止搜索
          this.stopScan();
          
          // 获取设备服务
          this.getDeviceServices(deviceId);
          
          uni.showToast({ title: '连接成功', icon: 'success' });
        },
        fail: (err) => {
          console.error('连接设备失败:', err);
          uni.showToast({ title: '连接失败', icon: 'none' });
        }
      });
    },
    
    // 获取设备服务
    getDeviceServices(deviceId) {
      uni.getBLEDeviceServices({
        deviceId: deviceId,
        success: (res) => {
          console.log('获取设备服务成功:', res.services);
          
          // 查找心率服务
          const heartRateService = res.services.find(service => service.uuid === '180D');
          if (heartRateService) {
            this.serviceId = heartRateService.uuid;
            this.getDeviceCharacteristics(deviceId, this.serviceId);
          } else {
            uni.showToast({ title: '未找到心率服务', icon: 'none' });
          }
        },
        fail: (err) => {
          console.error('获取设备服务失败:', err);
        }
      });
    },
    
    // 获取设备特征值
    getDeviceCharacteristics(deviceId, serviceId) {
      uni.getBLEDeviceCharacteristics({
        deviceId: deviceId,
        serviceId: serviceId,
        success: (res) => {
          console.log('获取设备特征值成功:', res.characteristics);
          
          // 查找心率测量特征值
          const heartRateChar = res.characteristics.find(char => char.uuid === '2A37');
          if (heartRateChar) {
            this.characteristicId = heartRateChar.uuid;
            
            // 启用通知
            uni.notifyBLECharacteristicValueChange({
              deviceId: deviceId,
              serviceId: serviceId,
              characteristicId: heartRateChar.uuid,
              state: true,
              success: (res) => {
                console.log('启用通知成功:', res);
                uni.showToast({ title: '开始监测心率', icon: 'success' });
              },
              fail: (err) => {
                console.error('启用通知失败:', err);
                uni.showToast({ title: '启用通知失败', icon: 'none' });
              }
            });
          } else {
            uni.showToast({ title: '未找到心率测量特征值', icon: 'none' });
          }
        },
        fail: (err) => {
          console.error('获取设备特征值失败:', err);
        }
      });
    },
    
    // 处理心率数据
    handleHeartRateData(res) {
      console.log('心率数据:', res);
      
      // 解析心率数据
      const value = res.value;
      const dataView = new DataView(value);
      
      // 心率数据格式解析(根据蓝牙心率服务规范)
      let offset = 1; // 跳过标志字节
      const heartRate = dataView.getUint8(offset);
      
      // 更新当前心率
      this.currentHeartRate = heartRate;
      
      // 添加到历史数据
      this.heartRateHistory.push(heartRate);
      
      // 限制历史数据长度
      if (this.heartRateHistory.length > 60) {
        this.heartRateHistory.shift();
      }
      
      // 更新统计数据
      this.updateHeartRateStats();
      
      // 更新图表
      this.updateChart();
    },
    
    // 更新心率统计数据
    updateHeartRateStats() {
      if (this.heartRateHistory.length === 0) {
        return;
      }
      
      // 计算平均值
      const sum = this.heartRateHistory.reduce((a, b) => a + b, 0);
      this.averageHeartRate = Math.round(sum / this.heartRateHistory.length);
      
      // 计算最大值和最小值
      this.maxHeartRate = Math.max(...this.heartRateHistory);
      this.minHeartRate = Math.min(...this.heartRateHistory);
    },
    
    // 更新图表
    updateChart() {
      if (!this.chartContext || this.heartRateHistory.length === 0) {
        return;
      }
      
      const canvasWidth = 375; // 假设画布宽度
      const canvasHeight = 300;
      const padding = 20;
      
      // 清除画布
      this.chartContext.clearRect(0, 0, canvasWidth, canvasHeight);
      
      // 绘制坐标轴
      this.chartContext.beginPath();
      this.chartContext.moveTo(padding, padding);
      this.chartContext.lineTo(padding, canvasHeight - padding);
      this.chartContext.lineTo(canvasWidth - padding, canvasHeight - padding);
      this.chartContext.strokeStyle = '#999';
      this.chartContext.stroke();
      
      // 计算数据范围
      const maxValue = Math.max(...this.heartRateHistory, 100);
      const minValue = Math.min(...this.heartRateHistory, 60);
      const valueRange = maxValue - minValue;
      
      // 绘制数据线
      this.chartContext.beginPath();
      this.heartRateHistory.forEach((value, index) => {
        const x = padding + (index / (this.heartRateHistory.length - 1)) * (canvasWidth - 2 * padding);
        const y = canvasHeight - padding - ((value - minValue) / valueRange) * (canvasHeight - 2 * padding);
        
        if (index === 0) {
          this.chartContext.moveTo(x, y);
        } else {
          this.chartContext.lineTo(x, y);
        }
      });
      this.chartContext.strokeStyle = '#007aff';
      this.chartContext.lineWidth = 2;
      this.chartContext.stroke();
      
      // 绘制数据点
      this.heartRateHistory.forEach((value, index) => {
        const x = padding + (index / (this.heartRateHistory.length - 1)) * (canvasWidth - 2 * padding);
        const y = canvasHeight - padding - ((value - minValue) / valueRange) * (canvasHeight - 2 * padding);
        
        this.chartContext.beginPath();
        this.chartContext.arc(x, y, 3, 0, 2 * Math.PI);
        this.chartContext.fillStyle = '#007aff';
        this.chartContext.fill();
      });
      
      // 绘制图表
      this.chartContext.draw();
    },
    
    // 断开连接
    disconnectDevice() {
      if (!this.connectedDevice) {
        return;
      }
      
      uni.closeBLEConnection({
        deviceId: this.connectedDevice.deviceId,
        success: (res) => {
          console.log('断开连接成功:', res);
          this.connectedDevice = null;
          this.currentHeartRate = 0;
          this.heartRateHistory = [];
          this.averageHeartRate = 0;
          this.maxHeartRate = 0;
          this.minHeartRate = 0;
          this.serviceId = '';
          this.characteristicId = '';
          uni.showToast({ title: '已断开连接', icon: 'success' });
        },
        fail: (err) => {
          console.error('断开连接失败:', err);
          uni.showToast({ title: '断开连接失败', icon: 'none' });
        }
      });
    }
  }
};
</script>

<style scoped>
.container {
  padding: 20rpx;
}

.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20rpx;
}

.title {
  font-size: 32rpx;
  font-weight: bold;
}

.status-bar {
  padding: 15rpx;
  background-color: #f5f5f5;
  border-radius: 8rpx;
  margin-bottom: 20rpx;
  font-size: 28rpx;
}

.controls {
  display: flex;
  justify-content: space-between;
  margin-bottom: 30rpx;
}

.controls button {
  flex: 1;
  margin: 0 10rpx;
}

.device-list {
  margin-bottom: 30rpx;
}

.section-title {
  font-size: 28rpx;
  font-weight: bold;
  margin-bottom: 15rpx;
  display: block;
}

.device-item {
  padding: 20rpx;
  border-bottom: 1rpx solid #eaeaea;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.device-info {
  flex: 1;
}

.device-name {
  font-size: 30rpx;
  margin-bottom: 5rpx;
  display: block;
}

.device-id {
  font-size: 24rpx;
  color: #666;
  margin-bottom: 5rpx;
  display: block;
}

.heart-rate-monitor {
  padding: 20rpx;
  background-color: #f5f5f5;
  border-radius: 8rpx;
}

.heart-rate-display {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin: 30rpx 0;
}

.heart-rate-value {
  font-size: 72rpx;
  font-weight: bold;
  color: #ff3b30;
}

.heart-rate-unit {
  font-size: 32rpx;
  color: #666;
  margin-top: 10rpx;
}

.heart-rate-chart {
  margin: 20rpx 0;
  background-color: #fff;
  border-radius: 8rpx;
  padding: 10rpx;
}

.heart-rate-stats {
  display: flex;
  justify-content: space-around;
  margin: 20rpx 0;
}

.stat-item {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.stat-label {
  font-size: 24rpx;
  color: #666;
  margin-bottom: 5rpx;
}

.stat-value {
  font-size: 28rpx;
  font-weight: bold;
  color: #007aff;
}
</style>

学习目标

通过本集的学习,你应该能够:

  1. 掌握 uni-app 蓝牙 API 的使用方法
  2. 理解蓝牙设备连接的基本流程
  3. 实现蓝牙设备的搜索、连接和控制
  4. 实现蓝牙数据的读写和监听
  5. 开发基于蓝牙功能的实际应用

小结

本集详细介绍了 uni-app 中的蓝牙功能,包括蓝牙 API、设备连接流程、数据传输等核心知识点,并通过两个实际案例展示了如何实现蓝牙设备控制和心率监测功能。

蓝牙功能是移动应用开发中的重要组成部分,特别是在物联网(IoT)应用中,掌握这些技能可以帮助你开发出更加丰富和实用的应用。在实际开发中,你还需要注意设备兼容性、连接稳定性等问题,以确保应用的可靠性和用户体验。

通过本集的学习,你已经具备了在 uni-app 中使用蓝牙功能的基本能力,可以开始开发需要蓝牙连接的应用了。

« 上一篇 uni-app 文件操作 下一篇 » uni-app NFC 功能