/** * 蓝牙设备搜索页面 * 功能:搜索、配对、连接蓝牙设备 */ import { router } from '@kit.ArkUI'; import { access, connection } from '@kit.ConnectivityKit'; import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit'; import { BusinessError } from '@kit.BasicServicesKit'; // 设备信息接口 interface BluetoothDevice { deviceId: string; name: string; displayName: string; address: string; isPaired: boolean; connected: boolean; } @Entry @Component struct BluetoothSearchPage { @State isSearching: boolean = false; @State devices: BluetoothDevice[] = []; @State bluetoothState: number = -1; // 0=关闭, 2=打开 @State hasPermission: boolean = false; @State connectedDeviceId: string = ''; @State toastMessage: string = ''; @State showToast: boolean = false; @State showConfirmDialog: boolean = false; @State confirmDialogDevice: BluetoothDevice | null = null; // 页面显示时初始化 aboutToAppear(): void { this.init(); } // 页面销毁时清理 aboutToDisappear(): void { this.stopSearch(); } // 初始化 async init(): Promise { await this.checkAndRequestPermission(); await this.checkBluetoothState(); this.registerBluetoothStateListener(); } // 显示Toast提示 showToastMessage(message: string): void { this.toastMessage = message; this.showToast = true; setTimeout(() => { this.showToast = false; }, 2000); } // 检查并请求蓝牙权限 async checkAndRequestPermission(): Promise { const permissions: Permissions[] = ['ohos.permission.ACCESS_BLUETOOTH']; try { const atManager = abilityAccessCtrl.createAtManager(); const context = getContext(this) as common.UIAbilityContext; // 请求权限 const result = await atManager.requestPermissionsFromUser(context, permissions); if (result.authResults[0] === 0) { this.hasPermission = true; console.info('蓝牙权限请求成功'); return true; } else { this.hasPermission = false; console.warn('蓝牙权限被拒绝'); return false; } } catch (err) { console.error('检查权限失败:', JSON.stringify(err)); return false; } } // 检查蓝牙状态 async checkBluetoothState(): Promise { try { const state = access.getState(); this.bluetoothState = state; console.info('蓝牙状态:', state); return state; } catch (err) { console.error('获取蓝牙状态失败:', JSON.stringify(err)); return -1; } } // 注册蓝牙状态监听 registerBluetoothStateListener(): void { try { access.on('stateChange', (data: access.BluetoothState) => { console.info('蓝牙状态变化:', data); this.bluetoothState = data; }); } catch (err) { console.error('注册蓝牙状态监听失败:', JSON.stringify(err)); } } // 打开蓝牙 openBluetooth(): void { try { access.enableBluetooth(); this.showToastMessage('正在打开蓝牙...'); // 等待蓝牙状态更新 setTimeout(() => { this.checkBluetoothState(); }, 1000); } catch (err) { console.error('打开蓝牙失败:', JSON.stringify(err)); this.showToastMessage('打开蓝牙失败'); } } // 开始搜索设备 async startSearch(): Promise { if (this.isSearching) { return; } // 检查权限 if (!this.hasPermission) { const granted = await this.checkAndRequestPermission(); if (!granted) { this.showToastMessage('请授予蓝牙权限'); return; } } // 检查蓝牙状态 const state = await this.checkBluetoothState(); if (state !== access.BluetoothState.STATE_ON) { this.showToastMessage('请先打开蓝牙'); return; } this.isSearching = true; this.devices = []; this.showToastMessage('正在搜索设备...'); // 加载已配对设备 this.loadPairedDevices(); // 注册设备发现监听 try { connection.on('bluetoothDeviceFind', (data: Array) => { console.info('发现设备:', JSON.stringify(data)); data.forEach((deviceId: string) => { this.onDeviceFound(deviceId); }); }); // 开始发现设备 connection.startBluetoothDiscovery(); console.info('开始搜索蓝牙设备'); // 30秒后自动停止搜索 setTimeout(() => { this.stopSearch(); if (this.devices.length > 0) { this.showToastMessage(`找到 ${this.devices.length} 个设备`); } else { this.showToastMessage('未找到设备'); } }, 30000); } catch (err) { console.error('开始搜索失败:', JSON.stringify(err)); this.isSearching = false; this.showToastMessage('搜索失败'); } } // 停止搜索 stopSearch(): void { this.isSearching = false; try { connection.stopBluetoothDiscovery(); connection.off('bluetoothDeviceFind'); console.info('停止搜索'); } catch (err) { console.error('停止搜索失败:', JSON.stringify(err)); } } // 处理发现的设备 onDeviceFound(deviceId: string): void { // 检查设备是否已存在 const exists = this.devices.find(d => d.deviceId === deviceId); if (exists) { return; } try { const deviceName = connection.getRemoteDeviceName(deviceId); const displayName = deviceName || deviceId || '未知设备'; const deviceInfo: BluetoothDevice = { deviceId: deviceId, name: deviceName, displayName: displayName, address: deviceId, isPaired: false, connected: false, }; this.devices = [...this.devices, deviceInfo]; console.info('添加设备:', displayName, deviceId); } catch (err) { console.error('获取设备信息失败:', JSON.stringify(err)); } } // 加载已配对设备 loadPairedDevices(): void { try { const pairedDevices = connection.getPairedDevices(); console.info('已配对设备:', JSON.stringify(pairedDevices)); pairedDevices.forEach((deviceId: string) => { const exists = this.devices.find(d => d.deviceId === deviceId); if (!exists) { const deviceName = connection.getRemoteDeviceName(deviceId); const displayName = deviceName || deviceId || '未知设备'; const deviceInfo: BluetoothDevice = { deviceId: deviceId, name: deviceName, displayName: displayName, address: deviceId, isPaired: true, connected: false, }; this.devices = [...this.devices, deviceInfo]; console.info('添加已配对设备:', displayName); } }); } catch (err) { console.error('获取已配对设备失败:', JSON.stringify(err)); } } // 连接设备 connectDevice(device: BluetoothDevice): void { this.confirmDialogDevice = device; this.showConfirmDialog = true; } // 执行连接 doConnect(device: BluetoothDevice): void { this.showToastMessage('正在连接...'); try { // 如果未配对,先进行配对 if (!device.isPaired) { connection.pairDevice(device.deviceId); console.info('开始配对:', device.deviceId); } // 更新设备状态 device.connected = true; device.isPaired = true; this.connectedDeviceId = device.deviceId; // 保存最后连接的设备 AppStorage.setOrCreate('lastBleAddress', device.deviceId); this.showToastMessage('连接成功'); // 跳转到控制页面 setTimeout(() => { this.navigateToControl(device); }, 500); } catch (err) { console.error('连接失败:', JSON.stringify(err)); this.showToastMessage('连接失败,请重试'); } } // 跳转到控制页面 navigateToControl(device: BluetoothDevice): void { router.pushUrl({ url: 'pages/BluetoothControlPage', params: { deviceId: device.deviceId, deviceName: device.displayName } }).catch((err: BusinessError) => { console.error('跳转失败:', JSON.stringify(err)); }); } // 判断是否为MAC地址格式 isMacAddress(str: string): boolean { if (!str) return false; const macRegex = /^([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})$/; return macRegex.test(str); } build() { Stack() { Column() { // 顶部搜索栏 Row() { Text('设备列表(鸿蒙)') .fontSize(16) .fontWeight(FontWeight.Medium) .fontColor('#eaf6fc') Blank() Button(this.isSearching ? '搜索中...' : '搜索') .fontSize(14) .fontWeight(FontWeight.Medium) .fontColor(this.isSearching ? '#eaf6fc' : '#0b1830') .backgroundColor(this.isSearching ? 'rgba(34, 253, 200, 0.5)' : '#22fdc8') .borderRadius(25) .padding({ left: 20, right: 20, top: 8, bottom: 8 }) .onClick(() => this.startSearch()) } .width('100%') .padding(15) .backgroundColor('rgba(15, 30, 60, 0.8)') .borderRadius(8) .margin({ bottom: 15 }) // 蓝牙状态提示 if (this.bluetoothState !== access.BluetoothState.STATE_ON) { Row() { Text(this.bluetoothState === access.BluetoothState.STATE_OFF ? '蓝牙已关闭' : '蓝牙状态未知') .fontSize(14) .fontColor('#ff9800') Blank() Button('打开蓝牙') .fontSize(13) .fontWeight(FontWeight.Medium) .fontColor('#ffffff') .backgroundColor('#ff9800') .borderRadius(25) .padding({ left: 16, right: 16, top: 6, bottom: 6 }) .onClick(() => this.openBluetooth()) } .width('100%') .padding(12) .backgroundColor('rgba(255, 152, 0, 0.15)') .border({ width: 1, color: 'rgba(255, 152, 0, 0.5)' }) .borderRadius(6) .margin({ bottom: 15 }) } // 设备列表 if (this.devices.length === 0 && !this.isSearching) { Text('暂无设备,请点击搜索按钮') .fontSize(14) .fontColor('#8a9ba8') .margin({ top: 50 }) } else { List({ space: 10 }) { ForEach(this.devices, (device: BluetoothDevice, index: number) => { ListItem() { Row() { // 设备信息 Column() { Row() { Text('设备名称:') .fontSize(14) .fontColor('#8a9ba8') Text(device.displayName || device.name || '未知设备') .fontSize(16) .fontWeight(FontWeight.Bold) .fontColor('#22fdc8') .layoutWeight(1) } .width('100%') .margin({ bottom: 4 }) Row() { Text(this.isMacAddress(device.deviceId) ? 'MAC地址:' : '设备ID:') .fontSize(12) .fontColor('#8a9ba8') Text(device.deviceId) .fontSize(12) .fontColor('#b2ebf2') .layoutWeight(1) } .width('100%') } .layoutWeight(1) .alignItems(HorizontalAlign.Start) // 状态标签 Text(device.isPaired ? '已配对' : '未配对') .fontSize(12) .fontColor(device.isPaired ? '#4caf50' : '#ff9800') .backgroundColor(device.isPaired ? 'rgba(76, 175, 80, 0.1)' : 'rgba(255, 152, 0, 0.1)') .border({ width: 1, color: device.isPaired ? '#4caf50' : '#ff9800' }) .borderRadius(25) .padding({ left: 12, right: 12, top: 6, bottom: 6 }) } .width('100%') .padding(15) .backgroundColor('rgba(15, 30, 60, 0.8)') .borderRadius(8) .border({ width: 1, color: 'rgba(34, 253, 200, 0.2)' }) .onClick(() => this.connectDevice(device)) } }) } .width('100%') .layoutWeight(1) } } .width('100%') .height('100%') .padding(10) .linearGradient({ direction: GradientDirection.RightBottom, colors: [['#0b1830', 0], ['#1d3553', 1]] }) // Toast 提示 if (this.showToast) { Text(this.toastMessage) .fontSize(14) .fontColor('#ffffff') .backgroundColor('rgba(0, 0, 0, 0.7)') .padding({ left: 20, right: 20, top: 10, bottom: 10 }) .borderRadius(20) .position({ x: '50%', y: '80%' }) .translate({ x: '-50%' }) } // 确认对话框 if (this.showConfirmDialog && this.confirmDialogDevice) { Column() { Column() { Text('提示') .fontSize(18) .fontWeight(FontWeight.Bold) .fontColor('#eaf6fc') .margin({ bottom: 15 }) Text(`确定要连接设备"${this.confirmDialogDevice.displayName}"吗?`) .fontSize(14) .fontColor('#b2ebf2') .margin({ bottom: 20 }) Row() { Button('取消') .fontSize(14) .fontColor('#999999') .backgroundColor('rgba(255, 255, 255, 0.1)') .borderRadius(4) .layoutWeight(1) .onClick(() => { this.showConfirmDialog = false; this.confirmDialogDevice = null; }) Button('确定') .fontSize(14) .fontColor('#22fdc8') .backgroundColor('rgba(34, 253, 200, 0.2)') .borderRadius(4) .layoutWeight(1) .margin({ left: 15 }) .onClick(() => { if (this.confirmDialogDevice) { this.doConnect(this.confirmDialogDevice); } this.showConfirmDialog = false; this.confirmDialogDevice = null; }) } .width('100%') } .width('80%') .padding(20) .backgroundColor('rgba(20, 38, 81, 0.98)') .borderRadius(12) .border({ width: 1, color: '#213b67' }) } .width('100%') .height('100%') .backgroundColor('rgba(0, 0, 0, 0.5)') .justifyContent(FlexAlign.Center) } } .width('100%') .height('100%') } }