蓝牙通信
简介
- 近期公司遇到的新需求,之前没有太了解过这一方面,捣鼓了两天终于写完了,主要需求是封装一个蓝牙工具包提供给小程序从而实现反复调用,需要注意的一点是接收方那边需要的数据类型。
本例是蓝牙通信功能
本例使用到的小程序原生api:
- 打开蓝牙适配器:wx.openBluetoothAdapter,后续所有蓝牙模块功能都需要先打开适配器才能进行
- 搜寻蓝牙设备:
- 开始搜寻:wx.startBluetoothDevicesDiscovery,此功能比较消耗性能,如果搜索到特定设备可即时停止
- 发现设备事件:wx.onBluetoothDeviceFound,在这儿添加实时更新设备列表业务代码
- 停止扫描:wx.onBluetoothDeviceFound,停止扫描新的蓝牙设备,当蓝牙扫描到指令设备时,需要即时关闭扫描保证性能
- 关闭发现设备事件监听:wx.offBluetoothDeviceFound
- 连接蓝牙设备: wx.createBLEConnection,通过传入蓝牙设备deviceId进行设备直连。这里的deviceId可通过上面扫描时wx.onBluetoothDeviceFound响应值获取
- 监听蓝牙设备连接状态:wx.onBLEConnectionStateChange: 包括开发者主动连接或断开连接,设备丢失,连接异常断开等等
- 获取蓝牙服务
- 获取蓝牙低功耗设备所有服务: wx.getBLEDeviceServices,通过
- 根据特定服务UUID查询所有特征:[wx.getBLEDeviceCharacteristics](wx.getBLEDeviceCharacteristics),
- 监听蓝牙数据(实时获取蓝牙传回的信息)
- 订阅特征变化:wx.notifyBLECharacteristicValueChange,开启订阅后续才能监听到蓝牙数据变化
- 监听特征值变化:wx.onBLECharacteristicValueChange,通过监听事件触发数据解析业务
- 发送数据(向蓝牙跳绳下发指令)
- 下发指令:wx.writeBLECharacteristicValue,通过向蓝牙特定服务的对应特征值写入数据,完成交互。注意:要对应支持“write"属性的特征值
- 关闭蓝牙活动
- wx.stopBluetoothDevicesDiscovery(): 停止扫描新设备
- wx.offBluetoothDeviceFound():关闭扫描新设备监听事件
- wx.offBLECharacteristicValueChange():关闭特征值变化监听(数据监听)
- wx.offBLEConnectionStateChange():移除蓝牙低功耗连接状态改变事件的监听函数
- wx.closeBLEConnection: 断开蓝牙连接
- wx.closeBluetoothAdapter():关闭蓝牙适配器
正式开发
1.初始化蓝牙设备
// 初始化蓝牙设备
function initBluetooth() {
// 版本过低不兼容
console.log('wx.openBluetoothAdapter', wx.openBluetoothAdapter)
if (!wx.openBluetoothAdapter) {
wx.showModal({
title: '提示',
showCancel: false,
content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。',
})
return
}
console.log('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'
})
}
}
})
}
2.查找已存在的设备(tips:安卓多次连接会导致搜索不到,同时避免直接从手机蓝牙连接设备,如连接则先从手机蓝牙中删除设备)
function findExistList() {
wx.getBluetoothDevices({
success: res => {
console.log(res, '进入查找事件')
if (res && res.devices && res.devices.length > 0) {
console.log('name', name)
res.devices.forEach(device => {
console.log('蓝牙名', device.name)
if ((device.name && device.name.indexOf(name) > -1)) {
console.log('5.2 查询已经搜索的列表,目标设备为:', device.name)
deviceId = device.deviceId
// 链接成功,需要断开蓝牙搜索
stopSearchBluetooth()
connectBluetooth()
} else {
console.log('查找失败', name)
watchBluetoothStateChange()
searchBluetooth()
}
})
} else {
console.log('查找失败', name)
watchBluetoothStateChange()
searchBluetooth()
}
},
fail: err => {
console.log('搜索蓝牙设备失败')
watchBluetoothStateChange()
searchBluetooth()
}
})
}
3.监听蓝牙适配器状态变化事件
function watchBluetoothStateChange() {
wx.onBluetoothAdapterStateChange((result) => {
console.log('3.获取蓝牙适配器状态改变', result)
// 搜索状态
if (discovering != result.discovering) {
discovering = result.discovering
// 移除蓝牙适配器状态变化的所有监听函数
wx.offBluetoothAdapterStateChange()
searchBluetooth()
}
// 蓝牙状态
if (available != result.available) {
available = result.available
if (!result.available) {
wx.showToast({
title: '蓝牙未开启',
icon: 'none'
})
console.log('init - 蓝牙适配器不可用')
} else {
if (!result.discovering && !devices.length) {
wx.offBluetoothAdapterStateChange()
searchBluetooth()
}
}
}
})
}
4.查找设备
function searchBluetooth() {
wx.startBluetoothDevicesDiscovery({
allowDuplicatesKey: false,
success: res => {
console.log('4.开始查找设备', res)
watchBluetoothFound()
// 30s停止搜索
let timer = setTimeout(() => {
stopSearchBluetooth()
clearTimeout(timer)
timer = null
}, 30000)
},
fail: err => {
console.log('4.1 查找设备失败- err', err)
}
})
}
5.监听寻找新设备
function watchBluetoothFound() {
wx.onBluetoothDeviceFound((res) => {
let device = res.devices[0]
if (device.localName && device.localName.indexOf(name) > -1) {
console.log('5. 搜索成功,目标设备为:', device.localName, res)
deviceId = device.deviceId
// 链接成功 断开蓝牙搜索
stopSearchBluetooth()
connectBluetooth()
}
})
}
6.停止查找
function stopSearchBluetooth() {
wx.stopBluetoothDevicesDiscovery({
success: res => {
console.log('6.蓝牙停止查找', res)
},
fail: (err) => {
console.log('6.1 蓝牙查找失败', err)
}
})
}
7.连接设备(此处为避免连接失败,反复执行连接,多次尝试)
let reconnectCounts = 0
function connectBluetooth() {
reconnectCounts++
wx.createBLEConnection({
deviceId,
success: (res) => {
console.log('7.建立设备连接', res)
reconnectCounts = null
getBluetoothServers()
},
fail: err => {
console.log('7.1 蓝牙连接失败', err)
console.log('重新建立蓝牙通讯第:', reconnectCounts, '次')
if (reconnectCounts <= 3) {
connectBluetooth()
} else {
reject({
deviceId
})
let tt = setTimeout(() => {
clearTimeout(tt)
tt = null
showToastSimple('蓝牙异常')
}, 200)
closeFromAirtu({
deviceId
})
}
}
})
}
8.获取设备服务
function getBluetoothServers() {
wx.getBLEDeviceServices({
deviceId,
success: res => {
if (res.services.length > 0) {
res.services.forEach(item => {
console.log('8.获取设备服务', item)
serviceId = item.uuid
services = res.services
getBluetoothCharacteristics()
})
} else {
console.log('8-1.获取设备服务', res)
serviceId = res.services[0].uuid
services = res.services
getBluetoothCharacteristics()
}
},
fail: err => {
console.log('8.1 获取设备服务失败', err)
reject({
deviceId
})
}
})
}
9.获取设备某个服务特征值列表
function getBluetoothCharacteristics() {
wx.getBLEDeviceCharacteristics({
deviceId: deviceId,
serviceId,
success(res) {
console.log('9.获取设备服务特征值', res)
res.characteristics.forEach(item => {
if (item.properties && item.properties.write && item.properties.notify) {
console.log('9-1.获取服务特征值', item)
characteristicId = item.uuid
notifyBluetoothCharacteristicValueChange(item.uuid)
.then(() => {
let timer = setTimeout(() => {
console.log('================设备连接成功================')
resolve({
deviceId,
serviceId,
characteristicId
})
clearTimeout(timer)
timer = null
// 字段重置
devices = []
available = false //设备可用
discovering = false //搜索状态
serviceId = '' //服务ID
characteristicId = '' //特征值
deviceId = '' // mac 地址 (ios的mac地址是UUID,由于隐私保护的原因引起的)
}, 20)
return
})
.catch(() => {
console.error('启用通知失败,尝试下一个特征值或停止')
})
return;
}
})
},
fail: err => {
console.log('9.1 获取设备服务特征值失败', err)
reject({
deviceId
})
}
})
}
10.启用设备特征值变化时的notify功能(主要用来接收蓝牙设备返回的回调)
function notifyBluetoothCharacteristicValueChange(characteristicIds) {
return new Promise((resolve, reject) => {
wx.notifyBLECharacteristicValueChange({
characteristicId: characteristicIds,
deviceId,
serviceId,
state: true,
success: () => {
console.log('10.启用设备特征值变化提醒')
resolve()
},
fail: err => {
console.log('10.1启用设备特征值失败', err)
reject(err)
}
})
})
}
11.发送指令给蓝牙(这一部分需根据实际情况修改发送的数据类型)
export async function writeBLECharacteristicValue({
deviceId,
serviceId,
characteristicId,
value
}) {
return new Promise((resolve, reject) => {
let hex = value
// 向蓝牙发送16进制的数据
let typedArray = new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function (h) {
return parseInt(h, 16)
}))
let buffer1 = typedArray.buffer
console.log('准备发送的指令:',value,typedArray,database.ab2hex(typedArray))
wx.writeBLECharacteristicValue({
characteristicId,
deviceId,
serviceId,
value:buffer1,
success(res) {
console.log('写入成功', res, value)
// 监听设备的特征值变化
wx.onBLECharacteristicValueChange((result) => {
// resolve(ab2hex(result.value))
console.log('特征值变化',result,database.ab2hex(result.value))
})
},
fail(err) {
console.log('写入失败,结束', err)
reject()
}
})
})
}
12.断开蓝牙
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()
}
})
}
一些数据处理方法
// 数据处理的公共方法
function inArray(arr, key, val) {
for (let i = 0; i < arr.length; i++) {
if (arr[i][key] === val) {
return i;
}
}
return -1;
}
function split_array(arr, len) {
var a_len = arr.length;
var result = [];
for (var i = 0; i < a_len; i += len) {
result.push(arr.slice(i, i + len));
}
return result;
}
function calcCrc(dataView) {
//计算指令的合取最低8位,是因为这个设备的指令是这个需求,实际按设备对接文档来
let crc = 0;
for (let i = 0; i < 15; i++) {
crc += dataView.getUint8(i);
}
return crc
}
/**
*ArrayBuffer转16进制字符串
* @param {buffer} buffer
*/
function ab2hex(buffer) {
let hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function (bit) {
return ('00' + bit.toString(16)).slice(-2)
}
)
return hexArr.join('');
}
/**
* 16进制字符串转字节数组
* @param {string} str
*/
function str2Bytes(str) {
let pos = 0;
let len = str.length;
if (len % 2 != 0) {
return null;
}
len /= 2;
let hexA = new Array();
for (let i = 0; i < len; i++) {
let s = str.substr(pos, 2);
let v = s; //处理
hexA.push(v);
pos += 2;
}
return hexA;
}
/**
* 反转
* @param {string} num
*/
function reverse(num) {
return num.split(',').reverse().join('');
}
module.exports = {
inArray,
split_array,
calcCrc,
ab2hex,
str2Bytes,
reverse
}
完整代码如下
/**
* 蓝牙链接公共方法工具包
*/
/**
* 蓝牙连接流程如下:
* 1.打开蓝牙适配器
* 2.查看蓝牙适配器状态
* 3.根据传入的设备名称搜索附近设备列表并筛选
* 4.根据列表找到对应项获取对应deviceId创建链接
* 5.创建链接后获取该设备的详细信息
* 6.根据不同指令写入对应的指令,同时根据接收的指令做出回馈
* 微信蓝牙Api:
* 打开蓝牙模块
* wx.openBluetoothAdapter
* 关闭蓝牙模块
* wx.closeBluetoothAdapter
* 获取本机蓝牙适配器状态
* wx.getBluetoothAdapterState
* 监听蓝牙适配器状态变化事件
* wx.onBluetoothAdapterStateChange
*
*/
import database from './database'
export async function initBLEFormSystem({
name
}) {
let devices = []
let available = false // 设备可用
let discovering = false // 搜索状态
let serviceId = '' // 服务Id
let services = [] //服务ID数组
let characteristicId = '' // 特征值
let deviceId = '' // mac 地址 (ios的mac地址是UUID,由于隐私保护的原因引起的)
return new Promise((resolve, reject) => {
initBluetooth()
// 初始化蓝牙设备
function initBluetooth() {
// 版本过低不兼容
console.log('wx.openBluetoothAdapter', wx.openBluetoothAdapter)
if (!wx.openBluetoothAdapter) {
wx.showModal({
title: '提示',
showCancel: false,
content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。',
})
return
}
console.log('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 => {
console.log(res, '进入查找事件')
if (res && res.devices && res.devices.length > 0) {
console.log('name', name)
res.devices.forEach(device => {
console.log('蓝牙名', device.name)
if ((device.name && device.name.indexOf(name) > -1)) {
console.log('5.2 查询已经搜索的列表,目标设备为:', device.name)
deviceId = device.deviceId
// 链接成功,需要断开蓝牙搜索
stopSearchBluetooth()
connectBluetooth()
} else {
console.log('查找失败', name)
watchBluetoothStateChange()
searchBluetooth()
}
})
} else {
console.log('查找失败', name)
watchBluetoothStateChange()
searchBluetooth()
}
},
fail: err => {
console.log('搜索蓝牙设备失败')
watchBluetoothStateChange()
searchBluetooth()
}
})
}
// 监听蓝牙适配器状态变化事件
function watchBluetoothStateChange() {
wx.onBluetoothAdapterStateChange((result) => {
console.log('3.获取蓝牙适配器状态改变', result)
// 搜索状态
if (discovering != result.discovering) {
discovering = result.discovering
// 移除蓝牙适配器状态变化的所有监听函数
wx.offBluetoothAdapterStateChange()
searchBluetooth()
}
// 蓝牙状态
if (available != result.available) {
available = result.available
if (!result.available) {
wx.showToast({
title: '蓝牙未开启',
icon: 'none'
})
console.log('init - 蓝牙适配器不可用')
} else {
if (!result.discovering && !devices.length) {
wx.offBluetoothAdapterStateChange()
searchBluetooth()
}
}
}
})
}
// 查找设备
function searchBluetooth() {
wx.startBluetoothDevicesDiscovery({
allowDuplicatesKey: false,
success: res => {
console.log('4.开始查找设备', res)
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]
if (device.localName && device.localName.indexOf(name) > -1) {
console.log('5. 搜索成功,目标设备为:', device.localName, res)
deviceId = device.deviceId
// 链接成功 断开蓝牙搜索
stopSearchBluetooth()
connectBluetooth()
}
})
}
// 停止查找
function stopSearchBluetooth() {
wx.stopBluetoothDevicesDiscovery({
success: res => {
console.log('6.蓝牙停止查找', res)
},
fail: (err) => {
console.log('6.1 蓝牙查找失败', err)
}
})
}
// 连接设备
// 避免蓝牙链接失败,反复执行连接,多次尝试
let reconnectCounts = 0
function connectBluetooth() {
reconnectCounts++
wx.createBLEConnection({
deviceId,
success: (res) => {
console.log('7.建立设备连接', res)
reconnectCounts = null
getBluetoothServers()
},
fail: err => {
console.log('7.1 蓝牙连接失败', 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 => {
if (res.services.length > 0) {
res.services.forEach(item => {
console.log('8.获取设备服务', item)
serviceId = item.uuid
services = res.services
getBluetoothCharacteristics()
})
} else {
console.log('8-1.获取设备服务', res)
serviceId = res.services[0].uuid
services = res.services
getBluetoothCharacteristics()
}
},
fail: err => {
console.log('8.1 获取设备服务失败', err)
reject({
deviceId
})
}
})
}
// 获取设备某个服务特征值列表
function getBluetoothCharacteristics() {
wx.getBLEDeviceCharacteristics({
deviceId: deviceId,
serviceId,
success(res) {
console.log('9.获取设备服务特征值', res)
res.characteristics.forEach(item => {
if (item.properties && item.properties.write && item.properties.notify) {
console.log('9-1.获取服务特征值', item)
characteristicId = item.uuid
notifyBluetoothCharacteristicValueChange(item.uuid)
.then(() => {
let timer = setTimeout(() => {
console.log('================设备连接成功================')
resolve({
deviceId,
serviceId,
characteristicId
})
clearTimeout(timer)
timer = null
// 字段重置
devices = []
available = false //设备可用
discovering = false //搜索状态
serviceId = '' //服务ID
characteristicId = '' //特征值
deviceId = '' // mac 地址 (ios的mac地址是UUID,由于隐私保护的原因引起的)
}, 20)
return
})
.catch(() => {
console.error('启用通知失败,尝试下一个特征值或停止')
})
return;
}
})
},
fail: err => {
console.log('9.1 获取设备服务特征值失败', err)
reject({
deviceId
})
}
})
}
// 启用设备特征值变化时的notify功能
function notifyBluetoothCharacteristicValueChange(characteristicIds) {
return new Promise((resolve, reject) => {
wx.notifyBLECharacteristicValueChange({
characteristicId: characteristicIds,
deviceId,
serviceId,
state: true,
success: () => {
console.log('10.启用设备特征值变化提醒')
resolve()
},
fail: err => {
console.log('10.1启用设备特征值失败', err)
reject(err)
}
})
})
}
})
}
/**
* 发送指令给蓝牙
* @params value String
*/
export async function writeBLECharacteristicValue({
deviceId,
serviceId,
characteristicId,
value
}) {
return new Promise((resolve, reject) => {
let hex = value
// 向蓝牙发送16进制的数据
let typedArray = new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function (h) {
return parseInt(h, 16)
}))
let buffer1 = typedArray.buffer
console.log('准备发送的指令:',value,typedArray,database.ab2hex(typedArray))
wx.writeBLECharacteristicValue({
characteristicId,
deviceId,
serviceId,
value:buffer1,
success(res) {
console.log('写入成功', res, value)
// 监听设备的特征值变化
wx.onBLECharacteristicValueChange((result) => {
// resolve(ab2hex(result.value))
console.log('特征值变化',result,database.ab2hex(result.value))
})
},
fail(err) {
console.log('写入失败,结束', err)
reject()
}
})
})
}
/**
* 断开蓝牙
* @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()
}
})
}
function showToastSimple(error) {
return new Promise(resolve => {
wx.showToast({
title: error,
icon: 'none',
duration: 2000,
success(res) {
resolve(res)
}
})
})
}