uniapp 蓝牙小程序-兼容安卓和iOS

uniapp 蓝牙小程序-兼容安卓和iOS_第1张图片

withTimeout方法可以在搜寻设备时等待指定的秒数,如果30秒内未搜索到则取消搜索 

/**
 * 超时控制函数
 * @param {Promise} promise 回调函数
 * @param {number} timeout 超时时间, 默认10s
 */
export function withTimeout(promise, timeout = 10000) {
	let timeoutEvent = null
	const logicPromise = new Promise((resolve, reject) => {
		promise.then((data) => {
			if (timeoutEvent) {
				// 清理超时
				clearTimeout(timeoutEvent)
				timeoutEvent = null
			}
			resolve(data)
		})
	})
	// 创建一个新的 Promise 对象,用于处理超时情况
	const timeoutPromise = new Promise((resolve, reject) => {
		timeoutEvent = setTimeout(() => {
			reject(`执行超时`);
		}, timeout);
	});

	// 将两个 Promise 对象(原始的 promise 和超时的 promise)合并为一个新的 Promise 对象
	return Promise.race([logicPromise, timeoutPromise]);
}

计算数据校验和:

校验字节等于命令字节与所有数据字节之和的反码。求和按带进位加 (ADDC)方式计算,每个进位都被加到本次结果的最低位(LSB)。

举例:如命令字节=0x01;

数据=0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF,0x00,0x01;则校验字节0x01+0xF0+0xF1+0xF2+0xF3+0xF4+0xF5+0xF6+0xF7+0xF8+0xF9+0xFA

+0xFB+0xFC+0xFD+0xFE+0xFF+0x00+0x01 = 0x0F79;

0x0F+0x79 = 0x88;

校验字节 = 0xFF – 0x88 = 0x77。

getAddc(str) {
		let itotal = 0,
			len = str.length,
			num = 0;
		var tempTotal = "";
		while (num < len) {
			let s = str.substring(num, num + 2);
			itotal += parseInt(s, 16);
			num = num + 2;
			if (itotal >= 256) {
				itotal = parseInt((itotal - itotal % 256) / 256) + itotal % 256
			}
		}
		itotal = 255 - itotal
		return itotal.toString(16).padStart(2, '0')
	}

vue页面代码  






js函数代码 

export class Fragrance {

	// 读UUID:0000FEC1-0000-1000-8000-XXXXX
	#appointNotifyCharacteristicId = '0000FEC1-0000-1000-8000-XXXXX'
	// 写UUID:0000FEC1-0000-1000-8000-XXXXX
	#appointWriteCharacteristicId = '0000FEC1-0000-1000-8000-XXXXX'
	// 指定服务ID
	#appointServiceId = '0000FEE7-0000-1000-8000-XXXXX'
	// 指定设备ID
	#appointDeviceId = '60E962AD-434B-F6EF-D82C-XXXXX'

	// 读UUID:0000fec1-0000-1000-8000-XXXXX
	#notifyCharacteristicId = '0000FEC1-0000-1000-8000-XXXXX'
	// 写UUID:0000fec1-0000-1000-8000-XXXXX
	#writeCharacteristicId = '0000FEC1-0000-1000-8000-XXXXX'
	// 
	#serviceId = '0000FEE7-0000-1000-8000-XXXXX'
	// 设备ID
	#deviceId = ''

	#deviceReturnData = ''


	#connected = false // 连接状态标志位

	isConnected() {
		return this.#connected
	}

	/**
	 * 关闭蓝牙连接, 关闭蓝牙模块
	 */
	async close() {
		try {
			this.#connected = false
			// 关闭蓝牙连接
			await uni.closeBLEConnection({
				deviceId: this.#deviceId
			})
			// 关闭蓝牙模块
			await uni.closeBluetoothAdapter()
		} catch (e) {
			// 这里错误 不处理
			console.error('关闭蓝牙错误', e)
		}
	}
	// 开始搜索蓝牙uni.startBluetoothDevicesDiscovery(),使用uni.onBluetoothDeviceFound 注册搜索到的设备,
	async ScanDevice() {
		var that = this

		return new Promise((resolve, reject) => {
			console.log("开始搜索")
			try {
				uni.startBluetoothDevicesDiscovery({
					interval: 0, // 上报设备的间隔。0 表示找到新设备立即上报,其他数值根据传入的间隔上报
					allowDuplicatesKey: true,
					// services: ['0000FEE7'], // 只搜索主服务 UUID 为 0000FEE7 的设备
					// 扫描模式,越高扫描越快,也越耗电。仅安卓微信客户端 7.0.12 及以上支持。
					// low:低,medium:中,high:高
					powerLevel: 'high',
					success(res) {
						uni.onBluetoothDeviceFound(function(devices) {
							var d = {}
							if (typeof devices == Array) {
								d = devices[0]
							} else {
								d = devices.devices[0]
							}
							// console.log('onBluetoothDeviceFound-', d.name)
							// 找到名称 KLOVEF-AirFragranceSystem 的设备是代表已搜索到
							if (d.name == 'KLOVEF-AirFragranceSystem') {
								// console.log('onBluetoothDeviceFound-', devices)
								that.#deviceId = d.deviceId
								resolve(d.deviceId)
								///console.log("this.#deviceId-aaa", that.#deviceId)
								// uni.stopBluetoothDevicesDiscovery({
								// 	success(res) {
								// 		console.log('关闭成功')
								// 	},
								// 	fail(res) {
								// 		console.log('关闭失败' + res.errMsg)

								// 	}
								// })
							}
						})
					},
					fail(res) {
						console.log("搜索失败", res.errMsg)
					}
				})
			} catch (e) {
				console.log("结束搜索-111")
				console.error('ScanDevice' + e)
			}
			console.log("结束搜索-222")
		})
	}
	HexToConfig(HexStr) {
		if (HexStr.length != 40) { //校验数据长度
			return null
		}
		if (!this.checkAddc(HexStr)) { //校验ADDC
			return null
		}
		var FragranceType = parseInt(HexStr.substring(2, 4), 16)
		var FragranceMode = parseInt(HexStr.substring(4, 6), 16) + 1
		//返回和写入时一致的数据,旧返回数据 (关闭状态=0x00, 打开状态=0x01)
		//目标返回数据 (未控制=0x00, 打开香氛=0x01, 关闭香氛=0x02)
		var FragranceWorkState = parseInt(HexStr.substring(6, 8), 16)
		if (FragranceWorkState != 1) {
			FragranceWorkState = 2
		}
		//返回和写入时一致的数据,旧返回数据   等离子工作状态(关闭状态=0x00, 打开状态=0x01)
		//目标返回数据  (未控制=0x00, 打开等离子=0x01, 关闭等离子=0x02)
		var IPCWorkState = parseInt(HexStr.substring(8, 10), 16)
		if (IPCWorkState != 1) {
			IPCWorkState = 2
		}
		var CurrentFragranceWorkChannel = parseInt(HexStr.substring(10, 12), 16) + 1
		var FirstFragranceType = parseInt(HexStr.substring(12, 14), 16)
		var SecondFragranceType = parseInt(HexStr.substring(14, 16), 16)
		var FirstFragranceResidue = parseInt(HexStr.substring(16, 18), 16)
		//console.log("FirstFragranceResidue",HexStr.substring(16, 18), 16)
		var SecondFragranceResidue = parseInt(HexStr.substring(18, 20), 16)
		var AirQualityLevel = parseInt(HexStr.substring(20, 22), 16)

		//返回和写入时一致的数据,旧返回数据   等离子工作状态((关闭状态=0x00, 打开状态=0x01)
		//目标返回数据  (未控制=0x00, 打开语音控制=0x01, 关闭语音控制=0x02)
		var VoiceControlState = parseInt(HexStr.substring(22, 24), 16)
		if (VoiceControlState != 1) {
			VoiceControlState = 2
		}
		//这里为了方便前端显示,转换成索引
		var LightFragranceModeStopTime = parseInt(HexStr.substring(24, 26), 16) / 2 - 1
		var LightFragranceModeWorkTime = parseInt(HexStr.substring(26, 28), 16) / 2 - 1
		var MiddleFragranceModeStopTime = parseInt(HexStr.substring(28, 30), 16) / 2 - 1
		var MiddleFragranceModeWorkTime = parseInt(HexStr.substring(30, 32), 16) / 2 - 1
		var StrongFragranceModeStopTime = parseInt(HexStr.substring(32, 34), 16) / 2 - 1
		var StrongFragranceModeWorkTime = parseInt(HexStr.substring(34, 36), 16) / 2 - 1
		return {
			//香氛机型(单香机型=0x01, 两香机型=0x02)
			FragranceType: FragranceType,
			//当前香氛模式(自动模式=0x00,淡香模式=0x01,中香模式=0x02,浓香模式=0x03);
			FragranceMode: FragranceMode,
			//香氛工作状态(关闭状态=0x00, 打开状态=0x01)
			FragranceWorkState: FragranceWorkState,
			//等离子工作状态(关闭状态=0x00, 打开状态=0x01)。
			IPCWorkState: IPCWorkState,
			//当前香氛工作通道(1号香氛=0x00, 2号香氛=0x01)
			CurrentFragranceWorkChannel: CurrentFragranceWorkChannel,
			//Byte6= 1号香氛类型(未配置=0x00,青茶香氛=0x01,鸢尾香氛=0x02,茉莉香氛=0x03,
			//森林香氛=0x04,蓝风铃香氛=0x05,水蜜桃香氛=0x06,海洋香氛=0x07,浪漫香氛=0x08,魅力香氛=0x09)
			FirstFragranceType: FirstFragranceType,
			//2号香氛类型(未配置=0x00,青茶香氛=0x01,鸢尾香氛=0x02,茉莉香氛=0x03,
			//森林香氛=0x04,蓝风铃香氛=0x05,水蜜桃香氛=0x06,海洋香氛=0x07,浪漫香氛=0x08,魅力香氛=0x09)
			SecondFragranceType: SecondFragranceType,
			//1号香氛余量(0~100)
			FirstFragranceResidue: FirstFragranceResidue,
			//2号香氛余量(0~100)
			SecondFragranceResidue: SecondFragranceResidue,
			//空气质量等级(未配置=0x00,优=0x01, 良=0x02, 中=0x03, 差=0x04)
			AirQualityLevel: AirQualityLevel,
			//语音控制使能状态(关闭状态=0x00, 打开状态=0x01)
			VoiceControlState: VoiceControlState,
			//淡香模式停止时间反馈(秒)
			LightFragranceModeStopTime: LightFragranceModeStopTime,
			//淡香模式工作时间反馈(秒)
			LightFragranceModeWorkTime: LightFragranceModeWorkTime,
			//中香模式停止时间反馈(秒)
			MiddleFragranceModeStopTime: MiddleFragranceModeStopTime,
			//中香模式工作时间反馈(秒)
			MiddleFragranceModeWorkTime: MiddleFragranceModeWorkTime,
			//浓香模式停止时间反馈(秒)
			StrongFragranceModeStopTime: StrongFragranceModeStopTime,
			//浓香模式工作时间反馈(秒)
			StrongFragranceModeWorkTime: StrongFragranceModeWorkTime,
		}
	}

	/**
	 * 开始通知
	 */
	startNotice() {
		console.log("startNotice-监听数据")
		var that = this;
		uni.notifyBLECharacteristicValueChange({
			state: true, // 启用 notify 功能
			// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接 
			deviceId: that.#deviceId,
			// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
			serviceId: that.#serviceId,
			// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
			characteristicId: that.#writeCharacteristicId,
			success(res) {
				//接收蓝牙返回消息
				uni.onBLECharacteristicValueChange((sjRes) => {
					// 此时可以拿到蓝牙设备返回来的数据是一个ArrayBuffer类型数据,
					//所以需要通过一个方法转换成字符串
					that.#deviceReturnData = that.ab2hex(sjRes.value) //10.0
					console.log("startNotice-that.#deviceReturnData", that.#deviceReturnData)
				})
			},
			fail(err) {
				// uni.showToast({
				// 	title: '通知失败',
				// 	icon: 'error',
				// 	duration: 2000
				// })
				console.log("startNotice", err)
			}
		})
	}

	//手机写入开关控制数据
	async WriteSwitchControlData(f) {
		var hexStr = "02" + f.FragranceMode.toString().padStart(2, '0') + f.CurrentFragranceWorkChannel.toString()
			.padStart(2, '0') + f.FragranceWorkState.toString().padStart(2, '0') + f.IPCWorkState.toString()
			.padStart(2, '0') + f.VoiceControlState.toString().padStart(2, '0')
		//console.log("hexStr", hexStr)
		hexStr = hexStr + "00000000000000"
		hexStr = this.addAddc(hexStr)
		var isOK = await this.SetFragranceInfo(hexStr)
		//console.log("手机写入香氛设置数据", isOK)
		return isOK
	}

	//手机写入香氛设置数据
	async WriteFragranceSetData(f) {
		var hexStr = "03" + f.LightFragranceModeStopTime.toString(16).padStart(2, '0') + f
			.LightFragranceModeWorkTime.toString(16).padStart(2, '0') + f.MiddleFragranceModeStopTime.toString(16)
			.padStart(2, '0') + f.MiddleFragranceModeWorkTime.toString(16).padStart(2, '0') + f
			.StrongFragranceModeStopTime.toString(16).padStart(2, '0') + f.StrongFragranceModeWorkTime.toString(16)
			.padStart(2, '0')
		hexStr = hexStr + "000000000000"
		hexStr = this.addAddc(hexStr)
		var isOK = await this.SetFragranceInfo(hexStr)
		//console.log("手机写入香氛设置数据", isOK)
		return await this.SetFragranceInfo(hexStr)
	}

	//设置香氛数据
	async SetFragranceInfo(hexStr) {
		//console.log("增加addc 后 SetFragranceInfo-hexStr", hexStr)
		var that = this
		var buffer = that.string2buffer(hexStr); //9.0
		return new Promise((resolve, reject) => {
			try {
				uni.writeBLECharacteristicValue({
					// 这里的 deviceId 需要在 getBluetoothDevices 或 onBluetoothDeviceFound 接口中获取
					deviceId: that.#deviceId,
					// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
					serviceId: that.#serviceId,
					// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
					characteristicId: that.#writeCharacteristicId,
					// 这里的 value 是ArrayBuffer类型
					value: buffer,
					success(res) {
						//that.startNotice()
						//console.log('SetFragranceInfo success', res)
						if (res.errCode == 0) {
							//console.log("返回写入成功")
							resolve(true)
						} else {
							resolve(false)
						}
					},
					fail(res) {
						console.log('writeBLECharacteristicValue fail', res.errMsg)
						console.log("返回写入失败")
						//return false 

						resolve(false)
					},
					complete(res) {
						console.log('writeBLECharacteristicValue ', JSON.stringify(res))
					}
				})
			} catch (e) {
				console.error('uni.onBLECharacteristicValueChange' + e)
			}
		})
	}

	//设置香氛数据
	async SetFragranceInfoByCode(code) {
		var that = this
		//向蓝牙设备发送一个0x00的16进制数据
		//打开香氛
		var hexStr = "02" + code + "0101010300000000000000"
		hexStr = this.addAddc(hexStr)
		//console.log(code + "写入开关控制数据-hexStr", hexStr)
		var buffer = that.string2buffer(hexStr); //9.0
		//console.log("开始发送数据-写入开关控制数据")
		uni.writeBLECharacteristicValue({
			// 这里的 deviceId 需要在 getBluetoothDevices 或 onBluetoothDeviceFound 接口中获取
			deviceId: that.#deviceId,
			// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
			serviceId: that.#serviceId,
			// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
			characteristicId: that.#writeCharacteristicId,
			// 这里的 value 是ArrayBuffer类型
			value: buffer,
			success(res) {
				//that.startNotice()
				console.log('SetFragranceInfo success', res.errMsg)
			},
			fail(res) {
				console.log('writeBLECharacteristicValue fail', res.errMsg)
			},
			complete(res) {
				console.log('writeBLECharacteristicValue ', JSON.stringify(res))
			}
		})
	}
	//增加ADDC校验
	addAddc(str) {
		return str + this.getAddc(str)
	}
	getAddc(str) {
		let itotal = 0,
			len = str.length,
			num = 0;
		var tempTotal = "";
		while (num < len) {
			let s = str.substring(num, num + 2);
			itotal += parseInt(s, 16);
			num = num + 2;
			if (itotal >= 256) {
				itotal = parseInt((itotal - itotal % 256) / 256) + itotal % 256
			}
		}
		itotal = 255 - itotal
		return itotal.toString(16).padStart(2, '0')
	}
	checkAddc(str) {
		if (str.length != 40) {
			return false
		}
		var data = str.substring(0, 38)
		var allData = this.addAddc(data)
		return str == allData
	}
	//读取香氛设备信息
	async ReadFragranceInfo() {
		var that = this
		// 向蓝牙设备发送一个0x00的16进制数据
		var buffer = this.string2buffer('01000000000000000000000000FE'); //9.0
		console.log("this.#deviceId", this.#deviceId)
		console.log("this.#serviceId", this.#serviceId)
		console.log("this.#writeCharacteristicId", this.#writeCharacteristicId)
		var that = this
		uni.writeBLECharacteristicValue({
			// 这里的 deviceId 需要在 getBluetoothDevices 或 onBluetoothDeviceFound 接口中获取
			deviceId: that.#deviceId,
			// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
			serviceId: that.#serviceId,
			// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
			characteristicId: that.#writeCharacteristicId,
			// 这里的 value 是ArrayBuffer类型
			value: buffer,
			success(res) {
				that.startNotice()
			},
			fail(res) {
				console.log('writeBLECharacteristicValue fail', res.errMsg)
			},
			complete(res) {
				console.log('writeBLECharacteristicValue ', JSON.stringify(res))
			}
		})
	}
	getReturnDataStr() {
		let returnData = this.#deviceReturnData
		return returnData
	}
	getReturnData() {
		//console.log("that.#deviceReturnData-999", this.#deviceReturnData)
		var config = this.HexToConfig(this.#deviceReturnData)
		//console.log("config", config)
		return config
	}
	// ArrayBuffer转16进度字符串示例
	ab2hex(buffer) {
		if (!buffer) return ""
		const hexArr = Array.prototype.map.call(
			new Uint8Array(buffer),
			function(bit) {
				return ('00' + bit.toString(16)).slice(-2)
			}
		)
		return hexArr.join('')
	}
	/**
	 * 将字符串转换成ArrayBufer  
	 */
	string2buffer(str) {
		let val = ""
		if (!str) return;
		let length = str.length;
		let index = 0;
		let array = []
		while (index < length) {
			array.push(str.substring(index, index + 2));
			index = index + 2;
		}
		val = array.join(",");

		// 将16进制转化为ArrayBuffer  
		return new Uint8Array(val.match(/[\da-f]{2}/gi).map(function(h) {
			return parseInt(h, 16)
		})).buffer
	}
	/**
	 * 连接低功耗蓝牙设备
	 * @param {string} deviceId 设备ID
	 * @param {number} timeout 蓝牙连接超时时间, 单位ms, 默认3000ms(3秒)
	 * @param {number} mut 每次传输的数据大小, 默认244bytes(C385协议上就是默认每次传一帧数据, 总大小为244byte)
	 * @param {number} retry 错误重试, 默认2次
	 * @param {number} first 是否首次进入,如果是则重置 #deviceId
	 */
	async BLEConnect(deviceId, timeout = 3000, mtu = 244, retry = 3, first = true) {
		console.log("开始连接蓝牙" + retry)
		if (retry < 0) {
			// 重试次数已达到, 执行回调反馈给调用端
			return [false, 0]
		}
		try {
			//1. 检查适配器是否启动 uni.getBluetoothAdapterState(),没有启动则启动蓝牙适配器 uni.openBluetoothAdapter
			// this.openBluetoothAdapter()
			if (first) {
				this.#deviceId = ''
			}
			const [bluetooth, state] = await uni.openBluetoothAdapter()
			console.log("bluetooth", bluetooth)
			console.log("state", state)
			if (bluetooth) {
				//console.log("typeof(bluetooth)",bluetooth)
				if (bluetooth.errno == 103 || bluetooth.errMsg.indexOf("fail auth deny") != -1) {
					return [false, 103] //103需要打开蓝牙权限
				}
				return [false, bluetooth.errCode] //10001需要打开蓝牙
			}
			const [adapter, adapterState] = await uni.getBluetoothAdapterState()
			console.log("adapter", adapter)
			console.log("adapterState", adapterState)
			if (typeof(adapterState) == "undefined") {
				//return showModal()
			}
			if (!adapterState.discovering && retry == 0) {
				return [false, 10002] //未开启定位权限
			}
			if (!adapterState.available) {
				return [false, 10001] //蓝牙未打开
			}
			if (first || this.#deviceId == '') { // 如果没有搜到过设备则重新进入搜索,如果已经搜到过就无需搜索了
				try {
					const deviceId = await withTimeout(this.ScanDevice(), 3000)
					console.log('deviceId', deviceId)
				} catch (e) {
					console.log('ScanDevice', err)
					return [false, 100]
				}
				uni.stopBluetoothDevicesDiscovery({
					success(res) {
						console.log('关闭成功')
					},
					fail(res) {
						console.log('关闭失败' + res.errMsg)
					}
				})
				// await this.ScanDevice()
				//console.log("this.#deviceId",this.#deviceId)
				if (this.#deviceId == "" && retry < 0) { //需要打开设备
					return [false, 100]
				}
				if (this.#deviceId == "") {
					console.log('deviceId not value')
					retry = retry - 1
					return that.BLEConnect(this.#deviceId, timeout, mtu, retry)
				}
			}
			const [createErr, createSccess] = await uni.createBLEConnection({
				deviceId: this.#deviceId,
				timeout
			})
			//console.log('createBLEConnection', createErr, createSccess)
			if (createErr) {
				try {
					// 需要成对出现,如果连接错误也需要退出
					await uni.closeBLEConnection({
						deviceId: this.#deviceId
					})
				} catch {}
				// 连接错误
				console.log("连接错误", createErr)
				if (createErr.errCode == 10012 && retry == 0) {
					return [false, createErr.errCode] //10012,需要打开设备
				} else if (createErr.errCode != -1) {
					retry = retry - 1
					return await this.BLEConnect(this.#deviceId, timeout + 2000, mtu, retry, false)
				}
			}
			var that = this
			//setTimeout(() => {
			var isok = await this.getBLEDeviceServices()
			console.log("isok-111", isok)
			if (!isok) {
				retry = retry - 1
				return await this.BLEConnect(deviceId, timeout, mtu, retry, false)
			}
			isok = await this.getBLEDeviceCharacteristics()
			console.log("isok-222", isok)
			if (!isok) {
				retry = retry - 1
				return await this.BLEConnect(deviceId, timeout, mtu, retry, false)
			}
			return [true, 0]
		} catch (e) {
			console.log("重试", e)
			// 重试
			retry = retry - 1
			//console.log("连接蓝牙失败-catch-333")
			return await this.BLEConnect(deviceId, timeout, mtu, retry, false)
		}
		return [false, 0]
	}
	async getBLEDeviceServices() {
		console.log("开始执行-getBLEDeviceServices")
		var that = this
		return new Promise((resolve, reject) => {
			uni.getBLEDeviceServices({
				deviceId: that.#deviceId,
				success(res) {
					// 5.启用notify
					//console.log('getBLEDeviceServices', res)
					// this.#connected = true
					resolve(true)
					console.log("开始执行-getBLEDeviceServices-执行成功")
					// console.log("getBLEDeviceServices-res", res)
					// if (res.errno == 0 && res.services.length > 0) {
					// 	//console.log('getBLEDeviceServices-成功')
					// 	if (res.services[0].uuid != that.#appointServiceId) {
					// 		resolve(false)
					// 	}
					// 	that.#serviceId = res.services[0].uuid
					// 	// that.#connected = true
					// 	// that.startNotice()
					// 	// console.log("蓝牙连接成功")
					// 	resolve(true)
					// 	console.log("开始执行-getBLEDeviceServices-执行成功")
					// 	//})
					// } else {
					// 	resolve(false)
					// }
				},
				fail(err) {
					resolve(false)
				}
			})
		})
	}
	async getBLEDeviceCharacteristics() {
		var that = this
		console.log("开始执行-getBLEDeviceCharacteristics")
		return new Promise((resolve, reject) => {
			uni.getBLEDeviceCharacteristics({
				deviceId: that.#deviceId,
				serviceId: that.#serviceId,
				success(res) {
					//console.log("getBLEDeviceCharacteristics-res", res)
					if (res.characteristics.length > 0) {
						// console.log("连接蓝牙成功-222")
						// console.log("res.characteristics", res.characteristics)
						if (res.characteristics[0].uuid != that
							.#appointNotifyCharacteristicId) {
							resolve(false)
						}
						that.#notifyCharacteristicId = res.characteristics[0].uuid
						that.#writeCharacteristicId = res.characteristics[0].uuid
						that.#connected = true
						console.log("开始执行-成功-getBLEDeviceCharacteristics")
						that.startNotice()
						// console.log("this.#deviceId-999", that.#deviceId)
						// console.log("this.#writeCharacteristicId ", that
						// 	.#writeCharacteristicId)
						console.log("蓝牙连接成功")
						resolve(true)
					}
				},
				fail() {
					resolve(false)
				}
			})
		})

	}
	/**
	 * 发送数据
	 * @param {ArrayBuffer} bytes
	 */
	async #sendData(bytes) {
		await uni.writeBLECharacteristicValue({
			deviceId: this.#deviceId, // 蓝牙设备 id
			serviceId: this.#serviceId, // 蓝牙特征值对应服务的 uuid
			characteristicId: this.#writeCharacteristicId, // 蓝牙特征值的 uuid
			value: bytes, // 这里的value是ArrayBuffer类型
			fail: (err) => {
				console.log('writeBLECharacteristicValue', err)
			},
		})
	}



	// notify 接收消息回调函数
	#sendDataCallback = null
	// notify 接收消息回调函数消息队列(防止漏接收消息)
	#sendDataCallbackMsgQueue = null



	/**
	 * 监听低功耗蓝牙设备的特征值变化事件。必须先启用 notifyBLECharacteristicValueChange 接口才能接收到设备推送的 notification。
	 * @param {Function} callback 回调函数
	 */
	onBLEConnectionStateChange(callback) {
		uni.onBLEConnectionStateChange((res) => {
			// 该方法回调中可以用于处理连接意外断开等异常情况
			// console.log(`设备 ${res.deviceId} 状态已经改变, connected: ${res.connected}`)
			this.#connected = res.connected // 更新连接状态
			callback(res)
		})
	}



}

/**
 * 16进制字符串转16进制 ArrayBuffer
 * @param {string} hexString 16进制字符串
 */
function string2HexArray(hexString) {
	if (!hexString) return;
	let length = hexString.length;
	let index = 0;
	let array = []
	while (index < length) {
		array.push(hexString.substring(index, index + 2));
		index = index + 2;
	}
	const val = array.join(",");
	return new Uint8Array(val.match(/[\da-f]{2}/gi).map((h) => parseInt(h, 16))).buffer
}

/**
 *  ArrayBuffer转16进度字符串示例
 * @param {Array} buffer 字节数组
 * @return {string} 转换后的字符串
 */
export function ab2hex(buffer) {
	const hexArr = Array.prototype.map.call(
		new Uint8Array(buffer),
		(bit) => {
			return ('00' + bit.toString(16)).slice(-2)
		}
	)
	return hexArr.join('')
}


/**
 * 判断是不是要连接的蓝牙设备 是不是以 OTA- 开头
 * @param {string} deviceName 设备名称
 */
export function isOTADevice(deviceName) {
	const prefix = "OTA-"
	return deviceName.toUpperCase().startsWith(prefix)
}

function crc16(str) {
	const data = Buffer.from(str, "hex")
	let crcValue = 0xFFFF;
	for (let i = 0; i < data.length; i++) {
		crcValue ^= data[i] & 0xFFFF
		for (let j = 0; j < 8; j++) {
			if (crcValue & 0x0001) {
				crcValue >>= 1
				crcValue ^= 0xA001
			} else {
				crcValue >>= 1
			}
		}
	}
	crcValue = crcValue.toString(16).toUpperCase().padStart(4, '0')
	return crcValue.substring(0, 2) + crcValue.substring(2, 4)
}


/**
 * 字符串转ASCII码函数
 * @param {string} str
 */
function toASCII(str) {
	return str.split('').map(char => char.charCodeAt(0)).join('');
}

/**
 * 十六进制字符串转ASCII码
 * @param {string} hexStr
 */
function hexToASCII(hexStr) {
	let result = '';
	for (let i = 0; i < hexStr.length; i += 2) {
		let hex = hexStr.substr(i, 2);
		result += String.fromCharCode(parseInt(hex, 16));
	}
	return result;
}

/**
 * 格式化长度
 * @param {number} fileLen
 */
function formatLen(fileLen) {
	return fileLen.toLocaleString('en-US', {
		minimumIntegerDigits: 6,
		useGrouping: false
	}).padStart(6, '0');
}



/**
 * 搜索监听C385升级设备
 * @param {Function} notifier 回调函数
 * @param {Function} callback 回调函数
 * @param {Function} cancelCallback 取消回调函数
 */
export async function listenUpdateDevice(notifier, callback, cancelCallback = null) {
	const showModal = () => {
		uni.showModal({
			title: '提示!',
			content: '初始化蓝牙失败,请打开本机蓝牙!',
			showCancel: true,
			cancelText: "取消",
			confirmText: "确定",
			success: (res) => {
				if (res.confirm) {
					// 用户点击了确定
					return listenUpdateDevice(notifier, callback, cancelCallback)
				} else if (res.cancel) {
					// 用户点击了取消
					if (cancelCallback) {
						cancelCallback()
					}
					return
				}
			}
		});
	}
	try {
		// 1.初始化蓝牙模块
		const res = await uni.openBluetoothAdapter()
		// 2.获取本机蓝牙适配器状态(正常状态下是一定能获取的)
		let [_, state] = await uni.getBluetoothAdapterState()
		if (!state) {
			return listenUpdateDevice(notifier, callback, cancelCallback)
		}
		// console.log(state)
		if (!state.available) {
			return showModal()
		}
		// 3.开启搜寻附近的蓝牙外围设备
		await uni.startBluetoothDevicesDiscovery()

		// 通知已经开始搜索设备了
		notifier()
		// 4.开始监听搜索设备
		uni.onBluetoothDeviceFound(async found => {
			const d = found.devices[0]
			if (isOTADevice(d.name) && d.connectable) {
				const device = {
					name: d.name, // 名称
					id: d.advertisServiceUUIDs.length > 0 ? d.advertisServiceUUIDs[0] : '', // 设备ID
					MAC: d.deviceId, // MAC地址
					advertisData: ab2hex(d.advertisData).trim(), // 广播数据
					RSSI: d.RSSI, // RSSI
					connectable: d.connectable // 是否能连接
				}
				callback(device)
				// 搜索到了要升级的设备旧关闭(startBluetoothDevicesDiscovery会很耗电, 所以搜索到蓝牙设备后要及时关毕)
				// await uni.stopBluetoothDevicesDiscovery()
			}
		})
	} catch (e) {
		console.error('listenUpdateDevice错误', e)
		return showModal()
	}
}

/**
 * 关闭搜索监听C385升级设备
 * @param {Function} callback 回调函数
 */
export async function closeListenUpdateDevice() {
	// 搜索到了要升级的设备旧关闭(startBluetoothDevicesDiscovery会很耗电, 所以搜索到蓝牙设备后要及时关毕)
	await uni.stopBluetoothDevicesDiscovery()
}

/**
 * ASCII 转 ArrayBuffer
 * @param {string} str ASCII 字符串
 */
function textEncode(str) {
	const bytes = [];
	for (let i = 0; i < str.length; i++) {
		let charcode = str.charCodeAt(i);
		if (charcode < 0x80) {
			bytes.push(charcode);
		} else if (charcode < 0x800) {
			bytes.push(0xC0 | (charcode >> 6),
				0x80 | (charcode & 0x3F));
		} else {
			bytes.push(0xE0 | (charcode >> 12),
				0x80 | ((charcode >> 6) & 0x3F),
				0x80 | (charcode & 0x3F));
		}
	}
	return new Uint8Array(bytes).buffer;
}

/**
 * 超时控制函数
 * @param {Promise} promise 回调函数
 * @param {number} timeout 超时时间, 默认10s
 */
export function withTimeout(promise, timeout = 10000) {
	let timeoutEvent = null
	const logicPromise = new Promise((resolve, reject) => {
		promise.then((data) => {
			if (timeoutEvent) {
				// 清理超时
				clearTimeout(timeoutEvent)
				timeoutEvent = null
			}
			resolve(data)
		})
	})
	// 创建一个新的 Promise 对象,用于处理超时情况
	const timeoutPromise = new Promise((resolve, reject) => {
		timeoutEvent = setTimeout(() => {
			reject(`执行超时`);
		}, timeout);
	});

	// 将两个 Promise 对象(原始的 promise 和超时的 promise)合并为一个新的 Promise 对象
	return Promise.race([logicPromise, timeoutPromise]);
}

你可能感兴趣的:(uni-app,小程序)