You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

512 lines
15 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/**
* 蓝牙设备搜索页面
* 功能:搜索、配对、连接蓝牙设备
*/
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<void> {
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<boolean> {
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<number> {
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<void> {
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<string>) => {
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%')
}
}