25_微信小程序蓝牙-蓝牙连接篇

注: 示例代码使用uniapp编写,下面通过uni.调用的API侧重于微信侧,等同于wx.,

上一篇文章: 24_微信小程序蓝牙-设备搜寻篇 中已经完成了蓝牙设备的搜寻,并展示搜索结果。

本文续接上一篇文章,扩展BluetoothManagerInstance对蓝牙设备连接状态管理。并实现蓝牙的连接与断开连接

注: 在微信小程序的蓝牙连接api中,只支持连接低功耗蓝牙,选购设备的时候需要特别留意,否则设备买错了也是无法通过微信小程序建立蓝牙连接的

一.在BluetoothManager中管理蓝牙设备连接状态
  • 首先需要申明一个数组connectedDeviceIds存放已经连接的设备id,通过uni.onBLEConnectionStateChange监听设备的连接状态改变。

  • 在uni.onBLEConnectionStateChange的回调中,会返回连接状态,以及对应的设备id,如果是已连接,把设备id添加到connectedDeviceIds,如果是断开连接,把设备id从connectedDeviceIds中移除。

  • 最后调用uni.getBluetoothDevices,获取最新搜索到的设备列表,并根据connectedDeviceIds的结果判断设备是否被连接。

  • 注: uni.onBluetoothDeviceFound注册的回调中,获取到的的设备列表,也需要根据connectedDeviceIds的结果判断设备是否被连接

    ...
    
    function getBluetoothDevices(): void {
    	uni.getBluetoothDevices().then((res) => {
    		let repeatDevices = (res || {}).devices || []
    		
    		//去重
    		let devices = []
    		repeatDevices.forEach((item) => {
    			let deviceId = item.deviceId
    			let existsIndex = devices.findIndex((existsItem) => existsItem.deviceId == deviceId)
    			if(existsIndex == -1) {
    				devices.push(item)
    			}
    		})
    		
    		//排序
    		devices.sort((item1, item2) => {
    			let name1 = (item1.localName || item1.name).toLowerCase()
    			let name2 = (item2.localName || item2.name).toLowerCase()
    			return name1.localeCompare(name2)
    		})
    		
    		devices.forEach((item) => {
    			let deviceId = item.deviceId
    			let existsIndex = this.connectedDeviceIds.findIndex((connectedId) => connectedId == deviceId)
    			item.connected = existsIndex != -1
    		})
    		
    		dispatchDeviceFound.call(this, devices)
    	})
    }
    
    function initializeBluetooth(): Promise<any> {
      ...
      
      return uni.authorize({scope: scope}).catch((err) => {
    		return getSetting()
    	}).catch((err) => {
    		this.status = BLUETOOTH_AUTHOR_REJECT
    		dispatchBluetoothStatus.call(this, this.status)
    		return Promise.reject()
    	}).then((res) => {
    		return uni.closeBluetoothAdapter().catch((err) => Promise.resolve())
    	}).then((res) => {
    		return openBluetoothAdapter()
    	}).then((res) => {
    		return stopScanBluetoothDevice.call(this)
    	}).then((res) => {
    		return startScanBluetoothDevice.call(this)
    	}).then((res) => {
    		//关闭蓝牙连接状态监听
    		uni.offBLEConnectionStateChange(this.onBLEConnectionStateChange)
    		//开启蓝牙连接状态监听
    		uni.onBLEConnectionStateChange(this.onBLEConnectionStateChange)
    	})
    }
    
    class BluetoothManagerInstance implements BluetoothManager {
      ...
      
      private onBluetoothDeviceFound: (result: any) => void = (result) => {
    		getBluetoothDevices.call(this)
    	}
      
      private connectedDeviceIds: Array<string> = [];
    
    	private onBLEConnectionStateChange: (result: any) => void = (result) => {
    		let connected = result.connected || false
    		let deviceId = result.deviceId || ""
    		
    		let existsIndex = this.connectedDeviceIds.findIndex((item) => item == deviceId)
    		if(connected) {
    			if(existsIndex == -1) {
    				this.connectedDeviceIds.push(deviceId)
    			}
    		} else {
    			if(existsIndex != -1) {
    				this.connectedDeviceIds.splice(existsIndex, 1)
    			}
    		}
    		
    		getBluetoothDevices.call(this)
    	}
      
      ...
      
      destroy(): void {
    		this.status = BLUETOOTH_AUTHOR_PENDING
    		
    		//关闭蓝牙连接状态监听
    		uni.offBLEConnectionStateChange(this.onBLEConnectionStateChange)
    		
    		stopScanBluetoothDevice.call(this)
    		
    		//关闭蓝牙适配器状态监听
    		uni.offBluetoothAdapterStateChange(this.adapterStateChange)
    		
    		uni.closeBluetoothAdapter().catch(() => undefined)
    		this.statusCallbacks = []
    		this.devicesCallbacks = []
    		this.connectedDeviceIds = []
    		
    		uni.offAppShow(this.onAppShow)
    	}
    }
    
二.连接蓝牙设备与断开蓝牙设备连接
  • 连接蓝牙设备,只需要调用uni.createBLEConnection,传入对应的设备id即可。

  • 断开蓝牙设备连接,只需要调用uni.closeBLEConnection,传入对应的设备id即可。

    <!-- index.vue -->
    <template>
    	<view class="index-page">
    		<view class="bluetooth-status">蓝牙状态: {{bluetoothStatus}}</view>
    		<scroll-view scroll-y class="device-list">
    			<view v-for="(item, index) in devices" class="device-item">
    				<view class="device-info-box">
    					<view class="device-info">设备名称: {{item.localName || item.name || item.deviceId}}</view>
    					<view class="device-info">设备ID: {{item.deviceId}}</view>
    				</view>
    				<view class="btn-change-connect" @click="toChangeConnect($event, index)">{{item.connected ? "断开连接":"连接"}}</view>
    			</view>
    		</scroll-view>
    	</view>
    </template>
    
    <script>
    	export default {
    		data() {
    			return {
    				bluetoothStatus: "BLUETOOTH_AUTHOR_PENDING",
    				devices: []
    			}
    		},
    		onLoad() {
    			getApp().globalData.bluetoothManager.on("statusChange", this.onBluetoothStatusChanged)
    			getApp().globalData.bluetoothManager.on("deviceFound", this.onDeviceFound)
    		},
    		onUnload() {
    			getApp().globalData.bluetoothManager.off("statusChange", this.onBluetoothStatusChanged)
    			getApp().globalData.bluetoothManager.off("deviceFound", this.onDeviceFound)
    		},
    		methods: {
    			onBluetoothStatusChanged: function(status) {
    				this.bluetoothStatus = status
    			},
    			onDeviceFound: function(devices) {
    				this.devices = devices
    			},
    			toChangeConnect: function(event, index) {
            //只管连接和断开连接,状态交给BluetoothManager
    				let item = this.devices[index]
    				
    				uni.showLoading()
    				let changeTask = undefined
    				if(!item.connected) {
    					changeTask = uni.createBLEConnection({
    						deviceId: item.deviceId
    					})
    				} else {
    					changeTask = uni.closeBLEConnection({
    						deviceId: item.deviceId
    					})
    				}
    				changeTask.finally(() => {
    					uni.hideLoading()
    				})
    			}
    		}
    	}
    </script>
    
    <style scoped>
    	.index-page {
    		width: 100%;
    		height: 100%;
    		display: flex;
    		flex-direction: column;
    		align-items: stretch;
    		justify-content: center;
    	}
    	
    	.bluetooth-status {
    		font-size: 24rpx;
    		padding: 20rpx 0;
    		margin: 0 20rpx;
    		box-sizing: border-box;
    		flex-shrink: 0;
    		border-bottom: thin solid #f2f2f2;
    	}
    	
    	.device-list {
    		flex: 1;
    		overflow: hidden;
    	}
    	
    	.device-item {
    		padding: 20rpx 0;
    		margin: 0 20rpx;
    		box-sizing: border-box;
    		background: white;
    		border-bottom: thin solid #f2f2f2;
    		display: flex;
    		flex-direction: row;
    		align-items: center;
    		justify-content: center;
    	}
    	
    	.device-info {
    		font-size: 28rpx;
    		margin-top: 20rpx;
    	}
    	
    	.device-info:first-child {
    		margin-top: 0;
    	}
    	
    	.device-info-box {
    		flex: 1;
    		overflow: hidden;
    		display: flex;
    		flex-direction: column;
    		align-items: stretch;
    		justify-content: center;
    	}
    	
    	.btn-change-connect {
    		padding: 20rpx 60rpx;
    		box-sizing: border-box;
    		background: #2BA3FF;
    		color: white;
    		font-size: 28rpx;
    		border-radius: 10rpx;
    	}
    </style>
    

    在这里插入图片描述

三.BluetoothManager完整代码
/**
 * 蓝牙权限待申请
 */
const BLUETOOTH_AUTHOR_PENDING:string = "BLUETOOTH_AUTHOR_PENDING"
/**
 * 蓝牙权限被拒绝
 */
const BLUETOOTH_AUTHOR_REJECT:string = "BLUETOOTH_AUTHOR_REJECT"
/**
 * BluetoothAdapter不可用
 */
const BLUETOOTH_ADAPTER_UNAVAILABLE:string = "BLUETOOTH_ADAPTER_UNAVAILABLE"
/**
 * 蓝牙适配器可用
 */
const BLUETOOTH_ADAPTER_AVAILABLE:string = "BLUETOOTH_ADAPTER_AVAILABLE"

export interface BluetoothManager {
	on(type: string, callback: (status: string) => void): void;
	off(type: string, callback: (status: string) => void): void;
	destroy(): void;
}

function dispatchDeviceFound(devices: Array<any>) {
	this.devicesCallbacks.forEach((func) => {
		func && func(devices)
	})
}

function dispatchBluetoothStatus(status: string): void {
	this.statusCallbacks.forEach((func) => {
		func && func(status)
	})
}

function startScanBluetoothDevice(): Promise<any> {
	return uni.startBluetoothDevicesDiscovery({
		allowDuplicatesKey: false,
		interval: 1000
	}).catch((err) => {
		return Promise.reject()
	}).then(() => {
		//添加搜索到新设备的事件的监听函数
		uni.offBluetoothDeviceFound(this.onBluetoothDeviceFound)
		uni.onBluetoothDeviceFound(this.onBluetoothDeviceFound)
		return Promise.resolve()
	})
}

function stopScanBluetoothDevice():Promise<any> {
	//移除搜索到新设备的事件的监听函数
	uni.offBluetoothDeviceFound(this.onBluetoothDeviceFound)
	return uni.stopBluetoothDevicesDiscovery().catch(() => undefined)
}

function getBluetoothDevices(): void {
	uni.getBluetoothDevices().then((res) => {
		let repeatDevices = (res || {}).devices || []
		
		//去重
		let devices = []
		repeatDevices.forEach((item) => {
			let deviceId = item.deviceId
			let existsIndex = devices.findIndex((existsItem) => existsItem.deviceId == deviceId)
			if(existsIndex == -1) {
				devices.push(item)
			}
		})
		
		//排序
		devices.sort((item1, item2) => {
			let name1 = (item1.localName || item1.name).toLowerCase()
			let name2 = (item2.localName || item2.name).toLowerCase()
			return name1.localeCompare(name2)
		})
		
		devices.forEach((item) => {
			let deviceId = item.deviceId
			let existsIndex = this.connectedDeviceIds.findIndex((connectedId) => connectedId == deviceId)
			item.connected = existsIndex != -1
		})
		
		dispatchDeviceFound.call(this, devices)
	})
}

function initializeBluetooth(): Promise<any> {
	this.status = BLUETOOTH_AUTHOR_PENDING
	dispatchBluetoothStatus.call(this, this.status)
	
	const scope:string = "scope.bluetooth"
	const openBluetoothAdapter = ():Promise<any> => {
		return uni.openBluetoothAdapter().catch((err) => {
			this.status = BLUETOOTH_ADAPTER_UNAVAILABLE
			dispatchBluetoothStatus.call(this, this.status)
			return Promise.reject()
		}).then((res) => {
			this.status = BLUETOOTH_ADAPTER_AVAILABLE
			dispatchBluetoothStatus.call(this, this.status)
			return Promise.resolve()
		}).finally(() => {
			//关闭蓝牙适配器状态监听
			uni.offBluetoothAdapterStateChange(this.adapterStateChange)
			//开启蓝牙适配器状态监听
			uni.onBluetoothAdapterStateChange(this.adapterStateChange)
		})
	}
	
	const openSetting = () => {
		return uni.showModal({
			content: "您需要在设置中开启\"蓝牙\"选项,才可以使用蓝牙哦!",
			cancelText: "任性不去",
			confirmText: "去设置",
		}).then((res) => {
			if (res.confirm) {
				return new Promise((resolve, reject) => {
					this.openSettingResolves.push(resolve)
					uni.openSetting().catch((error) => {
						reject({
							"errMsg": "openSetting:fail system error",
							"errno": -2
						})
					})
				}).then((res) => {
					let authSetting = res.authSetting || {}
					if (authSetting[scope]) {
						return Promise.resolve()
					} else {
						return openSetting()
					}
				})
			} else {
				return Promise.reject({
					"errMsg": "authorize:fail auth deny",
					"errno": 103
				})
			}
		})
	}
	
	const getSetting = () => {
		return uni.getSetting().catch((error) => {
			return Promise.reject({
				"errMsg": "authorize:fail system error",
				"errno": -2
			})
		}).then((res) => {
			let authSetting = res.authSetting || {}
			let keys = Object.keys(authSetting)
			
			if (keys.indexOf(scope) != -1) {
				if(!authSetting[scope]) {
					return openSetting()
				} else {
					return Promise.resolve() 
				}
			} else {
				return Promise.reject({
					"errMsg": "authorize:fail auth canceled",
					"errno": -3
				})
			}
		})
	}
	
	return uni.authorize({scope: scope}).catch((err) => {
		return getSetting()
	}).catch((err) => {
		this.status = BLUETOOTH_AUTHOR_REJECT
		dispatchBluetoothStatus.call(this, this.status)
		return Promise.reject()
	}).then((res) => {
		return uni.closeBluetoothAdapter().catch((err) => Promise.resolve())
	}).then((res) => {
		return openBluetoothAdapter()
	}).then((res) => {
		return stopScanBluetoothDevice.call(this)
	}).then((res) => {
		return startScanBluetoothDevice.call(this)
	}).then((res) => {
		//关闭蓝牙连接状态监听
		uni.offBLEConnectionStateChange(this.onBLEConnectionStateChange)
		//开启蓝牙连接状态监听
		uni.onBLEConnectionStateChange(this.onBLEConnectionStateChange)
	})
}

class BluetoothManagerInstance implements BluetoothManager {
	
	private status:string;
	private statusCallbacks: Array<(status: string) => void> = [];
	private openSettingResolves: Array<(result:any) => void> = [];
	private devicesCallbacks: Array<(devices: Array<any>) => void> = [];
	
	private onAppShow: (options: any) => void = (options): void => {
		uni.getSetting().then((res) => {
			if(this.openSettingResolves.length > 0) {
				this.openSettingResolves.forEach((resolveFunc) => resolveFunc(res))
				this.openSettingResolves = []
			} else {
				let authSetting = res.authSetting || {}
				let keys = Object.keys(authSetting)
				
				if(keys.indexOf("scope.bluetooth") != -1) {
					if (!authSetting["scope.bluetooth"]) {
						if(this.status != BLUETOOTH_AUTHOR_PENDING) {
							this.status = BLUETOOTH_AUTHOR_REJECT
							dispatchBluetoothStatus.call(this, this.status)
						}
					} else {
						if(this.status == BLUETOOTH_AUTHOR_REJECT) {
							initializeBluetooth.call(this);
						}
					}
				}
			}
			return Promise.resolve()
		}).catch((err) => {
			return Promise.resolve()
		})
	}
	
	private adapterStateChange: (result: any) => void = (res) => {
		let available = (res || {}).available || false
		let discovering = (res || {}).discovering || false
		
		let bluetoothStatus = BLUETOOTH_ADAPTER_UNAVAILABLE
		if(available) {
			bluetoothStatus = BLUETOOTH_ADAPTER_AVAILABLE
		}
		if(this.status != bluetoothStatus) {
			//蓝牙开关状态改变
			if(bluetoothStatus == BLUETOOTH_ADAPTER_AVAILABLE) {
				//搜索设备
				startScanBluetoothDevice.call(this)
			} else {
				//停止搜索设备
				stopScanBluetoothDevice.call(this)
			}
		}
		
		this.status = bluetoothStatus
	}
	
	private onBluetoothDeviceFound: (result: any) => void = (result) => {
		getBluetoothDevices.call(this)
	}
	
	private connectedDeviceIds: Array<string> = [];
	
	private onBLEConnectionStateChange: (result: any) => void = (result) => {
		let connected = result.connected || false
		let deviceId = result.deviceId || ""
		
		let existsIndex = this.connectedDeviceIds.findIndex((connectedId) => connectedId == deviceId)
		if(connected) {
			if(existsIndex == -1) {
				this.connectedDeviceIds.push(deviceId)
			}
		} else {
			if(existsIndex != -1) {
				this.connectedDeviceIds.splice(existsIndex, 1)
			}
		}
		
		getBluetoothDevices.call(this)
	}
	
	constructor() {
		uni.onAppShow(this.onAppShow);
		initializeBluetooth.call(this);
	}
	
	on(type: string, callback: (result: any) => void): void {
		if(type == "statusChange") {
			this.statusCallbacks.push(callback)
		} else if(type == "deviceFound") {
			this.devicesCallbacks.push(callback)
		}
	}
	
	off(type: string, callback: (result: any) => void): void {
		if(type == "statusChange") {
			let callbackIndex = this.statusCallbacks.findIndex((item) => item == callback)
			if(callbackIndex == -1) {
				return
			}
			this.statusCallbacks.splice(callbackIndex, 1)
		} else if(type == "deviceFound") {
			let callbackIndex = this.devicesCallbacks.findIndex((item) => item == callback)
			if(callbackIndex == -1) {
				return
			}
			this.devicesCallbacks.splice(callbackIndex, 1)
		}
	}
	
	destroy(): void {
		this.status = BLUETOOTH_AUTHOR_PENDING
		
		//关闭蓝牙连接状态监听
		uni.offBLEConnectionStateChange(this.onBLEConnectionStateChange)
		
		stopScanBluetoothDevice.call(this)
		
		//关闭蓝牙适配器状态监听
		uni.offBluetoothAdapterStateChange(this.adapterStateChange)
		
		uni.closeBluetoothAdapter().catch(() => undefined)
		this.statusCallbacks = []
		this.devicesCallbacks = []
		this.connectedDeviceIds = []
		
		uni.offAppShow(this.onAppShow)
	}
	
}

export const createBluetoothManager = ():BluetoothManager => {
	return new BluetoothManagerInstance();
}

更多推荐