Small procedures complete bluetooth operation process

1. Small program Bluetooth operation some weird problems listed

  • Search, connect, write, or read on ios Bluetooth is useless, but android has a lot of problems
  • After the bluetooth connection is successful, you’d better close the read and write operation
  • Android wx. OnBluetoothDeviceFound connection for the coma is less than the current equipment for many times, the reason is established before the communication, the late is searched
  • Wx. CreateBLEConnection creates a Bluetooth connection that causes an err error
  • Wx. OnBluetoothAdapterStateChange once change, need to immediately wx offBluetoothAdapterStateChange (), or you will have time to trigger events
  • A timer is required for device search
  • Characteristic value changes (wx notifyBLECharacteristicValueChange) need to add a timer, in order to take into account the phone compatibility
  • When you find has been wx. StartBluetoothDevicesDiscovery (), no response time, equivalent to the android mobile phone connection for many times, is in process, a solution to delete the current process of small programs A second, switch off bluetooth, wait a few seconds In the open.

Android bluetooth connection failure rate is about 10%, ios is basically OK (failure rate can be ignored)

2. Wrapped JS methods

// airtuBLE.js
export async function initBLEFromAirtu({ name }) {
  let devices = []
  let available = false // 设备可用
  let discovering = false // 搜索状态
  let serviceId = '' // 服务Id
  let characteristicId = '' // 特征值
  let deviceId = '' // mac 地址 (ios的mac地址是UUID,由于隐私保护的原因引起的)

  return new Promise((resolve, reject) => {
    initBluetooth()

    // 初始化蓝牙设备
    function initBluetooth() {
      // 版本过低兼容
      if (!wx.openBluetoothAdapter) {
        wx.showModal({
          title: '提示',
          showCancel: false,
          content:
            '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。'
        })
        return
      }
      console.info('1. 打开蓝牙适配器')

      wx.openBluetoothAdapter({
        success: res => {
          console.log('2. 初始化蓝牙模块')
          findExistList()
        },
        fail(err) {
          console.log('2.1 初始化蓝牙模块 - 失败')
          watchBluetoothStateChange()
          if (err.errCode == 10001) {
            wx.showToast({
              title: '蓝牙未开启',
              icon: 'none'
            })
          }
        }
      })
    }

     // 查找已经存在的设备( 安卓多次连接会导致导致搜索不到)
    function findExistList() {
      wx.getBluetoothDevices({
        success: res => {
          if (res && res.devices && res.devices.length) {
            res.devices.forEach(device => {
              // console.log('蓝牙名:', device.name);
              if (device.name && device.name.indexOf(`ET01-${name}`) > -1) {
                console.log('5.2 查询已经搜索的列表, 目标设备为:', device.name)
                deviceId = device.deviceId
                // 连接成功 需要断开蓝牙搜索
                stopSearchBluetooth()
                connectBluetooth()
              } else {
                watchBluetoothStateChange()
                searchBluetooth()
              }
            })
          } else {
            watchBluetoothStateChange()
            searchBluetooth()
          }
        },
        fail: () => {
          console.log('搜索蓝牙设备失败')
          watchBluetoothStateChange()
          searchBluetooth()
        }
      })
    }

    // 监听蓝牙适配器状态变化事件
    function watchBluetoothStateChange() {
      wx.onBluetoothAdapterStateChange(res => {
        console.log('3. 获取蓝牙适配器状态改变')

        // 搜索状态
        if (discovering != res.discovering) {
          discovering = res.discovering
          wx.offBluetoothAdapterStateChange()
          searchBluetooth()
        }
        // 蓝牙状态
        if (available != res.available) {
          available = res.available
          if (!res.available) {
            wx.showToast({
              title: '蓝牙未开启',
              icon: 'none'
            })
            console.log('init - 蓝牙适配器不可用')
          } else {
            if (!res.discovering && !devices.length) {
              wx.offBluetoothAdapterStateChange()
              searchBluetooth()
            }
          }
        }
      })
    }

    // 查找设备
    function searchBluetooth() {
      wx.startBluetoothDevicesDiscovery({
        allowDuplicatesKey: false,
        success: res => {
          console.log('4. 开始查找设备')
          watchBluetoothFound()
          // 30s 停止搜索
          let timer = setTimeout(() => {
            stopSearchBluetooth()
            clearTimeout(timer)
            timer = null
          }, 30000)
        },
        fail: err => {
          console.log('4.1-err', err)
        }
      })
    }

    // 监听寻找到新设备
    function watchBluetoothFound() {
      wx.onBluetoothDeviceFound(res => {
        let device = res.devices[0]
        // console.log('device', device.localName, device.name);
        if (device.localName && device.localName.indexOf(`ET01-${name}`) > -1) {
          console.log('5. 搜索成功, 目标设备为:', device.localName)
          deviceId = device.deviceId
          // 连接成功 需要断开蓝牙搜索
          stopSearchBluetooth()
          connectBluetooth()
        }
      })
    }
    // 停止查找
    function stopSearchBluetooth() {
      wx.stopBluetoothDevicesDiscovery({
        success: res => {
          console.log('6. 蓝牙停止查找')
        },
        fail: (err) => {
          console.log('6.1-err', err);
          // reject(err);
        },
      })
    }

    // 连接设备
    // 有时候比较诡异的问题,蓝牙连接失败,多连接几次就成功
    let reconnectCounts = 0
    function connectBluetooth() {
      reconnectCounts++
      wx.createBLEConnection({
        deviceId,
        success: res => {
          console.log('7. 建立设备连接')
          reconnectCounts = null
          getBluetoothServers()
        },
        fail: err => {
          console.log('7.1-err 连接失败,结束', err)
          console.log('重新连接蓝牙通讯第:', reconnectCounts, ' 次')
          if (reconnectCounts <= 3) {
            connectBluetooth()
          } else {
            reject({ deviceId })
            let tt = setTimeout(() => {
              clearTimeout(tt)
              tt = null
              showToastSimple('蓝牙异常')
            }, 200)
            closeFromAirtu({ deviceId })
          }
        }
      })
    }

    // 获取设备服务
    function getBluetoothServers() {
      wx.getBLEDeviceServices({
        deviceId,
        success: res => {
          console.log('8. 获取设备服务')
          serviceId = res.services[0].uuid
          getBluetoothCharacteristics()
        },
        fail: err => {
          console.log('8.1-err', err)
          reject({ deviceId })
        }
      })
    }

    // 获取设备某个服务特征值列表
    function getBluetoothCharacteristics() {
      wx.getBLEDeviceCharacteristics({
        deviceId,
        serviceId,
        success(res) {
          console.log('9. 获取设备服务特征值')
          for (let i = 0; i < res.characteristics.length; i++) {
            const characteristic = res.characteristics[i]
            if (characteristic.properties && characteristic.properties.notify) {
              characteristicId = characteristic.uuid
              notifyBluetoothCharacteristicValueChange()
              return
            }
          }
        },
        fail: err => {
          console.log('9.1-err', err)
          reject({ deviceId })
        }
      })
    }

    // 启用设备特征值变化时的 notify 功能
    function notifyBluetoothCharacteristicValueChange() {
      wx.notifyBLECharacteristicValueChange({
        deviceId,
        serviceId,
        characteristicId,
        state: true,
        type: 'notification',
        success(res) {
          console.log('10. 启用设备特征值变化提醒')
          let timer = setTimeout(() => {
            clearTimeout(timer)
            timer = null
            resolve({
              deviceId,
              serviceId,
              characteristicId
            })

            // 字段重置
            devices = []
            available = false // 设备可用
            discovering = false // 搜索状态
            serviceId = '' // 服务Id
            characteristicId = '' // 特征值
            deviceId = '' // mac 地址 (ios的mac地址是UUID,由于隐私保护的原因引起的)
          }, 20)
        },
        fail: err => {
          console.log('10.1-err', err)
          reject({ deviceId })
        }
      })
    }
  })
}

/**
 * 断开蓝牙
 * @params deviceId String
 */
export async function closeFromAirtu({ deviceId }) {
  return new Promise((resolve, reject) => {
    // wx.hideLoading()
    if (deviceId) {
      wx.closeBLEConnection({
        deviceId,
        success: () => {
          console.log('断开与低功耗蓝牙设备的连接')
          // 断开蓝牙的连接 (初始化所有的状态)
          wx.closeBluetoothAdapter({
            success: () => {
              console.log('关闭蓝牙模块')
              resolve()
            }
          })
        },
        fail: err => {
          console.log('断开与低功耗蓝牙设备的连接--err', err)
        }
      })
    } else {
      resolve()
    }
  })
}



/**
 * 获取设备时间
 * @params deviceId String
 * @params serviceId String
 * @params characteristicId String
 * @return "" String 设备时间
 */
export async function getTimeFromAirtu({
  deviceId,
  serviceId,
  characteristicId
}) {
  console.info('---- 获取设备时间 ----')
  return new Promise((resolve, reject) => {
    // 获取时间指令
    // 9A 00 03 82 00 E1
    var arr = ['9A', '00', '03', '82', '00', 'E1']

    wx.writeBLECharacteristicValue({
      deviceId,
      serviceId,
      characteristicId,
      value: strToBuf(arr),
      success(res) {
        console.log('写入成功', arr.join(''))
        // 监听设备的特征值变化
        wx.onBLECharacteristicValueChange(res => {
          resolve(ab2hex(res.value))
        })
      },
      fail(res) {
        console.log('写入失败 结束')
        reject()
      }
    })
  })
}




// hex转ArrayBuffer
function strToBuf(arr) {
  var length = arr.length
  var buffer = new ArrayBuffer(length + 2)
  var dataview = new DataView(buffer)
  for (let i = 0; i < length; i++) {
    dataview.setUint8(i, '0x' + arr[i])
  }
  return buffer
}

// 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('')
}


function pad(num, n) {
  var i = (num + '').length
  while (i++ < n) num = '0' + num
  return num
}


// 10 进制转 16 进制
function turnFrom10To16(num) {
  return num < 16 ? '0' + num.toString(16) : num.toString(16)
}

// 16进制转10进制
function turnFrom16To10(hex) {
  var dateArr = []
  for (var i = 0; i < hex.length; i += 2) {
    dateArr.push(hexToString(hex.substr(i, 2)))
  }
  return dateArr
}

// 字符串每隔几位切割数组
// source 目标字符串
// count  每隔几位切割
function joinStringByCount(source, count) {
  let arr = []
  for (let i = 0, len = source.length / count; i < len; i++) {
    let subStr = source.substr(0, count)
    arr.push(subStr)
    source = source.replace(subStr, '')
  }
  return arr
}

function hexToString(hex) {
  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)
}

function showToastSimple(error) {
  return new Promise(resolve => {
    wx.showToast({
      title: error,
      icon: 'none',
      duration: 2000,
      success(res) {
        resolve(res)
      }
    })
  })
}

Copy the code

3. Operation method

// Demo async updateDate() {wx.showloading ({title: 'in sync'}) this.startTime() await initBLEFromAirtu({name: 12345678 }) .then(async ble => { this.ble = ble await getTimeFromAirtu(ble) await closeFromAirtu(ble) }) .catch(async err => { await closeFromAirtu(this.ble) this.clearTime() }) }, StartTime () {console.log(' start timer ') this.timer = setTimeout(async () => {await closeFromAirtu(this.ble) clearTimeout(this.timer) this.timer = null }, 10000)} async clearTime() {console.log(' Clear timer ') clearTimeout(this.timer) this.timer = null}Copy the code