preface

Recently completed a project (UNIApp + VUe2 — The Bluetooth API in UNIApp is the same as the native wechat applets API), using wechat applets to connect low power Bluetooth (BLE) to realize communication with hardware devices. During the period, I conducted bluetooth search according to the development documents of wechat. The connection and communication were not smooth, and I encountered many pits, so I made a summary when I was free.


Note: When using each API, the phone is compatible with the base library version required under the API

The android

  • Open the bluetooth
  • Turn on the phone location (to search for Bluetooth)

Apple mobile phone

  • Open the bluetooth
  • Turn on phone location
File directory structure

Bluetooth initialization + search: [jr-bluetooth.js];

                                                                                JR-Bluetooth.js
export default class JRBluetooth {
    constructor(isIOS) {
        this.isIOS = isIOS; // Is it an iPhone
        this.available = false; // Bluetooth is available/enabled
        this.discovering = false; // Whether the device is being searched
        this.discoveringTimer = null; // Search for no device prompt delay
        this.isDiscovery = false; // Whether the device is found
        this.discoveringInterval = 20000; // The search takes 20 seconds
    }
    // TODO implements the following method
    // ...
}
Copy the code

1. Bluetooth initialization and search for Bluetooth devices

The bluetooth adapter state change event can only be listened after the initialization is successful. The two callback functions are based on the implementation logic of their own projects.

/** jr-bluetooth.js * Initializes the Bluetooth module *@param {function} DiscoveryCallback Callback after searching for a device *@param {function} StateChangeCallback Callback for state changes */
initBluetooth(DiscoveryCallback, stateChangeCallback) {
    return new Promise((resolve, reject) = > {
        uni.openBluetoothAdapter({
            success: res= > {
                console.log('Initialization of bluetooth module successful! ');
                // Listen for bluetooth adapter status change events
                this.onBluetoothStateChange(stateChangeCallback);
                // Start searching for nearby Bluetooth peripherals
                this.discoverBluetooth(discoveryCallback);
                resolve();
            },
            fail: errMsg= > {
                console.log('Failed to initialize Bluetooth module');
                // Listen for bluetooth adapter status change events
                this.onBluetoothStateChange(stateChangeCallback); reject(); }})})}Copy the code

After searching bluetooth devices and reporting device time interval Settings, the device list is confused, so it is not recommended to add

Pit 1: In this project, it is forbidden to search for other Bluetooth devices, so the UUID of the master service is added to services. Here, the UUID must be written in full to search only this type of Bluetooth device.

/** JR- bluetooth.js * Search for Bluetooth devices *@param {function} Cb callback after device search */
discoverBluetooth(cb) {
    if(! cb ||typeofcb ! = ='function') return;
    uni.showLoading({
        title: 'Bluetooth searching'
    });
    const self = this;
    uni.startBluetoothDevicesDiscovery({
        services: [00001812].// Search only for the device whose UUID is 00001812
        allowDuplicatesKey: false.// The same device cannot be reported repeatedly
        success: () = > {
            self.discoveringTimer = setTimeout(() = > {
                // TODO
                // The device is not found within the specified time
            }, self.discoveringInterval);
        },
        // interval: 200, // Interval for reporting devices
    })
    // Listen for Bluetooth device events
    self.onBluetoothDeviceFound().then(res= >{ cb && cb(res); })}Copy the code

The following methods are added or deleted as needed

/** jr-bluetooth.js * stop searching for Bluetooth devices */
stopDiscovery() {
    uni.stopBluetoothDevicesDiscovery();
    clearTimeout(this.discoveringTimer);
}
/** * close Native Bluetooth adapter */
closeBluetooth() {
    // console.log(' Turn off bluetooth adapter ');
    uni.closeBluetoothAdapter();
    clearTimeout(this.discoveringTimer);
}
/** * Unmount bluetooth event */
unloadBluetooth() {
    this.closeBluetooth();
    this.discovering && this.stopDiscovery();
}
/** * Get the bluetooth adapter status */
getBluetoothState() {
    return new Promise((resolve, reject) = > {
        uni.getBluetoothAdapterState({
            success: res= > {
                this.updateBluetoothState(res); // Update the bluetooth adapter statusresolve(res); }})})}/** * Listen for bluetooth adapter status change events *@param {function} Cb handles the state change function */
onBluetoothStateChange(cb) {
    uni.onBluetoothAdapterStateChange(res= > {
        const { available, discovering } = res;
        this.updateBluetoothState(res); // Update the bluetooth adapter status
        if(! available && ! discovering) {// Bluetooth is disconnected
                this.unloadBluetooth(); } cb && cb(res); })}/** * Update bluetooth adapter status */
updateBluetoothState(state) {
    // Available Indicates whether the server is in the search state
    // boolean
    const { available, discovering } = state;
    this.available = available;
    this.discovering = discovering;
}
Copy the code

Important: Processing the searched Bluetooth list

Pit 2: As we all know, iPhone is very secure, and those who have been compatible with iPhone should know it well. Since bluetooth MAC address is needed in this project, Android phones can get it directly, but iPhone phones can only get the UUID after processing by iPhone. After testing, it is found that: The UUID obtained by different IOS mobile phones connected to the same Bluetooth varies. Here’s how the iPhone gets a Bluetooth MAC address: By contract with a hardware development partner, they put the MAC into the broadcast and the following applet parses the Device. advertisData to get the MAC address.

/** jr-bluetooth.js * listen to search Bluetooth device events *@param {Array} DevicesList Device list */
onBluetoothDeviceFound(devicesList = []) {
    return new Promise((resolve, reject) = > {
        uni.onBluetoothDeviceFound(res= > {
            const devices = res.devices;
            const device = devices[0];
            // Filter named devices
            if (Utils.has(device['name']) {if (this.isIOS) { // If it is an iPhone
                    AdvertisData (advertisData)
                    if (device.advertisData) {
                        // 0~6 here only applies to low power Bluetooth of our company. Different products may be different and need to be tested
                        let bf = device.advertisData.slice(0.6);
                        let mac = Array.prototype.map.call(new Uint8Array(bf), x= > ('00' + x.toString(16)).slice(-2)).join(':');
                        mac = mac.toUpperCase();
                        // Save the MAC address
                        device.advertisMacData = mac;
                    }
                }
                devicesList.push(device);
                this.isDiscovery = true;
            }
            Resolve is executed only once, regardless of how many devices are listened onresolve(devicesList); })})}Copy the code

Bluetooth connection: [jr-ble. Js] serviceId Is used to write and notify messages to Bluetooth after a Bluetooth connection is successful. Get it from the getBLEServices method below

export default class JRBLE {
    constructor(deviceId, serviceId) {
        this.deviceId = deviceId; / / device id
        this.serviceId = serviceId; // Master service UUID
        this.writeCharacteristicId = ""; // write The characteristic value uuid
        this.notifyCharacteristicId = ""; // notify Characteristic value uuid
        this.connectionTimeout = 20000; // Connect BLE timeout Duration
    }
    // TODO implements the following method
    // ...
}
Copy the code

2, low power Bluetooth connection

Note the notification Characterisd (writeCharacteristicId).

/** jr-ble. Js * process the characteristic value to obtain notifyCharacteristicId and writeCharacteristicId *@param {array} characteristics
 * @return {object} [notifyCharacteristicId, writeCharacteristicId] 
 */
handleCharacteristics(characteristics) {
    const len = characteristics.length;
    for (let i = 0; i < len; i++) {
        const characteristic = characteristics[i];
        // If the serviceId matches to the bluetooth product being used, write the eigenvalue dead because it is unique.
        if (this.serviceId == '6E400001-B5A3-F393-E0A9-E50E24DCCA9E' || this.serviceId.toLocaleUpperCase() == '6E400001-B5A3-F393-E0A9-E50E24DCCA9E') {
            this.notifyCharacteristicId = '6E400003-B5A3-F393-E0A9-E50E24DCCA9E'
            this.writeCharacteristicId = '6E400002-B5A3-F393-E0A9-E50E24DCCA9E'
        } else {
            / / get notifyCharacteristicId
            if (characteristic.properties.notify) {
                 this.notifyCharacteristicId = characteristic.uuid;
            }
            / / get writeCharacteristicId
            if (characteristic.properties.write) {
                 this.writeCharacteristicId = characteristic.uuid; }}}return {
        notifyCharacteristicId: this.notifyCharacteristicId,
        writeCharacteristicId: this.writeCharacteristicId
    }
}
Copy the code

Pit 3: Note that the getBLEServices() method must delay calling uni.getbleDeviceservices (). Why is that? When the createBLEConnection method is called and BLE initialization is successful, the BLE service list cannot be retrieved immediately (uni.getBLEDeviceServices API). The BLE service list fails to be retrieved immediately on some phones

/** jr-ble. Js /** * connect BLE *@param {function} Cb Frame callback */
createBLEConnection() {
    return new Promise((resolve, reject) = > {
        uni.createBLEConnection({
            deviceId: this.deviceId,
            timeout: this.connectionTimeout,
            success: res= > {
                // console.log("res:createBLEConnection " + JSON.stringify(res));
                resolve(res);
            },
            fail: err= > {
                console.log(err);
                reject(err)
                // err.errCode == 10003 ? this.createBLEConnection() : reject(err)}})})}/** * Obtain the service table of a certain BLE device * This is a list of serviceIDS to select. After obtaining each serviceId, * obtain all the characteristic values of the corresponding services of the Bluetooth device. The serviceId can be written to the project ***(for bluetooth of the same model only).*** */
getBLEServices() {
    return new Promise((resolve, reject) = > {
        // After the connection is successful, you need to delay the operation to continue
        setTimeout(() = > {
            uni.getBLEDeviceServices({
                deviceId: this.deviceId,
                success: res= > {
                    const serviceList = [];
                    const services = res.services;
                    const len = services.length;
                    for (let i = 0; i < len; i++) {
                        if(services[i].isPrimary) { serviceList.push(services[i]); }}// console.log(serviceList);
                    resolve(serviceList);
                },
                fail: err= > {
                    console.log(err); reject(err); }})},600)})}/** * Get all the characteristic values in a service of bluetooth device ** /
getBLEDeviceCharacteristics() {
    return new Promise((resolve, reject) = > {
        uni.getBLEDeviceCharacteristics({
            deviceId: this.deviceId,
            serviceId: this.serviceId,
            success: res= > {
                // Process the characteristic value to obtain notifyCharacteristicId and writeCharacteristicId
                const result = this.handleCharacteristics(res.characteristics); resolve(result); }})})}/** * Writes binary data * to low-power Bluetooth device eigenvalues@param {Object} buffer* /
writeBLECharacteristicValue(buffer) {
    return new Promise((resolve, reject) = > {
        uni.writeBLECharacteristicValue({
            deviceId: this.deviceId,
            serviceId: this.serviceId,
            characteristicId: this.writeCharacteristicId,
            value: buffer,
            success: res= > {
                resolve(res);
            },
            fail: err= > {
                console.log('Message sending failed'.JSON.stringify(err)); reject(err); }}); }); }/** * Enable the notify function when the eigenvalues of low-power Bluetooth devices change, and subscribe to the eigenvalues */
notifyBLECharacteristicValue() {
    return new Promise((resolve, reject) = > {
        uni.notifyBLECharacteristicValueChange({
            state: true.// Enable the notify function
            deviceId: this.deviceId,
            serviceId: this.serviceId,
            characteristicId: this.notifyCharacteristicId,
            success: res= > {
                resolve(res)
            },
            fail: err= > {
                reject(err)
                console.log('notifyBLECharacteristicValueChange failed:'+ err.errMsg); }}); })}/** * Receive notification * pushed by the device@param {function} Cb processing callback */
onBLECharacteristicValueChange(cb) {
    uni.onBLECharacteristicValueChange(function (res) {
      cb && cb(res.value)
    })
}

/** * Listen for low-power Bluetooth connection status change events. * This includes the developer actively connecting or disconnecting, device loss, abnormal disconnection, etc. */
onBLEConnectionStateChange() {
    return new Promise(resolve= >{
        uni.onBLEConnectionStateChange(function (res) {
          // This method can be used to handle exceptions such as unexpected disconnections
          // console.log(`device ${res.deviceId} state has changed, connected: ${res.connected}`)
          if(! res.connected) { resolve(res) } }) }) }/** ** Disconnect the link */
closeBLEConnection() {
    uni.closeBLEConnection({
        deviceId: this.deviceId,
        success: res= > {
            // console.log(res)}})}Copy the code

Call: [test.vue].

import BluetoothJR from './JR-Bluetooth.js';
import BLEJR from './JR-BLE.js';
// serviceId Each Bluetooth product is different. This is just an example. You can obtain it from jr-ble
const serviceID = ['55535343-ad7d-2bc5-8fa9-9fafd205e455'];
let JRBluetooth = new BluetoothJR();
// The incoming callback function at initialization is implemented according to its needs.
JRBluetooth.initBluetooth()
    .then(res= > {
        // Initialization succeeded
        createBLEConnect(deviceId); / / connected BLE
    })
    .catch(() = > {
        // Initialization failed
    })
function createBLEConnect(deviceId) {
    JRBLE = new BLEJR(deviceId, serviceId);
    // Stop bluetooth search on this machine
    JRBluetooth.stopDiscovery();
    // Wait to connect the BLE device
    JRBLE.createBLEConnection()
        .then(res= > {
            JRBLE.getBLEServices()
                .then(res= > {
                    // Process the list of services obtained
                    handleBLEDeviceServices(res);
                    // Monitor BLE connection status
                    JRBLE.onBLEConnectionStateChange().then(res= > {
                        // Handle exceptions
                        // this.handleBLEStateChange(res);
                    });
                })
                .catch(error= > {
                    // this.handleBLEConnectError(error, cb);
                });
        })
        .catch(error= > {
            // this.handleBLEConnectError(error, cb);
        });
}
 /** * Handle BLE services *@param {Object} list* /
function handleBLEDeviceServices(list) {
    if (!Array.isArray(list)) {
        return;
    }
    list.forEach(async item => {
        if(serviceId.indexOf(item.uuid) ! = = -1|| serviceId.indexOf(item.uuid.toLocaleUpperCase()) ! = = -1|| serviceId.indexOf(item.uuid.toLocaleLowerCase()) ! = = -1) {
            // Update the current service UUID
            JRBLE.serviceId = item.uuid;
            // Get a service characteristic value
            await JRBLE.getBLEDeviceCharacteristics();
            / / enable notify
            await JRBLE.notifyBLECharacteristicValue();
            It receives data from BLE and can pass in a function (handleReceiveData) to process itJRBLE.onBLECharacteristicValueChange(handleReceiveData); }}); }Copy the code

conclusion

Wx applet connection to bluetooth low power is basically the process, there are a lot of problems, from initialization to connection to write data, the most difficult point is to obtain serviceId, namely the main service service, and the corresponding write and notify feature values of the main service, it will take a little time to test. The next article implements subcontracting sending.