目录
前言
连接蓝牙
开启蓝牙适配器
发现蓝牙
连接蓝牙
收发蓝牙数据
获取服务ID
获取特征值
读取蓝牙数据
写蓝牙数据
遇到的坑
获取serviceId的坑
特征值不支持读写
notify成功后立刻写蓝牙数据
工具方法
前言
原因是公司要搞个共享单车给内部员工使用,所以需要用手机连接锁蓝牙,然后扫码开锁。这个时候就要看看uni-app的蓝牙模块了。
uni-app的蓝牙模块看起来只支持低功耗蓝牙,即ble蓝牙。
然后公司就找了个厂商,这个厂商就给了我一份文档,如下图所示,
这份文档呢有一些指令,指令如下图,可以看到这些指令呢都是一些16进制的数据,即发送给蓝牙的数据为,0xfe 0x66 0x00 0x21 0x00 (app->车锁的模块)
最后的CRC是将前面的数据加密。
最后车锁会返回给APP数据,即 车锁->app模块。
厂家的蓝牙锁就是市面上常见的,如下图,有一个二维码,扫一下二维码开锁就是需要实现的逻辑。
连接蓝牙
首先需要开启蓝牙适配器,发现蓝牙,然后连接蓝牙。
对应模块分别为,
开启蓝牙适配器
发现蓝牙
当startBluetoothDevicesDiscovery调用后,搜索到的蓝牙就会在onBluetoothDeviceFound中被搜索到,
devices返回的数据如下,而本次的目标就是name:BleLock。
{
"devices": [
{
"deviceId": "2A:40:6F:28:24:90",
"name": "",
"RSSI": -53,
"localName": "",
.......
}
]
}
{
"devices": [
{
"deviceId": "32:71:0D:BF:43:FA",
"name": "",
"RSSI": -57,
"localName": "",
.......
}
]
}
{
"devices": [
{
"deviceId": "ED:C0:93:EF:0D:C5",
"name": "BleLock",
"RSSI": -45,
.......
}
]
}
连接蓝牙
与上面方法对应的还有,停止搜索,关闭蓝牙连接,关闭蓝牙适配器,在Uni-app的API中蓝牙栏目都有对应方法。
当发现到目标蓝牙后,一般就调用停止搜索方法,连接上蓝牙完成任务后就关闭蓝牙连接,关闭蓝牙适配器。
打开蓝牙适配器--> 监听搜索--> 开始搜索--> (发现目标蓝牙后) 关闭搜索-->
连接蓝牙--> 发送蓝牙指令 --> 断开蓝牙--> 关闭蓝牙适配器。
当然,打开蓝牙适配器只需要在onLoad中执行即可,不需要多次执行,但在我实际操作中,我发现有时候打开蓝牙适配器成功后,依旧无法连接蓝牙。
所以我每次在扫码时都是按照上述顺序来操作一遍,扫一次码走一遍流程,大不了失败后多扫码几次。
收发蓝牙数据
当连接上蓝牙后,需要收发蓝牙数据,收发蓝牙数据需要先获取服务ID,再获取特征值,即下图中的serviceId和characteristicId。
获取服务ID
调用此方法后,发现会输出如下数据,此处的uuid就是serviceId
[
{
"uuid": "00001800-0000-1000-8000-00805F9B34FB",
"isPrimary": true
},
{
"uuid": "00001801-0000-1000-8000-00805F9B34FB",
"isPrimary": true
},
{
"uuid": "6E400001-E6AC-A7E7-B1B3-E699BAE8D000",
"isPrimary": true
}
]
此时需要循环遍历每个serviceId获取到特征值,但是在厂商中只有6e4开头获取到的特征值才能操作蓝牙,所以就只需要提取此处6e4开头的serviceId即可。
获取特征值
下面的uuid就是特征值,其实都写数据只需要write:true和notify:true的特征值即可。
read:true表示支持读取蓝牙数据,
write:true表示支持向蓝牙写数据,
notify:true 表示支持监听蓝牙返回的数据,
[
{
"uuid": "6E400003-E6AC-A7E7-B1B3-E699BAE8D000",
"properties": {
"read": false,
"write": false,
"notify": true,
"indicate": false
}
},
{
"uuid": "6E400002-E6AC-A7E7-B1B3-E699BAE8D000",
"properties": {
"read": false,
"write": true,
"notify": false,
"indicate": false
}
}
]
读取蓝牙数据
一般是在连接蓝牙成功后就需要调用如下方法,在notify方法成功后就需要调用onBLECharacteristicValueChange方法,监听返回。
uni.notifyBLECharacteristicValueChange({
state: true, // 启用 notify 功能
// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
deviceId: ths.deviceInfo.deviceId,
// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
serviceId: serviceId,
// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
characteristicId: characteristicId,
success(res) {
// 必须在这里的回调才能获取
uni.onBLECharacteristicValueChange(function(res) {
var receiveValue = ths.ab2hex(res.value) //2进制数据转为16进制字符串
console.log("蓝牙返回数据为:"+receiveValue)
})
//获取key 此处一定要延迟,要等Notify成功后才能发送
setTimeout(function(){
ths.unlock();//开锁
},2000);
},
fail(res) {
console.log(res)
ths.startNotify = false;
},
})
// ArrayBuffer转16进度字符串示例
function ab2hex(buffer) {
const hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function (bit) {
return ('00' + bit.toString(16)).slice(-2)
}
)
return hexArr.join('')
}
蓝牙返回的数据是二进制的,是无法打印出来,所以需要转换为字符串。
写蓝牙数据
向蓝牙写入的是二进制数据,所以需要转为二进制,
根据厂商文档需要开锁,那么需要写入 0xfe 0x66 0x00 0x21 0x00
// 向蓝牙设备发送一个0x00的16进制数据
const buffer = new ArrayBuffer(6)
const dataView = new DataView(buffer)
dataView.setUint8(0,0xFE);//STX
dataView.setUint8(1,0x66);//NUM
dataView.setUint8(2,0x00);//通信秘钥 Key
dataView.setUint8(3,0x21);//CMd
dataView.setUint8(4,0x00);//LEN
dataView.setUint8(5,0xB4);//CRC
dataView.setUint8(6,0xF6);
uni.writeBLECharacteristicValue({
// 这里的 deviceId 需要在 getBluetoothDevices 或 onBluetoothDeviceFound 接口中获取
deviceId:deviceIdValue,
// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
serviceId:serviceIdValue,
// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
characteristicId:characteristicIdValue,
// 这里的value是ArrayBuffer类型
value: buffer,
success(res) {
console.log('writeBLECharacteristicValue success', res)
},
fail(res) {
console.log(res)
},
})
注意此处的success返回,只是告诉你蓝牙写成功否,并不是蓝牙的返回数据,蓝牙的返回数据在 onBLECharacteristicValueChange 中。
此时到这里应该是能操作蓝牙了,但是uni-app中还遇到一些坑。
遇到的坑
获取serviceId的坑
刚连接蓝牙成功时就调用getBLEDeviceServices获取,结果一直报蓝牙未连接,解决的方式是加个setTimeout,如果延迟后还获取不到serviceId,那么用户就重新扫码吧。
connectBle() {
var ths = this;
uni.createBLEConnection({
// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
deviceId:ths.deviceInfo.deviceId,
success(res) {
//此处也需要延迟,刚连接上时,无法获取到特征值
setTimeout(function(){
ths.getServices();
},2000)
},
fail(res) {
ths.showModalInfo("连接蓝牙失败");
},
complete(res) { //无论有没有连上蓝牙,都要停止搜索
ths.stopFindBluetooth(); //停止搜索蓝牙
}
})
},
特征值不支持读写
可以看到获取到了如下特征值,有write:true的(00002A00-0000-1000-8000-00805F9B34FB),但是用这个特征值写数据,你会发现就是写不了,会报特征值不支持写入。
原因是厂商设置了只支持6e4开头的特征值,麻蛋的,当时我以为是uni-app获取特征值数据错了呢!
[
{
"uuid": "00002A00-0000-1000-8000-00805F9B34FB",
"properties": {
"read": true,
"write": true,
"notify": false,
"indicate": false
}
},
{
"uuid": "00002A01-0000-1000-8000-00805F9B34FB",
"properties": {
"read": true,
"write": false,
"notify": false,
"indicate": false
}
},
{
"uuid": "00002A04-0000-1000-8000-00805F9B34FB",
"properties": {
"read": true,
"write": false,
"notify": false,
"indicate": false
}
}
]
notify成功后立刻写蓝牙数据
如下图可以看到,在notify成功后,我当时是立刻调用解锁方法,结果一直没有蓝牙数据返回,甚至写数据还报特征值不支持写入,no connection等,
这个鬼原因我也不知道,后面加个延迟解决了,推测应该是success调用应该不是真正的成功,需要等一会,
但不得不说,这个延迟就是定时炸弹,谁也不知道需要延迟多久。
可以看到在连接蓝牙成功后获取serviceId,Notify后发送指令都是需要延迟,明明给出success了,结果还是无法去调用。
针对这个问题,去看了官网,好像也没啥回应,大部分人也是延迟一下。
工具方法
/**
* 将一个比如abcd的字符串转为16进制数据
* @param {Object} str
*/
function stringToHex(str) {
var hexArray = [];
for (var i = 0; i < str.length; i++) {
var hexStr = str.charCodeAt(i).toString(16);
hexArray.push(str16(hexStr));
}
return hexArray;
}
var str = "yOTmK50z"
var dataArray = stringToHex("yOTmK50z"); //返回16进制的数组 0x79, 0x4f, 0x54, 0x6d, 0x4b, 0x35, 0x30,0x7a
/**
* 将字符串变为16进制数据
* @param {Object} str 16进制的字符串样式,不能是随便的字符串
*/
function str16(str) {
return parseInt(str, 16);
}
var hexStr = "0c"; //对应16进制,对应10进制12
var num = str16(str);//返回12,也可以是0x0c,两者一样
/**
* 将一个十进制的数变为十六进制的字符串
* @param {Object} value
*/
function int2HexStr(value) {
return value.toString(16);
}
var num = 12;
var hexStr = int2HexStr(num) //返回 0c
/**
* 字符串分割为数组,2位一个
* @param {Object} str
*/
function str2intArray(str) {
var length = str.length / 2;
var data = [];
for (var i = 0; i < length; i++) {
data[i] = str16(str.substring(i * 2, (i + 1) * 2));
}
return data;
}
//0x79, 0x4f, 0x54, 0x6d, 0x4b, 0x35, 0x30,0x7a
var hexStr = "794f546d4b35307a"
var dataArray = str2intArray(hexStr) //变为一个数组,里面保存16进制的数
/**
* 时间戳变为 yyyy-MM-dd HH:mm:ss格式的时间
* @param {Object} timestamp
*/
function timestamp2YYYYMMDDHHmmSS(timestamp){
var n=new Date(timestamp)
return n.toLocaleDateString().replace(/\//g, "-") + " " + n.toTimeString().substr(0, 8)
}
//获取时间戳
var timestampNum = Math.round(new Date() / 1000) // 返回秒级别的时间戳,1673248864
var hexStr = int2HexStr(timestampNum); // 1673248864 转换为16进制 63bbc060
var timestamp16Array = str2intArray(hexStr); // 将 63bbc060 变为 0x63 0xbb 0xc0 0x60的数组
//产生随机数
Math.round(Math.random() * 100); //[0,100)