Fading Coder

One Final Commit for the Last Sprint

Home > Tools > Content

Implementing Bluetooth Device Discovery and Connection in UniApp with Permission Management

Tools 1

Bluetooth Device Discovery and Connection

This implementation demonstrates how to search for, connect to, and manage Bluetooth Low Energy (BLE) devices in a UniApp application using Vuex for state management. The solution includes proper permission handling and device caching to avoid redundant connections.

Bluetooth Initialization and Connection

created() {
    // #ifdef APP-PLUS
    this.cachedDevices = [];
    // Check if location permissions are granted
    this.appPermissionStatus = uni.getAppAuthorizeSetting();
    if (this.appPermissionStatus.locationAuthorized === 'denied' || 
        this.appPermissionStatus.locationAuthorized === 'not determined') {
        this.requestLocationPermission();
        return;
    }
    // Initialize Bluetooth scanning
    this.initializeBluetooth();
    console.log(this.bluetoothStore, 'Checking cached Bluetooth data');
    if(this.bluetoothStore.deviceInfo.name) {
        this.cachedDevices.push(this.bluetoothStore.deviceInfo);
        this.connectToDevice(this.bluetoothStore.deviceInfo);
    }
    // #endif
},

methods: {
    // Check Bluetooth adapter status
    checkBluetoothAvailability() {
        const self = this;
        uni.getBluetoothAdapterState({
            success (res) {
                if (res.available) {
                    self.isScanning = true;
                    if (res.discovering) {
                        uni.showToast({
                            title: 'Scanning for nearby printers',
                            icon: "none"
                        });
                        return;
                    }
                    // Start discovering Bluetooth devices
                    self.discoverBluetoothDevices();
                } else {
                    uni.showModal({
                        title: 'Notice',
                        content: 'Bluetooth is not available on this device',
                    });
                }
            }
        });
    },
    
    // Get available Bluetooth devices
    discoverBluetoothDevices() {
        const self = this;
        self.availableDevices = [];
        
        uni.startBluetoothDevicesDiscovery({
            success (res) {
                console.log('Bluetooth discovery started', res);
                
                // Listen for discovered devices
                uni.onBluetoothDeviceFound((result) => {
                    console.log('New device found:', result.devices);
                    let currentList = self.availableDevices;
                    let newDevices = [];
                    let discoveredList = result.devices;
                    
                    for (let i = 0; i < discoveredList.length; ++i) {
                        if (discoveredList[i].name) {
                            // Check if device already exists
                            let existingDevice = currentList.filter((item) => {
                                return item.deviceId == discoveredList[i].deviceId;
                            });
                            
                            if (existingDevice.length === 0) {
                                newDevices.push(discoveredList[i]);
                            }
                        }
                    }
                    
                    self.availableDevices = currentList.concat(newDevices);
                });
                
                // Set timeout to get final list of devices
                self.scanTimeout = setTimeout(() => {
                    uni.getBluetoothDevices({
                        success (res) {
                            console.log('Final device list:', res);
                            let devices = [];
                            let deviceList = res.devices;
                            
                            // Filter out devices without names
                            for (let i = 0; i < deviceList.length; ++i) {
                                if (deviceList[i].name) {
                                    devices.push(deviceList[i]);
                                }
                            }
                            self.availableDevices = devices;
                        },
                        fail (error) {
                            console.log('Failed to get devices:', error);
                        }
                    });
                    
                    clearTimeout(self.scanTimeout);
                }, 1000);
            },
            fail (error) {
                console.log('Failed to start discovery:', error);
            }
        });
    },
    
    // Connect to a specific Bluetooth device
    connectToDevice(device) {
        const self = this;
        
        // Close existing connection if any
        if(self.currentDeviceId) {
            this.terminateConnection(self.currentDeviceId);
        }
        
        let deviceId = device.deviceId;
        let deviceName = device.name;
        
        // Reset connection state
        self.currentServiceId = 0;
        self.hasWriteCharacteristic = false;
        self.hasReadCharacteristic = false;
        self.hasNotifyCharacteristic = false;
        
        uni.showLoading({
            title: 'Connecting',
        });
        
        console.log('Connecting to device:', deviceId);
        
        uni.createBLEConnection({
            deviceId: deviceId,
            success (res) {
                self.currentDeviceId = deviceId;
                
                // Set MTU size
                uni.setBLEMTU({
                    deviceId: deviceId,
                    mtu: 512,
                    success (res) {
                        console.log('MTU set successfully');
                    },
                    fail (error) {
                        console.log('Failed to set MTU:', error);
                    }
                });
                
                // Store device info in Vuex
                self.$store.commit('updateDeviceInfo', {
                    deviceId: deviceId,
                    name: deviceName
                });
                
                // Get device services
                self.getDeviceServices();
            },
            fail (error) {
                self.isReadyToPrint = false;
                if (error.code == 1002) {
                    uni.showToast({
                        title:'Connection timeout, please try again',
                        icon: "none"
                    });
                } else {
                    uni.showToast({
                        title:'Please turn on your Bluetooth printer',
                        icon: "none"
                    });
                }
                console.log('Connection failed:', error);
                uni.hideLoading();
            }
        });
    },
    
    // Get all services from a connected device
    getDeviceServices() {
        const self = this;
        let { deviceInfo } = self.bluetoothStore;
        
        // Set timeout to wait for services
        let serviceTimeout = setTimeout(() => {
            uni.getBLEDeviceServices({
                deviceId: deviceInfo.deviceId,
                success (res) {
                    uni.hideLoading();
                    if (res.services.length === 0) {
                        uni.showToast({
                            title:'Failed to connect to Bluetooth, please try again',
                            icon: "none"
                        });
                        return;
                    }
                    
                    uni.showToast({
                        title:'Connected, ready to print',
                        icon: "none"
                    });
                    
                    self.currentServiceIndex = self.selectedDeviceIndex;
                    self.availableServices = res.services;
                    self.cachedDevices = [];
                    
                    if(deviceInfo.name) {
                        self.cachedDevices.push(deviceInfo);
                    }
                    
                    self.getDeviceCharacteristics();
                },
                fail (error) {
                    uni.hideLoading();
                    console.log('Failed to get services:', error);
                }
            });
            clearTimeout(serviceTimeout);
        }, 4000);
    },
    
    // Get characteristics from a service
    getDeviceCharacteristics() {
        var self = this;
        let {
            services: serviceList,
            currentServiceIndex: serviceIndex,
            hasWriteCharacteristic: write,
            hasReadCharacteristic: read,
            hasNotifyCharacteristic: notify
        } = self;
        let { deviceInfo } = self.bluetoothStore;
        
        uni.getBLEDeviceCharacteristics({
            deviceId: deviceInfo.deviceId,
            serviceId: serviceList[serviceIndex].uuid,
            success (res) {
                for (var i = 0; i < res.characteristics.length; ++i) {
                    var properties = res.characteristics[i].properties;
                    var item = res.characteristics[i].uuid;
                    
                    if (!notify && properties.notify) {
                        deviceInfo.notifyCharaterId = item;
                        deviceInfo.notifyServiceId = serviceList[serviceIndex].uuid;
                        self.$store.commit('updateDeviceInfo', deviceInfo);
                        notify = true;
                    }
                    
                    if (!write && properties.write) {
                        deviceInfo.writeCharaterId = item;
                        deviceInfo.writeServiceId = serviceList[serviceIndex].uuid;
                        self.$store.commit('updateDeviceInfo', deviceInfo);
                        write = true;
                    }
                    
                    if (!read && properties.read) {
                        deviceInfo.readCharaterId = item;
                        deviceInfo.readServiceId = serviceList[serviceIndex].uuid;
                        self.$store.commit('updateDeviceInfo', deviceInfo);
                        read = true;
                    }
                }
                
                if (!write || !notify || !read) {
                    serviceIndex++;
                    self.hasWriteCharacteristic = write;
                    self.hasReadCharacteristic = read;
                    self.hasNotifyCharacteristic = notify;
                    self.currentServiceIndex = serviceIndex;
                    
                    if (serviceIndex == serviceList.length) {
                        uni.showModal({
                            title: 'Notice',
                            content: 'Required characteristics not found',
                        });
                    } else {
                        self.getDeviceCharacteristics();
                    }
                }
            },
            fail (error) {
                console.log('Failed to get characteristics:', error);
            }
        })
    },
    
    // Terminate Bluetooth connection
    terminateConnection(deviceId) {
        uni.closeBLEConnection({
            deviceId: deviceId,
            success (res) {
                console.log('Connection closed:', res);
            },
            fail(error) {
                console.log('Failed to close connection:', error);
            }
        });
    },
    
    // Initialize Bluetooth adapter
    initializeBluetooth() {
        const self = this;
        self.closeBluetoothAdapter();
        
        uni.openBluetoothAdapter({
            success (res) {
                console.log('Bluetooth adapter initialized');
            },
            fail (error) {
                uni.showToast({
                    title:'Failed to initialize Bluetooth, please enable Bluetooth',
                    icon: "none"
                });
                console.log('Failed to initialize Bluetooth:', error);
            }
        });
    },
    
    // Stop Bluetooth device discovery
    stopScanning() {
        uni.stopBluetoothDevicesDiscovery({
            success: (res) => {
                console.log('Stopped scanning:', res);
            },
            fail: (error) => {
                console.log('Failed to stop scanning:', error);
            }
        });
    },
    
    // Close Bluetooth adapter
    closeBluetoothAdapter() {
        uni.closeBluetoothAdapter({
            success (res) {
                console.log('Bluetooth adapter closed');
            },
            fail (error) {
                console.log('Failed to close Bluetooth adapter:', error);
            }
        });
    }
}

Permission Management

Create a permission.js file to handle device permissions:

/**
 * Permission management module for Android and iOS
 * Handles permission checks, opening permission settings, and system service status
 */

var isIosDevice;
// #ifdef APP-PLUS
isIosDevice = (plus.os.name == "iOS");
// #endif

// Check iOS push notification permission
function checkIosPushPermission() {
    var result = false;
    var UIApplication = plus.ios.import("UIApplication");
    var app = UIApplication.sharedApplication();
    var enabledTypes = 0;
    
    if (app.currentUserNotificationSettings) {
        var settings = app.currentUserNotificationSettings();
        enabledTypes = settings.plusGetAttribute("types");
        
        if (enabledTypes == 0) {
            console.log("Push notifications not enabled");
        } else {
            result = true;
            console.log("Push notifications enabled");
        }
        plus.ios.deleteObject(settings);
    } else {
        enabledTypes = app.enabledRemoteNotificationTypes();
        if (enabledTypes == 0) {
            console.log("Push notifications not enabled");
        } else {
            result = true;
            console.log("Push notifications enabled");
        }
    }
    
    plus.ios.deleteObject(app);
    plus.ios.deleteObject(UIApplication);
    return result;
}

// Check iOS location permission
function checkIosLocationPermission() {
    var result = false;
    var cllocationManger = plus.ios.import("CLLocationManager");
    var status = cllocationManger.authorizationStatus();
    result = (status != 2);
    console.log("Location permission status:", result);
    plus.ios.deleteObject(cllocationManger);
    return result;
}

// Check iOS microphone permission
function checkIosMicrophonePermission() {
    var result = false;
    var avaudiosession = plus.ios.import("AVAudioSession");
    var avaudio = avaudiosession.sharedInstance();
    var permissionStatus = avaudio.recordPermission();
    
    if (permissionStatus == 1684369017 || permissionStatus == 1970168948) {
        console.log("Microphone permission not enabled");
    } else {
        result = true;
        console.log("Microphone permission enabled");
    }
    
    plus.ios.deleteObject(avaudiosession);
    return result;
}

// Check iOS camera permission
function checkIosCameraPermission() {
    var result = false;
    var AVCaptureDevice = plus.ios.import("AVCaptureDevice");
    var authStatus = AVCaptureDevice.authorizationStatusForMediaType('vide');
    
    if (authStatus == 3) {
        result = true;
        console.log("Camera permission enabled");
    } else {
        console.log("Camera permission not enabled");
    }
    
    plus.ios.deleteObject(AVCaptureDevice);
    return result;
}

// Check iOS photo library permission
function checkIosPhotoLibraryPermission() {
    var result = false;
    var PHPhotoLibrary = plus.ios.import("PHPhotoLibrary");
    var authStatus = PHPhotoLibrary.authorizationStatus();
    
    if (authStatus == 3) {
        result = true;
        console.log("Photo library permission enabled");
    } else {
        console.log("Photo library permission not enabled");
    }
    
    plus.ios.deleteObject(PHPhotoLibrary);
    return result;
}

// Check iOS contacts permission
function checkIosContactsPermission() {
    var result = false;
    var CNContactStore = plus.ios.import("CNContactStore");
    var cnAuthStatus = CNContactStore.authorizationStatusForEntityType(0);
    
    if (cnAuthStatus == 3) {
        result = true;
        console.log("Contacts permission enabled");
    } else {
        console.log("Contacts permission not enabled");
    }
    
    plus.ios.deleteObject(CNContactStore);
    return result;
}

// Check iOS calendar permission
function checkIosCalendarPermission() {
    var result = false;
    var EKEventStore = plus.ios.import("EKEventStore");
    var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(0);
    
    if (ekAuthStatus == 3) {
        result = true;
        console.log("Calendar permission enabled");
    } else {
        console.log("Calendar permission not enabled");
    }
    
    plus.ios.deleteObject(EKEventStore);
    return result;
}

// Check iOS notes permission
function checkIosNotesPermission() {
    var result = false;
    var EKEventStore = plus.ios.import("EKEventStore");
    var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(1);
    
    if (ekAuthStatus == 3) {
        result = true;
        console.log("Notes permission enabled");
    } else {
        console.log("Notes permission not enabled");
    }
    
    plus.ios.deleteObject(EKEventStore);
    return result;
}

// Android permission request
function requestAndroidPermission(permissionID) {
    return new Promise((resolve, reject) => {
        plus.android.requestPermissions(
            [permissionID],
            function(resultObj) {
                var result = 0;
                
                // Check granted permissions
                for (var i = 0; i < resultObj.granted.length; i++) {
                    var grantedPermission = resultObj.granted[i];
                    console.log('Granted permission:', grantedPermission);
                    result = 1;
                }
                
                // Check denied permissions
                for (var i = 0; i < resultObj.deniedPresent.length; i++) {
                    var deniedPermission = resultObj.deniedPresent[i];
                    console.log('Denied permission:', deniedPermission);
                    result = 0;
                }
                
                // Check permanently denied permissions
                for (var i = 0; i < resultObj.deniedAlways.length; i++) {
                    var permanentlyDeniedPermission = resultObj.deniedAlways[i];
                    console.log('Permanently denied permission:', permanentlyDeniedPermission);
                    result = -1;
                }
                
                resolve(result);
            },
            function(error) {
                console.log('Permission request error:', error.code, error.message);
                resolve({
                    code: error.code,
                    message: error.message
                });
            }
        );
    });
}

// Generic permission checker based on type
function checkIosPermission(permissionType) {
    switch(permissionType) {
        case "location":
            return checkIosLocationPermission();
        case "camera":
            return checkIosCameraPermission();
        case "photoLibrary":
            return checkIosPhotoLibraryPermission();
        case "microphone":
            return checkIosMicrophonePermission();
        case "push":
            return checkIosPushPermission();
        case "contacts":
            return checkIosContactsPermission();
        case "calendar":
            return checkIosCalendarPermission();
        case "notes":
            return checkIosNotesPermission();
        default:
            return false;
    }
}

// Navigate to app permission settings
function openAppPermissionSettings() {
    if (isIosDevice) {
        var UIApplication = plus.ios.import("UIApplication");
        var application = UIApplication.sharedApplication();
        var NSURL = plus.ios.import("NSURL");
        
        var settingsURL = NSURL.URLWithString("app-settings:");
        application.openURL(settingsURL);
        
        plus.ios.deleteObject(settingsURL);
        plus.ios.deleteObject(NSURL);
        plus.ios.deleteObject(application);
    } else {
        var Intent = plus.android.importClass("android.content.Intent");
        var Settings = plus.android.importClass("android.provider.Settings");
        var Uri = plus.android.importClass("android.net.Uri");
        
        var mainActivity = plus.android.runtimeMainActivity();
        var intent = new Intent();
        intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);
        intent.setData(uri);
        mainActivity.startActivity(intent);
    }
}

// Check if system location services are enabled
function checkSystemLocationServices() {
    if (isIosDevice) {
        var result = false;
        var cllocationManger = plus.ios.import("CLLocationManager");
        result = cllocationManger.locationServicesEnabled();
        console.log("System location services:", result);
        plus.ios.deleteObject(cllocationManger);
        return result;
    } else {
        var context = plus.android.importClass("android.content.Context");
        var locationManager = plus.android.importClass("android.location.LocationManager");
        var main = plus.android.runtimeMainActivity();
        var locationService = main.getSystemService(context.LOCATION_SERVICE);
        var result = locationService.isProviderEnabled(locationManager.GPS_PROVIDER);
        console.log("System location services:", result);
        return result;
    }
}

module.exports = {
    checkIosPermission: checkIosPermission,
    requestAndroidPermission: requestAndroidPermission,
    checkSystemLocationServices: checkSystemLocationServices,
    openAppPermissionSettings: openAppPermissionSettings
};

Handling Permission Denial

When location permission is denied, prompt the user to navigate to settings:

// Request location permission if denied
requestLocationPermission() {
    if (this.appPermissionStatus.locationAuthorized === 'denied' || 
        this.appPermissionStatus.locationAuthorized === 'not determined') {
        
        uni.showModal({
            title: "Location Access Required",
            content: "Location permission is needed for Bluetooth discovery. Please enable it in settings.",
            confirmText: "Open Settings",
            success (response) {
                if (response.confirm) {
                    permissionModule.openAppPermissionSettings();
                }
            }
        });
    }
},

Related Articles

Efficient Usage of HTTP Client in IntelliJ IDEA

IntelliJ IDEA incorporates a versatile HTTP client tool, enabling developres to interact with RESTful services and APIs effectively with in the editor. This functionality streamlines workflows, replac...

Installing CocoaPods on macOS Catalina (10.15) Using a User-Managed Ruby

System Ruby on macOS 10.15 frequently fails to build native gems required by CocoaPods (for example, ffi), leading to errors like: ERROR: Failed to build gem native extension checking for ffi.h... no...

Resolve PhpStorm "Interpreter is not specified or invalid" on WAMP (Windows)

Symptom PhpStorm displays: "Interpreter is not specified or invalid. Press ‘Fix’ to edit your project configuration." This occurs when the IDE cannot locate a valid PHP CLI executable or when the debu...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.