微信小程序拥有连接低功耗蓝牙的功能,经典蓝牙模式暂时不支持,按照官方文档说法是在开发中。用uni-app开发小程序只是api的wx.变为了uni.其他就是一些语法和配置的不同。

这次要连接的蓝牙设备是富士康给代工的,没接触过其他电子厂的蓝牙,不过数据格式应该大同小异。在给BLE设备发送指令的时候要求分为如下几个部分:

一、蓝牙设备基础

一般想连接蓝牙设备需要一次获取到:
蓝牙的设备id(安卓下为mac地址,ios下不固定)、
设备的服务id(一个设备有多个服务,你需要找到你所需特征值的服务)
服务下的特征值(特征值相当于设备的通道,一般分为中心设备下发数据的通道和蓝牙设备上传数据的通道)

二、小程序API连接

下面是我要开发的蓝牙设备需要传输的数据格式。这一块一般硬件厂商都会给相关文档,没有太大难度。
image.png

每一部分数据有自己的释义,这个是单纯公司业务方面的,不做展示。

难点就在于给的文档太过简单,没明白数据发送时候还要发送数据头、长度…只是每次发送数据,导致设备总是登录不上….

这里就不贴出代码了,简单介绍一下蓝牙传输数据的步骤。

①用uni.openBluetoothAdapter初始化蓝牙设备(初始化成功后可通过uni.onBluetoothAdapterStateChange监听设备的初始化状态)
②通过

  1. uni.startBluetoothDevicesDiscovery({
  2. allowDuplicatesKey: false,
  3. success (res) {
  4. // 调用uni.onBluetoothDeviceFound
  5. }
  6. })

方法来扫描蓝牙,并在uni.onBluetoothDeviceFound中获取到扫描的设备(扫描到你需要的设备后,通过uni.stopBluetoothDevicesDiscovery来中止扫描)
在这一步周可以获取设备的mac地址、设备的id、设备的名称等信息

③上一步扫描到蓝牙设备后,通过uni.createBLEConnection来创建连接

④连接成功后,需要获取设备的服务uni.getBLEDeviceServices,在这个方法中来获取你需要的服务id

⑤获取到服务id后就需要uni.getBLEDeviceCharacteristics来获取设备额特征值。
至此设备的deviceId、serviceId和特征值id都获取到了,就可以给设备传送数据了

⑥uni.writeBLECharacteristicValue方法用来像设备中传送数据。

三、案例

比如说,我遇到的需求如下
image.png

那么在写入数据时候需要记住,dataView对象的写法类似这样:

  1. writeData(code) {
  2. const self = this
  3. const deviceId = this.deviceId
  4. const serviceId = this.serviceId
  5. let buffer = new ArrayBuffer(20) // 设备要求固定为20字节
  6. let dataView = new DataView(buffer)
  7. const length = self.calcLength(code) // 计算长度的方法
  8. const check = self.calcSumCheck(code) // 计算和校验的方法
  9. dataView.setUint8(0, '0xDB') // 固定,标记数据为下行
  10. dataView.setUint8(1, length) // 设备的要求
  11. dataView.setUint8(2, '0x00') // 没有分包,因此固定为0x00
  12. dataView.setUint8(3, code) // 这里的code是需要发送的指令,也和设备相关
  13. dataView.setUint8(19, check)
  14. // 需要注意的是setUint8的偏移位是按照数据格式要求进行偏移的
  15. uni.writeBLECharacteristicValue({
  16. deviceId,
  17. serviceId,
  18. characteristicId: self.characteristicWriteId,
  19. value: buffer,
  20. success(res) {
  21. //指令发送成功后的回调
  22. },
  23. fail(err) {
  24. console.log('写入出错' + err)
  25. }
  26. })
  27. },
  28. // 计算长度
  29. calcLength(code) {
  30. // 长度= 总包数与分包编号 + 数据 + 校验码长度
  31. const sum1 = hex2int('0x00')
  32. const sum2 = hex2int(code)
  33. const sum3 = 1 // 校验码长度
  34. const intSum = sum1 + sum2 + sum3
  35. return int2hex(intSum)
  36. },
  37. // 和校验规则
  38. calcSumCheck(code) {
  39. // 数据头0xDB + 长度 + 总包数与分包编号 + 数据
  40. const a1 = hex2int('0xDB')
  41. const a2 = hex2int(this.calcLength())
  42. const a3 = hex2int('0x00')
  43. const a4 = hex2int(code)
  44. const sum = int2hex(a1 + a2 + a3 + a4)
  45. if(sum.length > 4) {
  46. const tail = sum.slice(sum.length -2)
  47. return '0x' + tail
  48. }
  49. return sum
  50. }

上面有一个16进制转为10进制的方法,如下:

  1. export function hex2int(hex) {
  2. /*** hex参数形式为:
  3. * 1、接受形如'0xA1'这样的形式的字符串,然后去掉0x,转为字符串A1*
  4. * 接收形如A1这样的字符串"***/
  5. if(typeof hex !== 'string') {
  6. return
  7. }
  8. if(hex.indexOf('0x') > -1) {
  9. hex = String(hex).slice(2)
  10. }
  11. var len = hex.length, a = new Array(len), code;
  12. for (var i = 0; i < len; i++) {
  13. code = hex.charCodeAt(i)
  14. if (48<=code && code < 58) {
  15. code -= 48;
  16. } else {
  17. code = (code & 0xdf) - 65 + 10;
  18. }
  19. a[i] = code;
  20. }
  21. return a.reduce(function(acc, c) {
  22. acc = 16 * acc + c;
  23. return acc;
  24. }, 0)
  25. }

四、数据加密

在数据传输的时候一般需要加密,我这次的需求是AES-CTR加密,有固定的偏移量和key算法。
比如我固定偏移量为iv = ‘1234567891234567’ 共计为16位
key算法为蓝牙设备的mac地址加上固定值3456,比如我的mac地址为: 3C3C3C3C3C3C,则我的key为3C3C3C3C3C3C3456

以上的偏移量iv和key是按照自己的硬件厂商实际规定来的,各个蓝牙设备不一样。
我用的插件为 “crypto-js”: “3.3.0” 固定版本号3.3.0。版本过高或者过低都不适合下面代码的写法,需要按照自己的实际需求来~

  1. const CryptoJS = require('crypto-js')
  2. // 加密方法
  3. aesEncryptCrypto(data) {
  4. /**
  5. * CipherOption, 加密的一些选项:
  6. * mode: 加密模式, 可取值(CBC, CFB, CTR, CTRGladman, OFB, ECB), 都在 CryptoJS.mode 对象下
  7. * padding: 填充方式, 可取值(Pkcs7, AnsiX923, Iso10126, Iso97971, ZeroPadding, NoPadding), 都在 CryptoJS.pad 对象下
  8. * iv: 偏移量, mode === ECB 时, 不需要 iv
  9. * 返回的是一个加密对象
  10. */
  11. const key = this.deviceMacStr + '3456'
  12. const cryptkey = CryptoJS.enc.Utf8.parse(key)
  13. const iv = CryptoJS.enc.Hex.parse('1234567891234567')
  14. let srcs = CryptoJS.enc.Utf8.parse(data);
  15. const cipher = CryptoJS.AES.encrypt(srcs, cryptkey, {
  16. mode: CryptoJS.mode.CTR,
  17. padding: CryptoJS.pad.NoPadding,
  18. iv,
  19. });
  20. // 将加密后的数据转换成 Base64
  21. // const base64Cipher = cipher.toString()
  22. const strHex = cipher.ciphertext.toString()
  23. return strHex
  24. }
  25. // 解密方法
  26. aesDecryptCrypto (encrypted) {
  27. let encryptedHexStr = CryptoJS.enc.Hex.parse(encrypted);
  28. let encryptedBase64Str = CryptoJS.enc.Base64.stringify(encryptedHexStr);
  29. const key = this.deviceMacStr + '3456'
  30. const cryptkey = CryptoJS.enc.Utf8.parse(key)
  31. const iv = CryptoJS.enc.Hex.parse('1234567891234567')
  32. const decipher = CryptoJS.AES.decrypt(encryptedBase64Str, cryptkey, {
  33. mode: CryptoJS.mode.CTR,
  34. padding: CryptoJS.pad.NoPadding,
  35. iv,
  36. });
  37. let text = decipher.toString(CryptoJS.enc.Utf8)
  38. return text
  39. }