From 0b4e612b889651a519490e4b39a1961199050df8 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Mon, 14 Jul 2025 11:57:16 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E8=8B=B9=E6=9E=9C=E7=89=88?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Podfile.lock | 4 +- ios/Runner.xcodeproj/project.pbxproj | 4 +- ios/Runner/Info.plist | 25 +- lib/data/services/ble_manager.dart | 10 + .../controllers/pairing_controller.dart | 46 +- .../Sources/ble_service/BleScanManager.swift | 586 ++++++++++++++++++ .../ble_service/SwiftBleServicePlugin.swift | 26 +- .../ble_service/lib/ble_service.dart | 11 + .../ota/ios/Classes/BleManager/JLBleManager.m | 1 + 9 files changed, 689 insertions(+), 24 deletions(-) create mode 100644 local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleScanManager.swift diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d0bbc4669..c9050a312 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -85,7 +85,7 @@ PODS: - SDWebImage/Core (~> 5.17) - spotify_sdk (0.0.1): - Flutter - - tencentcloud_cos_sdk_plugin (1.2.3): + - tencentcloud_cos_sdk_plugin (1.2.4): - Flutter - QCloudCOSXML (= 6.4.7) - WechatOpenSDK-XCFramework (2.0.4) @@ -203,7 +203,7 @@ SPEC CHECKSUMS: SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868 SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 spotify_sdk: a48400bb29f70c4fe251ebfdc9135c37097ac5ca - tencentcloud_cos_sdk_plugin: c3d78b2c3bae42b5a20009dc047f3f1621e6272b + tencentcloud_cos_sdk_plugin: 7bed564dbe72df23e7b5cacb1e51b1a20cb1639c WechatOpenSDK-XCFramework: 36fb2bea0754266c17184adf4963d7e6ff98b69f wifi_iot: f645260a2be8608517b2a9bf4c39b98e97003acc diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 4df97502d..02daa62e1 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -252,7 +252,7 @@ ); mainGroup = 97C146E51CF9000F007C117D; packageReferences = ( - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, D013B7972DFA83E400B2B69B /* XCRemoteSwiftPackageReference "flutterfire" */, ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; @@ -793,7 +793,7 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { isa = XCLocalSwiftPackageReference; relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; }; diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index bc3f4a793..b2cdd80bb 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -85,10 +85,6 @@ 语音助理耳机 - NSAccessorySetupKitSupports - - Bluetooth - NSAppTransportSecurity NSAllowsArbitraryLoads @@ -117,10 +113,25 @@ + NSBluetoothAlwaysUsageDescription 需要蓝牙权限用于连接和管理音频设备 NSBluetoothPeripheralUsageDescription 需要蓝牙权限用于连接和管理音频设备 + + + UIBackgroundModes + + bluetooth-central + audio + location + + + + NSAccessorySetupKitSupports + + Bluetooth + NSCalendarsUsageDescription 需要日历权限用于安排和管理您的音频会议和录音任务 NSCameraUsageDescription @@ -137,12 +148,6 @@ 需要相册权限用于保存录音文件和导入音频文件 UIApplicationSupportsIndirectInputEvents - UIBackgroundModes - - bluetooth-central - audio - location - UILaunchStoryboardName LaunchScreen UIMainStoryboardFile diff --git a/lib/data/services/ble_manager.dart b/lib/data/services/ble_manager.dart index 3cbc033c3..5d131da39 100644 --- a/lib/data/services/ble_manager.dart +++ b/lib/data/services/ble_manager.dart @@ -859,4 +859,14 @@ class BleManager extends GetxService { } return Future.value(Get.find()); } + + /// 请求蓝牙权限 + Future requestBluetoothPermission() async { + try { + return await _bleService.requestBluetoothPermission(); + } catch (e) { + Logger.error('请求蓝牙权限失败: ${e.toString()}'); + return false; + } + } } diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart index 53b1690fb..bed5baf39 100644 --- a/lib/modules/pairing/controllers/pairing_controller.dart +++ b/lib/modules/pairing/controllers/pairing_controller.dart @@ -418,17 +418,45 @@ class PairingController extends GetxController { /// 检查权限 Future _checkPermissions() async { - // Android权限检查 if (Platform.isAndroid) { - // 检查蓝牙相关权限 - final bluetoothScan = await Permission.bluetoothScan.request(); - final bluetoothConnect = await Permission.bluetoothConnect.request(); - final location = await Permission.locationWhenInUse.request(); - - return bluetoothScan.isGranted && - bluetoothConnect.isGranted && - location.isGranted; + // 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; } 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 new file mode 100644 index 000000000..1be2b0c46 --- /dev/null +++ b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/BleScanManager.swift @@ -0,0 +1,586 @@ +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() + } + + // 等待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 + + 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 + } + + checkCount += 1 + let currentAuth = CBManager.authorization + + 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: - 私有方法 + + + + 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 } + + 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 "未知状态" + } + } +} 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 99e8340cc..fb7f92d49 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 @@ -230,7 +230,31 @@ public class SwiftBleServicePlugin: NSObject, FlutterPlugin { 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) ?? "" + // 修复:支持所有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() + result(true) + } + + case "getDeviceInfo": + let arguments = call.arguments as? [String: Any] + let address = (arguments?["address"] as? String) ?? "" + let info = BleScanManager.shared.getDiscoveredDevices() + result(info) + case "requestBluetoothPermission": + BleScanManager.shared.requestBluetoothPermission(); + result(true) default: result(FlutterMethodNotImplemented) } @@ -394,4 +418,4 @@ class BleStatusStreamHandler: NSObject, FlutterStreamHandler { plugin?.statusEventSink = nil return nil } -} +} diff --git a/local_plugins/ble_service/lib/ble_service.dart b/local_plugins/ble_service/lib/ble_service.dart index a33ea1e94..844c9c594 100644 --- a/local_plugins/ble_service/lib/ble_service.dart +++ b/local_plugins/ble_service/lib/ble_service.dart @@ -435,4 +435,15 @@ class BleService { return false; } } + + /// 请求蓝牙权限(仅iOS) + Future requestBluetoothPermission() async { + try { + final result = await _methodChannel.invokeMethod('requestBluetoothPermission'); + return result ?? false; + } catch (e) { + print('请求蓝牙权限失败: $e'); + return false; + } + } } diff --git a/local_plugins/ota/ios/Classes/BleManager/JLBleManager.m b/local_plugins/ota/ios/Classes/BleManager/JLBleManager.m index 1147c552d..cb22ecfa8 100644 --- a/local_plugins/ota/ios/Classes/BleManager/JLBleManager.m +++ b/local_plugins/ota/ios/Classes/BleManager/JLBleManager.m @@ -218,6 +218,7 @@ NSString *FLT_BLE_RCSP_R = @"AE02"; //命令“读”通道 } - (void)addPeripheral:(CBPeripheral*)peripheral Info:(NSString*)macStr { + kJLLog(JLLOG_DEBUG, @"BLE addPeripheral ---> Device %@", peripheral.name); if (self.lastAddress && [macStr isEqualToString:self.lastAddress]) { [self stopScanBLE]; [self connectBLE:peripheral];