在 Uniapp 中实现蓝牙打卡功能,主要利用 Uniapp 提供的蓝牙 API 与周边蓝牙设备进行通信,通过识别特定蓝牙设备(如打卡器)完成打卡操作。以下是详细实现步骤:

一、功能流程设计

  1. 初始化蓝牙适配器
  2. 检查并开启手机蓝牙
  3. 搜索附近蓝牙设备(过滤目标打卡设备)
  4. 连接目标蓝牙设备
  5. 与设备交互(发送 / 接收打卡指令)
  6. 完成打卡后断开连接
  7. 关闭蓝牙适配器

二、实现步骤(代码示例)

1. 页面结构(template)
<template>
  <view class="container">
    <view class="status">
      <text>蓝牙状态:{{ bluetoothState }}</text>
    </view>
    
    <button 
      @click="initBluetooth" 
      :disabled="isProcessing"
    >
      初始化蓝牙
    </button>
    
    <button 
      @click="startDiscovery" 
      :disabled="!isBluetoothReady || isProcessing"
    >
      搜索设备
    </button>
    
    <button 
      @click="stopDiscovery" 
      :disabled="!isDiscovering || isProcessing"
    >
      停止搜索
    </button>
    
    <view class="device-list">
      <view class="device-item" v-for="device in devices" :key="device.deviceId">
        <text>设备名:{{ device.name || '未知设备' }}</text>
        <text>设备ID:{{ device.deviceId }}</text>
        <button @click="connectDevice(device.deviceId)">连接打卡</button>
      </view>
    </view>
    
    <view class="result">
      <text>{{打卡结果:{{ checkinResult }}}</text>
    </view>
  </view>
</template>
2. 逻辑实现(script)
export default {
  data() {
    return {
      bluetoothState: '未初始化',
      isBluetoothReady: false, // 蓝牙是否初始化成功
      isDiscovering: false, // 是否正在搜索设备
      isProcessing: false, // 是否正在执行操作
      devices: [], // 搜索到的设备列表
      connectedDeviceId: '', // 已连接的设备ID
      checkinResult: '', // 打卡结果
      // 目标打卡设备的特征值(需替换为实际设备的UUID)
      targetServiceId: '0000FFF0-0000-1000-8000-00805F9B34FB',
      targetCharacteristicId: '0000FFF3-0000-1000-8000-00805F9B34FB'
    };
  },
  onUnload() {
    // 页面卸载时关闭蓝牙
    this.closeBluetooth();
  },
  methods: {
    // 1. 初始化蓝牙适配器
    initBluetooth() {
      this.isProcessing = true;
      this.bluetoothState = '初始化中...';
      
      uni.openBluetoothAdapter({
        success: (res) => {
          console.log('蓝牙初始化成功', res);
          this.bluetoothState = '已开启';
          this.isBluetoothReady = true;
          // 监听蓝牙状态变化
          uni.onBluetoothAdapterStateChange((state) => {
            this.bluetoothState = state.available ? '已开启' : '已关闭';
            this.isBluetoothReady = state.available;
          });
        },
        fail: (err) => {
          console.error('蓝牙初始化失败', err);
          this.bluetoothState = '初始化失败,请开启蓝牙';
          // 提示用户开启蓝牙
          uni.showModal({
            title: '提示',
            content: '请开启蓝牙后重试',
            showCancel: false
          });
        },
        complete: () => {
          this.isProcessing = false;
        }
      });
    },
    
    // 2. 开始搜索设备
    startDiscovery() {
      this.isProcessing = true;
      this.devices = [];
      this.isDiscovering = true;
      this.bluetoothState = '搜索中...';
      
      // 清空已发现的设备
      uni.startBluetoothDevicesDiscovery({
        // 过滤目标设备的服务UUID(提高搜索效率)
        services: [this.targetServiceId],
        allowDuplicatesKey: false, // 不重复上报同一设备
        success: (res) => {
          console.log('开始搜索设备', res);
          // 监听新设备发现
          uni.onBluetoothDeviceFound((devices) => {
            this.handleFoundDevices(devices.devices);
          });
        },
        fail: (err) => {
          console.error('搜索设备失败', err);
          this.bluetoothState = '搜索失败';
        },
        complete: () => {
          this.isProcessing = false;
        }
      });
    },
    
    // 处理搜索到的设备
    handleFoundDevices(devices) {
      devices.forEach(device => {
        // 过滤重复设备和无名称设备(可根据实际设备名过滤)
        if (device.name && !this.devices.some(d => d.deviceId === device.deviceId)) {
          this.devices.push(device);
        }
      });
    },
    
    // 3. 停止搜索设备
    stopDiscovery() {
      uni.stopBluetoothDevicesDiscovery({
        success: () => {
          console.log('停止搜索设备');
          this.isDiscovering = false;
          this.bluetoothState = '已停止搜索';
        }
      });
    },
    
    // 4. 连接目标设备
    connectDevice(deviceId) {
      if (!deviceId) return;
      this.isProcessing = true;
      this.checkinResult = '连接中...';
      
      // 连接低功耗蓝牙设备
      uni.createBLEConnection({
        deviceId,
        success: (res) => {
          console.log('设备连接成功', res);
          this.connectedDeviceId = deviceId;
          this.checkinResult = '连接成功,准备打卡...';
          // 连接成功后获取服务
          this.getBLEServices(deviceId);
        },
        fail: (err) => {
          console.error('设备连接失败', err);
          this.checkinResult = '连接失败,请重试';
          this.isProcessing = false;
        }
      });
    },
    
    // 获取设备服务
    getBLEServices(deviceId) {
      uni.getBLEDeviceServices({
        deviceId,
        success: (res) => {
          console.log('设备服务列表', res.services);
          // 查找目标服务
          const targetService = res.services.find(
            service => service.uuid === this.targetServiceId
          );
          if (targetService) {
            this.getBLECharacteristics(deviceId, targetService.uuid);
          } else {
            this.checkinResult = '未找到目标服务';
            this.isProcessing = false;
          }
        },
        fail: (err) => {
          console.error('获取服务失败', err);
          this.checkinResult = '获取服务失败';
          this.isProcessing = false;
        }
      });
    },
    
    // 获取特征值(用于读写数据)
    getBLECharacteristics(deviceId, serviceId) {
      uni.getBLEDeviceCharacteristics({
        deviceId,
        serviceId,
        success: (res) => {
          console.log('特征值列表', res.characteristics);
          // 查找目标特征值
          const targetChar = res.characteristics.find(
            char => char.uuid === this.targetCharacteristicId
          );
          if (targetChar) {
            // 启用通知(监听设备返回的数据)
            this.notifyBLECharacteristicValueChange(deviceId, serviceId, targetChar.uuid);
            // 发送打卡指令
            this.sendCheckinCommand(deviceId, serviceId, targetChar.uuid);
          } else {
            this.checkinResult = '未找到目标特征值';
            this.isProcessing = false;
          }
        },
        fail: (err) => {
          console.error('获取特征值失败', err);
          this.checkinResult = '获取特征值失败';
          this.isProcessing = false;
        }
      });
    },
    
    // 启用特征值变化通知
    notifyBLECharacteristicValueChange(deviceId, serviceId, characteristicId) {
      uni.notifyBLECharacteristicValueChange({
        deviceId,
        serviceId,
        characteristicId,
        state: true, // 启用通知
        success: () => {
          console.log('启用通知成功');
          // 监听特征值变化(接收设备返回的数据)
          uni.onBLECharacteristicValueChange((res) => {
            this.handleCheckinResponse(res.value);
          });
        }
      });
    },
    
    // 发送打卡指令(根据设备协议定义指令)
    sendCheckinCommand(deviceId, serviceId, characteristicId) {
      // 示例:发送"CHECKIN"指令(需转为ArrayBuffer)
      const command = 'CHECKIN';
      const buffer = new ArrayBuffer(command.length);
      const dataView = new DataView(buffer);
      for (let i = 0; i < command.length; i++) {
        dataView.setUint8(i, command.charCodeAt(i));
      }
      
      uni.writeBLECharacteristicValue({
        deviceId,
        serviceId,
        characteristicId,
        value: buffer,
        success: () => {
          console.log('打卡指令发送成功');
          this.checkinResult = '打卡指令已发送...';
        },
        fail: (err) => {
          console.error('发送指令失败', err);
          this.checkinResult = '打卡失败';
          this.isProcessing = false;
        }
      });
    },
    
    // 处理设备返回的打卡结果
    handleCheckinResponse(value) {
      // 将ArrayBuffer转为字符串
      const result = String.fromCharCode.apply(null, new Uint8Array(value));
      console.log('打卡结果', result);
      
      if (result.includes('SUCCESS')) {
        this.checkinResult = '打卡成功!';
        // 这里可以添加上传服务器逻辑
      } else {
        this.checkinResult = '打卡失败:' + result;
      }
      
      // 打卡完成后断开连接
      setTimeout(() => {
        this.disconnectDevice();
      }, 2000);
    },
    
    // 断开设备连接
    disconnectDevice() {
      if (!this.connectedDeviceId) return;
      
      uni.closeBLEConnection({
        deviceId: this.connectedDeviceId,
        success: () => {
          console.log('断开设备连接');
          this.connectedDeviceId = '';
          this.isProcessing = false;
        }
      });
    },
    
    // 关闭蓝牙适配器
    closeBluetooth() {
      uni.closeBluetoothAdapter({
        success: () => {
          console.log('关闭蓝牙适配器');
          this.isBluetoothReady = false;
          this.bluetoothState = '已关闭';
        }
      });
    }
  }
};
3. 样式(style)
.container {
  padding: 20rpx;
  display: flex;
  flex-direction: column;
  gap: 20rpx;
}

.status, .result {
  padding: 20rpx;
  background-color: #f5f5f5;
  border-radius: 8rpx;
}

button {
  background-color: #007aff;
  color: white;
  padding: 15rpx 0;
  border-radius: 8rpx;
}

.device-list {
  margin-top: 20rpx;
  display: flex;
  flex-direction: column;
  gap: 15rpx;
}

.device-item {
  padding: 20rpx;
  border: 1px solid #eee;
  border-radius: 8rpx;
  display: flex;
  flex-direction: column;
  gap: 10rpx;
}

.device-item button {
  background-color: #4cd964;
  width: 200rpx;
  align-self: flex-end;
}
1. 权限配置

需在 manifest.json 中添加蓝牙权限:

  • Android(app-plus -> android -> permissions):
"android.permission.BLUETOOTH",
"android.permission.BLUETOOTH_ADMIN",
"android.permission.ACCESS_FINE_LOCATION",
"android.permission.BLUETOOTH_SCAN",
"android.permission.BLUETOOTH_CONNECT"

iOS(app-plus -> ios -> plist):

<key>NSBluetoothAlwaysUsageDescription</key>
<string>需要蓝牙权限进行打卡</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>需要蓝牙权限进行打卡</string>
2. 设备 UUID 替换

代码中的 targetServiceId 和 targetCharacteristicId 需替换为实际打卡设备的 UUID,可通过以下方式获取:

  • 设备说明书
  • 蓝牙调试工具(如 nRF Connect)扫描设备获取服务和特征值 UUID
3. 数据交互协议
  • 发送的打卡指令(sendCheckinCommand 中的 command)需与设备端协议一致。
  • 设备返回的结果解析(handleCheckinResponse)需根据设备协议调整。
4. 兼容性处理
  • 部分安卓机型需要开启定位权限才能搜索蓝牙设备。
  • iOS 对蓝牙后台运行有严格限制,建议在前台完成打卡操作。
  • 不同设备的连接超时时间可能不同,需合理设置重试机制。

四、功能扩展

  1. 自动打卡:搜索到目标设备后自动连接并完成打卡。
  2. 距离判断:通过蓝牙信号强度(RSSI)判断是否在有效打卡范围内。
  3. 打卡记录:本地存储打卡记录,同步至服务器。

通过以上步骤,即可在 Uniapp 中实现蓝牙打卡功能,核心是利用蓝牙 API 完成设备搜索、连接和数据交互。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐