uni-app蓝牙设备连接以及设备的传输,CRC16,ios蓝牙和安卓蓝牙获取数据不一致处理方案

前言: 因为需要开发与蓝牙设备控制相关,但是这不是常用的蓝牙打印,并且是由原生安卓已经做好的,但是需要移植到多平台(小程序 等)都可以使用所以就要移植成 uni-app ,所以这个是按照uni-app的蓝牙设备完成的。
以下涉及到的 有 蓝牙的连接 、 蓝牙的数据byte构造、数组构造的方法

uni-app蓝牙相关的API:https://uniapp.dcloud.io/api/system/ble
https://uniapp.dcloud.io/api/system/bluetooth

一、蓝牙的连接

// 初始化方法
			initializeBlue () {
				let _this = this
				//在页面加载时候初始化蓝牙适配器
				uni.openBluetoothAdapter({
					success:(res)=> { //已打开
						uni.getBluetoothAdapterState({//蓝牙的匹配状态
							success:(res1)=>{
								console.log(res1,'本机设备的蓝牙已打开')
								// 开始搜索蓝牙设备
								this.startBluetoothDeviceDiscovery()
							},
							fail(error) { 
								uni.showToast({icon:'none',title: '查看手机蓝牙是否打开'});
							}
						});
						
					},
					fail:err=>{ //未打开 
						uni.showToast({icon:'none',title: '查看手机蓝牙是否打开'});
					}
				})
			},
			// 开始搜索蓝牙设备
			startBluetoothDeviceDiscovery(){
				uni.startBluetoothDevicesDiscovery({
					success: (res) => {
						console.log('startBluetoothDevicesDiscovery success', res)
						// 发现外围设备
						 this.onBluetoothDeviceFound()
					},fail:err=>{
						console.log(err,'错误信息')
					}
				})
			},
			// (发现外围设备,如果你的设备是通过扫码获取到mac格式的id的话 可以不需要这个发现外围设备)
			// 发现外围设备
				 onBluetoothDeviceFound() {
				 	uni.onBluetoothDeviceFound((res) => {
				 		 console.log(JSON.stringify(res.devices))
						 if(this.list.indexOf(res.devices[0].deviceId)==-1){
						 	this.list.push({
								name:res.devices[0].name,
				 		 		deviceId:res.devices[0].deviceId
				 		 	})
				 		 }
				 	})
				 },

======== 以上的代码是初始化的 =======

以下代码 可以根据自身情况运用上面 文档的API(我这里用的是扫码后获取到的id,这个id需要我转成MAC格式的)
MAC格式,例如: 扫码获取到的id是 70512154dd9c,那么需要转成的MAC格式 就变成了 70:51:21:54:dd:9c 就是在每两个加“ : ”号 。
这里也把 转成这个格式的方法也写一下吧

 // id 获取回来的id 如果直接扫码获取到的就是MAC格式 或者 直接是附近蓝牙直接点击链接的话 那就不需要这个
 id.replace(/(.{2})/g,'$1:').slice(0,-1)

进入正题--------- (注意:以下方法中MTU是设置最大传输值,为了防止例如[12,45,5,1,23,12…],蓝牙只获取到[12,45,5]这种情况)

//选择设备连接吧deviceId传进来
				createBLEConnection(maIp){
				let _le = this
				this.deviceId = maIp // 如果不是MAC地址 replace(/(.{2})/g,'$1:').slice(0,-1)
					//连接蓝牙
					uni.createBLEConnection({
					// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
					  deviceId: maIp, // 如果不是MAC地址 replace(/(.{2})/g,'$1:').slice(0,-1)
					  success(res) {
						console.log(res) 
						console.log("蓝牙设备连接成功")
						_le.setMaxMTU() // 因为蓝牙默认的MTU传输值可能不够大,导致传输数据丢失 所以执行该方法扩大传输MTU
					  },fail(res) {
						  uni.showToast({
							  icon:'none',
						      title: '蓝牙连接失败',
						      duration: 2000
						  });
						console.log("蓝牙连接失败",res)
					  }
					})
				},
				// 设置最大MTU值 (可以参照文档)
				setMaxMTU () {
					let _this = this 
					uni.setBLEMTU({
						deviceId:this.deviceId, 
						mtu:200,
						success(res) {
							console.log(res)
							console.log("设置最大值成功")
							// this.stopBluetoothDevicesDiscovery()
							// _le.getBLEDeviceServices()
							_this.getBluetoothDevices()
						},fail(res) {
							uni.showToast({
								icon:'none',
								title:'设备初始化失败'
							})
						console.log("设置最大值失败!!!",res) 
					  }
					})
				},
				//获取在蓝牙模块生效期间所有已发现的蓝牙设备。包括已经和本机处于连接状态的设备。
			getBluetoothDevices() {
				console.log("获取蓝牙设备");
				uni.getBluetoothDevices({
					success: res => {
						console.log('获取蓝牙设备成功:');
						console.log(res.devices);
						res.devices.forEach(item => {
							if (item.deviceId === this.deviceId) {
								this.serviceId = item.advertisServiceUUIDs[0]
								this.getBLEDeviceCharacteristics()
							}
						})
					}
				});
			},
			//获取蓝牙特征
				getBLEDeviceCharacteristics(){
					console.log("进入特征",this.deviceId,this.serviceId);
					setTimeout(()=>{
						uni.getBLEDeviceCharacteristics({
						  // 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
						  deviceId:this.deviceId,
						  // 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
						  serviceId:this.serviceId, 
						  success:(res)=>{
							console.log(res,'特征getBLEDeviceCharacteristics')
							this.characteristics = res.characteristics 
							this.readUUID = res.characteristics[0].uuid
							this.writeUUID = res.characteristics[1].uuid
							console.log(this.characteristics)
							res.characteristics.forEach((item)=>{
								if(item.uuid.indexOf("0000FFF1") !== -1){
									this.characteristicId = item.uuid
									//console.log('characteristicId:', item.uuid)
									//利用传参的形势传给下面的notify,这里的uuid如果都需要用到,就不用做判断了,建议使用setTimeout进行间隔性的调用此方法
									this.notifyBLECharacteristicValueChange(item.uuid)
								}
							})
						  },
						  fail:(res)=>{
							console.log(res)
						  } 
						}) 
					},1000)
				},

// 重点来了,需要开启 监听才可以监听到 蓝牙根据你传过去的数据 传输回来的数据 所以

// 启用 notify 功能 监听
				notifyBLECharacteristicValueChange(characteristicId){
					console.log(characteristicId,'characteristicId')
					       uni.notifyBLECharacteristicValueChange({
					         state: true, // 启用 notify 功能
					         // 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
					         deviceId:this.deviceId,
					         // 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
					         serviceId:this.serviceId,
					         // 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
					         characteristicId:characteristicId,
					         success:(res)=> {
					       	console.log(res)
					       	// console.log(this.characteristicId)
					       	console.log('notifyBLECharacteristicValueChange success', res.errMsg)
					       	uni.onBLECharacteristicValueChange(characteristic => {
					       		console.log('监听低功耗蓝牙设备的特征值变化事件成功',characteristic);
					       		var array = new Uint8Array(characteristic.value); // 这是监听蓝牙返回的数据
					       		console.log('包' + array); 
					       	})
					        },
					         fail:(res)=> {
								console.log('notifyBLECharacteristicValueChange fail', res.errMsg)
					         }
					       })
				},

上面的代码 是我根据自身项目 作出了相对应的调整过的,仅供参考。
也可以参考一下demo 这个博主的 https://blog.csdn.net/qq_42783654/article/details/115324789
好了 到这里 才是我噩梦的开始。

二、 数据的传递

以下是 你把构建好的byte数组 传进该方法 然后 把Byte数组传输给蓝牙,蓝牙会在 第 一 点中的监听方法返回蓝牙设备响应后返回的值。你可以使用 我下面为你们准备好的 Byte数组 和 字符串方法进行解析。

//写入蓝牙  
				writeBLECharacteristicValue(b) {
					console.log("传进来的B~~~~~",b)
					// 向蓝牙设备发送一个0x00的16进制数据
					let buffer = new ArrayBuffer(b.length)
					let dataView = new DataView(buffer)
					b.forEach((item,index) => { 
						dataView.setUint8(index,item) 
					}) 
					uni.writeBLECharacteristicValue({
						deviceId: this.deviceId,
						serviceId: this.serviceId,
						characteristicId: this.writeUUID,
						value:buffer,
						success(res) {
							console.log('writeBLECharacteristicValue success', res)
						},
						fail(res) {  
							console.log(JSON.stringify(res))
							console.log(JSON.stringify(buffer))
						}
					})
				},

很多连接了蓝牙之后 数据的传递就有问题了,因为蓝牙接收的是 byte数组 ,所以 你也需要按照后端大哥调试好的设备文档 你传输相对应的 byte数组 给蓝牙设备接收 才能做出相对应的处理

蓝牙连接 CRC16值肯定是少不了的了,网上的方法也很多,但是项目需求不一样,导致CRC16构造出来的值也不一样,移植项目中的CRC16构造需要设置相对应的初始值。以下是我找到的方法 注释 有详解

这是从另外一个博主身上学回来 然后根据自身 作出微调 和 注释的CRC16。
https://www.jianshu.com/p/75fa84050f0a

//CRC蓝牙校验
		crc16(buffer) {
		  var crc = 0x000; // 这是 设置初始值的 可以根据自己的CRC16协议进行调整
		  var odd;
		  for(var i = 0; i < buffer.length; i++) {
		    crc ^= (buffer[i] << 8)
		    for(var j = 0; j < 8; j++) {
		      odd = crc & 0x8000;
		      crc = crc << 1;
		      if(odd) {
		        crc = crc ^ 0x1021
		      }
		    }
		  }
		  var hi = ((crc & 0xFF00) >> 8); //高位置
		  var lo = (crc & 0x00FF); //低位置
		  var crcArr = []
		  crcArr.push(hi)
		  crcArr.push(lo)
		  crc &= 0xFFFF // 偏差值
		  return this.crcToString(crcArr, false) // 传true false 是看你参数是高位在前 还是 低位在前,各人情况不同定
		},
		//转为大写String
		crcToString(arr, isReverse) {
		  if(typeof isReverse == 'undefined') {
		    isReverse = true;
		  }
		  var hi = arr[0],
		    lo = arr[1];
		  return this.padLeft((isReverse ? hi + lo * 0x100 : hi * 0x100 + lo).toString(16).toUpperCase(), 4, '0');
		},
		padLeft(s, w, pc) {
		    if (pc == undefined) {
		        pc = '0';
		    }
		    for (var i = 0, c = w - s.length; i < c; i++) {
		        s = pc + s;
		    }
		    return s;
		},

// 以上是 CRC16的难题解决方案之一,如果这个和你的有出入,那就可以去问万能的浏览器了

难题二: 数据的转换

这里直接贴出 各种数据的转换方法吧,不多阐述,需要什么用什么就好

!!!! 重点 下面方法中 bin2Str 方法

// byte 转 16进制 用:间隔模式
		Bytes2Str(arrBytes){
				var str = "";
				for (var i = 0; i < arrBytes.length; i++) {
					var tmp;
					var num = arrBytes[i];
					if (num < 0) {
						//此处填坑,当byte因为符合位导致数值为负时候,需要对数据进行处理
						tmp = (255 + num + 1).toString(16);
					} else {
						tmp = num.toString(16);
					}
					if (tmp.length == 1) {
						tmp = "0" + tmp;
					}
					if(i>0){
						str += ":"+tmp; // 可以修改 : 成你需要的间隔符号,如果不需要间隔符号那就空着
					}else{
						str += tmp;
					}
				}
				return str;
			},
			//比如 [12,5,8,41,2]
			// 返回的是 12:5:8:41:2
			
// byte转纯字符串 
			BytesToStr(arrBytes){
					var str = "";
					for (var i = 0; i < arrBytes.length; i++) {
						var tmp;
						var num = arrBytes[i];
						if (num < 0) {
							//此处填坑,当byte因为符合位导致数值为负时候,需要对数据进行处理
							tmp = (255 + num + 1).toString(16);
						} else {
							tmp = num.toString(16);
						}
						if (tmp.length == 1) {
							tmp = "0" + tmp;
						}
						if(i>0){
							str += tmp;
						}else{
							str += tmp;
						}
					}
					return str;
				}, 
				
				// !!!!重点!!!!  很多时候因为你返回的16进制 是 2个数字为 1 字节
				// 比如 442288C7 这种,用简单的字符转byte 会把字符串 44 分开编译成两个Byte数组 
				// 以下方法是 44 22 88 C7 这样为一组编译
				// 字符 转 16进制Byte // 两个为一组
		bin2Str(str){
			 let a = []
			 let arr = []
			 for (var i = 0; i < str.length; i += 2) {
			     a.push("0x" + str.substr(i, 2)); a.join(" "); 
			 }
			 if (a.length) {
				 a.forEach(item => {
					arr.push(parseInt(item,16))
				 })
			 } else {
				 return []
			 }
			 return arr
		},
		// Hex字符 转换成 Byte
		HexString2Bytes(str) {
		    var pos = 0;
		    var len = str.length;
		    if (len % 2 != 0) {
		      return null;
		    }
		    len /= 2;
		    var arrBytes = new Array();
		    for (var i = 0; i < len; i++) {
		      var s = str.substr(pos, 2);
		      var v = parseInt(s, 16);
		      arrBytes.push(v);
		      pos += 2;
		    }
		    return arrBytes;
		  },

我做的蓝牙项目基本就用到这些,如果还有什么需要补充的,欢迎评论。
可能写的比较乱,但是又很多都做好了注释,和详细的说明。配合uni-app的文档看 会比较好点。
在这里插入图片描述
在这里插入图片描述
// ==================2021- 5 -14 ======================//

项目测试了 IOS系统的蓝牙 发现了闪退问题,随之引发的一系列操作,原以为已经完成了,谁知道IOS要另做适配方法,害…。

问题一、 当IOS使用扫码获取到上面所说的 MAC地址ID 然后直接赋值成为 deviceId连接蓝牙,会直接闪退。

原因是,IOS的连接不可以通过MAC地址,而是需要获取到他指定的deviceId才可以(安卓不变,IOS的deviceId会变),所以会导致闪退。所以直接用他返回的deviceId进行连接就行了

问题二、我既然要扫码获取到了MAC地址,但是IOS返回的 和扫码的deviceId 又不一致,怎么办呢?

这里有一个解决方案的博主 这也是一个方法
https://blog.csdn.net/weixin_39603132/article/details/110574594?utm_term=deviceidios%E8%93%9D%E7%89%99&utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2allsobaiduweb~default-0-110574594&spm=3001.4430

但是呢,不符合我的要求,因为我的蓝牙设备没有额外返回data,所以我选择最繁琐但是却有效的方法:

1.当我扫码获取到MAC地址的未格式化ID,因为我使用指令读取蓝牙是可以获取到蓝牙返回的MAC地址的ID的,所以我们用扫码获取到的构造成指令,然后去把周围蓝牙设备符合我们过滤信息的逐个逐个连接,把他们返回的设备ID 和 我们扫码获取到的做一个对比,如果对比成功就用该设备的deviceId作为蓝牙uni-app 的 API 通信中的deviceId。

注意:uni-app的API通信 用deviceId是IOS获取回来那个deviceId, 但是蓝牙的指令构造依然是用的扫码后的MAC地址作为构造指令才能通信。

IOS 除了调用uni-app所使用的deviceId不一样,其他方式和安卓的是完全没区别的。唯一的区别就是需要识别这个设备就是你需要连接的设备。

新手小白文章写的比较繁琐有点乱,请多多包涵········

你可能感兴趣的:(微信开发,蓝牙,uni-app,vue.js,ios,android)