前言: 因为需要开发与蓝牙设备控制相关,但是这不是常用的蓝牙打印,并且是由原生安卓已经做好的,但是需要移植到多平台(小程序 等)都可以使用所以就要移植成 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不一样,其他方式和安卓的是完全没区别的。唯一的区别就是需要识别这个设备就是你需要连接的设备。
新手小白文章写的比较繁琐有点乱,请多多包涵········