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