In order to simplify the Bluetooth access process in the micro-program environment of wechat, after one year of operation of the online formal project, IT was found that BLE API had many pits and was difficult to be transplanted and reused, so it was encapsulated to improve its maintainability and portability.
Specific project address: github.com/arsize/ble
How to use
Install Eventenitter
npm install eventemitter2 --save
Copy the code
The introduction of
Add the following files to the utils folder: ble. Js, bleHandler.js, tools.js, error. ✨
const emitter = new EventEmitter2();
const ble = new BLE(blename, emitter)
ble.listen(res= > {
if (res.type == 'connect') {
switch(res.data){
case "Adapter not open":break
case "Bluetooth connected":break
case ""
break}}else if (res.type == "response") {
console.log('Received device message response:', res)
//TODO
}
})
ble.init()
Copy the code
Implementation details
Use method as above, very simple, just need to maintain a global BLE instance, you can carry out various functions of Bluetooth operations. What are the files introduced in Part 2 for? Generally speaking, the connection, communication and maintenance process of Bluetooth can be divided into three layers according to the complexity of functions: BLE, BLEHandler and Tool. BLE is more oriented to the user layer. BLEHandler provides some flow control, while Tool is completely encapsulated wechat API to isolate some complicated work and make the code look simpler.
The source code parsing
BLE (provides user-oriented process control) :
import BLEHandler from "./bleHandler"
class BLE extends BLEHandler {
constructor(blename, emitter) {
super(blename, emitter)
}
listen(callback) {
// Bluetooth event register, open channel
this.emitter.removeAllListeners("channel")
this.emitter.on("channel", callback)
}
removeListen() {
// Remove all Bluetooth events
this.emitter.removeAllListeners("channel")}async init() {
let flow = false
// Enable bluetooth adapter status listening
this.onBLEConnectionStateChange()
// The Bluetooth adapter is initialized
await this.openAdapter()
// Search for Bluetooth devices
await this.startSearch()
// Obtain the device ID
flow = await this.onBluetoothFound()
// Stop the search device
await this.stopSearchBluetooth()
if(! flow)return
// Connect to Bluetooth
await this.connectBlue();
/ / get serviceId
await this.getBLEServices()
// Set eigenvalues
await this.getCharacteristics();
// Subscribe to eigenvalues
await this.notifyBLECharacteristicValueChange()
// Open the transmission listener and wait for the device to feedback data
this.onBLECharacteristicValueChange()
}
// Send the command
async send(mudata, cmd) {
let flow = await this.sentOrder(mudata, cmd)
return flow
}
async close() {
await this.closeBLEConnection()
await this.closeBLEAdapter()
}
}
export { BLE };
Copy the code
BLEHandler (Promise encapsulation, and Eventenitter communication control)
import * as t from "./tools"
import { HTTP } from ".. /server";
/** * Bluetooth utility class * encapsulation applet Bluetooth flow methods * handle event communication */
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: "Adapter not open"
})
return;
}
return true
}
async startSearch() {
let [err, res] = await t._startSearch.call(this);
if(err ! =null) {
return;
}
this.emitter.emit("channel", {
type: "connect".data: "Bluetooth search underway."})}async onBluetoothFound() {
let [err, res] = await t._onBluetoothFound.call(this);
if(err ! =null) {
this.emitter.emit("channel", {
type: "connect".data: "Equipment not found"
})
// Cancel the adapter
this.closeBLEAdapter()
wx.setStorageSync("bluestatus"."");
return;
}
this.emitter.emit("channel", {
type: "connect".data: "Connecting"
})
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: "Unable to subscribe to eigenvalues"
})
// Cancel the connection
this.closeBLEConnection()
this.closeBLEAdapter()
wx.setStorageSync("bluestatus"."");
return;
}
return true
}
async notifyBLECharacteristicValueChange() {
let [err, res] = await t._notifyBLECharacteristicValueChange.call(this);
if(err ! =null) {
// Cancel the connection
this.emitter.emit("channel", {
type: "connect".data: "Unable to subscribe to eigenvalues"
})
this.closeBLEConnection()
this.closeBLEAdapter()
wx.setStorageSync("bluestatus"."");
return;
}
this.emitter.emit("channel", {
type: "connect".data: "Bluetooth connected"
})
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("-- send data :", data)
let arrayBuffer = new Uint8Array(data).buffer;
let [err, res] = await t._writeBLECharacteristicValue.call(this, arrayBuffer)
if(err ! =null) {
return
}
return true
}
// Enable bluetooth adapter status listening
onBLEConnectionStateChange() {
wx.onBLEConnectionStateChange(res= > {
// This method can be used to handle exceptions such as unexpected disconnections
if(! res.connected) {this.closeBLEAdapter()
wx.setStorageSync("bluestatus"."");
this.emitter.emit("channel", {
type: "connect".data: "Bluetooth disconnected"})}},err= > {
console.log('err', err)
})
}
// Received notification notification pushed by the device
onBLECharacteristicValueChange() {
wx.onBLECharacteristicValueChange(res= > {
let arrbf = new Uint8Array(res.value)
console.log("Received upload data:",arrbf)
console.log("Timestamp".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('-- throttling 900 ms, Lock! ')
this.lastDate = nowDate
this._uploadInfo(arrbf)
this.emitter.emit("channel", {
type: "response".data: arrbf
})
}
}
}
})
}
_uploadInfo(message) {
console.log("-- Ready for data synchronization!".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("Configure data synchronization successfully!")},err= > {
console.log(- Data synchronization failed, err)
})
}
_mapToArray(arrbf) {
let arr = []
arrbf.map(item= > {
arr.push(item)
})
return arr
}
// Verify data correctness
_checkData(arrbf) {
// Check the first frame and the last frame
if (arrbf[0] != 0xEE || arrbf[1] != 0xFA || arrbf[arrbf.length - 1] != 0xFF || arrbf[arrbf.length - 2] != 0xFC) {
console.log(- Start and end of frames don't match, please resend ')
console.log('the frame head:', arrbf[0])
console.log('the frame head:', arrbf[1])
console.log('tail frame:, arrbf[arrbf.length - 1])
console.log('tail frame:, arrbf[arrbf.length - 2])
return false
}
/ / check the 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 error - Please retry)
return false
}
let time = new Date().toLocaleTimeString()
console.log(Stocking CRC data check success!${arrbf[3] = =0 ? '❤' : 'Command code:' + arrbf[3]}, the time:${time}`)
return true}}export default BLEHandler
Copy the code
Tools (encapsulation and transformation of wechat Bluetooth API, as well as encapsulation of some low-level interfaces)
import errToString from "./error";
let PRINT_SHOW = true // Whether to enable Bluetooth debugging
function _openAdapter() {
print(` -- -- -- -- -- -- -- -- -- `);
print('Ready to initialize the Bluetooth adapter... `);
return wx.openBluetoothAdapter().then(
(res) = > {
print(Stocking adapter initialization successful! `);
return [null, res]
},
(err) = > {
print(- Initialization failed!${errToString(err)}`);
return [errToString(err), null]}); }/ * * *@param {Array<string>} services
* @param { Int } interval* /
function _startSearch() {
print('Ready to search nearby Bluetooth peripherals... `);
return promisify(wx.startBluetoothDevicesDiscovery, {
interval: 1000
}).then(
(res) = > {
print(➤ Search for success! `);
return [null, res]
},
(err) = > {
print(- Searching for Bluetooth devices failed!${errToString(err)}`);
return [errToString(err), null]}); }/ * * *@param {Array<string>} devices
*@deviceId The device ID * /
function _onBluetoothFound() {
print('Listen for new device event... `);
return _onBluetoothFound_promise.call(this).then(res= > {
print(Stocking device ID found success! `);
return [null, res]
}, err= > {
print(- Device ID failed to be found! `);
return [errToString(err), null]})}/ * * *@param {Array} Devices found the device array *@param {int} Count counter - Sniff 2 times */
function _onBluetoothFound_promise() {
let devices = []
let count = 0
print(`blename:The ${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('Number of Bluetooth devices sniffed:${devices.length}. `)},err= > {
reject(err)
})
})
}
function _stopSearchBluetooth() {
print('Stop looking for new devices... `);
return wx.stopBluetoothDevicesDiscovery().then(
(res) = > {
print(✔ Stop looking for devices successfully! `);
return [null, res]
},
(err) = > {
print(- Failed to stop querying the device!${errToString(err)}`);
return [errToString(err), null]}); }function _connectBlue() {
print('Ready to connect devices... `);
return promisify(wx.createBLEConnection, {
deviceId: this.deviceId,
}).then(
(res) = > {
print(✔ Link Bluetooth successfully! `);
return [null, res]
},
(err) = > {
print(- Bluetooth connection failed!${errToString(err)}`);
return [errToString(err), null]}); }function _closeBLEConnection() {
print('Disconnect bluetooth connection... `)
return promisify(wx.closeBLEConnection, {
deviceId: this.deviceId,
}).then(
(res) = > {
print(✔ Disconnect bluetooth successfully! `);
return [null, res]
},
(err) = > {
print(- Disconnecting Bluetooth failed!${errToString(err)}`);
return [errToString(err), null]}); }function _closeBLEAdapter() {
print('Release bluetooth adapter... `)
return wx.closeBluetoothAdapter().then(res= > {
print(➤ ➤ Release the adapter successfully! `)
return [null, res]
}, err= > {
print(- Failed to free the adapter!${errToString(err)}`)
return [errToString(err), null]})}function _getBLEServices() {
print('Access to all bluetooth device services... `)
return promisify(wx.getBLEDeviceServices, {
deviceId: this.deviceId
}).then(res= > {
print(✔ Get service success! `)
return [null, res]
}, err= > {
print(- Failed to obtain the service!${errToString(err)}`)
return [errToString(err), null]})}function _getCharacteristics() {
print('Start getting eigenvalues... `);
return promisify(wx.getBLEDeviceCharacteristics, {
deviceId: this.deviceId,
serviceId: this.serviceId,
}).then(
(res) = > {
print(➤ Get the feature value successfully! `);
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(- Failed to obtain characteristic values!${errToString(err)}`);
return [errToString(err), null]}); }// Subscribe to eigenvalues
function _notifyBLECharacteristicValueChange() {
return promisify(wx.notifyBLECharacteristicValueChange, {
deviceId: this.deviceId,
serviceId: this.serviceId,
characteristicId: this.notifyCharacteristicId,
state: true
}).then(res= > {
print(✔ Subscribe to notify success! `)
return [null, res]
}, err= > {
print(- Failed to subscribe to notify!${errToString(err)}`)
return [errToString(err), null]})}/** * instruction encapsulation *@param {Array} mudata
*/
function _sentOrder(mudata, cmd) {
print('Start encapsulating instructions... `)
let uarr = new Array(mudata.length + 8)
uarr[0] = 0xEE / / frame head
uarr[1] = 0xFA / / frame head
uarr[2] = mudata.length + 1
uarr[3] = cmd / / command code
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 / / frame the tail
uarr[uarr.length - 1] = 0xFF / / frame the tail
print(Stocking success! `)
return uarr
}
// CRC16 check algorithm
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(✔ Write data successfully! `)
return [null, res]
}, err= > {
print(- Failed to write data!${errToString(err)}`)
return [errToString(err), null]})}/** * Promise encapsulation for wechat interface *@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),
});
});
}
/** * Encapsulate the wechat interface callback function *@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,
};
Copy the code