为了简化微信小程序环境下的蓝牙接入流程,经过线上正式项目一年的运行,发现BLE这块API许多坑,且难以移植复用,所以将它封装出来提高可维护性以及可移植性。
具体项目地址:github.com/arsize/ble
如何使用
安装Eventenitter
npm install eventemitter2 --save
复制代码
引入
在项目根目录utils文件夹下添加如下文件:ble.js、bleHandler.js、tools.js、error.js
完成上面步骤,就可以直接在小程序中使用蓝牙功能了。✨
const emitter = new EventEmitter2();
const ble = new BLE(blename, emitter)
ble.listen(res => {
if (res.type == 'connect') {
switch(res.data){
case "未打开适配器":
break
case "蓝牙已连接":
break
case ""
break
}
}else if (res.type == "response") {
console.log('收到设备消息响应:', res)
//TODO
}
})
ble.init()
复制代码
实现细节
使用方法如上,很简单,只需要维护一个全局的ble实例,则可以进行蓝牙的各种功能操作。第二部引入的那几个文件是用来干嘛的呢?
大体上将蓝牙的连接、通讯、维护过程按功能的复杂程度分为三层:BLE、BLEHandler、Tool,ble更偏向用户层,blehandler提供一些流程性控制,tool则完全是封装的微信API,隔离一些繁复的工作,使代码看起来简洁一些。
源码解析
BLE(提供面向用户的流程控制):
import BLEHandler from "./bleHandler"
class BLE extends BLEHandler {
constructor(blename, emitter) {
super(blename, emitter)
}
listen(callback) {
// 蓝牙事件注册,打开channel
this.emitter.removeAllListeners("channel")
this.emitter.on("channel", callback)
}
removeListen() {
// 移除所有蓝牙事件
this.emitter.removeAllListeners("channel")
}
async init() {
let flow = false
// 打开蓝牙适配器状态监听
this.onBLEConnectionStateChange()
// 蓝牙适配器初始化
await this.openAdapter()
// 搜索蓝牙设备
await this.startSearch()
// 获取设备ID
flow = await this.onBluetoothFound()
// 停止搜索设备
await this.stopSearchBluetooth()
if (!flow) return
// 连接蓝牙
await this.connectBlue();
// 获取serviceId
await this.getBLEServices()
// 设置特征值
await this.getCharacteristics();
// 订阅特征值
await this.notifyBLECharacteristicValueChange()
// 打开传输监听,等待设备反馈数据
this.onBLECharacteristicValueChange()
}
// 发送指令
async send(mudata, cmd) {
let flow = await this.sentOrder(mudata, cmd)
return flow
}
async close() {
await this.closeBLEConnection()
await this.closeBLEAdapter()
}
}
export { BLE };
复制代码
BLEHandler(promise的封装,及Eventenitter通信控制)
import * as t from "./tools"
import { HTTP } from "../server";
/**
* 蓝牙工具类
* 封装小程序蓝牙流程方法
* 处理事件通信
*/
class BLEHandler {
constructor(blename, emitter) {
this.blename = blename
this.emitter = emitter
this.readCharacteristicId = "";
this.writeCharacteristicId = "";
this.notifyCharacteristicId = "";
this.deviceId = "";
this.serviceId = "";
this.lastDate = new Date().getTime()
}
async openAdapter() {
let [err, res] = await t._openAdapter.call(this);
if (err != null) {
this.emitter.emit("channel", {
type: "connect",
data: "未打开适配器"
})
return;
}
return true
}
async startSearch() {
let [err, res] = await t._startSearch.call(this);
if (err != null) {
return;
}
this.emitter.emit("channel", {
type: "connect",
data: "蓝牙搜索中"
})
}
async onBluetoothFound() {
let [err, res] = await t._onBluetoothFound.call(this);
if (err != null) {
this.emitter.emit("channel", {
type: "connect",
data: "未找到设备"
})
// 取消适配器
this.closeBLEAdapter()
wx.setStorageSync("bluestatus", "");
return;
}
this.emitter.emit("channel", {
type: "connect",
data: "正在连接中"
})
return true
}
async stopSearchBluetooth() {
let [err, res] = await t._stopSearchBluetooth.call(this);
if (err != null) {
return;
}
}
async connectBlue() {
let [err, res] = await t._connectBlue.call(this);
if (err != null) {
return;
}
}
async getBLEServices() {
let [err, res] = await t._getBLEServices.call(this);
if (err != null) {
return;
}
}
async getCharacteristics() {
let [err, res] = await t._getCharacteristics.call(this);
if (err != null) {
this.emitter.emit("channel", {
type: "connect",
data: "无法订阅特征值"
})
// 取消连接
this.closeBLEConnection()
this.closeBLEAdapter()
wx.setStorageSync("bluestatus", "");
return;
}
return true
}
async notifyBLECharacteristicValueChange() {
let [err, res] = await t._notifyBLECharacteristicValueChange.call(this);
if (err != null) {
// 取消连接
this.emitter.emit("channel", {
type: "connect",
data: "无法订阅特征值"
})
this.closeBLEConnection()
this.closeBLEAdapter()
wx.setStorageSync("bluestatus", "");
return;
}
this.emitter.emit("channel", {
type: "connect",
data: "蓝牙已连接"
})
wx.setStorageSync("bluestatus", "on");
return true
}
async closeBLEConnection() {
let [err, res] = await t._closeBLEConnection.call(this);
if (err != null) {
return;
}
}
async closeBLEAdapter() {
let [err, res] = await t._closeBLEAdapter.call(this);
if (err != null) {
return;
}
}
async sentOrder(mudata, cmd) {
let data = t._sentOrder(mudata, cmd)
console.log("-- 发送数据:", data)
let arrayBuffer = new Uint8Array(data).buffer;
let [err, res] = await t._writeBLECharacteristicValue.call(this, arrayBuffer)
if (err != null) {
return
}
return true
}
// 打开蓝牙适配器状态监听
onBLEConnectionStateChange() {
wx.onBLEConnectionStateChange(res => {
// 该方法回调中可以用于处理连接意外断开等异常情况
if (!res.connected) {
this.closeBLEAdapter()
wx.setStorageSync("bluestatus", "");
this.emitter.emit("channel", {
type: "connect",
data: "蓝牙已断开"
})
}
}, err => {
console.log('err', err)
})
}
// 收到设备推送的notification
onBLECharacteristicValueChange() {
wx.onBLECharacteristicValueChange(res => {
let arrbf = new Uint8Array(res.value)
console.log("收到上传数据:",arrbf)
console.log("时间戳",new Date().getTime())
arrbf.map(res=>{
console.log(res)
})
if (this._checkData(arrbf)) {
if (arrbf[3] != 0x00) {
let nowDate = new Date().getTime()
if ((nowDate - this.lastDate) > 900) {
console.log('-- 节流900ms,Lock!')
this.lastDate = nowDate
this._uploadInfo(arrbf)
this.emitter.emit("channel", {
type: "response",
data: arrbf
})
}
}
}
})
}
_uploadInfo(message) {
console.log("-- 准备数据同步!", this._mapToArray(message))
let bleorder = wx.getStorageSync("bleorder");
let blecabinet = wx.getStorageSync("blecabinet")
HTTP({
url: "cabinet/uploadBlueData",
methods: "post",
data: {
cabinetQrCode: blecabinet,
order: bleorder,
message: this._mapToArray(message)
}
}).then(res => {
console.log("✔ 数据同步成功!")
}, err => {
console.log('✘ 数据同步失败', err)
})
}
_mapToArray(arrbf) {
let arr = []
arrbf.map(item => {
arr.push(item)
})
return arr
}
// 校验数据正确性
_checkData(arrbf) {
// 校验帧头帧尾
if (arrbf[0] != 0xEE || arrbf[1] != 0xFA || arrbf[arrbf.length - 1] != 0xFF || arrbf[arrbf.length - 2] != 0xFC) {
console.log('✘ 帧头帧尾不匹配,请重发')
console.log('帧头:', arrbf[0])
console.log('帧头:', arrbf[1])
console.log('帧尾:', arrbf[arrbf.length - 1])
console.log('帧尾:', arrbf[arrbf.length - 2])
return false
}
// 校验CRC
let crc = t._modBusCRC16(arrbf, 2, arrbf.length - 5)
if (arrbf[arrbf.length - 3] != crc & 0xff && arrbf[arrbf.length - 4] != (crc >> 8) & 0xff) {
console.log('✘ crc校验错误,请重发')
return false
}
let time = new Date().toLocaleTimeString()
console.log(`✔ CRC数据校验成功!${arrbf[3] == 0 ? '❤' : '命令码:' + arrbf[3]},time:${time}`)
return true
}
}
export default BLEHandler
复制代码
Tools(对微信蓝牙API的封装,改造,以及一些偏底层接口的封装)
import errToString from "./error";
let PRINT_SHOW = true //是否开启蓝牙调试
function _openAdapter() {
print(`—————— —————— ——————`);
print(`准备初始化蓝牙适配器...`);
return wx.openBluetoothAdapter().then(
(res) => {
print(`✔ 适配器初始化成功!`);
return [null, res]
},
(err) => {
print(`✘ 初始化失败!${errToString(err)}`);
return [errToString(err), null]
}
);
}
/**
* @param {Array<string>} services
* @param { Int } interval
*/
function _startSearch() {
print(`准备搜寻附近的蓝牙外围设备...`);
return promisify(wx.startBluetoothDevicesDiscovery, {
interval: 1000
}).then(
(res) => {
print(`✔ 搜索成功!`);
return [null, res]
},
(err) => {
print(`✘ 搜索蓝牙设备失败!${errToString(err)}`);
return [errToString(err), null]
}
);
}
/**
*@param {Array<string>} devices
*@deviceId 设备ID
*/
function _onBluetoothFound() {
print(`监听搜寻新设备事件...`);
return _onBluetoothFound_promise.call(this).then(res => {
print(`✔ 设备ID找到成功!`);
return [null, res]
}, err => {
print(`✘ 设备ID找到失败!`);
return [errToString(err), null]
})
}
/**
* @param {Array} devices 查找到设备数组
* @param {int} count 计数器-嗅探2次
*/
function _onBluetoothFound_promise() {
let devices = []
let count = 0
print(`blename:${this.blename}`)
return new Promise((resolve, reject) => {
wx.onBluetoothDeviceFound(res => {
devices.push(...res.devices)
count++
if (count > 1) {
devices.forEach(element => {
if ((element.name && element.name == this.blename) || (element.localName && element.localName == this.blename)) {
this.deviceId = element.deviceId
resolve(res)
}
});
reject('device not found')
}
print(`已嗅探蓝牙设备数:${devices.length}...`)
}, err => {
reject(err)
})
})
}
function _stopSearchBluetooth() {
print(`停止查找新设备...`);
return wx.stopBluetoothDevicesDiscovery().then(
(res) => {
print(`✔ 停止查找设备成功!`);
return [null, res]
},
(err) => {
print(`✘ 停止查询设备失败!${errToString(err)}`);
return [errToString(err), null]
}
);
}
function _connectBlue() {
print(`准备连接设备...`);
return promisify(wx.createBLEConnection, {
deviceId: this.deviceId,
}).then(
(res) => {
print(`✔ 连接蓝牙成功!`);
return [null, res]
},
(err) => {
print(`✘ 连接蓝牙失败!${errToString(err)}`);
return [errToString(err), null]
}
);
}
function _closeBLEConnection() {
print(`断开蓝牙连接...`)
return promisify(wx.closeBLEConnection, {
deviceId: this.deviceId,
}).then(
(res) => {
print(`✔ 断开蓝牙成功!`);
return [null, res]
},
(err) => {
print(`✘ 断开蓝牙连接失败!${errToString(err)}`);
return [errToString(err), null]
}
);
}
function _closeBLEAdapter() {
print(`释放蓝牙适配器...`)
return wx.closeBluetoothAdapter().then(res => {
print(`✔ 释放适配器成功!`)
return [null, res]
}, err => {
print(`✘ 释放适配器失败!${errToString(err)}`)
return [errToString(err), null]
})
}
function _getBLEServices() {
print(`获取蓝牙设备所有服务...`)
return promisify(wx.getBLEDeviceServices, {
deviceId: this.deviceId
}).then(res => {
print(`✔ 获取service成功!`)
return [null, res]
}, err => {
print(`✘ 获取service失败!${errToString(err)}`)
return [errToString(err), null]
})
}
function _getCharacteristics() {
print(`开始获取特征值...`);
return promisify(wx.getBLEDeviceCharacteristics, {
deviceId: this.deviceId,
serviceId: this.serviceId,
}).then(
(res) => {
print(`✔ 获取特征值成功!`);
for (let i = 0; i < res.characteristics.length; i++) {
let item = res.characteristics[i];
if (item.properties.read) {
this.readCharacteristicId = item.uuid;
}
if (item.properties.write && !item.properties.read) {
this.writeCharacteristicId = item.uuid;
}
if (item.properties.notify || item.properties.indicate) {
this.notifyCharacteristicId = item.uuid;
}
}
return [null, res]
},
(err) => {
print(`✘ 获取特征值失败!${errToString(err)}`);
return [errToString(err), null]
}
);
}
// 订阅特征值
function _notifyBLECharacteristicValueChange() {
return promisify(wx.notifyBLECharacteristicValueChange, {
deviceId: this.deviceId,
serviceId: this.serviceId,
characteristicId: this.notifyCharacteristicId,
state: true
}).then(res => {
print(`✔ 订阅notify成功!`)
return [null, res]
}, err => {
print(`✘ 订阅notify失败!${errToString(err)}`)
return [errToString(err), null]
})
}
/**
* 指令封装
* @param {Array} mudata
*/
function _sentOrder(mudata, cmd) {
print(`开始封装指令...`)
let uarr = new Array(mudata.length + 8)
uarr[0] = 0xEE //帧头
uarr[1] = 0xFA //帧头
uarr[2] = mudata.length + 1
uarr[3] = cmd //命令码
mudata.map((item, index) => {
uarr[index + 4] = item
})
let crc = _modBusCRC16(uarr, 2, mudata.length + 3)
uarr[uarr.length - 4] = (crc >> 8) & 0xff
uarr[uarr.length - 3] = crc & 0xff
uarr[uarr.length - 2] = 0xFC //帧尾
uarr[uarr.length - 1] = 0xFF //帧尾
print(`✔ 封装成功!`)
return uarr
}
// CRC16 校验算法
function _modBusCRC16(data, startIdx, endIdx) {
var crc = 0xffff;
do {
if (endIdx <= startIdx) {
break;
}
if (data.length <= endIdx) {
break;
}
for (var i = startIdx; i <= endIdx; i++) {
var byte = data[i] & 0xffff;
for (var j = 0; j < 8; j++) {
crc = (byte ^ crc) & 0x01 ? (crc >> 1) ^ 0xa001 : crc >> 1;
byte >>= 1;
}
}
} while (0);
return ((crc << 8) | (crc >> 8)) & 0xffff;
}
function _writeBLECharacteristicValue(mudata) {
return promisify(wx.writeBLECharacteristicValue, {
deviceId: this.deviceId,
serviceId: this.serviceId,
characteristicId: this.writeCharacteristicId,
value: mudata,
}).then(res => {
print(`✔ 写入数据成功!`)
return [null, res]
}, err => {
print(`✘ 写入数据失败!${errToString(err)}`)
return [errToString(err), null]
})
}
/**
* 对微信接口的promise封装
* @param {function} fn
* @param {object} args
*/
function promisify(fn, args) {
return new Promise((resolve, reject) => {
fn({
...(args || {}),
success: (res) => resolve(res),
fail: (err) => reject(err),
});
});
}
/**
* 对微信接口回调函数的封装
* @param {function} fn
*/
function promisify_callback(fn) {
return new Promise((resolve, reject) => {
fn(
(res) => {
resolve(res);
},
(rej) => {
reject(rej);
}
);
});
}
function print(str) {
PRINT_SHOW ? console.log(str) : null;
}
export {
print,
_getCharacteristics,
_connectBlue,
_getBLEServices,
_closeBLEConnection,
_closeBLEAdapter,
_stopSearchBluetooth,
_notifyBLECharacteristicValueChange,
_onBluetoothFound,
_startSearch,
_openAdapter,
_sentOrder,
_writeBLECharacteristicValue,
_modBusCRC16,
promisify,
promisify_callback,
};
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END