微信小程序拥有连接低功耗蓝牙的功能,经典蓝牙模式暂时不支持,按照官方文档说法是在开发中。用uni-app开发小程序只是api的wx.变为了uni.其他就是一些语法和配置的不同。
这次要连接的蓝牙设备是富士康给代工的,没接触过其他电子厂的蓝牙,不过数据格式应该大同小异。在给BLE设备发送指令的时候要求分为如下几个部分:
一、蓝牙设备基础
一般想连接蓝牙设备需要一次获取到:
蓝牙的设备id(安卓下为mac地址,ios下不固定)、
设备的服务id(一个设备有多个服务,你需要找到你所需特征值的服务)
服务下的特征值(特征值相当于设备的通道,一般分为中心设备下发数据的通道和蓝牙设备上传数据的通道)
二、小程序API连接
下面是我要开发的蓝牙设备需要传输的数据格式。这一块一般硬件厂商都会给相关文档,没有太大难度。
每一部分数据有自己的释义,这个是单纯公司业务方面的,不做展示。
难点就在于给的文档太过简单,没明白数据发送时候还要发送数据头、长度…只是每次发送数据,导致设备总是登录不上….
这里就不贴出代码了,简单介绍一下蓝牙传输数据的步骤。
①用uni.openBluetoothAdapter初始化蓝牙设备(初始化成功后可通过uni.onBluetoothAdapterStateChange监听设备的初始化状态)
②通过
uni.startBluetoothDevicesDiscovery({
allowDuplicatesKey: false,
success (res) {
// 调用uni.onBluetoothDeviceFound
}
})
方法来扫描蓝牙,并在uni.onBluetoothDeviceFound中获取到扫描的设备(扫描到你需要的设备后,通过uni.stopBluetoothDevicesDiscovery来中止扫描)
在这一步周可以获取设备的mac地址、设备的id、设备的名称等信息
③上一步扫描到蓝牙设备后,通过uni.createBLEConnection来创建连接
④连接成功后,需要获取设备的服务uni.getBLEDeviceServices,在这个方法中来获取你需要的服务id
⑤获取到服务id后就需要uni.getBLEDeviceCharacteristics来获取设备额特征值。
至此设备的deviceId、serviceId和特征值id都获取到了,就可以给设备传送数据了
⑥uni.writeBLECharacteristicValue方法用来像设备中传送数据。
三、案例
比如说,我遇到的需求如下
那么在写入数据时候需要记住,dataView对象的写法类似这样:
writeData(code) {
const self = this
const deviceId = this.deviceId
const serviceId = this.serviceId
let buffer = new ArrayBuffer(20) // 设备要求固定为20字节
let dataView = new DataView(buffer)
const length = self.calcLength(code) // 计算长度的方法
const check = self.calcSumCheck(code) // 计算和校验的方法
dataView.setUint8(0, '0xDB') // 固定,标记数据为下行
dataView.setUint8(1, length) // 设备的要求
dataView.setUint8(2, '0x00') // 没有分包,因此固定为0x00
dataView.setUint8(3, code) // 这里的code是需要发送的指令,也和设备相关
dataView.setUint8(19, check)
// 需要注意的是setUint8的偏移位是按照数据格式要求进行偏移的
uni.writeBLECharacteristicValue({
deviceId,
serviceId,
characteristicId: self.characteristicWriteId,
value: buffer,
success(res) {
//指令发送成功后的回调
},
fail(err) {
console.log('写入出错' + err)
}
})
},
// 计算长度
calcLength(code) {
// 长度= 总包数与分包编号 + 数据 + 校验码长度
const sum1 = hex2int('0x00')
const sum2 = hex2int(code)
const sum3 = 1 // 校验码长度
const intSum = sum1 + sum2 + sum3
return int2hex(intSum)
},
// 和校验规则
calcSumCheck(code) {
// 数据头0xDB + 长度 + 总包数与分包编号 + 数据
const a1 = hex2int('0xDB')
const a2 = hex2int(this.calcLength())
const a3 = hex2int('0x00')
const a4 = hex2int(code)
const sum = int2hex(a1 + a2 + a3 + a4)
if(sum.length > 4) {
const tail = sum.slice(sum.length -2)
return '0x' + tail
}
return sum
}
上面有一个16进制转为10进制的方法,如下:
export function hex2int(hex) {
/*** hex参数形式为:
* 1、接受形如'0xA1'这样的形式的字符串,然后去掉0x,转为字符串A1*
* 接收形如A1这样的字符串"***/
if(typeof hex !== 'string') {
return
}
if(hex.indexOf('0x') > -1) {
hex = String(hex).slice(2)
}
var len = hex.length, a = new Array(len), code;
for (var i = 0; i < len; i++) {
code = hex.charCodeAt(i)
if (48<=code && code < 58) {
code -= 48;
} else {
code = (code & 0xdf) - 65 + 10;
}
a[i] = code;
}
return a.reduce(function(acc, c) {
acc = 16 * acc + c;
return acc;
}, 0)
}
四、数据加密
在数据传输的时候一般需要加密,我这次的需求是AES-CTR加密,有固定的偏移量和key算法。
比如我固定偏移量为iv = ‘1234567891234567’ 共计为16位
key算法为蓝牙设备的mac地址加上固定值3456,比如我的mac地址为: 3C3C3C3C3C3C,则我的key为3C3C3C3C3C3C3456
以上的偏移量iv和key是按照自己的硬件厂商实际规定来的,各个蓝牙设备不一样。
我用的插件为 “crypto-js”: “3.3.0” 固定版本号3.3.0。版本过高或者过低都不适合下面代码的写法,需要按照自己的实际需求来~
const CryptoJS = require('crypto-js')
// 加密方法
aesEncryptCrypto(data) {
/**
* CipherOption, 加密的一些选项:
* mode: 加密模式, 可取值(CBC, CFB, CTR, CTRGladman, OFB, ECB), 都在 CryptoJS.mode 对象下
* padding: 填充方式, 可取值(Pkcs7, AnsiX923, Iso10126, Iso97971, ZeroPadding, NoPadding), 都在 CryptoJS.pad 对象下
* iv: 偏移量, mode === ECB 时, 不需要 iv
* 返回的是一个加密对象
*/
const key = this.deviceMacStr + '3456'
const cryptkey = CryptoJS.enc.Utf8.parse(key)
const iv = CryptoJS.enc.Hex.parse('1234567891234567')
let srcs = CryptoJS.enc.Utf8.parse(data);
const cipher = CryptoJS.AES.encrypt(srcs, cryptkey, {
mode: CryptoJS.mode.CTR,
padding: CryptoJS.pad.NoPadding,
iv,
});
// 将加密后的数据转换成 Base64
// const base64Cipher = cipher.toString()
const strHex = cipher.ciphertext.toString()
return strHex
}
// 解密方法
aesDecryptCrypto (encrypted) {
let encryptedHexStr = CryptoJS.enc.Hex.parse(encrypted);
let encryptedBase64Str = CryptoJS.enc.Base64.stringify(encryptedHexStr);
const key = this.deviceMacStr + '3456'
const cryptkey = CryptoJS.enc.Utf8.parse(key)
const iv = CryptoJS.enc.Hex.parse('1234567891234567')
const decipher = CryptoJS.AES.decrypt(encryptedBase64Str, cryptkey, {
mode: CryptoJS.mode.CTR,
padding: CryptoJS.pad.NoPadding,
iv,
});
let text = decipher.toString(CryptoJS.enc.Utf8)
return text
}