UniApp微信小程序-实现蓝牙功能

发布于:2025-09-12 ⋅ 阅读:(18) ⋅ 点赞:(0)

前言:UniApp提供了跨平台的蓝牙API,官方文档。最近有个需求,需要搜索并连接到对应的蓝牙设备,然后连接到对应的 wifi 网络,有同样需求的伙伴可以参考下。
本次功能主要使用到了以下几种蓝牙API:
在这里插入图片描述
本次功能涉及到两个页面:【本次ui页面的代码没有放出来,大家可以根据自己的实际需求进行调整】
在这里插入图片描述

一、第一个页面内涉及到的API:

页面初始值:

data() {
	return {
		bluetoothDeviceList: [],
		isShowLoading: false,
	};
},
onLoad() {
	this.searchBluetoothDevice()
},
1. 初始化蓝牙设备。uni.openBluetoothAdapter()
// 初始化蓝牙设备
searchBluetoothDevice(){
	let that = this
	uni.openBluetoothAdapter({
	  success(res) {
	    console.log('openBluetoothAdapter success', res)
			that.startBluetoothDevicesDiscovery()
	  },
		fail(err) {
			console.log('openBluetoothAdapter err=', err);
			if (err.errCode === 10001) {
				uni.$u.toast('请打开蓝牙')
			}
		}
	})
},
2. 开始搜索附近的蓝牙设备。uni.startBluetoothDevicesDiscovery()

(此操作比较耗费系统资源,请在搜索并连接到设备后调用 uni.stopBluetoothDevicesDiscovery 方法停止搜索。)

// 搜索蓝牙设备
startBluetoothDevicesDiscovery() {
	let that = this;
	if (this.isShowLoading) {
		this.stopBluetoothDevicesDiscovery()
		return
	}
	this.isShowLoading = true
	uni.startBluetoothDevicesDiscovery({
		allowDuplicatesKey: true,
		// services: ['0000abcd-0000-1000-8000-00805f9bffff'], //传入这个参数,只搜索主服务为该UUID的设备
		success(res) {
			console.log('startBluetoothDevicesDiscovery success', res)
			that.onBluetoothDeviceFound()
			setTimeout(() => {
				console.log("----BluetoothDevicesDiscovery finish---- ");
				if (that.isShowLoading){
					that.stopBluetoothDevicesDiscovery()
				}
			}, 10000);
		},
		fail(err) {
			console.log('startBluetoothDevicesDiscovery err=', err);
		}
	})
},
3. 监听已经搜索到的蓝牙设备。uni.onBluetoothDeviceFound()

(并且展示到页面上)

// 监听搜索到的蓝牙设备
onBluetoothDeviceFound() {
	uni.onBluetoothDeviceFound((res) => {
		res.devices.forEach(device => {
			if (!device.name && !device.localName) {
				return
			}
			const idx = this.bluetoothDeviceList.findIndex(d => d.deviceId === device.deviceId)
			if (idx === -1) {
				this.bluetoothDeviceList.push(device)
			} else {
				this.bluetoothDeviceList[idx] = device
			}
		})
	})
},
4. 停止搜索蓝牙设备。uni.stopBluetoothDevicesDiscovery()

(搜索到之后或者搜索失败,都需要调用停止搜索蓝牙设备api)

// 停止蓝牙设备的搜索
stopBluetoothDevicesDiscovery() {
	this.isShowLoading = false
	uni.stopBluetoothDevicesDiscovery()
},
5. 关闭蓝牙模块。uni.closeBluetoothAdapter()

( 页面关闭时,调用该api )

onUnload() {
	uni.closeBluetoothAdapter()
},
6. 连接低功耗蓝牙设备。uni.createBLEConnection()
// 点击列表里需要的蓝牙进行连接,蓝牙列表里可以拿到deviceId
createBLEConnection(deviceId){
	uni.showLoading({
		mask: true
	})
	let that = this
	uni.createBLEConnection({
	  // 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
	  deviceId,
	  success(res) {
			uni.hideLoading()
			uni.$u.toast('连接成功')
			setTimeout(()=>{
				uni.$u.route(`/pages3/bluetooth/form?deviceId=${deviceId}`)
			}, 1000)
	  },
		fail(err) {
			uni.hideLoading()
			console.log('createBLEConnection err:', err)
		}
	})
},

二、第二个页面内涉及到的API:

页面初始值:

import { hexToUtf8Text, stringToArrayBuffer } from '@/util/hexUtils.js'
data() {
	return {
		deviceId: null,
		globalServiceuuId: '0000abcd-0000-1000-8000-00805f9b34fb', // 自己设备提供的serviceId
		globalWriteId:  '0000abce-0000-1000-8000-00805f9b34fb', // 自己设备提供的写入需要的characteristicId
		globalNotifyId: '0000abd0-0000-1000-8000-00805f9b34fb', // 自己设备提供的notify需要的characteristicId
		ismy_service: false,
		characteristicId: '',
		serviceId: '',
	};
},
onLoad({deviceId}) {
	this.deviceId = deviceId
	this.getBLEDeviceServices(deviceId)
},
1. 获取蓝牙设备所有服务(service)。uni.getBLEDeviceServices()
// 获取serviceId
getBLEDeviceServices(deviceId) {
	let that = this
	uni.getBLEDeviceServices({
		deviceId,
		success: (res) => {
			console.log("service size = ", res.services.length)
			for (let i = 0; i < res.services.length; i++) {
				console.log(res.services[i].uuid, 'res.services[i].uuid');
				if (this.globalServiceuuId.toUpperCase() == res.services[i].uuid){
					that.getBLEDeviceCharacteristics(deviceId, res.services[i].uuid)
				}
			}
		}
	})
},
2. 获取蓝牙设备某个服务中所有特征值(characteristic)。uni.getBLEDeviceCharacteristics()

注:因为 2 3 4 涉及到的三个api写到一个了方法里,所以这三个模块的代码粘贴的一样。

// 获取characteristicId
getBLEDeviceCharacteristics(deviceId, serviceId) {
	uni.getBLEDeviceCharacteristics({
		deviceId,
		serviceId,
		success: (res) => {
			var ismy_service = false
			console.log("compute ", serviceId, this.globalServiceuuId.toUpperCase())
			if (serviceId == this.globalServiceuuId.toUpperCase()) {
				ismy_service = true
				console.warn("this is my service ")
			}
			console.log('getBLEDeviceCharacteristics success', res.characteristics)
			for (let i = 0; i < res.characteristics.length; i++) {
				let item = res.characteristics[i]
				// 该特征值是否支持 write 操作
				if (item.properties.write) {
					if (ismy_service && (this.globalWriteId.toUpperCase() == item.uuid)){
						console.warn("find write uuid  ready to ", item.uuid)
						this.characteristicId = item.uuid
						this.serviceId = serviceId
					}
				}
				// 该特征值是否支持 notify 操作
				if (item.properties.notify || item.properties.indicate) {
					console.log("[Notify]", item.uuid)
					if (ismy_service && (this.globalNotifyId.toUpperCase() == item.uuid)){
						uni.notifyBLECharacteristicValueChange({  //开启通知
							deviceId,
							serviceId,
							characteristicId: item.uuid,
							state: true, 
							success(res) {
								console.log('notifyBLECharacteristicValueChange success', res)
							},
							fail(err) {
								console.warn("notifyBLECharacteristicValueChange err", err)
							}
						})
					}
				}
			}
		},
		fail(err) {
			console.error('getBLEDeviceCharacteristics err', err)
		}
	})
				
	// 后端返回的数据-进行接收
	// 操作之前先监听,保证第一时间获取数据
	uni.onBLECharacteristicValueChange((res) => {
		uni.showLoading({
			mask: true
		})
		const buffer = res.value;
		if (buffer.byteLength > 0) {
			uni.hideLoading()
			const uint8Arr = new Uint8Array(buffer); // 转成 Uint8Array 查看二进制值
			const hexStr = Array.from(uint8Arr, byte => byte.toString(16).padStart(2, '0')).join(''); // 转16进制字符串
			let resData = hexToUtf8Text(hexStr) // 16进制转换成文本
			console.log(resData, '===resData');
			if(resData==='A1'){
				uni.$u.toast('连接成功')
			}else if(resData==='A0'){
				uni.$u.toast('连接失败')
			}else if(resData==='B1'){
				uni.$u.toast('检测联网成功')
			}else if(resData==='B0'){
				uni.$u.toast('检测联网失败')
			}else if(resData==='B2'){
				uni.$u.toast('检测联网超时')
			}else{
				uni.showToast({
					title: resData, 
					icon:'none',
					duration: 3000
				})
			}
		}else{
			uni.hideLoading()
		}
	})
},
3. 启用 notify 功能,订阅特征值。uni.notifyBLECharacteristicValueChange()
// 获取characteristicId
getBLEDeviceCharacteristics(deviceId, serviceId) {
	uni.getBLEDeviceCharacteristics({
		deviceId,
		serviceId,
		success: (res) => {
			var ismy_service = false
			console.log("compute ", serviceId, this.globalServiceuuId.toUpperCase())
			if (serviceId == this.globalServiceuuId.toUpperCase()) {
				ismy_service = true
				console.warn("this is my service ")
			}
			console.log('getBLEDeviceCharacteristics success', res.characteristics)
			for (let i = 0; i < res.characteristics.length; i++) {
				let item = res.characteristics[i]
				// 该特征值是否支持 write 操作
				if (item.properties.write) {
					if (ismy_service && (this.globalWriteId.toUpperCase() == item.uuid)){
						console.warn("find write uuid  ready to ", item.uuid)
						this.characteristicId = item.uuid
						this.serviceId = serviceId
					}
				}
				// 该特征值是否支持 notify 操作
				if (item.properties.notify || item.properties.indicate) {
					console.log("[Notify]", item.uuid)
					if (ismy_service && (this.globalNotifyId.toUpperCase() == item.uuid)){
						uni.notifyBLECharacteristicValueChange({  //开启通知
							deviceId,
							serviceId,
							characteristicId: item.uuid,
							state: true, 
							success(res) {
								console.log('notifyBLECharacteristicValueChange success', res)
							},
							fail(err) {
								console.warn("notifyBLECharacteristicValueChange err", err)
							}
						})
					}
				}
			}
		},
		fail(err) {
			console.error('getBLEDeviceCharacteristics err', err)
		}
	})
				
	// 后端返回的数据-进行接收
	// 操作之前先监听,保证第一时间获取数据
	uni.onBLECharacteristicValueChange((res) => {
		uni.showLoading({
			mask: true
		})
		const buffer = res.value;
		if (buffer.byteLength > 0) {
			uni.hideLoading()
			const uint8Arr = new Uint8Array(buffer); // 转成 Uint8Array 查看二进制值
			const hexStr = Array.from(uint8Arr, byte => byte.toString(16).padStart(2, '0')).join(''); // 转16进制字符串
			let resData = hexToUtf8Text(hexStr) // 16进制转换成文本
			console.log(resData, '===resData');
			if(resData==='A1'){
				uni.$u.toast('连接成功')
			}else if(resData==='A0'){
				uni.$u.toast('连接失败')
			}else if(resData==='B1'){
				uni.$u.toast('检测联网成功')
			}else if(resData==='B0'){
				uni.$u.toast('检测联网失败')
			}else if(resData==='B2'){
				uni.$u.toast('检测联网超时')
			}else{
				uni.showToast({
					title: resData, 
					icon:'none',
					duration: 3000
				})
			}
		}else{
			uni.hideLoading()
		}
	})
},
4. 监听低功耗蓝牙设备的特征值变化事件。uni.onBLECharacteristicValueChange()
// 获取characteristicId
getBLEDeviceCharacteristics(deviceId, serviceId) {
	uni.getBLEDeviceCharacteristics({
		deviceId,
		serviceId,
		success: (res) => {
			var ismy_service = false
			console.log("compute ", serviceId, this.globalServiceuuId.toUpperCase())
			if (serviceId == this.globalServiceuuId.toUpperCase()) {
				ismy_service = true
				console.warn("this is my service ")
			}
			console.log('getBLEDeviceCharacteristics success', res.characteristics)
			for (let i = 0; i < res.characteristics.length; i++) {
				let item = res.characteristics[i]
				// 该特征值是否支持 write 操作
				if (item.properties.write) {
					if (ismy_service && (this.globalWriteId.toUpperCase() == item.uuid)){
						console.warn("find write uuid  ready to ", item.uuid)
						this.characteristicId = item.uuid
						this.serviceId = serviceId
					}
				}
				// 该特征值是否支持 notify 操作
				if (item.properties.notify || item.properties.indicate) {
					console.log("[Notify]", item.uuid)
					if (ismy_service && (this.globalNotifyId.toUpperCase() == item.uuid)){
						uni.notifyBLECharacteristicValueChange({  //开启通知
							deviceId,
							serviceId,
							characteristicId: item.uuid,
							state: true, 
							success(res) {
								console.log('notifyBLECharacteristicValueChange success', res)
							},
							fail(err) {
								console.warn("notifyBLECharacteristicValueChange err", err)
							}
						})
					}
				}
			}
		},
		fail(err) {
			console.error('getBLEDeviceCharacteristics err', err)
		}
	})
				
	// 后端返回的数据-进行接收
	// 操作之前先监听,保证第一时间获取数据
	uni.onBLECharacteristicValueChange((res) => {
		uni.showLoading({
			mask: true
		})
		const buffer = res.value;
		if (buffer.byteLength > 0) {
			uni.hideLoading()
			const uint8Arr = new Uint8Array(buffer); // 转成 Uint8Array 查看二进制值
			const hexStr = Array.from(uint8Arr, byte => byte.toString(16).padStart(2, '0')).join(''); // 转16进制字符串
			let resData = hexToUtf8Text(hexStr) // 16进制转换成文本
			console.log(resData, '===resData'); // 这里是设备端返回的数据,根据情况进行提示
			if(resData==='A1'){
				uni.$u.toast('连接成功')
			}else if(resData==='A0'){
				uni.$u.toast('连接失败')
			}else if(resData==='B1'){
				uni.$u.toast('检测联网成功')
			}else if(resData==='B0'){
				uni.$u.toast('检测联网失败')
			}else if(resData==='B2'){
				uni.$u.toast('检测联网超时')
			}else{
				uni.showToast({
					title: resData, 
					icon:'none',
					duration: 3000
				})
			}
		}else{
			uni.hideLoading()
		}
	})
},
5. 向低功耗蓝牙设备特征值中写入二进制数据。uni.writeBLECharacteristicValue()

(这里写入数据成功之后,onBLECharacteristicValueChange这个api里面可以接收到设备端返回的数据,然后进行判断或提示)

// 一键连接
writeBLECharacteristicValue() {
	uni.showLoading({
		mask: true
	})
	let body = {
		...this.form,
		msgTag: "wifi_connect", 
	}
	const str = JSON.stringify(body)  // 这是我们设备端要求的参数,其他人可根据需求情况定
	var buffer = stringToArrayBuffer(str)
	uni.writeBLECharacteristicValue({
	  // 这里的 deviceId 需要在 getBluetoothDevices 或 onBluetoothDeviceFound 接口中获取
	  deviceId: this.deviceId,
	  // 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
	  serviceId: this.serviceId,
	  // 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
	  characteristicId: this.characteristicId,
	  // 这里的value是ArrayBuffer类型
	  value: buffer,
	  success(res) {
			// uni.hideLoading()
	    console.log('writeBLECharacteristicValue success', res)
	  },
		fail(err) {
			uni.hideLoading()
			console.log('writeBLECharacteristicValue err', err)
			uni.$u.toast('连接失败')
		}
	})
},
6. 断开与低功耗蓝牙的连接。uni.closeBLEConnection()
onUnload() {
	if (this.deviceId) {
		uni.closeBLEConnection({
			deviceId: this.deviceId
		})
	}
},

提示:这里是转换二进制,或者16进制转换成可读文本用到的两个方法,单独封装到了utils里面。

// 1. 先定义 utf8Decode 函数(内部函数,无需导出,但需在调用前声明)
function utf8Decode(byteArray) {
  let str = '';
  let i = 0;
  const len = byteArray.length;

  while (i < len) {
    if (byteArray[i] < 0x80) {
      // 1字节字符(0xxxxxxx)
      str += String.fromCharCode(byteArray[i]);
      i++;
    } else if (byteArray[i] >= 0xC0 && byteArray[i] < 0xE0) {
      // 2字节字符(110xxxxx 10xxxxxx)
      if (i + 1 >= len) break;
      const charCode = ((byteArray[i] & 0x1F) << 6) | (byteArray[i + 1] & 0x3F);
      str += String.fromCharCode(charCode);
      i += 2;
    } else if (byteArray[i] >= 0xE0 && byteArray[i] < 0xF0) {
      // 3字节字符(1110xxxx 10xxxxxx 10xxxxxx)
      if (i + 2 >= len) break;
      const charCode = ((byteArray[i] & 0x0F) << 12) | ((byteArray[i + 1] & 0x3F) << 6) | (byteArray[i + 2] & 0x3F);
      str += String.fromCharCode(charCode);
      i += 3;
    } else if (byteArray[i] >= 0xF0 && byteArray[i] < 0xF8) {
      // 4字节字符(11110xxx 10xxxxxx 10xxxxxx 10xxxxxx)
      if (i + 3 >= len) break;
      let charCode = ((byteArray[i] & 0x07) << 18) | ((byteArray[i + 1] & 0x3F) << 12) | ((byteArray[i + 2] & 0x3F) << 6) | (byteArray[i + 3] & 0x3F);
      if (charCode > 0xFFFF) {
        // 处理UTF-16代理对
        charCode -= 0x10000;
        str += String.fromCharCode((charCode >> 10) + 0xD800, (charCode & 0x3FF) + 0xDC00);
      } else {
        str += String.fromCharCode(charCode);
      }
      i += 4;
    } else {
      // 无效字节,跳过
      i++;
    }
  }
  return str;
}

// 2. 再定义 hexToUtf8Text 函数(调用 utf8Decode,此时函数已声明)
export function hexToUtf8Text(hexStr) {
  try {
    const cleanHex = hexStr.replace(/\s/g, '').toLowerCase();
    if (!/^[0-9a-f]+$/.test(cleanHex)) {
      throw new Error('16进制格式错误:仅允许0-9、a-f字符');
    }
    if (cleanHex.length % 2 !== 0) {
      throw new Error('16进制长度错误:需为偶数(每2位对应1个字节)');
    }

    // 16进制转字节数组
    const byteLength = cleanHex.length / 2;
    const byteArray = new Uint8Array(byteLength);
    for (let i = 0; i < byteLength; i++) {
      byteArray[i] = parseInt(cleanHex.substr(i * 2, 2), 16);
    }

    // 调用 utf8Decode(此时函数已存在,不会报“未找到”错误)
    return utf8Decode(byteArray);
  } catch (error) {
    console.error('16进制转文本失败:', error);
    return `转换失败:${error.message}`;
  }
}


// 3. 转成二进制
export function stringToArrayBuffer(str) {
	var bytes = new Array();
	var len, c;
	len = str.length;
	for (var i = 0; i < len; i++) {
		c = str.charCodeAt(i);
		if (c >= 0x010000 && c <= 0x10ffff) {
			bytes.push(((c >> 18) & 0x07) | 0xf0);
			bytes.push(((c >> 12) & 0x3f) | 0x80);
			bytes.push(((c >> 6) & 0x3f) | 0x80);
			bytes.push((c & 0x3f) | 0x80);
		} else if (c >= 0x000800 && c <= 0x00ffff) {
			bytes.push(((c >> 12) & 0x0f) | 0xe0);
			bytes.push(((c >> 6) & 0x3f) | 0x80);
			bytes.push((c & 0x3f) | 0x80);
		} else if (c >= 0x000080 && c <= 0x0007ff) {
			bytes.push(((c >> 6) & 0x1f) | 0xc0);
			bytes.push((c & 0x3f) | 0x80);
		} else {
			bytes.push(c & 0xff);
		}
	}
	var array = new Int8Array(bytes.length);
	for (var i in bytes) {
		array[i] = bytes[i];
	}
	return array.buffer;
}



快乐学习!


网站公告

今日签到

点亮在社区的每一天
去签到