小程序完整的蓝牙操作过程
1. 小程序蓝牙操作一些诡异的问题罗列一下
- ios蓝牙的搜,连,写 或者 读 都是没有用问题,但是安卓问题很多
- 蓝牙连接成功后读写操作最好close一下
- 安卓的wx.onBluetoothDeviceFound 多次连接后发现在搜不到当前设备,原因是之前已经建立的通讯,后期则搜不到了
- 安卓在wx.createBLEConnection创建蓝牙连接后会的出现异常的err,需要重发机制几次即可连接成功
- wx.onBluetoothAdapterStateChange一旦发生变化,需要立刻 wx.offBluetoothAdapterStateChange(),不然会都次触发事件
- 搜索设备的时候需要定时器,不能无限制的搜索
- 特征值变化( wx.notifyBLECharacteristicValueChange)的时候需要添加定时器,为了考虑到手机兼容性
- 当你发现 一直在wx.startBluetoothDevicesDiscovery(),没有任何反应时候的,相当于安卓手机多次连接,产生很对进程,一种方案删除当前小程序进程 第二种,关闭蓝牙开关,等几秒 在开启。
安卓连接蓝牙失败率大概10%,ios基本都是ok(失败率可忽略)
2. 封装的js方法
// 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)
}
})
})
}
3. 操作方法
// demo
async updateDate() {
wx.showLoading({ title: '同步时间中' })
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('开启定时器')
this.timer = setTimeout(async () => {
await closeFromAirtu(this.ble)
clearTimeout(this.timer)
this.timer = null
}, 10000)
}
async clearTime() {
console.log('清除定时器')
clearTimeout(this.timer)
this.timer = null
}