diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index c9050a312..77f53258d 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -77,9 +77,9 @@ PODS:
- QCloudTrack/Beacon (6.4.7)
- record_darwin (1.0.0):
- Flutter
- - SDWebImage (5.21.0):
- - SDWebImage/Core (= 5.21.0)
- - SDWebImage/Core (5.21.0)
+ - SDWebImage (5.21.1):
+ - SDWebImage/Core (= 5.21.1)
+ - SDWebImage/Core (5.21.1)
- SDWebImageWebPCoder (0.14.6):
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.17)
@@ -200,7 +200,7 @@ SPEC CHECKSUMS:
QCloudCOSXML: 7205e76aa9cf613468222615483c9d7c074e4298
QCloudTrack: 3b53a7fc4fe3920e407f2aa73f2452992a61f7f3
record_darwin: fb1f375f1d9603714f55b8708a903bbb91ffdb0a
- SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868
+ SDWebImage: f29024626962457f3470184232766516dee8dfea
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
spotify_sdk: a48400bb29f70c4fe251ebfdc9135c37097ac5ca
tencentcloud_cos_sdk_plugin: 7bed564dbe72df23e7b5cacb1e51b1a20cb1639c
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 02daa62e1..34f3271ab 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -549,6 +549,7 @@
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = 537SFY28XS;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
MARKETING_VERSION = 1.0;
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index b2cdd80bb..6dd9a6874 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -72,66 +72,10 @@
LSRequiresIPhoneOS
- NSAccessorySetupBluetoothCompanyIdentifiers
-
- EFAB
-
- NSAccessorySetupDescriptions
-
-
- AccessoryMediaType
- Headphones
- LocalizedAccessoryName
- 语音助理耳机
-
-
- NSAppTransportSecurity
-
- NSAllowsArbitraryLoads
-
- NSAllowsArbitraryLoadsInWebContent
-
- NSExceptionDomains
-
- mcp.amap.com
-
- NSExceptionAllowsInsecureHTTPLoads
-
- NSExceptionMinimumTLSVersion
- TLSv1.0
- NSIncludesSubdomains
-
-
- mcp.ideapsound.com
-
- NSExceptionAllowsInsecureHTTPLoads
-
- NSExceptionMinimumTLSVersion
- TLSv1.0
- NSIncludesSubdomains
-
-
-
-
-
NSBluetoothAlwaysUsageDescription
需要蓝牙权限用于连接和管理音频设备
NSBluetoothPeripheralUsageDescription
需要蓝牙权限用于连接和管理音频设备
-
-
- UIBackgroundModes
-
- bluetooth-central
- audio
- location
-
-
-
- NSAccessorySetupKitSupports
-
- Bluetooth
-
NSCalendarsUsageDescription
需要日历权限用于安排和管理您的音频会议和录音任务
NSCameraUsageDescription
@@ -148,6 +92,12 @@
需要相册权限用于保存录音文件和导入音频文件
UIApplicationSupportsIndirectInputEvents
+ UIBackgroundModes
+
+ bluetooth-central
+ audio
+ location
+
UILaunchStoryboardName
LaunchScreen
UIMainStoryboardFile
@@ -165,5 +115,7 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
+ UIViewControllerBasedStatusBarAppearance
+
diff --git a/lib/modules/ble/bindings/ble_binding.dart b/lib/modules/ble/bindings/ble_binding.dart
new file mode 100644
index 000000000..4fc2cc5c7
--- /dev/null
+++ b/lib/modules/ble/bindings/ble_binding.dart
@@ -0,0 +1,9 @@
+import 'package:get/get.dart';
+import '../controllers/ble_controller.dart';
+
+class BleBinding extends Bindings {
+ @override
+ void dependencies() {
+ Get.put(BleController());
+ }
+}
\ No newline at end of file
diff --git a/lib/modules/ble/controllers/ble_controller.dart b/lib/modules/ble/controllers/ble_controller.dart
new file mode 100644
index 000000000..91ce3f8dd
--- /dev/null
+++ b/lib/modules/ble/controllers/ble_controller.dart
@@ -0,0 +1,281 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:flutter_blue_plus/flutter_blue_plus.dart' as fbp;
+import 'package:permission_handler/permission_handler.dart';
+import 'package:app_settings/app_settings.dart';
+
+class BleController extends GetxController {
+ // 扫描状态
+ final RxBool isScanning = false.obs;
+ final RxBool isBluetoothOn = false.obs;
+ final RxBool hasPermissions = false.obs;
+
+ // 发现的设备列表
+ final RxList scanResults = [].obs;
+
+ // 连接的设备
+ final Rx connectedDevice =
+ Rx(null);
+
+ // 错误信息
+ final RxString errorMessage = ''.obs;
+
+ @override
+ void onInit() {
+ super.onInit();
+ _initBluetooth();
+ }
+
+ @override
+ void onClose() {
+ stopScan();
+ super.onClose();
+ }
+
+ // 初始化蓝牙
+ Future _initBluetooth() async {
+ try {
+ // 先检查权限
+ await _checkPermissions();
+
+ // 检查蓝牙适配器状态
+ fbp.FlutterBluePlus.adapterState.listen((state) {
+ isBluetoothOn.value = state == fbp.BluetoothAdapterState.on;
+ if (state == fbp.BluetoothAdapterState.on) {
+ errorMessage.value = '';
+ } else {
+ errorMessage.value = '请开启蓝牙';
+ stopScan();
+ }
+ });
+
+ // 监听扫描结果
+ fbp.FlutterBluePlus.scanResults.listen((results) {
+ scanResults.value = results;
+ });
+
+ // 监听扫描状态
+ fbp.FlutterBluePlus.isScanning.listen((scanning) {
+ isScanning.value = scanning;
+ });
+ } catch (e) {
+ errorMessage.value = '蓝牙初始化失败: $e';
+ }
+ }
+
+ // 检查权限(不请求)
+ Future _checkPermissions() async {
+ try {
+ Map statuses = await [
+ Permission.bluetoothScan,
+ Permission.bluetoothConnect,
+ Permission.location,
+ ].request();
+
+ hasPermissions.value =
+ statuses.values.every((status) => status == PermissionStatus.granted);
+
+ if (!hasPermissions.value) {
+ errorMessage.value = '需要蓝牙和位置权限才能扫描设备';
+ }
+ } catch (e) {
+ print('检查权限失败: $e');
+ hasPermissions.value = false;
+ }
+ }
+
+ // 请求权限
+ Future _requestPermissions() async {
+ try {
+ Map statuses = await [
+ Permission.bluetoothScan,
+ Permission.bluetoothConnect,
+ Permission.location,
+ ].request();
+
+ bool allGranted =
+ statuses.values.every((status) => status == PermissionStatus.granted);
+ hasPermissions.value = allGranted;
+
+ if (!allGranted) {
+ // 检查是否有权限被永久拒绝
+ bool hasPermanentlyDenied = statuses.values
+ .any((status) => status == PermissionStatus.permanentlyDenied);
+
+ if (hasPermanentlyDenied) {
+ _showPermissionDialog();
+ } else {
+ errorMessage.value = '需要蓝牙和位置权限才能扫描设备';
+ }
+ }
+
+ return allGranted;
+ } catch (e) {
+ print('请求权限失败: $e');
+ errorMessage.value = '权限请求失败: $e';
+ return false;
+ }
+ }
+
+ // 显示权限对话框
+ void _showPermissionDialog() {
+ Get.dialog(
+ AlertDialog(
+ title: Text('需要权限'),
+ content: Text('应用需要蓝牙和位置权限才能扫描设备。请在设置中手动开启权限。'),
+ actions: [
+ TextButton(
+ onPressed: () => Get.back(),
+ child: Text('取消'),
+ ),
+ TextButton(
+ onPressed: () {
+ Get.back();
+ AppSettings.openAppSettings();
+ },
+ child: Text('去设置'),
+ ),
+ ],
+ ),
+ );
+ }
+
+ // 显示蓝牙开启对话框
+ void _showBluetoothDialog() {
+ Get.dialog(
+ AlertDialog(
+ title: Text('需要开启蓝牙'),
+ content: Text('请开启蓝牙以扫描和连接设备。'),
+ actions: [
+ TextButton(
+ onPressed: () => Get.back(),
+ child: Text('取消'),
+ ),
+ TextButton(
+ onPressed: () {
+ Get.back();
+ // 尝试开启蓝牙
+ fbp.FlutterBluePlus.turnOn();
+ },
+ child: Text('开启蓝牙'),
+ ),
+ ],
+ ),
+ );
+ }
+
+ // 开始扫描
+ Future startScan() async {
+ try {
+ errorMessage.value = '';
+
+ // 检查权限
+ if (!hasPermissions.value) {
+ if (!await _requestPermissions()) {
+ return;
+ }
+ }
+
+ // 检查蓝牙状态
+ if (!isBluetoothOn.value) {
+ _showBluetoothDialog();
+ return;
+ }
+
+ // 清空之前的结果
+ scanResults.clear();
+
+ // 开始扫描
+ await fbp.FlutterBluePlus.startScan(
+ timeout: const Duration(seconds: 30),
+ androidUsesFineLocation: true,
+ );
+ } catch (e) {
+ errorMessage.value = '扫描失败: $e';
+ }
+ }
+
+ // 停止扫描
+ Future stopScan() async {
+ try {
+ await fbp.FlutterBluePlus.stopScan();
+ } catch (e) {
+ print('停止扫描失败: $e');
+ }
+ }
+
+ // 连接设备
+ Future connectToDevice(fbp.BluetoothDevice device) async {
+ try {
+ errorMessage.value = '';
+
+ // 停止扫描
+ await stopScan();
+
+ // 连接设备
+ await device.connect(timeout: const Duration(seconds: 15));
+ connectedDevice.value = device;
+
+ Get.snackbar(
+ '连接成功',
+ '已连接到 ${device.platformName.isNotEmpty ? device.platformName : '未知设备'}',
+ backgroundColor: Colors.green,
+ colorText: Colors.white,
+ );
+ } catch (e) {
+ errorMessage.value = '连接失败: $e';
+ Get.snackbar(
+ '连接失败',
+ e.toString(),
+ backgroundColor: Colors.red,
+ colorText: Colors.white,
+ );
+ }
+ }
+
+ // 断开连接
+ Future disconnectDevice() async {
+ try {
+ if (connectedDevice.value != null) {
+ await connectedDevice.value!.disconnect();
+ connectedDevice.value = null;
+
+ Get.snackbar(
+ '已断开连接',
+ '设备已断开连接',
+ backgroundColor: Colors.orange,
+ colorText: Colors.white,
+ );
+ }
+ } catch (e) {
+ errorMessage.value = '断开连接失败: $e';
+ }
+ }
+
+ // 获取设备名称
+ String getDeviceName(fbp.ScanResult result) {
+ if (result.device.platformName.isNotEmpty) {
+ return result.device.platformName;
+ }
+ if (result.advertisementData.localName.isNotEmpty) {
+ return result.advertisementData.localName;
+ }
+ return '未知设备';
+ }
+
+ // 获取信号强度描述
+ String getSignalStrengthText(int rssi) {
+ if (rssi >= -50) return '信号强';
+ if (rssi >= -70) return '信号中等';
+ if (rssi >= -90) return '信号弱';
+ return '信号很弱';
+ }
+
+ // 获取信号强度图标
+ IconData getSignalStrengthIcon(int rssi) {
+ if (rssi >= -50) return Icons.signal_wifi_4_bar;
+ if (rssi >= -70) return Icons.signal_wifi_4_bar;
+ if (rssi >= -90) return Icons.signal_wifi_4_bar;
+ return Icons.signal_wifi_4_bar;
+ }
+}
diff --git a/lib/modules/ble/views/ble_view.dart b/lib/modules/ble/views/ble_view.dart
new file mode 100644
index 000000000..08398c95f
--- /dev/null
+++ b/lib/modules/ble/views/ble_view.dart
@@ -0,0 +1,355 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:flutter_blue_plus/flutter_blue_plus.dart' as fbp;
+import '../controllers/ble_controller.dart';
+
+class BleView extends GetView {
+ const BleView({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ final bool isDarkMode = Theme.of(context).brightness == Brightness.dark;
+
+ return Scaffold(
+ backgroundColor: isDarkMode ? Colors.black : Colors.white,
+ appBar: AppBar(
+ title: Text(
+ 'BLE设备扫描',
+ style: TextStyle(
+ color: isDarkMode ? Colors.white : Colors.black,
+ fontSize: 18.sp,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ backgroundColor: isDarkMode ? Colors.grey[900] : Colors.white,
+ elevation: 0,
+ iconTheme: IconThemeData(
+ color: isDarkMode ? Colors.white : Colors.black,
+ ),
+ actions: [
+ Obx(() => IconButton(
+ onPressed: controller.isScanning.value
+ ? controller.stopScan
+ : controller.startScan,
+ icon: Icon(
+ controller.isScanning.value ? Icons.stop : Icons.refresh,
+ color: controller.isScanning.value
+ ? Colors.red
+ : (isDarkMode ? Colors.white : Colors.black),
+ ),
+ )),
+ ],
+ ),
+ body: SafeArea(
+ child: Column(
+ children: [
+ // 状态栏
+ _buildStatusBar(isDarkMode),
+
+ // 连接的设备信息
+ _buildConnectedDevice(isDarkMode),
+
+ // 设备列表
+ Expanded(
+ child: _buildDeviceList(isDarkMode),
+ ),
+ ],
+ ),
+ ),
+ floatingActionButton: Obx(() => FloatingActionButton(
+ onPressed: controller.isScanning.value
+ ? controller.stopScan
+ : controller.startScan,
+ backgroundColor:
+ controller.isScanning.value ? Colors.red : Colors.blue,
+ child: Icon(
+ controller.isScanning.value
+ ? Icons.stop
+ : Icons.bluetooth_searching,
+ color: Colors.white,
+ ),
+ )),
+ );
+ }
+
+ // 状态栏
+ Widget _buildStatusBar(bool isDarkMode) {
+ return Obx(() => Container(
+ width: double.infinity,
+ padding: EdgeInsets.all(16.w),
+ margin: EdgeInsets.all(16.w),
+ decoration: BoxDecoration(
+ color: isDarkMode ? Colors.grey[800] : Colors.grey[100],
+ borderRadius: BorderRadius.circular(12.r),
+ ),
+ child: Column(
+ children: [
+ Row(
+ children: [
+ Icon(
+ controller.isBluetoothOn.value
+ ? Icons.bluetooth
+ : Icons.bluetooth_disabled,
+ color: controller.isBluetoothOn.value
+ ? Colors.blue
+ : Colors.red,
+ size: 24.w,
+ ),
+ SizedBox(width: 8.w),
+ Text(
+ controller.isBluetoothOn.value ? '蓝牙已开启' : '蓝牙已关闭',
+ style: TextStyle(
+ color: isDarkMode ? Colors.white : Colors.black,
+ fontSize: 16.sp,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ Spacer(),
+ if (controller.isScanning.value)
+ SizedBox(
+ width: 20.w,
+ height: 20.w,
+ child: CircularProgressIndicator(
+ strokeWidth: 2,
+ valueColor: AlwaysStoppedAnimation(Colors.blue),
+ ),
+ ),
+ ],
+ ),
+ if (controller.errorMessage.value.isNotEmpty) ...[
+ SizedBox(height: 8.h),
+ Text(
+ controller.errorMessage.value,
+ style: TextStyle(
+ color: Colors.red,
+ fontSize: 14.sp,
+ ),
+ ),
+ ],
+ if (controller.isScanning.value) ...[
+ SizedBox(height: 8.h),
+ Text(
+ '正在扫描设备...',
+ style: TextStyle(
+ color: Colors.blue,
+ fontSize: 14.sp,
+ ),
+ ),
+ ],
+ ],
+ ),
+ ));
+ }
+
+ // 已连接设备信息
+ Widget _buildConnectedDevice(bool isDarkMode) {
+ return Obx(() {
+ if (controller.connectedDevice.value == null) {
+ return SizedBox.shrink();
+ }
+
+ final device = controller.connectedDevice.value!;
+ return Container(
+ width: double.infinity,
+ padding: EdgeInsets.all(16.w),
+ margin: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
+ decoration: BoxDecoration(
+ color: Colors.green.withOpacity(0.1),
+ border: Border.all(color: Colors.green, width: 1),
+ borderRadius: BorderRadius.circular(12.r),
+ ),
+ child: Row(
+ children: [
+ Icon(
+ Icons.bluetooth_connected,
+ color: Colors.green,
+ size: 24.w,
+ ),
+ SizedBox(width: 12.w),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ '已连接设备',
+ style: TextStyle(
+ color: Colors.green,
+ fontSize: 12.sp,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ Text(
+ device.platformName.isNotEmpty
+ ? device.platformName
+ : '未知设备',
+ style: TextStyle(
+ color: isDarkMode ? Colors.white : Colors.black,
+ fontSize: 16.sp,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ Text(
+ device.remoteId.toString(),
+ style: TextStyle(
+ color: isDarkMode ? Colors.white70 : Colors.black54,
+ fontSize: 12.sp,
+ ),
+ ),
+ ],
+ ),
+ ),
+ IconButton(
+ onPressed: controller.disconnectDevice,
+ icon: Icon(
+ Icons.close,
+ color: Colors.red,
+ size: 20.w,
+ ),
+ ),
+ ],
+ ),
+ );
+ });
+ }
+
+ // 设备列表
+ Widget _buildDeviceList(bool isDarkMode) {
+ return Obx(() {
+ if (controller.scanResults.isEmpty) {
+ return Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Icon(
+ Icons.bluetooth_searching,
+ size: 64.w,
+ color: isDarkMode ? Colors.white54 : Colors.black54,
+ ),
+ SizedBox(height: 16.h),
+ Text(
+ controller.isScanning.value ? '正在搜索设备...' : '点击扫描按钮开始搜索BLE设备',
+ style: TextStyle(
+ color: isDarkMode ? Colors.white54 : Colors.black54,
+ fontSize: 16.sp,
+ ),
+ textAlign: TextAlign.center,
+ ),
+ ],
+ ),
+ );
+ }
+
+ return ListView.builder(
+ padding: EdgeInsets.symmetric(horizontal: 16.w),
+ itemCount: controller.scanResults.length,
+ itemBuilder: (context, index) {
+ final result = controller.scanResults[index];
+ return _buildDeviceItem(result, isDarkMode);
+ },
+ );
+ });
+ }
+
+ // 设备项
+ Widget _buildDeviceItem(fbp.ScanResult result, bool isDarkMode) {
+ final deviceName = controller.getDeviceName(result);
+ final isConnected =
+ controller.connectedDevice.value?.remoteId == result.device.remoteId;
+
+ return Container(
+ margin: EdgeInsets.only(bottom: 12.h),
+ decoration: BoxDecoration(
+ color: isDarkMode ? Colors.grey[800] : Colors.white,
+ borderRadius: BorderRadius.circular(12.r),
+ border: Border.all(
+ color: isConnected
+ ? Colors.green
+ : (isDarkMode ? Colors.grey[700]! : Colors.grey[300]!),
+ width: isConnected ? 2 : 1,
+ ),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.black.withOpacity(0.05),
+ blurRadius: 8,
+ offset: Offset(0, 2),
+ ),
+ ],
+ ),
+ child: ListTile(
+ contentPadding: EdgeInsets.all(16.w),
+ leading: Container(
+ width: 48.w,
+ height: 48.w,
+ decoration: BoxDecoration(
+ color: isConnected
+ ? Colors.green.withOpacity(0.1)
+ : Colors.blue.withOpacity(0.1),
+ borderRadius: BorderRadius.circular(24.r),
+ ),
+ child: Icon(
+ isConnected ? Icons.bluetooth_connected : Icons.bluetooth,
+ color: isConnected ? Colors.green : Colors.blue,
+ size: 24.w,
+ ),
+ ),
+ title: Text(
+ deviceName,
+ style: TextStyle(
+ color: isDarkMode ? Colors.white : Colors.black,
+ fontSize: 16.sp,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ subtitle: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SizedBox(height: 4.h),
+ Text(
+ result.device.remoteId.toString(),
+ style: TextStyle(
+ color: isDarkMode ? Colors.white70 : Colors.black54,
+ fontSize: 12.sp,
+ ),
+ ),
+ SizedBox(height: 4.h),
+ Row(
+ children: [
+ Icon(
+ controller.getSignalStrengthIcon(result.rssi),
+ size: 16.w,
+ color: isDarkMode ? Colors.white70 : Colors.black54,
+ ),
+ SizedBox(width: 4.w),
+ Text(
+ '${result.rssi} dBm (${controller.getSignalStrengthText(result.rssi)})',
+ style: TextStyle(
+ color: isDarkMode ? Colors.white70 : Colors.black54,
+ fontSize: 12.sp,
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ trailing: isConnected
+ ? Icon(
+ Icons.check_circle,
+ color: Colors.green,
+ size: 24.w,
+ )
+ : IconButton(
+ onPressed: () => controller.connectToDevice(result.device),
+ icon: Icon(
+ Icons.connect_without_contact,
+ color: Colors.blue,
+ size: 24.w,
+ ),
+ ),
+ onTap: isConnected
+ ? null
+ : () => controller.connectToDevice(result.device),
+ ),
+ );
+ }
+}
diff --git a/lib/modules/login/views/login_view.dart b/lib/modules/login/views/login_view.dart
index 543ba1827..9df8ffb0f 100644
--- a/lib/modules/login/views/login_view.dart
+++ b/lib/modules/login/views/login_view.dart
@@ -39,13 +39,13 @@ class LoginView extends GetView {
SizedBox(height: 32.h),
_buildWelcomeText(isDarkMode),
- // 输入框和继续按钮
- SizedBox(height: 24.h),
- _buildInputField(isDarkMode),
- SizedBox(height: 16.h),
- _buildVerificationInputField(isDarkMode),
- SizedBox(height: 16.h),
- _buildContinueButton(isDarkMode),
+ // // 输入框和继续按钮
+ // SizedBox(height: 24.h),
+ // _buildInputField(isDarkMode),
+ // SizedBox(height: 16.h),
+ // _buildVerificationInputField(isDarkMode),
+ // SizedBox(height: 16.h),
+ // _buildContinueButton(isDarkMode),
// 社交媒体登录
SizedBox(height: 32.h),
diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart
index ee27f518a..2c31301aa 100644
--- a/lib/modules/pairing/controllers/pairing_controller.dart
+++ b/lib/modules/pairing/controllers/pairing_controller.dart
@@ -1,6 +1,3 @@
-import 'dart:convert';
-import 'dart:typed_data';
-
import 'package:app_settings/app_settings.dart';
import '/data/models/appconfig.dart';
import '/data/models/user_Info.dart';
@@ -14,7 +11,6 @@ import '../../../core/utils/logger.dart';
import 'dart:io';
import '../../../data/services/bluetooth_manager.dart';
import '../../../data/services/ble_manager.dart';
-import '../../../core/utils/permission_util.dart';
/// 配对状态枚举 - 简化版本
enum PairingState {
@@ -418,46 +414,16 @@ class PairingController extends GetxController {
/// 检查权限
Future _checkPermissions() async {
- if (Platform.isAndroid) {
- // Android 平台权限检查
- Map statuses = await [
- Permission.bluetoothScan,
- Permission.bluetoothConnect,
- Permission.locationWhenInUse,
- ].request();
-
- return statuses[Permission.bluetoothScan]!.isGranted &&
- statuses[Permission.bluetoothConnect]!.isGranted &&
- statuses[Permission.locationWhenInUse]!.isGranted;
- } else if (Platform.isIOS) {
- // iOS 平台权限检查
- Map statuses = await [
- Permission.locationWhenInUse,
- Permission.bluetooth, // 添加蓝牙权限检查
- ].request();
-
- // 检查位置权限
- bool locationGranted = statuses[Permission.locationWhenInUse]!.isGranted;
-
- // 检查蓝牙权限
- bool bluetoothGranted = statuses[Permission.bluetooth]!.isGranted;
-
- // 如果蓝牙权限未确定,强制触发权限申请
- if (!bluetoothGranted) {
- try {
- await _bleManager.requestBluetoothPermission();
- // 重新检查蓝牙权限状态
- final newBluetoothStatus = await Permission.bluetooth.status;
- bluetoothGranted = newBluetoothStatus.isGranted;
- } catch (e) {
- Logger.e(_tag, '蓝牙权限申请失败: $e');
- }
- }
-
- return locationGranted && bluetoothGranted;
- }
-
- return true;
+ // Android 平台权限检查
+ Map statuses = await [
+ Permission.bluetoothScan,
+ Permission.bluetoothConnect,
+ Permission.locationWhenInUse,
+ ].request();
+
+ return statuses[Permission.bluetoothScan]!.isGranted &&
+ statuses[Permission.bluetoothConnect]!.isGranted &&
+ statuses[Permission.locationWhenInUse]!.isGranted;
}
/// 验证设备
diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart
index 88f7f07c6..48efd4fe9 100644
--- a/lib/routes/app_pages.dart
+++ b/lib/routes/app_pages.dart
@@ -1,3 +1,6 @@
+import 'package:deep_voice/modules/ble/bindings/ble_binding.dart';
+import 'package:deep_voice/modules/ble/views/ble_view.dart';
+
import '../../modules/devices/bindings/devices_binding.dart';
import '../../modules/devices/views/devices_view.dart';
import 'package:get/get.dart';
@@ -249,5 +252,10 @@ abstract class AppPages {
page: () => const DevicesView(),
binding: DevicesBinding(),
),
+ GetPage(
+ name: Routes.ble,
+ page: () => const BleView(),
+ binding: BleBinding(),
+ ),
];
}
diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart
index 7942bfd73..e955f025e 100644
--- a/lib/routes/app_routes.dart
+++ b/lib/routes/app_routes.dart
@@ -56,4 +56,7 @@ abstract class Routes {
// 设备管理
static const devices = '/devices';
+
+ // ble设备管理
+ static const ble = '/ble';
}
diff --git a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleAccessorySetup.swift b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleAccessorySetup.swift
index 3cf85910e..07f6803d0 100644
--- a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleAccessorySetup.swift
+++ b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleAccessorySetup.swift
@@ -1,306 +1,306 @@
-import Foundation
-import CoreBluetooth
-import os.log
-import AVFoundation
-import AccessorySetupKit
-import UIKit
+// import Foundation
+// import CoreBluetooth
+// import os.log
+// import AVFoundation
+// import AccessorySetupKit
+// import UIKit
-@available(iOS 18.0, *)
-class BleAccessorySetup: NSObject {
- // MARK: - 属性
- static let shared = BleAccessorySetup()
+// @available(iOS 18.0, *)
+// class BleAccessorySetup: NSObject {
+// // MARK: - 属性
+// static let shared = BleAccessorySetup()
- private var bleService: BleService?
- private var session: ASAccessorySession?
- private var isSessionActivated = false
- private var isInitializing = false
- private let logger = OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleAccessorySetup")
+// private var bleService: BleService?
+// private var session: ASAccessorySession?
+// private var isSessionActivated = false
+// private var isInitializing = false
+// private let logger = OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleAccessorySetup")
- // 杰理蓝牙设备的公司标识符
- private let deviceCompanyIdentifier: UInt16 = 0xEFAB
- private let deviceDisplayName = "语音助理耳机"
+// // 杰理蓝牙设备的公司标识符
+// private let deviceCompanyIdentifier: UInt16 = 0xEFAB
+// private let deviceDisplayName = "语音助理耳机"
- // 用于存储配对设备UUID的UserDefaults键
- private let pairedDeviceUUIDKey = "com.yunqiinnovation.ble_service.paired_device_uuid"
+// // 用于存储配对设备UUID的UserDefaults键
+// private let pairedDeviceUUIDKey = "com.yunqiinnovation.ble_service.paired_device_uuid"
- typealias DeviceDiscoveredCallback = (UUID) -> Void
- private var onDeviceDiscovered: DeviceDiscoveredCallback?
+// typealias DeviceDiscoveredCallback = (UUID) -> Void
+// private var onDeviceDiscovered: DeviceDiscoveredCallback?
- // MARK: - 初始化
- private override init() { super.init() }
+// // MARK: - 初始化
+// private override init() { super.init() }
- // MARK: - 公共方法
- func setBleService(_ service: Any) {
- self.bleService = service as? BleService
- }
+// // MARK: - 公共方法
+// func setBleService(_ service: Any) {
+// self.bleService = service as? BleService
+// }
- func setDeviceDiscoveredCallback(_ callback: @escaping (UUID) -> Void) {
- self.onDeviceDiscovered = callback
- }
+// func setDeviceDiscoveredCallback(_ callback: @escaping (UUID) -> Void) {
+// self.onDeviceDiscovered = callback
+// }
- // 处理发现的设备并连接
- func connectToAccessory(uuid: UUID) {
- guard let service = bleService else {
- return
- }
+// // 处理发现的设备并连接
+// func connectToAccessory(uuid: UUID) {
+// guard let service = bleService else {
+// return
+// }
- service.connectToAccessory(uuid: uuid)
- }
+// service.connectToAccessory(uuid: uuid)
+// }
- func addToReconnectQueue(uuid: UUID) {
- guard let service = bleService else {
- return
- }
+// func addToReconnectQueue(uuid: UUID) {
+// guard let service = bleService else {
+// return
+// }
- service.addToReconnectQueue(uuid: uuid)
- }
+// service.addToReconnectQueue(uuid: uuid)
+// }
- // 从视图控制器显示设备选择器 - 主要入口点
- func showDevicePickerFromViewController(_ viewController: UIViewController) {
- initializeSession()
- }
+// // 从视图控制器显示设备选择器 - 主要入口点
+// func showDevicePickerFromViewController(_ viewController: UIViewController) {
+// initializeSession()
+// }
- // 清除蓝牙状态(仅用于测试或重置,不会清除系统授权)
- func resetBluetoothState() {
- if let service = bleService {
- service.disconnect()
- }
- }
+// // 清除蓝牙状态(仅用于测试或重置,不会清除系统授权)
+// func resetBluetoothState() {
+// if let service = bleService {
+// service.disconnect()
+// }
+// }
- // MARK: - 会话管理
- func initializeSession() {
- if isInitializing {
- os_log("会话正在初始化中,跳过", log: logger, type: .info)
- return
- }
+// // MARK: - 会话管理
+// func initializeSession() {
+// if isInitializing {
+// os_log("会话正在初始化中,跳过", log: logger, type: .info)
+// return
+// }
- // 如果会话已激活,检查是否有配对设备
- if session != nil && isSessionActivated {
- os_log("会话已激活,检查配对设备", log: logger, type: .info)
- // 会话已激活,检查是否有配对设备
- if let accessories = session?.accessories, !accessories.isEmpty {
- os_log("发现 %d 个已配对设备,尝试连接", log: logger, type: .info, accessories.count)
- // 有配对设备,尝试连接
- if let service = bleService {
- let _ = service.getCentralManager()
+// // 如果会话已激活,检查是否有配对设备
+// if session != nil && isSessionActivated {
+// os_log("会话已激活,检查配对设备", log: logger, type: .info)
+// // 会话已激活,检查是否有配对设备
+// if let accessories = session?.accessories, !accessories.isEmpty {
+// os_log("发现 %d 个已配对设备,尝试连接", log: logger, type: .info, accessories.count)
+// // 有配对设备,尝试连接
+// if let service = bleService {
+// let _ = service.getCentralManager()
- for accessory in accessories {
- if let uuid = accessory.bluetoothIdentifier {
- onDeviceDiscovered?(uuid)
- connectToAccessory(uuid: uuid)
- }
- }
- }
- } else {
- os_log("没有已配对设备,显示配对框", log: logger, type: .info)
- // 没有配对设备,直接显示配对框
- showDevicePicker()
- }
- return
- }
+// for accessory in accessories {
+// if let uuid = accessory.bluetoothIdentifier {
+// onDeviceDiscovered?(uuid)
+// connectToAccessory(uuid: uuid)
+// }
+// }
+// }
+// } else {
+// os_log("没有已配对设备,显示配对框", log: logger, type: .info)
+// // 没有配对设备,直接显示配对框
+// showDevicePicker()
+// }
+// return
+// }
- os_log("开始初始化新会话", log: logger, type: .info)
- isInitializing = true
+// os_log("开始初始化新会话", log: logger, type: .info)
+// isInitializing = true
- do {
- session = try ASAccessorySession()
- session?.activate(on: .main) { [weak self] event in
- guard let self = self else { return }
- self.handleSessionEvent(event)
- }
- } catch {
- os_log("会话初始化失败: %{public}@", log: logger, type: .error, error.localizedDescription)
- handleSessionInitError(error)
- }
- }
+// do {
+// session = try ASAccessorySession()
+// session?.activate(on: .main) { [weak self] event in
+// guard let self = self else { return }
+// self.handleSessionEvent(event)
+// }
+// } catch {
+// os_log("会话初始化失败: %{public}@", log: logger, type: .error, error.localizedDescription)
+// handleSessionInitError(error)
+// }
+// }
- func resetSession() {
- session?.invalidate()
- session = nil
- isSessionActivated = false
- isInitializing = false
- }
+// func resetSession() {
+// session?.invalidate()
+// session = nil
+// isSessionActivated = false
+// isInitializing = false
+// }
- // MARK: - 设备选择器显示
- private func showDevicePicker() {
- guard let session = session, isSessionActivated else {
- os_log("无法显示配对框:会话未激活", log: logger, type: .error)
- return
- }
+// // MARK: - 设备选择器显示
+// private func showDevicePicker() {
+// guard let session = session, isSessionActivated else {
+// os_log("无法显示配对框:会话未激活", log: logger, type: .error)
+// return
+// }
- os_log("准备显示设备配对框", log: logger, type: .info)
+// os_log("准备显示设备配对框", log: logger, type: .info)
- let descriptor = ASDiscoveryDescriptor()
- descriptor.bluetoothCompanyIdentifier = ASBluetoothCompanyIdentifier(deviceCompanyIdentifier)
+// let descriptor = ASDiscoveryDescriptor()
+// descriptor.bluetoothCompanyIdentifier = ASBluetoothCompanyIdentifier(deviceCompanyIdentifier)
- let deviceImage = UIImage(systemName: "headphones") ?? UIImage()
- let displayItem = ASPickerDisplayItem(
- name: deviceDisplayName,
- productImage: deviceImage,
- descriptor: descriptor
- )
+// let deviceImage = UIImage(systemName: "headphones") ?? UIImage()
+// let displayItem = ASPickerDisplayItem(
+// name: deviceDisplayName,
+// productImage: deviceImage,
+// descriptor: descriptor
+// )
- DispatchQueue.main.async {
- os_log("正在显示设备配对框", log: self.logger, type: .info)
- session.showPicker(for: [displayItem]) { [weak self] error in
- guard let self = self else { return }
+// DispatchQueue.main.async {
+// os_log("正在显示设备配对框", log: self.logger, type: .info)
+// session.showPicker(for: [displayItem]) { [weak self] error in
+// guard let self = self else { return }
- if let error = error {
- let nsError = error as NSError
- os_log("设备选择器错误: %{public}@", log: self.logger, type: .error, error.localizedDescription)
- } else {
- os_log("设备选择器完成", log: self.logger, type: .info)
- }
- }
- }
- }
+// if let error = error {
+// let nsError = error as NSError
+// os_log("设备选择器错误: %{public}@", log: self.logger, type: .error, error.localizedDescription)
+// } else {
+// os_log("设备选择器完成", log: self.logger, type: .info)
+// }
+// }
+// }
+// }
- // MARK: - 私有辅助方法
- private func handleSessionEvent(_ event: ASAccessoryEvent) {
- switch event.eventType {
- case .activated:
- os_log("会话已激活", log: logger, type: .info)
- handleSessionActivated()
+// // MARK: - 私有辅助方法
+// private func handleSessionEvent(_ event: ASAccessoryEvent) {
+// switch event.eventType {
+// case .activated:
+// os_log("会话已激活", log: logger, type: .info)
+// handleSessionActivated()
- case .accessoryAdded:
- os_log("发现新设备: %{public}@", log: logger, type: .info, event.accessory?.bluetoothIdentifier?.uuidString ?? "Unknown")
- handleAccessoryAdded(event.accessory)
+// case .accessoryAdded:
+// os_log("发现新设备: %{public}@", log: logger, type: .info, event.accessory?.bluetoothIdentifier?.uuidString ?? "Unknown")
+// handleAccessoryAdded(event.accessory)
- case .accessoryRemoved:
- os_log("设备已移除: %{public}@", log: logger, type: .info, event.accessory?.bluetoothIdentifier?.uuidString ?? "Unknown")
- handleAccessoryRemoved(event.accessory)
+// case .accessoryRemoved:
+// os_log("设备已移除: %{public}@", log: logger, type: .info, event.accessory?.bluetoothIdentifier?.uuidString ?? "Unknown")
+// handleAccessoryRemoved(event.accessory)
- case .accessoryChanged:
- os_log("设备状态已改变", log: logger, type: .info)
- break
+// case .accessoryChanged:
+// os_log("设备状态已改变", log: logger, type: .info)
+// break
- case .pickerDidPresent:
- os_log("配对框已显示", log: logger, type: .info)
- break
+// case .pickerDidPresent:
+// os_log("配对框已显示", log: logger, type: .info)
+// break
- case .pickerDidDismiss:
- os_log("配对框已关闭", log: logger, type: .info)
- break
+// case .pickerDidDismiss:
+// os_log("配对框已关闭", log: logger, type: .info)
+// break
- case .pickerSetupFailed:
- os_log("选择器设置失败: %{public}@", log: logger, type: .error,
- event.error?.localizedDescription ?? "未知错误")
+// case .pickerSetupFailed:
+// os_log("选择器设置失败: %{public}@", log: logger, type: .error,
+// event.error?.localizedDescription ?? "未知错误")
- default:
- os_log("未处理的会话事件: %d", log: logger, type: .info, event.eventType.rawValue)
- break
- }
- }
+// default:
+// os_log("未处理的会话事件: %d", log: logger, type: .info, event.eventType.rawValue)
+// break
+// }
+// }
- private func handleSessionActivated() {
- isSessionActivated = true
- isInitializing = false
+// private func handleSessionActivated() {
+// isSessionActivated = true
+// isInitializing = false
- if let accessories = session?.accessories, !accessories.isEmpty {
- if let service = bleService {
- let _ = service.getCentralManager()
+// if let accessories = session?.accessories, !accessories.isEmpty {
+// if let service = bleService {
+// let _ = service.getCentralManager()
- for accessory in accessories {
- if let uuid = accessory.bluetoothIdentifier {
- onDeviceDiscovered?(uuid)
- connectToAccessory(uuid: uuid)
- }
- }
- }
- } else {
- showDevicePicker()
- }
- }
+// for accessory in accessories {
+// if let uuid = accessory.bluetoothIdentifier {
+// onDeviceDiscovered?(uuid)
+// connectToAccessory(uuid: uuid)
+// }
+// }
+// }
+// } else {
+// showDevicePicker()
+// }
+// }
- private func handleAccessoryAdded(_ accessory: ASAccessory?) {
- guard let accessory = accessory, let uuid = accessory.bluetoothIdentifier else {
- return
- }
+// private func handleAccessoryAdded(_ accessory: ASAccessory?) {
+// guard let accessory = accessory, let uuid = accessory.bluetoothIdentifier else {
+// return
+// }
- // 保存配对设备的UUID
- savePairedMacAddress(uuid)
+// // 保存配对设备的UUID
+// savePairedMacAddress(uuid)
- onDeviceDiscovered?(uuid)
- connectToAccessory(uuid: uuid)
- }
+// onDeviceDiscovered?(uuid)
+// connectToAccessory(uuid: uuid)
+// }
- private func handleAccessoryRemoved(_ accessory: ASAccessory?) {
- guard let accessory = accessory, let uuid = accessory.bluetoothIdentifier else {
- return
- }
+// private func handleAccessoryRemoved(_ accessory: ASAccessory?) {
+// guard let accessory = accessory, let uuid = accessory.bluetoothIdentifier else {
+// return
+// }
- if let service = bleService {
- service.disconnect()
- }
- }
+// if let service = bleService {
+// service.disconnect()
+// }
+// }
- private func handleSessionInitError(_ error: Error) {
- isInitializing = false
- session = nil
- }
+// private func handleSessionInitError(_ error: Error) {
+// isInitializing = false
+// session = nil
+// }
- // MARK: - 蓝牙状态检查
- func isBluetoothAvailable() -> Bool {
- do {
- let audioSession = AVAudioSession.sharedInstance()
- let outputs = audioSession.currentRoute.outputs
+// // MARK: - 蓝牙状态检查
+// func isBluetoothAvailable() -> Bool {
+// do {
+// let audioSession = AVAudioSession.sharedInstance()
+// let outputs = audioSession.currentRoute.outputs
- for output in outputs {
- if output.portType == .bluetoothA2DP ||
- output.portType == .bluetoothHFP ||
- output.portType == .bluetoothLE {
- return true
- }
- }
- } catch {
- os_log("检查蓝牙状态出错: %{public}@", log: logger, type: .error, error.localizedDescription)
- }
+// for output in outputs {
+// if output.portType == .bluetoothA2DP ||
+// output.portType == .bluetoothHFP ||
+// output.portType == .bluetoothLE {
+// return true
+// }
+// }
+// } catch {
+// os_log("检查蓝牙状态出错: %{public}@", log: logger, type: .error, error.localizedDescription)
+// }
- return true
- }
+// return true
+// }
- // MARK: - 清除配对关系
- func clearAssociations() {
- resetBluetoothState()
+// // MARK: - 清除配对关系
+// func clearAssociations() {
+// resetBluetoothState()
- // 清除保存的配对设备UUID
- clearPairedMacAddress()
+// // 清除保存的配对设备UUID
+// clearPairedMacAddress()
- #if canImport(AccessorySetupKit) && swift(>=5.9)
- if #available(iOS 18.0, *) {
- resetSession()
- initializeSession()
- }
- #endif
- }
+// #if canImport(AccessorySetupKit) && swift(>=5.9)
+// if #available(iOS 18.0, *) {
+// resetSession()
+// initializeSession()
+// }
+// #endif
+// }
- // MARK: - 配对设备UUID管理
+// // MARK: - 配对设备UUID管理
- /// 获取已配对设备的UUID字符串
- /// 返回存储的配对设备UUID,如果没有则返回nil
- func getPairedMacAddress() -> String? {
- let uuidString = UserDefaults.standard.string(forKey: pairedDeviceUUIDKey)
- if let uuidString = uuidString, !uuidString.isEmpty {
- os_log("获取已配对设备UUID: %{public}@", log: logger, type: .info, uuidString)
- return uuidString
- }
- os_log("未找到已配对设备UUID", log: logger, type: .info)
- return nil
- }
+// /// 获取已配对设备的UUID字符串
+// /// 返回存储的配对设备UUID,如果没有则返回nil
+// func getPairedMacAddress() -> String? {
+// let uuidString = UserDefaults.standard.string(forKey: pairedDeviceUUIDKey)
+// if let uuidString = uuidString, !uuidString.isEmpty {
+// os_log("获取已配对设备UUID: %{public}@", log: logger, type: .info, uuidString)
+// return uuidString
+// }
+// os_log("未找到已配对设备UUID", log: logger, type: .info)
+// return nil
+// }
- /// 保存配对设备的UUID
- /// - Parameter uuid: 要保存的设备UUID
- func savePairedMacAddress(_ uuid: UUID) {
- let uuidString = uuid.uuidString
- UserDefaults.standard.set(uuidString, forKey: pairedDeviceUUIDKey)
- UserDefaults.standard.synchronize()
- os_log("已保存配对设备UUID: %{public}@", log: logger, type: .info, uuidString)
- }
+// /// 保存配对设备的UUID
+// /// - Parameter uuid: 要保存的设备UUID
+// func savePairedMacAddress(_ uuid: UUID) {
+// let uuidString = uuid.uuidString
+// UserDefaults.standard.set(uuidString, forKey: pairedDeviceUUIDKey)
+// UserDefaults.standard.synchronize()
+// os_log("已保存配对设备UUID: %{public}@", log: logger, type: .info, uuidString)
+// }
- /// 清除已保存的配对设备UUID
- private func clearPairedMacAddress() {
- UserDefaults.standard.removeObject(forKey: pairedDeviceUUIDKey)
- UserDefaults.standard.synchronize()
- os_log("已清除配对设备UUID", log: logger, type: .info)
- }
-}
\ No newline at end of file
+// /// 清除已保存的配对设备UUID
+// private func clearPairedMacAddress() {
+// UserDefaults.standard.removeObject(forKey: pairedDeviceUUIDKey)
+// UserDefaults.standard.synchronize()
+// os_log("已清除配对设备UUID", log: logger, type: .info)
+// }
+// }
\ No newline at end of file
diff --git a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleScanManager.swift b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleScanManager.swift
index 1be2b0c46..692984773 100644
--- a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleScanManager.swift
+++ b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleScanManager.swift
@@ -1,586 +1,586 @@
-import Foundation
-import CoreBluetooth
-import UIKit // 添加UIKit导入
-import os.log
+// import Foundation
+// import CoreBluetooth
+// import UIKit // 添加UIKit导入
+// import os.log
-/// 简化的 BLE 扫描管理器
-/// 只提供扫描和获取设备列表功能
-@available(iOS 13.0, *)
-public class BleScanManager: NSObject {
-
- // MARK: - 单例
- public static let shared = BleScanManager()
-
- // MARK: - 数据模型
- public struct BleDeviceInfo {
- public let identifier: UUID
- public let name: String?
- public let rssi: NSNumber
- public let advertisementData: [String: Any]
- public let discoveryMethod: DiscoveryMethod
- public let lastSeen: Date
-
- public enum DiscoveryMethod {
- case traditional
- case accessorySetupKit
- }
- }
-
- // MARK: - 属性
- private var centralManager: CBCentralManager!
- private let logger = OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleScanManager")
-
- // 扫描状态
- private var isScanning = false
- private var scanTimeout: Timer?
-
- // 设备列表
- private var discoveredDevices: [UUID: BleDeviceInfo] = [:]
-
-
- // MARK: - 初始化
- private override init() {
- super.init()
- setupCentralManager()
- }
-
- // 在setupCentralManager后添加权限检查
- private func setupCentralManager() {
- let options: [String: Any] = [
- CBCentralManagerOptionShowPowerAlertKey: true,
- CBCentralManagerOptionRestoreIdentifierKey: "BleScanManager"
- ]
-
- centralManager = CBCentralManager(delegate: self, queue: nil, options: options)
-
- // 添加权限检查和触发逻辑
- DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
- self?.checkAndTriggerPermission()
- }
- }
-
- private func checkAndTriggerPermission() {
- // 检查蓝牙权限状态,兼容不同iOS版本
- let authorization: CBManagerAuthorization
- if #available(iOS 13.1, *) {
- authorization = CBManager.authorization
- } else if #available(iOS 13.0, *) {
- // iOS 13.0 使用实例方法,需要确保centralManager已初始化
- authorization = centralManager?.authorization ?? .notDetermined
- } else {
- // iOS 13.0以下版本没有权限检查API,默认为未确定
- authorization = .notDetermined
- }
-
- os_log("📊 蓝牙权限状态: %d", log: logger, type: .info, authorization.rawValue)
- os_log("📊 CBCentralManager状态: %d", log: logger, type: .info, centralManager?.state.rawValue ?? -1)
-
- // 只在权限未确定且蓝牙状态为关闭时触发扫描
- if authorization == .notDetermined && centralManager?.state == .poweredOff {
- os_log("📊 主动触发一次扫描来申请权限", log: logger, type: .info)
- // 主动触发一次扫描来申请权限
- centralManager?.scanForPeripherals(withServices: nil, options: nil)
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
- self?.centralManager?.stopScan()
- }
- }
- }
-
-
-
- // MARK: - 公共接口
-
- /// 开始扫描 BLE 设备
- /// - Parameter timeout: 扫描超时时间,默认 10 秒
- /// - Returns: 是否成功开始扫描
- public func startScan(timeout: TimeInterval = 30.0) -> Bool {
- os_log("🔍 开始 BLE 扫描,超时时间: %.1f 秒", log: logger, type: .info, timeout)
-
- guard !isScanning else {
- os_log("⚠️ 扫描已在进行中", log: logger, type: .info)
- return false
- }
-
- // 清空之前的设备列表
- discoveredDevices.removeAll()
-
- // iOS 18 兼容性检查
- if #available(iOS 18.0, *) {
- // iOS 18 使用新的权限检查方式
- return startScanForIOS18(timeout: timeout)
- } else {
- // iOS 17 及以下使用传统方式
- return startScanLegacy(timeout: timeout)
- }
- }
-
- @available(iOS 18.0, *)
- private func startScanForIOS18(timeout: TimeInterval) -> Bool {
- // 对于 iOS 18,直接检查 CBCentralManager 状态
- guard let centralManager = centralManager else {
- os_log("❌ CBCentralManager 未初始化", log: logger, type: .error)
- return false
- }
-
- let currentState = centralManager.state
- os_log("📊 iOS 18 蓝牙状态: %d (%{public}@)", log: logger, type: .info, currentState.rawValue, getBluetoothStateString(currentState))
-
- switch currentState {
- case .poweredOn:
- return performScanWithPermission(timeout: timeout)
- case .unauthorized:
- os_log("❌ 蓝牙权限被拒绝", log: logger, type: .error)
- showPermissionAlert()
- return false
- case .unknown:
- os_log("⚠️ 蓝牙状态未知,等待状态更新", log: logger, type: .info)
- // 设置监听器等待状态更新
- waitForBluetoothState(timeout: timeout)
- return true
- default:
- os_log("❌ 蓝牙不可用: %{public}@", log: logger, type: .error, getBluetoothStateString(currentState))
- return false
- }
- }
-
- private func startScanLegacy(timeout: TimeInterval) -> Bool {
- // 检查并申请蓝牙权限
- let authorization: CBManagerAuthorization = CBManager.authorization
- os_log("📊 当前蓝牙权限状态: %d", log: logger, type: .info, authorization.rawValue)
-
- switch authorization {
- case .allowedAlways:
- return performScanWithPermission(timeout: timeout)
- case .denied, .restricted:
- os_log("❌ 蓝牙权限被拒绝或受限", log: logger, type: .error)
- showPermissionAlert()
- return false
- case .notDetermined:
- // 权限未确定,需要主动触发蓝牙操作来显示权限弹窗
- if centralManager == nil {
- setupCentralManager()
- }
+// /// 简化的 BLE 扫描管理器
+// /// 只提供扫描和获取设备列表功能
+// @available(iOS 13.0, *)
+// public class BleScanManager: NSObject {
+
+// // MARK: - 单例
+// public static let shared = BleScanManager()
+
+// // MARK: - 数据模型
+// public struct BleDeviceInfo {
+// public let identifier: UUID
+// public let name: String?
+// public let rssi: NSNumber
+// public let advertisementData: [String: Any]
+// public let discoveryMethod: DiscoveryMethod
+// public let lastSeen: Date
+
+// public enum DiscoveryMethod {
+// case traditional
+// case accessorySetupKit
+// }
+// }
+
+// // MARK: - 属性
+// private var centralManager: CBCentralManager!
+// private let logger = OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleScanManager")
+
+// // 扫描状态
+// private var isScanning = false
+// private var scanTimeout: Timer?
+
+// // 设备列表
+// private var discoveredDevices: [UUID: BleDeviceInfo] = [:]
+
+
+// // MARK: - 初始化
+// private override init() {
+// super.init()
+// setupCentralManager()
+// }
+
+// // 在setupCentralManager后添加权限检查
+// private func setupCentralManager() {
+// let options: [String: Any] = [
+// CBCentralManagerOptionShowPowerAlertKey: true,
+// CBCentralManagerOptionRestoreIdentifierKey: "BleScanManager"
+// ]
+
+// centralManager = CBCentralManager(delegate: self, queue: nil, options: options)
+
+// // 添加权限检查和触发逻辑
+// DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
+// self?.checkAndTriggerPermission()
+// }
+// }
+
+// private func checkAndTriggerPermission() {
+// // 检查蓝牙权限状态,兼容不同iOS版本
+// let authorization: CBManagerAuthorization
+// if #available(iOS 13.1, *) {
+// authorization = CBManager.authorization
+// } else if #available(iOS 13.0, *) {
+// // iOS 13.0 使用实例方法,需要确保centralManager已初始化
+// authorization = centralManager?.authorization ?? .notDetermined
+// } else {
+// // iOS 13.0以下版本没有权限检查API,默认为未确定
+// authorization = .notDetermined
+// }
+
+// os_log("📊 蓝牙权限状态: %d", log: logger, type: .info, authorization.rawValue)
+// os_log("📊 CBCentralManager状态: %d", log: logger, type: .info, centralManager?.state.rawValue ?? -1)
+
+// // 只在权限未确定且蓝牙状态为关闭时触发扫描
+// if authorization == .notDetermined && centralManager?.state == .poweredOff {
+// os_log("📊 主动触发一次扫描来申请权限", log: logger, type: .info)
+// // 主动触发一次扫描来申请权限
+// centralManager?.scanForPeripherals(withServices: nil, options: nil)
+// DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
+// self?.centralManager?.stopScan()
+// }
+// }
+// }
+
+
+
+// // MARK: - 公共接口
+
+// /// 开始扫描 BLE 设备
+// /// - Parameter timeout: 扫描超时时间,默认 10 秒
+// /// - Returns: 是否成功开始扫描
+// public func startScan(timeout: TimeInterval = 30.0) -> Bool {
+// os_log("🔍 开始 BLE 扫描,超时时间: %.1f 秒", log: logger, type: .info, timeout)
+
+// guard !isScanning else {
+// os_log("⚠️ 扫描已在进行中", log: logger, type: .info)
+// return false
+// }
+
+// // 清空之前的设备列表
+// discoveredDevices.removeAll()
+
+// // iOS 18 兼容性检查
+// if #available(iOS 18.0, *) {
+// // iOS 18 使用新的权限检查方式
+// return startScanForIOS18(timeout: timeout)
+// } else {
+// // iOS 17 及以下使用传统方式
+// return startScanLegacy(timeout: timeout)
+// }
+// }
+
+// @available(iOS 18.0, *)
+// private func startScanForIOS18(timeout: TimeInterval) -> Bool {
+// // 对于 iOS 18,直接检查 CBCentralManager 状态
+// guard let centralManager = centralManager else {
+// os_log("❌ CBCentralManager 未初始化", log: logger, type: .error)
+// return false
+// }
+
+// let currentState = centralManager.state
+// os_log("📊 iOS 18 蓝牙状态: %d (%{public}@)", log: logger, type: .info, currentState.rawValue, getBluetoothStateString(currentState))
+
+// switch currentState {
+// case .poweredOn:
+// return performScanWithPermission(timeout: timeout)
+// case .unauthorized:
+// os_log("❌ 蓝牙权限被拒绝", log: logger, type: .error)
+// showPermissionAlert()
+// return false
+// case .unknown:
+// os_log("⚠️ 蓝牙状态未知,等待状态更新", log: logger, type: .info)
+// // 设置监听器等待状态更新
+// waitForBluetoothState(timeout: timeout)
+// return true
+// default:
+// os_log("❌ 蓝牙不可用: %{public}@", log: logger, type: .error, getBluetoothStateString(currentState))
+// return false
+// }
+// }
+
+// private func startScanLegacy(timeout: TimeInterval) -> Bool {
+// // 检查并申请蓝牙权限
+// let authorization: CBManagerAuthorization = CBManager.authorization
+// os_log("📊 当前蓝牙权限状态: %d", log: logger, type: .info, authorization.rawValue)
+
+// switch authorization {
+// case .allowedAlways:
+// return performScanWithPermission(timeout: timeout)
+// case .denied, .restricted:
+// os_log("❌ 蓝牙权限被拒绝或受限", log: logger, type: .error)
+// showPermissionAlert()
+// return false
+// case .notDetermined:
+// // 权限未确定,需要主动触发蓝牙操作来显示权限弹窗
+// if centralManager == nil {
+// setupCentralManager()
+// }
- // 等待CBCentralManager状态更新后,主动开始扫描来触发权限弹窗
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
- guard let self = self else { return }
- if self.centralManager.state == .poweredOn {
- // 开始一个短暂的扫描来触发权限申请
- self.centralManager.scanForPeripherals(withServices: nil, options: nil)
- // 立即停止扫描
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
- self.centralManager.stopScan()
- }
- } else if self.centralManager.state == .unauthorized {
- // 权限被拒绝,显示设置提示
- self.showPermissionAlert()
- }
- }
- return true
- @unknown default:
- os_log("⚠️ 未知的蓝牙权限状态", log: logger, type: .info)
- return false
- }
- }
-
- private func waitForBluetoothState(timeout: TimeInterval) {
- var checkCount = 0
- let maxChecks = 20
-
- let timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] timer in
- guard let self = self else {
- timer.invalidate()
- return
- }
+// // 等待CBCentralManager状态更新后,主动开始扫描来触发权限弹窗
+// DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
+// guard let self = self else { return }
+// if self.centralManager.state == .poweredOn {
+// // 开始一个短暂的扫描来触发权限申请
+// self.centralManager.scanForPeripherals(withServices: nil, options: nil)
+// // 立即停止扫描
+// DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
+// self.centralManager.stopScan()
+// }
+// } else if self.centralManager.state == .unauthorized {
+// // 权限被拒绝,显示设置提示
+// self.showPermissionAlert()
+// }
+// }
+// return true
+// @unknown default:
+// os_log("⚠️ 未知的蓝牙权限状态", log: logger, type: .info)
+// return false
+// }
+// }
+
+// private func waitForBluetoothState(timeout: TimeInterval) {
+// var checkCount = 0
+// let maxChecks = 20
+
+// let timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] timer in
+// guard let self = self else {
+// timer.invalidate()
+// return
+// }
- checkCount += 1
- let currentState = self.centralManager?.state ?? .unknown
+// checkCount += 1
+// let currentState = self.centralManager?.state ?? .unknown
- os_log("🔍 检查蓝牙状态 (%d/%d): %{public}@", log: self.logger, type: .info, checkCount, maxChecks, self.getBluetoothStateString(currentState))
+// os_log("🔍 检查蓝牙状态 (%d/%d): %{public}@", log: self.logger, type: .info, checkCount, maxChecks, self.getBluetoothStateString(currentState))
- switch currentState {
- case .poweredOn:
- timer.invalidate()
- _ = self.performScanWithPermission(timeout: timeout)
- case .unauthorized:
- timer.invalidate()
- self.showPermissionAlert()
- case .poweredOff, .unsupported:
- timer.invalidate()
- os_log("❌ 蓝牙不可用", log: self.logger, type: .error)
- default:
- if checkCount >= maxChecks {
- timer.invalidate()
- os_log("⏰ 等待蓝牙状态超时", log: self.logger, type: .error)
- }
- }
- }
- }
-
- private func forceTriggerPermissionRequest(timeout: TimeInterval) {
- // 完全清理旧的 centralManager
- centralManager?.delegate = nil
- centralManager = nil
-
- // 强制重新初始化,确保触发权限申请
- let options: [String: Any] = [
- CBCentralManagerOptionShowPowerAlertKey: true,
- CBCentralManagerOptionRestoreIdentifierKey: "BleScanManager"
- ]
-
- centralManager = CBCentralManager(delegate: self, queue: nil, options: options)
-
- // 设置一个监听器来等待权限状态更新
- waitForPermissionResponse(timeout: timeout)
- }
-
- private func waitForPermissionResponse(timeout: TimeInterval) {
- // 设置一个定时器来检查权限状态变化
- var checkCount = 0
- let maxChecks = 20 // 最多检查20次,每次间隔0.5秒,总共10秒
-
- let timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] timer in
- guard let self = self else {
- timer.invalidate()
- return
- }
+// switch currentState {
+// case .poweredOn:
+// timer.invalidate()
+// _ = self.performScanWithPermission(timeout: timeout)
+// case .unauthorized:
+// timer.invalidate()
+// self.showPermissionAlert()
+// case .poweredOff, .unsupported:
+// timer.invalidate()
+// os_log("❌ 蓝牙不可用", log: self.logger, type: .error)
+// default:
+// if checkCount >= maxChecks {
+// timer.invalidate()
+// os_log("⏰ 等待蓝牙状态超时", log: self.logger, type: .error)
+// }
+// }
+// }
+// }
+
+// private func forceTriggerPermissionRequest(timeout: TimeInterval) {
+// // 完全清理旧的 centralManager
+// centralManager?.delegate = nil
+// centralManager = nil
+
+// // 强制重新初始化,确保触发权限申请
+// let options: [String: Any] = [
+// CBCentralManagerOptionShowPowerAlertKey: true,
+// CBCentralManagerOptionRestoreIdentifierKey: "BleScanManager"
+// ]
+
+// centralManager = CBCentralManager(delegate: self, queue: nil, options: options)
+
+// // 设置一个监听器来等待权限状态更新
+// waitForPermissionResponse(timeout: timeout)
+// }
+
+// private func waitForPermissionResponse(timeout: TimeInterval) {
+// // 设置一个定时器来检查权限状态变化
+// var checkCount = 0
+// let maxChecks = 20 // 最多检查20次,每次间隔0.5秒,总共10秒
+
+// let timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] timer in
+// guard let self = self else {
+// timer.invalidate()
+// return
+// }
- checkCount += 1
- let currentAuth = CBManager.authorization
+// checkCount += 1
+// let currentAuth = CBManager.authorization
- os_log("🔍 检查权限状态 (%d/%d): %d", log: self.logger, type: .info, checkCount, maxChecks, currentAuth.rawValue)
+// os_log("🔍 检查权限状态 (%d/%d): %d", log: self.logger, type: .info, checkCount, maxChecks, currentAuth.rawValue)
- switch currentAuth {
- case .allowedAlways:
- timer.invalidate()
- os_log("✅ 蓝牙权限已获得,开始扫描", log: self.logger, type: .info)
- _ = self.performScanWithPermission(timeout: timeout)
- case .denied, .restricted:
- timer.invalidate()
- os_log("❌ 蓝牙权限被拒绝", log: self.logger, type: .error)
- self.showPermissionAlert()
- case .notDetermined:
- if checkCount >= maxChecks {
- timer.invalidate()
- os_log("⏰ 等待权限响应超时", log: self.logger, type: .error)
- self.showPermissionAlert()
- }
- // 继续等待
- @unknown default:
- timer.invalidate()
- os_log("⚠️ 未知权限状态", log: self.logger, type: .error)
- }
- }
- }
-
- private func reinitializeCentralManager() {
- // 清理旧的 centralManager
- centralManager?.delegate = nil
- centralManager = nil
-
- // 重新初始化
- setupCentralManager()
- }
-
- /// 检查蓝牙权限状态
- private func checkBluetoothPermissionStatus() -> (hasPermission: Bool, needsRequest: Bool) {
- // 检查权限状态
- let authorization: CBManagerAuthorization
- if #available(iOS 13.1, *) {
- authorization = CBManager.authorization
- } else if #available(iOS 13.0, *) {
- if centralManager == nil {
- setupCentralManager()
- }
- authorization = centralManager?.authorization ?? .notDetermined
- } else {
- // iOS 13以下没有权限概念
- return (true, false)
- }
-
- os_log("📊 蓝牙权限状态: %d, CBCentralManager状态: %d", log: logger, type: .info, authorization.rawValue, centralManager?.state.rawValue ?? -1)
-
- switch authorization {
- case .allowedAlways:
- return (true, false)
- case .denied, .restricted:
- return (false, false)
- case .notDetermined:
- return (false, true)
- @unknown default:
- return (false, true)
- }
- }
-
- private func performScanWithPermission(timeout: TimeInterval) -> Bool {
- // 首先检查权限状态
- let permissionStatus = checkBluetoothPermissionStatus()
-
- if !permissionStatus.hasPermission {
- if permissionStatus.needsRequest {
- os_log("🔐 需要申请蓝牙权限", log: logger, type: .info)
- _ = requestBluetoothPermission()
- return false
- } else {
- os_log("❌ 蓝牙权限被拒绝", log: logger, type: .error)
- showPermissionAlert()
- return false
- }
- }
-
- // 权限已获得,检查蓝牙硬件状态
- let currentState = centralManager.state
- os_log("📊 蓝牙硬件状态: %d (%{public}@)", log: logger, type: .info, currentState.rawValue, getBluetoothStateString(currentState))
-
- switch currentState {
- case .poweredOn:
- // 蓝牙已开启,可以扫描
- break
- case .unknown:
- os_log("⚠️ 蓝牙状态未知,等待状态更新...", log: logger, type: .info)
- DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
- _ = self?.performScanWithPermission(timeout: timeout)
- }
- return false
- case .poweredOff:
- // 区分是权限问题还是硬件关闭
- if permissionStatus.hasPermission {
- os_log("❌ 蓝牙硬件已关闭,请在设备设置中开启蓝牙", log: logger, type: .error)
- } else {
- os_log("❌ 可能是权限问题导致的蓝牙状态异常", log: logger, type: .error)
- _ = requestBluetoothPermission()
- }
- return false
- case .unauthorized:
- os_log("❌ 应用未授权使用蓝牙", log: logger, type: .error)
- showPermissionAlert()
- return false
- case .unsupported:
- os_log("❌ 设备不支持蓝牙", log: logger, type: .error)
- return false
- default:
- os_log("⚠️ 其他蓝牙状态: %d", log: logger, type: .error, currentState.rawValue)
- return false
- }
-
- // 开始BLE扫描
- centralManager.scanForPeripherals(withServices: nil, options: [
- CBCentralManagerScanOptionAllowDuplicatesKey: true
- ])
-
- isScanning = true
-
- // 设置扫描超时
- scanTimeout = Timer.scheduledTimer(withTimeInterval: timeout, repeats: false) { [weak self] _ in
- self?.stopScan()
- }
-
- os_log("✅ 开始BLE扫描,超时时间: %.1f 秒", log: logger, type: .info, timeout)
- return true
- }
-
- private func showPermissionAlert() {
- guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
- let window = windowScene.windows.first else {
- os_log("❌ 无法获取当前窗口", log: logger, type: .error)
- return
- }
-
- let alert = UIAlertController(
- title: "需要蓝牙权限",
- message: "请在设置中开启蓝牙权限以使用设备扫描功能",
- preferredStyle: .alert
- )
-
- alert.addAction(UIAlertAction(title: "去设置", style: .default) { _ in
- if let settingsUrl = URL(string: UIApplication.openSettingsURLString) {
- UIApplication.shared.open(settingsUrl)
- }
- })
-
- alert.addAction(UIAlertAction(title: "取消", style: .cancel))
-
- window.rootViewController?.present(alert, animated: true)
- }
-
- /// 停止扫描
- public func stopScan() {
- guard isScanning else { return }
-
- os_log("🛑 停止 BLE 扫描", log: logger, type: .info)
-
- centralManager.stopScan() // 直接使用,不需要可选绑定
- isScanning = false
-
- scanTimeout?.invalidate()
- scanTimeout = nil
- }
-
- /// 获取已发现的设备列表
- /// - Returns: 按最后发现时间排序的设备列表
- public func getDiscoveredDevices() -> [BleDeviceInfo] {
- return Array(discoveredDevices.values).sorted { $0.lastSeen > $1.lastSeen }
- }
-
- /// 清除设备列表
- public func clearDeviceList() {
- discoveredDevices.removeAll()
- os_log("🗑️ 已清除设备列表", log: logger, type: .info)
- }
-
- /// 检查是否正在扫描
- public func isScanningActive() -> Bool {
- return isScanning
- }
-
- /// 获取蓝牙状态
- public func getBluetoothState() -> CBManagerState {
- return centralManager.state // 直接返回,不需要 ?? .unknown
- }
-
- // MARK: - 私有方法
+// switch currentAuth {
+// case .allowedAlways:
+// timer.invalidate()
+// os_log("✅ 蓝牙权限已获得,开始扫描", log: self.logger, type: .info)
+// _ = self.performScanWithPermission(timeout: timeout)
+// case .denied, .restricted:
+// timer.invalidate()
+// os_log("❌ 蓝牙权限被拒绝", log: self.logger, type: .error)
+// self.showPermissionAlert()
+// case .notDetermined:
+// if checkCount >= maxChecks {
+// timer.invalidate()
+// os_log("⏰ 等待权限响应超时", log: self.logger, type: .error)
+// self.showPermissionAlert()
+// }
+// // 继续等待
+// @unknown default:
+// timer.invalidate()
+// os_log("⚠️ 未知权限状态", log: self.logger, type: .error)
+// }
+// }
+// }
+
+// private func reinitializeCentralManager() {
+// // 清理旧的 centralManager
+// centralManager?.delegate = nil
+// centralManager = nil
+
+// // 重新初始化
+// setupCentralManager()
+// }
+
+// /// 检查蓝牙权限状态
+// private func checkBluetoothPermissionStatus() -> (hasPermission: Bool, needsRequest: Bool) {
+// // 检查权限状态
+// let authorization: CBManagerAuthorization
+// if #available(iOS 13.1, *) {
+// authorization = CBManager.authorization
+// } else if #available(iOS 13.0, *) {
+// if centralManager == nil {
+// setupCentralManager()
+// }
+// authorization = centralManager?.authorization ?? .notDetermined
+// } else {
+// // iOS 13以下没有权限概念
+// return (true, false)
+// }
+
+// os_log("📊 蓝牙权限状态: %d, CBCentralManager状态: %d", log: logger, type: .info, authorization.rawValue, centralManager?.state.rawValue ?? -1)
+
+// switch authorization {
+// case .allowedAlways:
+// return (true, false)
+// case .denied, .restricted:
+// return (false, false)
+// case .notDetermined:
+// return (false, true)
+// @unknown default:
+// return (false, true)
+// }
+// }
+
+// private func performScanWithPermission(timeout: TimeInterval) -> Bool {
+// // 首先检查权限状态
+// let permissionStatus = checkBluetoothPermissionStatus()
+
+// if !permissionStatus.hasPermission {
+// if permissionStatus.needsRequest {
+// os_log("🔐 需要申请蓝牙权限", log: logger, type: .info)
+// _ = requestBluetoothPermission()
+// return false
+// } else {
+// os_log("❌ 蓝牙权限被拒绝", log: logger, type: .error)
+// showPermissionAlert()
+// return false
+// }
+// }
+
+// // 权限已获得,检查蓝牙硬件状态
+// let currentState = centralManager.state
+// os_log("📊 蓝牙硬件状态: %d (%{public}@)", log: logger, type: .info, currentState.rawValue, getBluetoothStateString(currentState))
+
+// switch currentState {
+// case .poweredOn:
+// // 蓝牙已开启,可以扫描
+// break
+// case .unknown:
+// os_log("⚠️ 蓝牙状态未知,等待状态更新...", log: logger, type: .info)
+// DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
+// _ = self?.performScanWithPermission(timeout: timeout)
+// }
+// return false
+// case .poweredOff:
+// // 区分是权限问题还是硬件关闭
+// if permissionStatus.hasPermission {
+// os_log("❌ 蓝牙硬件已关闭,请在设备设置中开启蓝牙", log: logger, type: .error)
+// } else {
+// os_log("❌ 可能是权限问题导致的蓝牙状态异常", log: logger, type: .error)
+// _ = requestBluetoothPermission()
+// }
+// return false
+// case .unauthorized:
+// os_log("❌ 应用未授权使用蓝牙", log: logger, type: .error)
+// showPermissionAlert()
+// return false
+// case .unsupported:
+// os_log("❌ 设备不支持蓝牙", log: logger, type: .error)
+// return false
+// default:
+// os_log("⚠️ 其他蓝牙状态: %d", log: logger, type: .error, currentState.rawValue)
+// return false
+// }
+
+// // 开始BLE扫描
+// centralManager.scanForPeripherals(withServices: nil, options: [
+// CBCentralManagerScanOptionAllowDuplicatesKey: true
+// ])
+
+// isScanning = true
+
+// // 设置扫描超时
+// scanTimeout = Timer.scheduledTimer(withTimeInterval: timeout, repeats: false) { [weak self] _ in
+// self?.stopScan()
+// }
+
+// os_log("✅ 开始BLE扫描,超时时间: %.1f 秒", log: logger, type: .info, timeout)
+// return true
+// }
+
+// private func showPermissionAlert() {
+// guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
+// let window = windowScene.windows.first else {
+// os_log("❌ 无法获取当前窗口", log: logger, type: .error)
+// return
+// }
+
+// let alert = UIAlertController(
+// title: "需要蓝牙权限",
+// message: "请在设置中开启蓝牙权限以使用设备扫描功能",
+// preferredStyle: .alert
+// )
+
+// alert.addAction(UIAlertAction(title: "去设置", style: .default) { _ in
+// if let settingsUrl = URL(string: UIApplication.openSettingsURLString) {
+// UIApplication.shared.open(settingsUrl)
+// }
+// })
+
+// alert.addAction(UIAlertAction(title: "取消", style: .cancel))
+
+// window.rootViewController?.present(alert, animated: true)
+// }
+
+// /// 停止扫描
+// public func stopScan() {
+// guard isScanning else { return }
+
+// os_log("🛑 停止 BLE 扫描", log: logger, type: .info)
+
+// centralManager.stopScan() // 直接使用,不需要可选绑定
+// isScanning = false
+
+// scanTimeout?.invalidate()
+// scanTimeout = nil
+// }
+
+// /// 获取已发现的设备列表
+// /// - Returns: 按最后发现时间排序的设备列表
+// public func getDiscoveredDevices() -> [BleDeviceInfo] {
+// return Array(discoveredDevices.values).sorted { $0.lastSeen > $1.lastSeen }
+// }
+
+// /// 清除设备列表
+// public func clearDeviceList() {
+// discoveredDevices.removeAll()
+// os_log("🗑️ 已清除设备列表", log: logger, type: .info)
+// }
+
+// /// 检查是否正在扫描
+// public func isScanningActive() -> Bool {
+// return isScanning
+// }
+
+// /// 获取蓝牙状态
+// public func getBluetoothState() -> CBManagerState {
+// return centralManager.state // 直接返回,不需要 ?? .unknown
+// }
+
+// // MARK: - 私有方法
- private func addOrUpdateDevice(_ deviceInfo: BleDeviceInfo) {
- let identifier = deviceInfo.identifier
- discoveredDevices[identifier] = deviceInfo
-
- os_log("📱 发现设备: %{public}@", log: logger, type: .debug, deviceInfo.name ?? "Unknown")
- }
-
- /// 强制触发蓝牙权限申请
- public func requestBluetoothPermission() -> Bool {
- os_log("🔐 强制触发蓝牙权限申请", log: logger, type: .info)
-
- // 检查当前权限状态
- let authorization: CBManagerAuthorization
- if #available(iOS 13.1, *) {
- authorization = CBManager.authorization
- } else if #available(iOS 13.0, *) {
- // 确保centralManager已初始化
- if centralManager == nil {
- setupCentralManager()
- }
- authorization = centralManager.authorization
- } else {
- // iOS 13以下没有权限概念,直接返回true
- return true
- }
-
- os_log("📊 当前权限状态: %d", log: logger, type: .info, authorization.rawValue)
-
- switch authorization {
- case .notDetermined:
- // 权限未确定,需要主动触发蓝牙操作来显示权限弹窗
- if centralManager == nil {
- os_log("📊 初始化蓝牙管理对象", log: logger, type: .info, "")
- setupCentralManager()
- }
+// private func addOrUpdateDevice(_ deviceInfo: BleDeviceInfo) {
+// let identifier = deviceInfo.identifier
+// discoveredDevices[identifier] = deviceInfo
+
+// os_log("📱 发现设备: %{public}@", log: logger, type: .debug, deviceInfo.name ?? "Unknown")
+// }
+
+// /// 强制触发蓝牙权限申请
+// public func requestBluetoothPermission() -> Bool {
+// os_log("🔐 强制触发蓝牙权限申请", log: logger, type: .info)
+
+// // 检查当前权限状态
+// let authorization: CBManagerAuthorization
+// if #available(iOS 13.1, *) {
+// authorization = CBManager.authorization
+// } else if #available(iOS 13.0, *) {
+// // 确保centralManager已初始化
+// if centralManager == nil {
+// setupCentralManager()
+// }
+// authorization = centralManager.authorization
+// } else {
+// // iOS 13以下没有权限概念,直接返回true
+// return true
+// }
+
+// os_log("📊 当前权限状态: %d", log: logger, type: .info, authorization.rawValue)
+
+// switch authorization {
+// case .notDetermined:
+// // 权限未确定,需要主动触发蓝牙操作来显示权限弹窗
+// if centralManager == nil {
+// os_log("📊 初始化蓝牙管理对象", log: logger, type: .info, "")
+// setupCentralManager()
+// }
- // 等待CBCentralManager状态更新后,主动开始扫描来触发权限弹窗
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
- guard let self = self else { return }
+// // 等待CBCentralManager状态更新后,主动开始扫描来触发权限弹窗
+// DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
+// guard let self = self else { return }
- if self.centralManager.state == .poweredOn {
- // 开始一个短暂的扫描来触发权限申请
- self.centralManager.scanForPeripherals(withServices: nil, options: nil)
- // 立即停止扫描
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
- self.centralManager.stopScan()
- }
- } else if self.centralManager.state == .unauthorized {
- // 权限被拒绝,显示设置提示
- self.showPermissionAlert()
- }
- }
- return true
- case .allowedAlways:
- os_log("✅ 权限已授予", log: logger, type: .info)
- return true
- case .denied, .restricted:
- os_log("❌ 权限被拒绝,显示设置提示", log: logger, type: .error)
- showPermissionAlert()
- return false
- @unknown default:
- os_log("⚠️ 未知权限状态", log: logger, type: .info)
- return false
- }
- }
-}
+// if self.centralManager.state == .poweredOn {
+// // 开始一个短暂的扫描来触发权限申请
+// self.centralManager.scanForPeripherals(withServices: nil, options: nil)
+// // 立即停止扫描
+// DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
+// self.centralManager.stopScan()
+// }
+// } else if self.centralManager.state == .unauthorized {
+// // 权限被拒绝,显示设置提示
+// self.showPermissionAlert()
+// }
+// }
+// return true
+// case .allowedAlways:
+// os_log("✅ 权限已授予", log: logger, type: .info)
+// return true
+// case .denied, .restricted:
+// os_log("❌ 权限被拒绝,显示设置提示", log: logger, type: .error)
+// showPermissionAlert()
+// return false
+// @unknown default:
+// os_log("⚠️ 未知权限状态", log: logger, type: .info)
+// return false
+// }
+// }
+// }
-// MARK: - CBCentralManagerDelegate
-@available(iOS 13.0, *)
-extension BleScanManager: CBCentralManagerDelegate {
-
- public func centralManagerDidUpdateState(_ central: CBCentralManager) {
- let stateString = getBluetoothStateString(central.state)
- os_log("🔧 CBCentralManager 状态更新: %{public}@ (原始值: %d)", log: logger, type: .info, stateString, central.state.rawValue)
-
- switch central.state {
- case .poweredOn:
- os_log("✅ 蓝牙已开启,可以开始扫描", log: logger, type: .info)
- // 如果有待处理的扫描请求,现在可以执行
- case .poweredOff:
- os_log("❌ 蓝牙已关闭", log: logger, type: .error)
- if isScanning {
- stopScan()
- }
- case .unauthorized:
- os_log("❌ 蓝牙权限被拒绝", log: logger, type: .error)
- if isScanning {
- stopScan()
- }
- case .unsupported:
- os_log("❌ 设备不支持蓝牙", log: logger, type: .error)
- if isScanning {
- stopScan()
- }
- case .unknown:
- os_log("⚠️ 蓝牙状态未知,等待进一步更新", log: logger, type: .error)
- case .resetting:
- os_log("⚠️ 蓝牙正在重置", log: logger, type: .error)
- if isScanning {
- stopScan()
- }
- @unknown default:
- os_log("⚠️ 未知蓝牙状态: %d", log: logger, type: .error, central.state.rawValue)
- }
- }
-
- public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral,
- advertisementData: [String : Any], rssi RSSI: NSNumber) {
-
- let deviceInfo = BleDeviceInfo(
- identifier: peripheral.identifier,
- name: peripheral.name ?? advertisementData[CBAdvertisementDataLocalNameKey] as? String,
- rssi: RSSI,
- advertisementData: advertisementData,
- discoveryMethod: .traditional,
- lastSeen: Date()
- )
-
- addOrUpdateDevice(deviceInfo)
- }
-
- public func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
- os_log("🔄 恢复 CBCentralManager 状态", log: logger, type: .info)
-
- // 恢复扫描状态
- if dict[CBCentralManagerRestoredStateScanServicesKey] != nil {
- isScanning = true
- }
- }
-
- private func getBluetoothStateString(_ state: CBManagerState) -> String {
- switch state {
- case .unknown: return "未知状态"
- case .resetting: return "重置中"
- case .unsupported: return "设备不支持蓝牙"
- case .unauthorized: return "应用未授权使用蓝牙"
- case .poweredOff: return "蓝牙已关闭"
- case .poweredOn: return "蓝牙已开启"
- @unknown default: return "未知状态"
- }
- }
-}
+// // MARK: - CBCentralManagerDelegate
+// @available(iOS 13.0, *)
+// extension BleScanManager: CBCentralManagerDelegate {
+
+// public func centralManagerDidUpdateState(_ central: CBCentralManager) {
+// let stateString = getBluetoothStateString(central.state)
+// os_log("🔧 CBCentralManager 状态更新: %{public}@ (原始值: %d)", log: logger, type: .info, stateString, central.state.rawValue)
+
+// switch central.state {
+// case .poweredOn:
+// os_log("✅ 蓝牙已开启,可以开始扫描", log: logger, type: .info)
+// // 如果有待处理的扫描请求,现在可以执行
+// case .poweredOff:
+// os_log("❌ 蓝牙已关闭", log: logger, type: .error)
+// if isScanning {
+// stopScan()
+// }
+// case .unauthorized:
+// os_log("❌ 蓝牙权限被拒绝", log: logger, type: .error)
+// if isScanning {
+// stopScan()
+// }
+// case .unsupported:
+// os_log("❌ 设备不支持蓝牙", log: logger, type: .error)
+// if isScanning {
+// stopScan()
+// }
+// case .unknown:
+// os_log("⚠️ 蓝牙状态未知,等待进一步更新", log: logger, type: .error)
+// case .resetting:
+// os_log("⚠️ 蓝牙正在重置", log: logger, type: .error)
+// if isScanning {
+// stopScan()
+// }
+// @unknown default:
+// os_log("⚠️ 未知蓝牙状态: %d", log: logger, type: .error, central.state.rawValue)
+// }
+// }
+
+// public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral,
+// advertisementData: [String : Any], rssi RSSI: NSNumber) {
+
+// let deviceInfo = BleDeviceInfo(
+// identifier: peripheral.identifier,
+// name: peripheral.name ?? advertisementData[CBAdvertisementDataLocalNameKey] as? String,
+// rssi: RSSI,
+// advertisementData: advertisementData,
+// discoveryMethod: .traditional,
+// lastSeen: Date()
+// )
+
+// addOrUpdateDevice(deviceInfo)
+// }
+
+// public func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
+// os_log("🔄 恢复 CBCentralManager 状态", log: logger, type: .info)
+
+// // 恢复扫描状态
+// if dict[CBCentralManagerRestoredStateScanServicesKey] != nil {
+// isScanning = true
+// }
+// }
+
+// private func getBluetoothStateString(_ state: CBManagerState) -> String {
+// switch state {
+// case .unknown: return "未知状态"
+// case .resetting: return "重置中"
+// case .unsupported: return "设备不支持蓝牙"
+// case .unauthorized: return "应用未授权使用蓝牙"
+// case .poweredOff: return "蓝牙已关闭"
+// case .poweredOn: return "蓝牙已开启"
+// @unknown default: return "未知状态"
+// }
+// }
+// }
diff --git a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift
index 93673ada8..ae25e7e3b 100644
--- a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift
+++ b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleService.swift
@@ -3,15 +3,16 @@ import CoreBluetooth
import os.log
import AVFoundation
-/// BLE服务类 - 专为AccessorySetupKit设备设计,支持自动重连和状态恢复
+/// BLE服务类 - 传统BLE扫描和连接实现
///
/// 核心特性:
-/// 1. 外设掉电→上电后自动重连(通过pendingReconnectUUIDs队列)
-/// 2. App被系统终止后,外设广播或特征通知时自动恢复连接
-/// 3. 使用CBCentralManagerOptionRestoreIdentifierKey实现系统级状态保留
-/// 4. 支持bluetooth-central后台模式,允许后台BLE操作
-/// 5. willRestoreState恢复系统保留的外设、扫描条件
-/// 6. 统一的重连队列管理,适配AccessorySetupKit工作流程
+/// 1. 传统BLE扫描和连接逻辑
+/// 2. 外设掉电→上电后自动重连(通过pendingReconnectUUIDs队列)
+/// 3. App被系统终止后,外设广播或特征通知时自动恢复连接
+/// 4. 使用CBCentralManagerOptionRestoreIdentifierKey实现系统级状态保留
+/// 5. 支持bluetooth-central后台模式,允许后台BLE操作
+/// 6. willRestoreState恢复系统保留的外设、扫描条件
+/// 7. 统一的重连队列管理
@available(iOS 13.0, *)
public class BleService: NSObject {
// MARK: - 单例实现
@@ -43,6 +44,13 @@ public class BleService: NSObject {
private var audioCharacteristic: CBCharacteristic?
private var isScanning = false
+ private var scanTimer: Timer?
+
+ // 新增:设备缓存和广播数据记录
+ private var discoveredDevices: [String: [String: Any]] = [:] // 以设备ID为key的设备信息缓存
+ private var deviceAdvertisementData: [String: [String: Any]] = [:] // 设备广播数据缓存
+ private var scanStartTime: Date?
+ private let maxCacheTime: TimeInterval = 300 // 5分钟缓存时间
// 是否是ota模式
var isEnterOta = false
@@ -50,8 +58,6 @@ public class BleService: NSObject {
private var callbacks = [Callback]()
private var connectionState = BleConst.STATE_DISCONNECTED
-
-
// 状态恢复相关
private var restoredPeripherals: [CBPeripheral] = []
@@ -66,22 +72,11 @@ public class BleService: NSObject {
// MARK: - 初始化
private override init() {
super.init()
-
- #if canImport(AccessorySetupKit) && swift(>=5.9)
- if #available(iOS 18.0, *) {
- // iOS 18+下不立即初始化,等待AccessorySetupKit
- } else {
- initializeCentralManager()
- }
- #else
initializeCentralManager()
- #endif
-
- // 初始化Opus音频处理器
initializeOpusProcessor()
}
- // 延迟初始化中央管理器
+ // 初始化中央管理器
private func initializeCentralManager() {
if centralManager != nil {
os_log("🔧 CBCentralManager已存在,跳过初始化", log: logger, type: .debug)
@@ -170,19 +165,13 @@ public class BleService: NSObject {
}
/// 开始扫描BLE设备
- public func startScan() {
- #if canImport(AccessorySetupKit) && swift(>=5.9)
- if #available(iOS 18.0, *) {
- // iOS 18+不使用传统扫描
- return
- }
- #endif
-
+ public func startScan(timeout: TimeInterval = 30.0) {
if centralManager == nil {
initializeCentralManager()
}
guard let manager = centralManager, manager.state == .poweredOn else {
+ os_log("❌ 蓝牙未就绪,无法开始扫描", log: logger, type: .error)
return
}
@@ -190,6 +179,9 @@ public class BleService: NSObject {
stopScan()
}
+ // 清空之前的扫描结果
+ discoveredDevices.removeAll()
+
isScanning = true
let serviceUUIDs = [BleConst.PRIMARY_SERVICE_UUID, BleConst.AUDIO_SERVICE_UUID]
@@ -197,7 +189,14 @@ public class BleService: NSObject {
CBCentralManagerScanOptionAllowDuplicatesKey: false
])
- os_log("开始扫描BLE设备...", log: logger, type: .info)
+ os_log("🔍 开始扫描BLE设备,超时时间: %.1f秒", log: logger, type: .info, timeout)
+
+ // 设置扫描超时
+ scanTimer?.invalidate()
+ scanTimer = Timer.scheduledTimer(withTimeInterval: timeout, repeats: false) { [weak self] _ in
+ self?.stopScan()
+ os_log("⏰ 扫描超时,自动停止", log: self?.logger ?? OSLog.default, type: .info)
+ }
}
/// 停止扫描
@@ -205,10 +204,37 @@ public class BleService: NSObject {
if isScanning && centralManager != nil {
centralManager?.stopScan()
isScanning = false
- os_log("停止BLE扫描", log: logger, type: .info)
+ scanTimer?.invalidate()
+ scanTimer = nil
+ os_log("⏹️ 停止BLE扫描", log: logger, type: .info)
}
}
+ /// 连接到指定UUID的设备
+ public func connectToDevice(uuid: String) -> Bool {
+ guard let deviceUUID = UUID(uuidString: uuid) else {
+ os_log("❌ 无效的设备UUID: %@", log: logger, type: .error, uuid)
+ return false
+ }
+
+ guard let manager = centralManager else {
+ os_log("❌ CBCentralManager未初始化", log: logger, type: .error)
+ return false
+ }
+
+ // 先尝试从已知外设中获取
+ let knownPeripherals = manager.retrievePeripherals(withIdentifiers: [deviceUUID])
+ if let peripheral = knownPeripherals.first {
+ connectPeripheral(peripheral)
+ return true
+ }
+
+ // 如果没有找到,添加到重连队列
+ addToReconnectQueue(uuid: deviceUUID)
+ os_log("📱 设备未找到,已添加到重连队列: %@", log: logger, type: .info, uuid)
+ return false
+ }
+
/// 连接到指定设备
public func connect(to peripheral: CBPeripheral) {
connectPeripheral(peripheral)
@@ -231,18 +257,18 @@ public class BleService: NSObject {
}
if peripheral.state == .connected {
- os_log("设备已连接,恢复会话: %@", log: logger, type: .info, peripheral.identifier.uuidString)
+ os_log("📱 设备已连接,恢复会话: %@", log: logger, type: .info, peripheral.identifier.uuidString)
resumeSession(for: peripheral)
return
}
if peripheral.state == .connecting {
- os_log("设备正在连接中: %@", log: logger, type: .info, peripheral.identifier.uuidString)
+ os_log("📱 设备正在连接中: %@", log: logger, type: .info, peripheral.identifier.uuidString)
return
}
if connectionState == BleConst.STATE_CONNECTED && peripheralDevice?.identifier != peripheral.identifier {
- os_log("断开当前设备,连接新设备", log: logger, type: .info)
+ os_log("📱 断开当前设备,连接新设备", log: logger, type: .info)
disconnect()
}
@@ -257,7 +283,7 @@ public class BleService: NSObject {
CBConnectPeripheralOptionNotifyOnNotificationKey: true
])
- os_log("开始连接到设备: %@", log: logger, type: .info, peripheral.identifier.uuidString)
+ os_log("🔗 开始连接到设备: %@", log: logger, type: .info, peripheral.identifier.uuidString)
}
/// 恢复已连接设备的会话
@@ -278,7 +304,7 @@ public class BleService: NSObject {
os_log("🔍 恢复会话时重新发现服务和特征", log: logger, type: .info)
peripheral.discoverServices([BleConst.PRIMARY_SERVICE_UUID, BleConst.AUDIO_SERVICE_UUID])
- os_log("已恢复设备会话: %@", log: logger, type: .info, peripheral.identifier.uuidString)
+ os_log("✅ 已恢复设备会话: %@", log: logger, type: .info, peripheral.identifier.uuidString)
}
/// 断开当前连接
@@ -291,7 +317,7 @@ public class BleService: NSObject {
if centralManager != nil {
updateConnectionState(BleConst.STATE_DISCONNECTING)
centralManager?.cancelPeripheralConnection(peripheral)
- os_log("开始断开设备连接", log: logger, type: .info)
+ os_log("🔌 开始断开设备连接", log: logger, type: .info)
}
}
@@ -300,6 +326,11 @@ public class BleService: NSObject {
return connectionState == BleConst.STATE_CONNECTED
}
+ /// 获取扫描到的设备列表
+ public func getDiscoveredDevices() -> [[String: Any]] {
+ return Array(discoveredDevices.values)
+ }
+
// MARK: - 回调通知方法
/// 通知所有回调扫描结果
@@ -903,25 +934,7 @@ public class BleService: NSObject {
]
}
- // MARK: - AccessorySetupKit
-
- /// 在接收到AccessorySetupKit的accessoryAdded事件后调用
- func connectToAccessory(uuid: UUID) {
- #if canImport(AccessorySetupKit) && swift(>=5.9)
- if #available(iOS 18.0, *) {
- os_log("开始连接设备: %{public}@", log: logger, type: .info, uuid.uuidString)
-
- // 确保CBCentralManager已初始化
- if centralManager == nil {
- os_log("CBCentralManager未初始化,开始初始化", log: logger, type: .debug)
- initializeCentralManager()
- }
-
- // 添加到重连队列
- addToReconnectQueue(uuid: uuid)
- }
- #endif
- }
+
}
// MARK: - SwiftOpusAudioProcessor.AudioDataCallback
@@ -943,7 +956,7 @@ extension BleService: CBCentralManagerDelegate {
case .poweredOn:
os_log("🔧 蓝牙已开启", log: logger, type: .info)
- // 注:AccessorySetupKit应用不需要恢复传统BLE扫描
+ // 蓝牙就绪后可以开始扫描
// 恢复外设连接
if !restoredPeripherals.isEmpty {
@@ -1042,22 +1055,72 @@ extension BleService: CBCentralManagerDelegate {
// 原有方法
public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
- // 将扫描结果转换为字典格式
+ let deviceId = peripheral.identifier.uuidString
+ let deviceName = peripheral.name ?? "Unknown"
+
+ // 构建设备基本信息
var deviceInfo: [String: Any] = [
- "name": peripheral.name ?? "Unknown",
- "id": peripheral.identifier.uuidString,
- "rssi": RSSI.intValue
+ "name": deviceName,
+ "id": deviceId,
+ "rssi": RSSI.intValue,
+ "discoveredAt": Date().timeIntervalSince1970
]
- // 添加广播数据
+ // 详细的广播数据记录
+ var advData: [String: Any] = [:]
+
+ // 制造商数据
if let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data {
deviceInfo["manufacturerData"] = manufacturerData
+ advData["manufacturerData"] = manufacturerData.map { String(format: "%02x", $0) }.joined(separator: " ")
+ advData["manufacturerDataRaw"] = manufacturerData
}
+ // 服务UUID列表
if let serviceUUIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] {
- deviceInfo["serviceUUIDs"] = serviceUUIDs.map { $0.uuidString }
+ let serviceUUIDStrings = serviceUUIDs.map { $0.uuidString }
+ deviceInfo["serviceUUIDs"] = serviceUUIDStrings
+ advData["serviceUUIDs"] = serviceUUIDStrings
+ }
+
+ // 本地名称
+ if let localName = advertisementData[CBAdvertisementDataLocalNameKey] as? String {
+ advData["localName"] = localName
+ }
+
+ // 发射功率
+ if let txPowerLevel = advertisementData[CBAdvertisementDataTxPowerLevelKey] as? NSNumber {
+ advData["txPowerLevel"] = txPowerLevel.intValue
}
+ // 服务数据
+ if let serviceData = advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID: Data] {
+ var serviceDataDict: [String: String] = [:]
+ for (uuid, data) in serviceData {
+ serviceDataDict[uuid.uuidString] = data.map { String(format: "%02x", $0) }.joined(separator: " ")
+ }
+ advData["serviceData"] = serviceDataDict
+ }
+
+ // 可连接性
+ if let isConnectable = advertisementData[CBAdvertisementDataIsConnectable] as? NSNumber {
+ advData["isConnectable"] = isConnectable.boolValue
+ }
+
+ // 完整的广播数据
+ advData["rssi"] = RSSI.intValue
+ advData["timestamp"] = Date().timeIntervalSince1970
+
+ // 缓存设备信息和广播数据
+ discoveredDevices[deviceId] = deviceInfo
+ deviceAdvertisementData[deviceId] = advData
+
+ // 添加广播数据到设备信息中
+ deviceInfo["advertisementData"] = advData
+
+ os_log("📡 发现设备: %{public}@ (ID: %{public}@), RSSI: %d", log: logger, type: .info, deviceName, deviceId, RSSI.intValue)
+ os_log("📡 广播数据: %{public}@", log: logger, type: .debug, String(describing: advData))
+
// 报告给回调
notifyScanResult(devices: [deviceInfo])
}
@@ -1267,4 +1330,4 @@ extension BleService: CBPeripheralDelegate {
os_log("✅ 描述符写入成功: %{public}@", log: logger, type: .debug, descriptor.uuid.uuidString)
}
}
-}
+}
diff --git a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftBleServicePlugin.swift b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftBleServicePlugin.swift
index fb7f92d49..4d94982ec 100644
--- a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftBleServicePlugin.swift
+++ b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftBleServicePlugin.swift
@@ -12,79 +12,20 @@ public class SwiftBleServicePlugin: NSObject, FlutterPlugin {
internal var statusEventSink: FlutterEventSink?
private let logger = OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleServicePlugin")
- // 标记是否已初始化所有服务
- private var servicesInitialized = false
-
// MARK: - 初始化方法
init(_ channel: FlutterMethodChannel) {
self.methodChannel = channel
super.init()
- #if canImport(AccessorySetupKit) && swift(>=5.9)
- if #available(iOS 18.0, *) {
-
- // iOS 18环境下,只设置回调但不主动初始化会话
- let bleAccessorySetup = BleAccessorySetup.shared
-
- // 在accessoryAdded回调中延迟设置BleService
- bleAccessorySetup.setDeviceDiscoveredCallback { [weak self] uuid in
- // 确保服务已初始化
- self?.ensureServicesInitialized()
- // 然后处理发现的设备
- self?.handleDiscoveredDevice(uuid: uuid)
- }
-
- // 注释掉自动初始化,改为等待用户主动调用startDeviceAssociation
- // bleAccessorySetup.initializeSession()
-
- os_log("iOS 18环境:已设置设备发现回调,等待用户主动调用关联", log: logger, type: .info)
- } else {
- // 低于iOS 18,直接初始化所有服务
- initializeAllServices()
- }
- #else
- // 非AccessorySetupKit环境,直接初始化所有服务
- initializeAllServices()
- #endif
+ // 直接初始化BLE服务
+ initializeBleService()
}
-
- // 确保所有服务都已初始化
- private func ensureServicesInitialized() {
- if !servicesInitialized {
- // 设置BLE服务回调
- BleService.shared.addCallback(self)
-
- // 设置BleService到AccessorySetup
- #if canImport(AccessorySetupKit) && swift(>=5.9)
- if #available(iOS 18.0, *) {
- BleAccessorySetup.shared.setBleService(BleService.shared)
- }
- #endif
-
- servicesInitialized = true
- }
- }
-
- // 初始化所有服务(iOS 17及以下版本的标准行为)
- private func initializeAllServices() {
+ // 初始化BLE服务
+ private func initializeBleService() {
// 设置BLE服务回调
BleService.shared.addCallback(self)
-
- // 初始化AccessorySetup,但不自动开始会话
- #if canImport(AccessorySetupKit) && swift(>=5.9)
- if #available(iOS 18.0, *) {
- BleAccessorySetup.shared.setBleService(BleService.shared)
- BleAccessorySetup.shared.setDeviceDiscoveredCallback { [weak self] uuid in
- self?.handleDiscoveredDevice(uuid: uuid)
- }
- // 移除自动初始化,统一由startDeviceAssociation方法触发
- // BleAccessorySetup.shared.initializeSession()
- }
- #endif
-
- os_log("非iOS 18环境:已设置BLE服务但不自动初始化会话", log: logger, type: .info)
- servicesInitialized = true
+ os_log("✅ BLE服务已初始化", log: logger, type: .info)
}
// MARK: - FlutterPlugin Protocol
@@ -106,9 +47,6 @@ public class SwiftBleServicePlugin: NSObject, FlutterPlugin {
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
- // 检查是否需要初始化服务
- checkAndInitializeServices()
-
switch call.method {
case "isConnected":
result(BleService.shared.isConnected())
@@ -123,31 +61,6 @@ public class SwiftBleServicePlugin: NSObject, FlutterPlugin {
details: nil))
}
- case "isCompanionSupported":
- #if canImport(AccessorySetupKit) && swift(>=5.9)
- if #available(iOS 18.0, *) {
- result(true)
- } else {
- result(false)
- }
- #else
- result(false)
- #endif
-
- case "getPairedMacAddress":
- #if canImport(AccessorySetupKit) && swift(>=5.9)
- if #available(iOS 18.0, *) {
- let pairedUUID = BleAccessorySetup.shared.getPairedMacAddress()
- result(pairedUUID)
- } else {
- result(nil)
- }
- #else
- result(nil)
- #endif
-
-
-
case "isBleConnected":
result(BleService.shared.isConnected())
@@ -193,90 +106,46 @@ public class SwiftBleServicePlugin: NSObject, FlutterPlugin {
case "playA2DP":
result(BleService.shared.playA2DP())
- // iOS 18+ AccessorySetupKit相关方法
- case "clearAssociations":
- // 清除设备配对关系
- #if canImport(AccessorySetupKit) && swift(>=5.9)
- if #available(iOS 18.0, *) {
- os_log("清除设备配对关系", log: logger, type: .info)
- BleAccessorySetup.shared.clearAssociations()
- result(true)
- } else {
- result(true)
- }
- #else
- result(true)
- #endif
-
- case "startDeviceAssociation":
- // 用户主动触发设备关联流程
- #if canImport(AccessorySetupKit) && swift(>=5.9)
- if #available(iOS 18.0, *) {
- os_log("用户主动触发设备关联流程", log: logger, type: .info)
-
- // 确保服务已初始化
- ensureServicesInitialized()
-
- // 重新初始化会话
- BleAccessorySetup.shared.initializeSession()
-
- // 返回成功,实际配对结果会通过回调通知
- result(true)
- } else {
- os_log("当前iOS版本不支持AccessorySetupKit", log: logger, type: .info)
- result(false)
- }
- #else
- os_log("当前环境不支持AccessorySetupKit", log: logger, type: .info)
- result(false)
- #endif
case "startScan":
let arguments = call.arguments as? [String: Any]
- let devicename = (arguments?["devicename"] as? String) ?? ""
+ let timeout = (arguments?["timeout"] as? Double) ?? 30.0
- // 修复:支持所有iOS版本
- if #available(iOS 18.0, *) {
- // iOS 18+ 使用新的BleScanManager
- os_log("iOS 18+ 使用 BleScanManager 进行扫描", log: logger, type: .info)
- let succ = BleScanManager.shared.startScan(timeout: 15.0)
- result(succ)
- } else {
- // iOS 17及以下使用传统BleService
- os_log("iOS 17及以下使用传统 BleService 进行扫描", log: logger, type: .info)
- BleService.shared.startScan()
+ os_log("🔍 开始传统BLE扫描,超时: %.1f秒", log: logger, type: .info, timeout)
+ BleService.shared.startScan(timeout: timeout)
result(true)
- }
-
- case "getDeviceInfo":
+
+ case "stopScan":
+ os_log("⏹️ 停止BLE扫描", log: logger, type: .info)
+ BleService.shared.stopScan()
+ result(true)
+
+ case "connectToDevice":
let arguments = call.arguments as? [String: Any]
- let address = (arguments?["address"] as? String) ?? ""
- let info = BleScanManager.shared.getDiscoveredDevices()
- result(info)
+ guard let deviceId = arguments?["deviceId"] as? String else {
+ result(FlutterError(code: "INVALID_ARGUMENT",
+ message: "缺少设备ID参数",
+ details: nil))
+ return
+ }
+
+ os_log("🔗 连接到设备: %@", log: logger, type: .info, deviceId)
+ let success = BleService.shared.connectToDevice(uuid: deviceId)
+ result(success)
+
+ case "getDiscoveredDevices":
+ let devices = BleService.shared.getDiscoveredDevices()
+ result(devices)
+
case "requestBluetoothPermission":
- BleScanManager.shared.requestBluetoothPermission();
- result(true)
+ // 移除这个方法调用,让iOS系统自然处理权限
+ result(FlutterMethodNotImplemented)
+
default:
result(FlutterMethodNotImplemented)
}
}
// MARK: - 私有方法
- // 检查并按需初始化服务
- private func checkAndInitializeServices() {
- #if canImport(AccessorySetupKit) && swift(>=5.9)
- if #available(iOS 18.0, *) {
- ensureServicesInitialized()
- }
- #endif
- }
-
- // AccessorySetupKit发现设备的处理
- private func handleDiscoveredDevice(uuid: UUID) {
- // BleAccessorySetup内部已进行连接,此处不再重复调用
- // os_log("收到设备发现回调: %{public}@", log: logger, type: .info, uuid.uuidString)
- // 可以在这里添加设备记录或其他操作
- }
-
private func sendEvent(_ eventSink: FlutterEventSink?, data: T, errorPrefix: String) {
if let eventSink = eventSink {
DispatchQueue.main.async {
@@ -284,25 +153,6 @@ public class SwiftBleServicePlugin: NSObject, FlutterPlugin {
}
}
}
-
- // 获取顶层视图控制器的辅助方法
- private func getTopViewController(_ viewController: UIViewController) -> UIViewController {
- if let presented = viewController.presentedViewController {
- return getTopViewController(presented)
- }
-
- if let tabBarController = viewController as? UITabBarController,
- let selected = tabBarController.selectedViewController {
- return getTopViewController(selected)
- }
-
- if let navigationController = viewController as? UINavigationController,
- let visibleViewController = navigationController.visibleViewController {
- return getTopViewController(visibleViewController)
- }
-
- return viewController
- }
}
// MARK: - BleService.Callback
diff --git a/local_plugins/user_portrait/ios/user_portrait.podspec b/local_plugins/user_portrait/ios/user_portrait.podspec
deleted file mode 100644
index 65cae4b79..000000000
--- a/local_plugins/user_portrait/ios/user_portrait.podspec
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
-# Run `pod lib lint user_portrait.podspec` to validate before publishing.
-#
-Pod::Spec.new do |s|
- s.name = 'user_portrait'
- s.version = '0.0.1'
- s.summary = '用户画像'
- s.description = <<-DESC
-用户画像
- DESC
- s.homepage = 'http://example.com'
- s.license = { :file => '../LICENSE' }
- s.author = { 'Your Company' => 'email@example.com' }
- s.source = { :path => '.' }
- s.source_files = 'user_portrait/Sources/user_portrait/**/*'
- s.dependency 'Flutter'
- s.platform = :ios, '12.0'
-
- # Flutter.framework does not contain a i386 slice.
- s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
- s.swift_version = '5.0'
-
- # If your plugin requires a privacy manifest, for example if it uses any
- # required reason APIs, update the PrivacyInfo.xcprivacy file to describe your
- # plugin's privacy impact, and then uncomment this line. For more information,
- # see https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
- # s.resource_bundles = {'user_portrait_privacy' => ['user_portrait/Sources/user_portrait/PrivacyInfo.xcprivacy']}
-end
diff --git a/local_plugins/user_portrait/ios/user_portrait/Package.swift b/local_plugins/user_portrait/ios/user_portrait/Package.swift
index d8d28ea06..4d8e0e3fc 100644
--- a/local_plugins/user_portrait/ios/user_portrait/Package.swift
+++ b/local_plugins/user_portrait/ios/user_portrait/Package.swift
@@ -13,20 +13,12 @@ let package = Package(
],
dependencies: [],
targets: [
- .target(
- name: "user_portrait",
- dependencies: [],
- resources: [
- // If your plugin requires a privacy manifest, for example if it uses any required
- // reason APIs, update the PrivacyInfo.xcprivacy file to describe your plugin's
- // privacy impact, and then uncomment these lines. For more information, see
- // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
- // .process("PrivacyInfo.xcprivacy"),
-
- // If you have other resources that need to be bundled with your plugin, refer to
- // the following instructions to add them:
- // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package
- ]
- )
- ]
+ .target(
+ name: "user_portrait",
+ dependencies: [
+
+ ],
+ path: "Sources/user_portrait"
+ )
+ ]
)
diff --git a/local_plugins/user_portrait/ios/user_portrait/Sources/user_portrait/PrivacyInfo.xcprivacy b/local_plugins/user_portrait/ios/user_portrait/Sources/user_portrait/PrivacyInfo.xcprivacy
deleted file mode 100644
index a34b7e2e6..000000000
--- a/local_plugins/user_portrait/ios/user_portrait/Sources/user_portrait/PrivacyInfo.xcprivacy
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
- NSPrivacyTrackingDomains
-
- NSPrivacyAccessedAPITypes
-
- NSPrivacyCollectedDataTypes
-
- NSPrivacyTracking
-
-
-
diff --git a/local_plugins/user_portrait/ios/user_portrait/Sources/user_portrait/UserPortraitPlugin.swift b/local_plugins/user_portrait/ios/user_portrait/Sources/user_portrait/UserPortraitPlugin.swift
index b8cdc8b21..f43d0de1a 100644
--- a/local_plugins/user_portrait/ios/user_portrait/Sources/user_portrait/UserPortraitPlugin.swift
+++ b/local_plugins/user_portrait/ios/user_portrait/Sources/user_portrait/UserPortraitPlugin.swift
@@ -10,8 +10,8 @@ public class UserPortraitPlugin: NSObject, FlutterPlugin {
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
- case "getPlatformVersion":
- result("iOS " + UIDevice.current.systemVersion)
+ case "clean":
+ result(true)
default:
result(FlutterMethodNotImplemented)
}
diff --git a/local_plugins/user_portrait/pubspec.yaml b/local_plugins/user_portrait/pubspec.yaml
index 1e3f104cc..862da4ec5 100644
--- a/local_plugins/user_portrait/pubspec.yaml
+++ b/local_plugins/user_portrait/pubspec.yaml
@@ -17,21 +17,8 @@ dev_dependencies:
sdk: flutter
flutter_lints: ^5.0.0
-# For information on the generic Dart part of this file, see the
-# following page: https://dart.dev/tools/pub/pubspec
-# The following section is specific to Flutter packages.
flutter:
- # This section identifies this Flutter project as a plugin project.
- # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
- # which should be registered in the plugin registry. This is required for
- # using method channels.
- # The Android 'package' specifies package in which the registered class is.
- # This is required for using method channels on Android.
- # The 'ffiPlugin' specifies that native code should be built and bundled.
- # This is required for using `dart:ffi`.
- # All these are used by the tooling to maintain consistency when
- # adding or updating assets for this project.
plugin:
platforms:
android:
@@ -39,34 +26,5 @@ flutter:
pluginClass: UserPortraitPlugin
ios:
pluginClass: UserPortraitPlugin
-
- # To add assets to your plugin package, add an assets section, like this:
- # assets:
- # - images/a_dot_burr.jpeg
- # - images/a_dot_ham.jpeg
- #
- # For details regarding assets in packages, see
- # https://flutter.dev/to/asset-from-package
- #
- # An image asset can refer to one or more resolution-specific "variants", see
- # https://flutter.dev/to/resolution-aware-images
-
- # To add custom fonts to your plugin package, add a fonts section here,
- # in this "flutter" section. Each entry in this list should have a
- # "family" key with the font family name, and a "fonts" key with a
- # list giving the asset and other descriptors for the font. For
- # example:
- # fonts:
- # - family: Schyler
- # fonts:
- # - asset: fonts/Schyler-Regular.ttf
- # - asset: fonts/Schyler-Italic.ttf
- # style: italic
- # - family: Trajan Pro
- # fonts:
- # - asset: fonts/TrajanPro.ttf
- # - asset: fonts/TrajanPro_Bold.ttf
- # weight: 700
- #
- # For details regarding fonts in packages, see
- # https://flutter.dev/to/font-from-package
+ swiftPackage:
+ path: ios/user_portrait
diff --git a/local_plugins/user_portrait/test/user_portrait_method_channel_test.dart b/local_plugins/user_portrait/test/user_portrait_method_channel_test.dart
deleted file mode 100644
index 7a2dd7e32..000000000
--- a/local_plugins/user_portrait/test/user_portrait_method_channel_test.dart
+++ /dev/null
@@ -1,27 +0,0 @@
-import 'package:flutter/services.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:user_portrait/user_portrait_method_channel.dart';
-
-void main() {
- TestWidgetsFlutterBinding.ensureInitialized();
-
- MethodChannelUserPortrait platform = MethodChannelUserPortrait();
- const MethodChannel channel = MethodChannel('user_portrait');
-
- setUp(() {
- TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
- channel,
- (MethodCall methodCall) async {
- return '42';
- },
- );
- });
-
- tearDown(() {
- TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null);
- });
-
- test('getPlatformVersion', () async {
- expect(await platform.getPlatformVersion(), '42');
- });
-}
diff --git a/local_plugins/user_portrait/test/user_portrait_test.dart b/local_plugins/user_portrait/test/user_portrait_test.dart
deleted file mode 100644
index 02c2ecd8a..000000000
--- a/local_plugins/user_portrait/test/user_portrait_test.dart
+++ /dev/null
@@ -1,29 +0,0 @@
-import 'package:flutter_test/flutter_test.dart';
-import 'package:user_portrait/user_portrait.dart';
-import 'package:user_portrait/user_portrait_platform_interface.dart';
-import 'package:user_portrait/user_portrait_method_channel.dart';
-import 'package:plugin_platform_interface/plugin_platform_interface.dart';
-
-class MockUserPortraitPlatform
- with MockPlatformInterfaceMixin
- implements UserPortraitPlatform {
-
- @override
- Future getPlatformVersion() => Future.value('42');
-}
-
-void main() {
- final UserPortraitPlatform initialPlatform = UserPortraitPlatform.instance;
-
- test('$MethodChannelUserPortrait is the default instance', () {
- expect(initialPlatform, isInstanceOf());
- });
-
- test('getPlatformVersion', () async {
- UserPortrait userPortraitPlugin = UserPortrait();
- MockUserPortraitPlatform fakePlatform = MockUserPortraitPlatform();
- UserPortraitPlatform.instance = fakePlatform;
-
- expect(await userPortraitPlugin.getPlatformVersion(), '42');
- });
-}
diff --git a/pubspec.yaml b/pubspec.yaml
index 2d8ec5299..a6c743df9 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -45,7 +45,6 @@ dependencies:
haptic_feedback: ^0.5.1+1
tencentcloud_cos_sdk_plugin: ^1.2.3
photo_view: ^0.15.0
-
window_size:
git:
url: https://github.com/google/flutter-desktop-embedding.git