目录

前言   

 连接蓝牙

开启蓝牙适配器

 发现蓝牙

连接蓝牙

收发蓝牙数据

 获取服务ID

获取特征值

读取蓝牙数据

写蓝牙数据

遇到的坑

获取serviceId的坑

  特征值不支持读写

 notify成功后立刻写蓝牙数据

工具方法


前言   

        原因是公司要搞个共享单车给内部员工使用,所以需要用手机连接锁蓝牙,然后扫码开锁。这个时候就要看看uni-app的蓝牙模块了。

   uni-app的蓝牙模块看起来只支持低功耗蓝牙,即ble蓝牙。

uniapp android 蓝牙写权限 uniapp开发蓝牙_数据

         然后公司就找了个厂商,这个厂商就给了我一份文档,如下图所示,

uniapp android 蓝牙写权限 uniapp开发蓝牙_16进制_02

         这份文档呢有一些指令,指令如下图,可以看到这些指令呢都是一些16进制的数据,即发送给蓝牙的数据为,0xfe 0x66 0x00 0x21 0x00 (app->车锁的模块)

最后的CRC是将前面的数据加密。

最后车锁会返回给APP数据,即 车锁->app模块。

uniapp android 蓝牙写权限 uniapp开发蓝牙_数据_03

        厂家的蓝牙锁就是市面上常见的,如下图,有一个二维码,扫一下二维码开锁就是需要实现的逻辑。

uniapp android 蓝牙写权限 uniapp开发蓝牙_数据_04

 连接蓝牙

        首先需要开启蓝牙适配器,发现蓝牙,然后连接蓝牙。

   对应模块分别为,

开启蓝牙适配器

uniapp android 蓝牙写权限 uniapp开发蓝牙_uni-app_05

 发现蓝牙

uniapp android 蓝牙写权限 uniapp开发蓝牙_16进制_06

 

uniapp android 蓝牙写权限 uniapp开发蓝牙_uni-app_07

 当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,
             .......
         }
     ]
 }


 

连接蓝牙

uniapp android 蓝牙写权限 uniapp开发蓝牙_uni-app_08

        与上面方法对应的还有,停止搜索,关闭蓝牙连接,关闭蓝牙适配器,在Uni-app的API中蓝牙栏目都有对应方法。

        当发现到目标蓝牙后,一般就调用停止搜索方法,连接上蓝牙完成任务后就关闭蓝牙连接,关闭蓝牙适配器。

打开蓝牙适配器--> 监听搜索--> 开始搜索--> (发现目标蓝牙后) 关闭搜索-->

连接蓝牙--> 发送蓝牙指令 --> 断开蓝牙--> 关闭蓝牙适配器。

    当然,打开蓝牙适配器只需要在onLoad中执行即可,不需要多次执行,但在我实际操作中,我发现有时候打开蓝牙适配器成功后,依旧无法连接蓝牙。

      所以我每次在扫码时都是按照上述顺序来操作一遍,扫一次码走一遍流程,大不了失败后多扫码几次。

收发蓝牙数据

        当连接上蓝牙后,需要收发蓝牙数据,收发蓝牙数据需要先获取服务ID,再获取特征值,即下图中的serviceId和characteristicId。

uniapp android 蓝牙写权限 uniapp开发蓝牙_数据_09

 

uniapp android 蓝牙写权限 uniapp开发蓝牙_16进制_10

 获取服务ID

uniapp android 蓝牙写权限 uniapp开发蓝牙_16进制_11

 调用此方法后,发现会输出如下数据,此处的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即可。

获取特征值

uniapp android 蓝牙写权限 uniapp开发蓝牙_特征值_12

        下面的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

uniapp android 蓝牙写权限 uniapp开发蓝牙_uni-app_13

// 向蓝牙设备发送一个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调用应该不是真正的成功,需要等一会,

        但不得不说,这个延迟就是定时炸弹,谁也不知道需要延迟多久。        

uniapp android 蓝牙写权限 uniapp开发蓝牙_16进制_14

        可以看到在连接蓝牙成功后获取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)