Fading Coder

One Final Commit for the Last Sprint

Home > Tools > Content

Implementing Bluetooth Device Discovery and Connection in UniApp with Permission Management

Tools May 7 45

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...

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...

Capturing Android Screenshots and Screen Recordings with ADB

Two practical ways to grab images and videos from an Android device: Mirror the phone display to a computer and use desktop tools for screenshots and GIFs Use ADB commands (no UI mirroring required)...

Leave a Comment

Anonymous

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