前言:前段时间接到一个需求,需要支付宝小程序通过蓝牙连接支持蓝牙的模块并给蓝牙设备发送指令(有2G和蓝牙两个下单流程),第一次做蓝牙的项目,所以踩了很多坑,特此记录下来!因蓝牙连接启动流程横跨了我们整个项目,所以把需要的蓝牙API都封装在了一个js文件里面,方便调用;当然,蓝牙的API接口有20左右个,这里就不一一讲了,毕竟官方文档写的还是蛮详细的,就只记录一下自己碰到的问题。
1.my.openBluetoothAdapter(初始化蓝牙)
官方API文档上是如下说法:
ios上没有问题,但安卓手机上蓝牙未开启的状态下初始化蓝牙监听蓝牙状态是没有效果的;
解决办法:可以在调用my.openBluetoothAdapter方法监听蓝牙开启状态的时候设置一个全局变量,在没有开启的情况下可以重新再调用一次。
//初始化蓝牙设备
const openBluetoothAdapter = () => {
return new Promise((resolve, rej) => {
my.openBluetoothAdapter({
success:(res) => {
if (!res.isSupportBLE) { //蓝牙不可用
rej(res);
return;
}
onBluetoothAdapterStateChange() //监听蓝牙状态
BluetoothDetail.isBluetooth = true;
resolve(res); //初始化成功
},
fail: (error) => {
//ios手机初始化蓝牙失败的时候也可以监听成功
onBluetoothAdapterStateChange()
BluetoothDetail.isBluetooth = false;
rej(error)
},
});
})
}
//监听蓝牙设状态
const onBluetoothAdapterStateChange =()=>{
//防止重复调用监听事件,先关闭监听
my.offBluetoothAdapterStateChange();
my.onBluetoothAdapterStateChange({ //监听蓝牙状态
success: (e) => {
if(e.available){
BluetoothDetail.isBluetooth = true;
}else{
BluetoothDetail.isBluetooth = false;
}
},
})
}
2.my.startBluetoothDevicesDiscovery(开始搜寻附近的蓝牙设备)
搜索结果将在 my.onBluetoothDeviceFound 事件中返回。
Android 上获取到的deviceId为设备 MAC 地址,iOS 上则为设备 uuid; 因此,搜索匹配蓝牙的时候不能简单的直接匹配deviceId,可以根据设备的唯一标示写入设备属性(localName/advertisData/manufacturerData)进行动态匹配。匹配成功的时候获取deviceId去连接,并且停止搜索,释放系统资源。
还有一个需要注意的地方,获取到的蓝牙设备的值都是hex编码,进行对比的时候需要进行转换。
封装的定时器和字符转换:
//定时器
const linkTimer = (time) => {
return new Promise((resolve, rej) => {
let _time = 0
Timer = setInterval (function() {
if(_time < time){
_time ++
}else{
rej(false)
clearInterval(Timer)
}
}, 1000)
})
}
//转换成hex编码
const stringToHex = (str) => {
var val = '';
for (var i = 0; i < str.length; i++) {
if (val == ""){
val = str.charCodeAt(i).toString(16);
}else{
val += str.charCodeAt(i).toString(16);
}
}
return val;
}
以下代码是根据我们设备的唯一IMEI号转换成16进制进行匹配;并设置了10s的搜索时间,超过即超时(蓝牙走不通的需要换其他流程):
//开始搜索,扫描蓝牙设备
const startBluetoothDevicesDiscovery = (IMEI) => {
return new Promise((resolve, rej) => {
BluetoothDetail.imei = IMEI
let _imei = stringToHex(BluetoothDetail.imei)
my.startBluetoothDevicesDiscovery({
allowDuplicatesKey: true,
success: () => {
//搜索10s后提示蓝牙设备匹配连接超时
linkTimer(10).catch((error) =>{
rej(error)
return
})
my.offBluetoothDeviceFound();
my.onBluetoothDeviceFound({
success: res => {
var deviceArray = res.devices;
for (let i = deviceArray.length - 1; i >= 0; i--) {
let deviceObj = deviceArray[i];
let _advertisData = deviceObj.advertisData
if (_advertisData.search(_imei) != -1) {
BluetoothDetail.deviceId = deviceObj.deviceId
//清除定时器
clearInterval(Timer)
my.offBluetoothDeviceFound();
//停止搜索
stopBluetoothDevicesDiscovery();
//连接设备
connectBLEDevice().then((res) => {
resolve(res)
}).catch((error) => {
rej(error)
});
break;
}
}
},
fail: error => {
rej(error)
},
});
},
fail: error => {
rej(error)
},
});
})
}
还有一个问题,在这里记录一下,毕竟之前在这里踩到了这个坑,如下左图为ios搜索到的附近设备,右图为Android搜索到的,Android的advertisData是我们需要匹配的,而ios上面后面有一大串重复了,具体出现的原因还不太清楚,也许是我们蓝牙模块出现的问题!
3.my.getBLEDeviceCharacteristics(获取设备characteristic特征值)
蓝牙设备一个service对应有好几个characteristic(特征值),分别支持读写还有通知等操作,具体查看API文档,所以需要分别把这些characteristic保存下来,然后开始监听设备特征值变化my.notifyBLECharacteristicValueChange:必须设备的特征值支持notify或者indicate才可以成功调用
//获取连接设备的charid,必须要再连接状态状态之下才能获取(这里分别筛选出读写特征字)
const getBLEDeviceCharacteristics = () => {
return new Promise((resolve, rej) => {
getConnectedBluetoothDevices().then(() =>{
my.getBLEDeviceCharacteristics({
deviceId: BluetoothDetail.deviceId,
serviceId: BluetoothDetail.serviceId,
success: res => {
for (let i = 0; i < res.characteristics.length; i++) {
let charc = res.characteristics[i];
if (charc.properties.notify || charc.properties.indicate) {
//通知特征
BluetoothDetail.notify_id = charc.characteristicId;
}
if (charc.properties.write) {
//读特征
BluetoothDetail.write_id = charc.characteristicId
}
if (charc.properties.read) {
//写特征
BluetoothDetail.read_id = charc.characteristicId
}
}
notifyBLECharacteristicValueChange().then((res) => {
resolve(res)
}).catch((error) => {
rej(error)
})
},
fail: error => {
rej(error)
console.log('获取设备特征值失败' + JSON.stringify(error))
},
});
})
})
}
4.my.writeBLECharacteristicValue(向蓝牙写入数据)
Api文档上其实比较简单,直接在value写入要传的数据就可以,但有个限制,16进制字符串且20字节以内,对于要传数据比较多的,像我们加密之后的密文有1、2百字节,就只能进行分包处理;
如下图,_startCmd是需要写入的数据,转换成16进制字符串,结尾加入‘0A’换行符是和为了告诉硬件(需和硬件协议)我最后一包已经发送完了。看到有人说发完一包立马发第二包的时候会出现丢包的问题,所以需要做延迟处理,但我这边一直没有发现这个问题,所以没有做延迟。
//给蓝牙分包传输数据
const writeBLECharacteristicValue = () => {
return new Promise((resolve, rej) => {
let _startCmd = stringToHex(BluetoothDetail.startCmd) + '0A'
var i = 0;
writeBLESync(i, _startCmd).catch((error) => {
rej(error)
});
})
}
const writeBLESync = (i, writeData) => {
i += 20
return new Promise((resolve, rej) => {
//多包发送
if (i < writeData.length) {
var senddata = writeData
var dataSend = senddata.substring(i - 20, i)
my.writeBLECharacteristicValue({
deviceId: BluetoothDetail.deviceId,
serviceId: BluetoothDetail.serviceId,
characteristicId: BluetoothDetail.write_id,
value: dataSend,
success: res => {
writeBLESync(i, writeData);
},
fail: error => {
rej(error)
},
});
} else {
//第一包或者最后一包
var senddata = writeData
var endLength = senddata.length
var dataSend = senddata.substring(i - 20, endLength)
my.writeBLECharacteristicValue({
deviceId: BluetoothDetail.deviceId,
serviceId: BluetoothDetail.serviceId,
characteristicId: BluetoothDetail.write_id,
value: dataSend,
success: res => {
},
fail: error => {
rej(error)
},
});
}
})
}
最后附上这一段的代码:
const BluetoothDetail = {
isBluetooth:false,//用户是否打开蓝牙
startCmd:null,//启动指令
imei:'',
notify_id:'',//支持notify的特征值
read_id: '',//支持read的特征值
write_id: '',//支持write的特征值
deviceId:'',//设备ID
serviceId: 'edfec62e-9910-0bac-5241-d8bda6932a2f',//固定的服务id
readCode:null,//蓝牙回码
}
let Timer = null
//开始使用蓝牙
const startUseBlueTooth = (IMEI, startCmd) =>{
return new Promise((resolve, rej) => {
BluetoothDetail.startCmd = startCmd;
BluetoothDetail.readCode = null,
getConnectedBluetoothDevices().then(() => {
//蓝牙连接状态、
if(BluetoothDetail.imei != IMEI){ //连接和下单的不是同一台设备
startBluetoothDevicesDiscovery(IMEI).then((e) => {
resolve(e)
}).catch((error) => {
rej(error)
})
}else{
notifyBLECharacteristicValueChange().then((res) => {
resolve(res)
}).catch((error) => {
rej(error)
})
}
}).catch((error) => {
startBluetoothDevicesDiscovery(IMEI).then((e) => {
resolve(e)
}).catch((error) => {
rej(error)
})
})
})
}
//初始化蓝牙设备
const openBluetoothAdapter = () => {
return new Promise((resolve, rej) => {
my.openBluetoothAdapter({
success:(res) => {
if (!res.isSupportBLE) { //蓝牙不可用
rej(res);
return;
}
onBluetoothAdapterStateChange() //监听蓝牙状态
BluetoothDetail.isBluetooth = true;
resolve(res); //初始化成功
},
fail: (error) => {
//ios手机初始化蓝牙失败的时候也可以监听成功
onBluetoothAdapterStateChange()
BluetoothDetail.isBluetooth = false;
rej(error)
},
});
})
}
//监听蓝牙设状态
const onBluetoothAdapterStateChange =()=>{
//防止重复调用监听事件,先关闭监听
my.offBluetoothAdapterStateChange();
my.onBluetoothAdapterStateChange({ //监听蓝牙状态
success: (e) => {
if(e.available){
BluetoothDetail.isBluetooth = true;
}else{
BluetoothDetail.isBluetooth = false;
}
},
})
}
//开始搜索,扫描蓝牙设备
const startBluetoothDevicesDiscovery = (IMEI) => {
return new Promise((resolve, rej) => {
BluetoothDetail.imei = IMEI
let _imei = stringToHex(BluetoothDetail.imei)
my.startBluetoothDevicesDiscovery({
allowDuplicatesKey: true,
success: () => {
//搜索10s后提示蓝牙设备匹配连接超时
linkTimer(10).catch((error) =>{
rej(error)
return
})
my.offBluetoothDeviceFound();
my.onBluetoothDeviceFound({
success: res => {
var deviceArray = res.devices;
for (let i = deviceArray.length - 1; i >= 0; i--) {
let deviceObj = deviceArray[i];
let _advertisData = deviceObj.advertisData
if (_advertisData.search(_imei) != -1) {
BluetoothDetail.deviceId = deviceObj.deviceId
//清除定时器
clearInterval(Timer)
my.offBluetoothDeviceFound();
//停止搜索
stopBluetoothDevicesDiscovery();
//连接设备
connectBLEDevice().then((res) => {
resolve(res)
}).catch((error) => {
rej(error)
});
break;
}
}
},
fail: error => {
rej(error)
},
});
},
fail: error => {
rej(error)
},
});
})
}
//定时器
const linkTimer = (time) => {
return new Promise((resolve, rej) => {
let _time = 0
Timer = setInterval (function() {
if(_time < time){
_time ++
}else{
rej(false)
clearInterval(Timer)
}
}, 1000)
})
}
//转换成hex编码
const stringToHex = (str) => {
var val = '';
for (var i = 0; i < str.length; i++) {
if (val == ""){
val = str.charCodeAt(i).toString(16);
}else{
val += str.charCodeAt(i).toString(16);
}
}
return val;
}
//清除蓝牙回码
const clearReadCode = () => {
BluetoothDetail.readCode = null;
}
//连接设备
const connectBLEDevice = () => {
return new Promise((resolve, rej) => {
my.connectBLEDevice({
deviceId: BluetoothDetail.deviceId,
success: res => {
//开始监听蓝牙状态
BLEConnectionStateChanged();
//获取服务特征
getBLEDeviceCharacteristics().then((res) => {
resolve(res)
}).catch((err) => {
rej(err)
});
},
fail: error => {
rej(error)
},
});
})
}
//停止搜索扫描
const stopBluetoothDevicesDiscovery = () => {
my.stopBluetoothDevicesDiscovery({
success: res => {
my.offBluetoothDeviceFound();
},
fail: error => {
},
});
}
//获取连接设备的charid,必须要再连接状态状态之下才能获取(这里分别筛选出读写特征字)
const getBLEDeviceCharacteristics = () => {
return new Promise((resolve, rej) => {
getConnectedBluetoothDevices().then(() =>{
my.getBLEDeviceCharacteristics({
deviceId: BluetoothDetail.deviceId,
serviceId: BluetoothDetail.serviceId,
success: res => {
for (let i = 0; i < res.characteristics.length; i++) {
let charc = res.characteristics[i];
if (charc.properties.notify || charc.properties.indicate) {
//通知特征
BluetoothDetail.notify_id = charc.characteristicId;
}
if (charc.properties.write) {
//读特征
BluetoothDetail.write_id = charc.characteristicId
}
if (charc.properties.read) {
//写特征
BluetoothDetail.read_id = charc.characteristicId
}
}
notifyBLECharacteristicValueChange().then((res) => {
resolve(res)
}).catch((error) => {
rej(error)
})
},
fail: error => {
rej(error)
console.log('获取设备特征值失败' + JSON.stringify(error))
},
});
})
})
}
//监听特征值数据变化
const notifyBLECharacteristicValueChange = () => {
//先取消特征值监听
my.offBLECharacteristicValueChange()
return new Promise((resolve, rej) => {
getConnectedBluetoothDevices().then(() => {
my.notifyBLECharacteristicValueChange({
state: true,
deviceId: BluetoothDetail.deviceId,
serviceId: BluetoothDetail.serviceId,
characteristicId: BluetoothDetail.notify_id,
success: () => {
//监听特征值变化的事件
my.onBLECharacteristicValueChange({
success: res => {
if(res.value == '4F6B'){
BluetoothDetail.readCode = res.value;
disconnectBLEDevice()
resolve(res)
}else{
rej(res.value)
}
},
});
writeBLECharacteristicValue().catch((e) => {
rej(e)
})
},
fail: error => {
rej(error)
},
});
});
})
}
//给蓝牙分包传输数据
const writeBLECharacteristicValue = () => {
return new Promise((resolve, rej) => {
let _startCmd = stringToHex(BluetoothDetail.startCmd) + '0A'
var i = 0;
writeBLESync(i, _startCmd).catch((error) => {
rej(error)
});
})
}
const writeBLESync = (i, writeData) => {
i += 20
return new Promise((resolve, rej) => {
//多包发送
if (i < writeData.length) {
var senddata = writeData
var dataSend = senddata.substring(i - 20, i)
my.writeBLECharacteristicValue({
deviceId: BluetoothDetail.deviceId,
serviceId: BluetoothDetail.serviceId,
characteristicId: BluetoothDetail.write_id,
value: dataSend,
success: res => {
writeBLESync(i, writeData);
},
fail: error => {
rej(error)
},
});
} else {
//第一包或者最后一包
var senddata = writeData
var endLength = senddata.length
var dataSend = senddata.substring(i - 20, endLength)
my.writeBLECharacteristicValue({
deviceId: BluetoothDetail.deviceId,
serviceId: BluetoothDetail.serviceId,
characteristicId: BluetoothDetail.write_id,
value: dataSend,
success: res => {
},
fail: error => {
rej(error)
},
});
}
})
}
//监听蓝牙连接状态
const BLEConnectionStateChanged = () => {
//防止重复调用监听事件,先关闭监听
my.offBLEConnectionStateChanged();
my.onBLEConnectionStateChanged({
success: (e) => {
//重新连接蓝牙
if (!e.connected) {
my.connectBLEDevice({
deviceId: BluetoothDetail.deviceId,
success: res => {
},
fail: error => {
},
});
}
},
})
}
//获取正在连接中的设备
const getConnectedBluetoothDevices = () => {
return new Promise((resolve, rej) => {
my.getConnectedBluetoothDevices({
success: res => {
if (res.devices.length === 0) {
rej(res)
return;
}
resolve(res)
},
fail: error => {
rej(error)
// console.log('获取正在连接的设备失败' + JSON.stringify(error))
},
});
})
}
//断开连接
const disconnectBLEDevice = () => {
my.disconnectBLEDevice({
deviceId: BluetoothDetail.deviceId,
success: () => {
my.offBLEConnectionStateChanged();
},
});
}
export default {
linkTimer,
startUseBlueTooth,
openBluetoothAdapter,
startBluetoothDevicesDiscovery,
connectBLEDevice,
getConnectedBluetoothDevices,
notifyBLECharacteristicValueChange,
writeBLECharacteristicValue,
BluetoothDetail,
clearReadCode,
}