|
|
|
@ -1,6 +1,8 @@ |
|
|
|
import Foundation |
|
|
|
import CoreBluetooth |
|
|
|
import os.log |
|
|
|
import ObjectiveC // 添加ObjC运行时支持 |
|
|
|
import AVFoundation |
|
|
|
|
|
|
|
// 条件编译只导入在iOS 18+上可用的框架 |
|
|
|
#if canImport(AccessorySetupKit) && swift(>=5.9) |
|
|
|
@ -21,23 +23,51 @@ class BleAccessorySetup: NSObject { |
|
|
|
@available(iOS 18.0, *) |
|
|
|
class AccessorySessionWrapper { |
|
|
|
var session: ASAccessorySession |
|
|
|
var isActivated = false |
|
|
|
|
|
|
|
init() throws { |
|
|
|
do { |
|
|
|
os_log("尝试初始化ASAccessorySession...", log: BleAccessorySetup.shared.logger, type: .debug) |
|
|
|
// 根据WWDC24示例,使用无参初始化 |
|
|
|
self.session = try ASAccessorySession() |
|
|
|
os_log("ASAccessorySession初始化成功", log: BleAccessorySetup.shared.logger, type: .info) |
|
|
|
} catch { |
|
|
|
print("ASAccessorySession 初始化错误: \(error.localizedDescription)") |
|
|
|
os_log("ASAccessorySession初始化错误: %{public}@", log: BleAccessorySetup.shared.logger, type: .error, error.localizedDescription) |
|
|
|
|
|
|
|
// 检查Info.plist配置 |
|
|
|
if let plistSupports = Bundle.main.object(forInfoDictionaryKey: "NSAccessorySetupKitSupports") { |
|
|
|
os_log("NSAccessorySetupKitSupports: %{public}@", log: BleAccessorySetup.shared.logger, type: .debug, String(describing: plistSupports)) |
|
|
|
} else { |
|
|
|
os_log("NSAccessorySetupKitSupports未配置", log: BleAccessorySetup.shared.logger, type: .error) |
|
|
|
} |
|
|
|
|
|
|
|
if let services = Bundle.main.object(forInfoDictionaryKey: "NSAccessorySetupBluetoothServices") { |
|
|
|
os_log("NSAccessorySetupBluetoothServices: %{public}@", log: BleAccessorySetup.shared.logger, type: .debug, String(describing: services)) |
|
|
|
} else { |
|
|
|
os_log("NSAccessorySetupBluetoothServices未配置", log: BleAccessorySetup.shared.logger, type: .error) |
|
|
|
} |
|
|
|
|
|
|
|
if let companyIds = Bundle.main.object(forInfoDictionaryKey: "NSAccessorySetupBluetoothCompanyIdentifiers") { |
|
|
|
os_log("NSAccessorySetupBluetoothCompanyIdentifiers: %{public}@", log: BleAccessorySetup.shared.logger, type: .debug, String(describing: companyIds)) |
|
|
|
} else { |
|
|
|
os_log("NSAccessorySetupBluetoothCompanyIdentifiers未配置", log: BleAccessorySetup.shared.logger, type: .error) |
|
|
|
} |
|
|
|
|
|
|
|
throw error |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
var accessorySessionWrapper: Any? |
|
|
|
// 添加初始化完成回调 |
|
|
|
private var onSessionActivated: (() -> Void)? |
|
|
|
|
|
|
|
typealias DeviceDiscoveredCallback = (UUID) -> Void |
|
|
|
private var onDeviceDiscovered: DeviceDiscoveredCallback? |
|
|
|
#endif |
|
|
|
|
|
|
|
private var isInitializing = false |
|
|
|
|
|
|
|
private override init() { |
|
|
|
super.init() |
|
|
|
} |
|
|
|
@ -45,6 +75,7 @@ class BleAccessorySetup: NSObject { |
|
|
|
// 设置BLE服务实例 |
|
|
|
func setBleService(_ service: Any) { |
|
|
|
self.bleService = service |
|
|
|
os_log("BLE服务实例已设置", log: logger, type: .info) |
|
|
|
} |
|
|
|
|
|
|
|
// 设置设备发现回调 |
|
|
|
@ -52,6 +83,7 @@ class BleAccessorySetup: NSObject { |
|
|
|
#if canImport(AccessorySetupKit) && swift(>=5.9) |
|
|
|
if #available(iOS 18.0, *) { |
|
|
|
self.onDeviceDiscovered = callback |
|
|
|
os_log("设备发现回调已设置", log: logger, type: .debug) |
|
|
|
} |
|
|
|
#endif |
|
|
|
} |
|
|
|
@ -60,29 +92,77 @@ class BleAccessorySetup: NSObject { |
|
|
|
func initializeDiscoverySession() { |
|
|
|
#if canImport(AccessorySetupKit) && swift(>=5.9) |
|
|
|
if #available(iOS 18.0, *) { |
|
|
|
os_log("初始化iOS 18 AccessorySetupKit会话", log: logger, type: .info) |
|
|
|
// 避免重复初始化 |
|
|
|
if isInitializing { |
|
|
|
os_log("初始化正在进行中,请等待...", log: logger, type: .debug) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// 检查Info.plist配置 |
|
|
|
let plistCheck = Bundle.main.object(forInfoDictionaryKey: "NSAccessorySetupKitSupports") |
|
|
|
os_log("Info.plist NSAccessorySetupKitSupports: %{public}@", log: logger, type: .info, String(describing: plistCheck)) |
|
|
|
// 如果会话已经存在且已激活,无需重新初始化 |
|
|
|
if let wrapper = accessorySessionWrapper as? AccessorySessionWrapper, wrapper.isActivated { |
|
|
|
os_log("会话已经初始化并激活", log: logger, type: .info) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
isInitializing = true |
|
|
|
os_log("初始化iOS 18 AccessorySetupKit会话...", log: logger, type: .debug) |
|
|
|
|
|
|
|
// 检查Info.plist配置并详细打印 |
|
|
|
if let plistCheck = Bundle.main.object(forInfoDictionaryKey: "NSAccessorySetupKitSupports") { |
|
|
|
os_log("NSAccessorySetupKitSupports配置: %{public}@", log: logger, type: .debug, String(describing: plistCheck)) |
|
|
|
} else { |
|
|
|
os_log("NSAccessorySetupKitSupports未配置", log: logger, type: .error) |
|
|
|
} |
|
|
|
|
|
|
|
if let services = Bundle.main.object(forInfoDictionaryKey: "NSAccessorySetupBluetoothServices") { |
|
|
|
os_log("NSAccessorySetupBluetoothServices配置: %{public}@", log: logger, type: .debug, String(describing: services)) |
|
|
|
} else { |
|
|
|
os_log("NSAccessorySetupBluetoothServices未配置", log: logger, type: .error) |
|
|
|
} |
|
|
|
|
|
|
|
if let companyIds = Bundle.main.object(forInfoDictionaryKey: "NSAccessorySetupBluetoothCompanyIdentifiers") { |
|
|
|
os_log("NSAccessorySetupBluetoothCompanyIdentifiers配置: %{public}@", log: logger, type: .debug, String(describing: companyIds)) |
|
|
|
} else { |
|
|
|
os_log("NSAccessorySetupBluetoothCompanyIdentifiers未配置", log: logger, type: .error) |
|
|
|
} |
|
|
|
|
|
|
|
// 创建会话 (使用try-catch) |
|
|
|
do { |
|
|
|
os_log("尝试创建AccessorySessionWrapper...", log: logger, type: .debug) |
|
|
|
accessorySessionWrapper = try AccessorySessionWrapper() |
|
|
|
os_log("AccessorySessionWrapper创建成功", log: logger, type: .info) |
|
|
|
|
|
|
|
// 激活会话并设置事件处理 |
|
|
|
if let wrapper = accessorySessionWrapper as? AccessorySessionWrapper { |
|
|
|
os_log("尝试激活ASAccessorySession...", log: logger, type: .debug) |
|
|
|
wrapper.session.activate(on: .main) { [weak self] event in |
|
|
|
guard let self = self else { return } |
|
|
|
guard let self = self else { |
|
|
|
os_log("self已被释放,无法处理事件", log: BleAccessorySetup.shared.logger, type: .error) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
os_log("收到ASAccessorySession事件: %{public}d", log: self.logger, type: .debug, event.eventType.rawValue) |
|
|
|
|
|
|
|
switch event.eventType { |
|
|
|
case .activated: |
|
|
|
os_log("AccessorySetupKit会话已激活", log: self.logger, type: .info) |
|
|
|
|
|
|
|
// 标记会话已激活 |
|
|
|
if let wrapper = self.accessorySessionWrapper as? AccessorySessionWrapper { |
|
|
|
wrapper.isActivated = true |
|
|
|
// 触发激活回调 |
|
|
|
DispatchQueue.main.async { |
|
|
|
self.onSessionActivated?() |
|
|
|
self.onSessionActivated = nil // 防止重复调用 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
case .accessoryAdded: |
|
|
|
if let accessory = event.accessory, let uuid = accessory.bluetoothIdentifier { |
|
|
|
os_log("发现新设备: %{public}@", log: self.logger, type: .info, accessory.displayName) |
|
|
|
self.onDeviceDiscovered?(uuid) |
|
|
|
} else { |
|
|
|
os_log("设备添加事件但缺少设备信息或UUID", log: self.logger, type: .debug) |
|
|
|
} |
|
|
|
|
|
|
|
case .accessoryRemoved: |
|
|
|
@ -100,6 +180,8 @@ class BleAccessorySetup: NSObject { |
|
|
|
case .pickerSetupFailed: |
|
|
|
if let error = event.error { |
|
|
|
os_log("会话出错: %{public}@", log: self.logger, type: .error, error.localizedDescription) |
|
|
|
} else { |
|
|
|
os_log("会话出错但无错误详情", log: self.logger, type: .debug) |
|
|
|
} |
|
|
|
|
|
|
|
default: |
|
|
|
@ -108,76 +190,239 @@ class BleAccessorySetup: NSObject { |
|
|
|
} |
|
|
|
|
|
|
|
// 检查现有授权设备 |
|
|
|
if !wrapper.session.accessories.isEmpty { |
|
|
|
os_log("发现%{public}d个已授权设备", log: logger, type: .info, wrapper.session.accessories.count) |
|
|
|
let accessoriesCount = wrapper.session.accessories.count |
|
|
|
os_log("发现%{public}d个已授权设备", log: logger, type: .info, accessoriesCount) |
|
|
|
|
|
|
|
if accessoriesCount > 0 { |
|
|
|
// 如果有现有设备,触发回调 |
|
|
|
for accessory in wrapper.session.accessories { |
|
|
|
if let uuid = accessory.bluetoothIdentifier { |
|
|
|
os_log("处理已授权设备: %{public}@", log: logger, type: .info, accessory.displayName) |
|
|
|
self.onDeviceDiscovered?(uuid) |
|
|
|
} else { |
|
|
|
os_log("已授权设备缺少UUID: %{public}@", log: logger, type: .debug, accessory.displayName) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
os_log("accessorySessionWrapper类型转换失败", log: logger, type: .error) |
|
|
|
} |
|
|
|
} catch { |
|
|
|
os_log("AccessorySetupKit初始化错误: %{public}@", log: logger, type: .error, error.localizedDescription) |
|
|
|
os_log("致命错误:AccessorySetupKit初始化失败: %{public}@", log: logger, type: .error, error.localizedDescription) |
|
|
|
|
|
|
|
// 重置初始化状态 |
|
|
|
isInitializing = false |
|
|
|
|
|
|
|
// 释放失败的会话 |
|
|
|
accessorySessionWrapper = nil |
|
|
|
|
|
|
|
// 尝试通知可能正在等待的回调 |
|
|
|
DispatchQueue.main.async { |
|
|
|
self.onSessionActivated?() |
|
|
|
self.onSessionActivated = nil |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
os_log("iOS 18 AccessorySetupKit不可用", log: logger, type: .info) |
|
|
|
os_log("当前iOS版本不支持AccessorySetupKit", log: logger, type: .info) |
|
|
|
|
|
|
|
// 即使在不支持的系统上,也要调用回调 |
|
|
|
DispatchQueue.main.async { |
|
|
|
self.onSessionActivated?() |
|
|
|
self.onSessionActivated = nil |
|
|
|
} |
|
|
|
} |
|
|
|
#else |
|
|
|
os_log("iOS 18 AccessorySetupKit不可用", log: logger, type: .info) |
|
|
|
os_log("构建环境不支持AccessorySetupKit", log: logger, type: .info) |
|
|
|
|
|
|
|
// 即使在不支持的环境中,也要调用回调 |
|
|
|
DispatchQueue.main.async { |
|
|
|
self.onSessionActivated?() |
|
|
|
self.onSessionActivated = nil |
|
|
|
} |
|
|
|
#endif |
|
|
|
|
|
|
|
// 完成初始化过程 |
|
|
|
isInitializing = false |
|
|
|
} |
|
|
|
|
|
|
|
// 初始化会话并在完成后执行回调 |
|
|
|
func initializeSessionAndRun(completion: @escaping () -> Void) { |
|
|
|
#if canImport(AccessorySetupKit) && swift(>=5.9) |
|
|
|
if #available(iOS 18.0, *) { |
|
|
|
os_log("安全初始化会话...", log: logger, type: .debug) |
|
|
|
|
|
|
|
// 如果会话已存在且已激活 |
|
|
|
if let wrapper = accessorySessionWrapper as? AccessorySessionWrapper, wrapper.isActivated { |
|
|
|
os_log("会话已初始化并激活,直接执行回调", log: logger, type: .info) |
|
|
|
completion() |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// 如果会话存在但未激活,设置回调 |
|
|
|
if let wrapper = accessorySessionWrapper as? AccessorySessionWrapper, !wrapper.isActivated { |
|
|
|
os_log("会话已创建但未激活,等待激活...", log: logger, type: .debug) |
|
|
|
self.onSessionActivated = completion |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// 需要从头创建会话 |
|
|
|
os_log("需要创建新会话...", log: logger, type: .debug) |
|
|
|
self.onSessionActivated = completion |
|
|
|
initializeDiscoverySession() |
|
|
|
} else { |
|
|
|
// 不支持iOS 18,直接执行回调 |
|
|
|
completion() |
|
|
|
} |
|
|
|
#else |
|
|
|
// 不支持AccessorySetupKit,直接执行回调 |
|
|
|
completion() |
|
|
|
#endif |
|
|
|
} |
|
|
|
|
|
|
|
// 重置会话状态 |
|
|
|
func resetSession() { |
|
|
|
#if canImport(AccessorySetupKit) && swift(>=5.9) |
|
|
|
if #available(iOS 18.0, *) { |
|
|
|
os_log("重置AccessorySetupKit会话...", log: logger, type: .debug) |
|
|
|
|
|
|
|
// 取消可能挂起的回调 |
|
|
|
onSessionActivated = nil |
|
|
|
|
|
|
|
// 清理现有会话 |
|
|
|
if let wrapper = accessorySessionWrapper as? AccessorySessionWrapper { |
|
|
|
os_log("清理现有会话", log: logger, type: .debug) |
|
|
|
wrapper.session.invalidate() |
|
|
|
} |
|
|
|
|
|
|
|
// 重置状态 |
|
|
|
accessorySessionWrapper = nil |
|
|
|
isInitializing = false |
|
|
|
|
|
|
|
os_log("会话已重置", log: logger, type: .info) |
|
|
|
} |
|
|
|
#endif |
|
|
|
} |
|
|
|
|
|
|
|
// 安全显示设备选择器 |
|
|
|
func safeShowDevicePicker() { |
|
|
|
// 重置会话状态,确保干净的重新初始化 |
|
|
|
resetSession() |
|
|
|
|
|
|
|
initializeSessionAndRun { [weak self] in |
|
|
|
guard let self = self else { return } |
|
|
|
self.showDevicePicker() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 显示设备选择器 |
|
|
|
func showDevicePicker() { |
|
|
|
#if canImport(AccessorySetupKit) && swift(>=5.9) |
|
|
|
if #available(iOS 18.0, *) { |
|
|
|
if let wrapper = accessorySessionWrapper as? AccessorySessionWrapper { |
|
|
|
os_log("显示iOS 18设备选择器", log: logger, type: .info) |
|
|
|
os_log("尝试显示设备选择器...", log: logger, type: .debug) |
|
|
|
|
|
|
|
// 创建描述符和选择器项,这些API也需要iOS 18 |
|
|
|
@available(iOS 18.0, *) |
|
|
|
func createAndShowPicker() { |
|
|
|
// 根据WWDC24视频,正确的类名是ASDiscoveryDescriptor |
|
|
|
let descriptor = ASDiscoveryDescriptor() |
|
|
|
// 检查会话是否已初始化 |
|
|
|
if accessorySessionWrapper == nil { |
|
|
|
os_log("会话未初始化,尝试自动初始化...", log: logger, type: .debug) |
|
|
|
safeShowDevicePicker() |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// 设置过滤条件 |
|
|
|
// 使用UInt16格式的公司标识符 |
|
|
|
descriptor.bluetoothCompanyIdentifier = ASBluetoothCompanyIdentifier(UInt16(0xABEF)) // BleConst.MANUFACTURER_ID |
|
|
|
descriptor.bluetoothServiceUUID = CBUUID(string: "0000abc0-0000-1111-2222-123456789abc") // BleConst.PRIMARY_SERVICE_UUID |
|
|
|
if let wrapper = accessorySessionWrapper as? AccessorySessionWrapper { |
|
|
|
// 检查会话是否已激活 |
|
|
|
if !wrapper.isActivated { |
|
|
|
os_log("会话尚未激活,等待激活后再显示...", log: logger, type: .debug) |
|
|
|
onSessionActivated = { [weak self] in |
|
|
|
self?.showDevicePicker() |
|
|
|
} |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
os_log("找到会话包装器,准备显示选择器", log: logger, type: .info) |
|
|
|
|
|
|
|
// 创建默认图像以防系统图标不可用 |
|
|
|
let headphonesImage = UIImage(systemName: "headphones") ?? UIImage() |
|
|
|
// 根据WWDC24示例代码创建设备描述符和显示项 |
|
|
|
os_log("创建设备描述符...", log: logger, type: .debug) |
|
|
|
let descriptor = ASDiscoveryDescriptor() |
|
|
|
|
|
|
|
// 根据WWDC24视频,正确的类名是ASPickerDisplayItem |
|
|
|
// 设置蓝牙服务UUID |
|
|
|
// let serviceUUID = "0000abc0-0000-1111-2222-123456789abc" // BleConst.PRIMARY_SERVICE_UUID |
|
|
|
// os_log("📝 设置服务UUID: %{public}@", log: logger, type: .debug, serviceUUID) |
|
|
|
// descriptor.bluetoothServiceUUID = CBUUID(string: serviceUUID) |
|
|
|
|
|
|
|
// 设置制造商ID |
|
|
|
os_log("设置公司ID: 0xABEF", log: logger, type: .debug) |
|
|
|
// descriptor.setValue(UInt16(0xABEF), forKey: "bluetoothCompanyIdentifier") |
|
|
|
descriptor.bluetoothCompanyIdentifier = ASBluetoothCompanyIdentifier(0xEFAB) |
|
|
|
// 创建默认图像 |
|
|
|
os_log("获取设备图标...", log: logger, type: .debug) |
|
|
|
let deviceImage = UIImage(systemName: "headphones") ?? UIImage() |
|
|
|
|
|
|
|
// 创建显示项 |
|
|
|
os_log("创建选择器显示项...", log: logger, type: .debug) |
|
|
|
let displayItem = ASPickerDisplayItem( |
|
|
|
name: "语音助理耳机", |
|
|
|
productImage: headphonesImage, |
|
|
|
productImage: deviceImage, |
|
|
|
descriptor: descriptor |
|
|
|
) |
|
|
|
|
|
|
|
// 显示选择器 |
|
|
|
wrapper.session.showPicker(for: [displayItem]) { error in |
|
|
|
// 确保在主线程上调用 |
|
|
|
DispatchQueue.main.async { [weak self] in |
|
|
|
guard let self = self else { return } |
|
|
|
|
|
|
|
os_log("调用showPicker方法...", log: self.logger, type: .debug) |
|
|
|
|
|
|
|
// 再次检查Info.plist配置 |
|
|
|
let bundle = Bundle.main |
|
|
|
os_log("Info.plist检查:", log: self.logger, type: .debug) |
|
|
|
if let supports = bundle.object(forInfoDictionaryKey: "NSAccessorySetupKitSupports") as? [String] { |
|
|
|
os_log("NSAccessorySetupKitSupports: %{public}@", log: self.logger, type: .debug, supports.description) |
|
|
|
} |
|
|
|
if let services = bundle.object(forInfoDictionaryKey: "NSAccessorySetupBluetoothServices") as? [String] { |
|
|
|
os_log("NSAccessorySetupBluetoothServices: %{public}@", log: self.logger, type: .debug, services.description) |
|
|
|
} |
|
|
|
if let companyIds = bundle.object(forInfoDictionaryKey: "NSAccessorySetupBluetoothCompanyIdentifiers") as? [String] { |
|
|
|
os_log("NSAccessorySetupBluetoothCompanyIdentifiers: %{public}@", log: self.logger, type: .debug, companyIds.description) |
|
|
|
} |
|
|
|
|
|
|
|
os_log("iOS 18环境下不检查蓝牙状态,避免创建CBCentralManager", log: self.logger, type: .debug) |
|
|
|
|
|
|
|
// 使用标准方法显示选择器 |
|
|
|
wrapper.session.showPicker(for: [displayItem]) { [weak self] error in |
|
|
|
guard let self = self else { return } |
|
|
|
|
|
|
|
if let error = error { |
|
|
|
// 修复OSLog的错误用法 |
|
|
|
os_log("设备选择器显示错误: %{public}@", log: self.logger, type: .error, error.localizedDescription) |
|
|
|
|
|
|
|
// 错误码解析 |
|
|
|
if let nsError = error as? NSError { |
|
|
|
os_log("错误域: %{public}@, 错误码: %{public}d", log: self.logger, type: .error, nsError.domain, nsError.code) |
|
|
|
|
|
|
|
// 解析常见错误 |
|
|
|
switch nsError.code { |
|
|
|
case 550: |
|
|
|
os_log("错误550: CBCentralManager冲突 - 进程中已存在使用全局权限的CBCentralManager", log: self.logger, type: .error) |
|
|
|
case 500...599: |
|
|
|
os_log("错误%{public}d: 可能是AccessorySetupKit内部错误", log: self.logger, type: .error, nsError.code) |
|
|
|
default: |
|
|
|
os_log("未知错误码", log: self.logger, type: .error) |
|
|
|
} |
|
|
|
|
|
|
|
if let info = nsError.userInfo as? [String: Any] { |
|
|
|
os_log("错误信息: %{public}@", log: self.logger, type: .error, info.description) |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
os_log("设备选择器显示已触发", log: self.logger, type: .info) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 调用内部函数 |
|
|
|
createAndShowPicker() |
|
|
|
} else { |
|
|
|
os_log("会话未初始化", log: logger, type: .error) |
|
|
|
os_log("会话未初始化,无法显示设备选择器", log: logger, type: .error) |
|
|
|
} |
|
|
|
} else { |
|
|
|
os_log("iOS 18 AccessorySetupKit不可用", log: logger, type: .info) |
|
|
|
os_log("当前iOS版本不支持AccessorySetupKit", log: logger, type: .info) |
|
|
|
} |
|
|
|
#else |
|
|
|
os_log("iOS 18 AccessorySetupKit不可用", log: logger, type: .info) |
|
|
|
os_log("构建环境不支持AccessorySetupKit", log: logger, type: .info) |
|
|
|
#endif |
|
|
|
} |
|
|
|
|
|
|
|
@ -185,24 +430,133 @@ class BleAccessorySetup: NSObject { |
|
|
|
func handleDiscoveredAccessory(uuid: UUID) { |
|
|
|
#if canImport(AccessorySetupKit) && swift(>=5.9) |
|
|
|
if #available(iOS 18.0, *) { |
|
|
|
os_log("处理UUID为%{public}@的发现设备", log: logger, type: .debug, uuid.uuidString) |
|
|
|
|
|
|
|
guard let service = bleService as? BleService else { |
|
|
|
os_log("BLE服务未设置", log: logger, type: .error) |
|
|
|
os_log("BLE服务未设置或类型转换失败", log: logger, type: .error) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// 获取中央管理器 |
|
|
|
let centralManager = service.getCentralManager() |
|
|
|
os_log("按照iOS 18规范,直接使用UUID调用特定连接方法", log: logger, type: .debug) |
|
|
|
service.connectToAccessory(uuid: uuid) |
|
|
|
} |
|
|
|
#endif |
|
|
|
} |
|
|
|
|
|
|
|
// 通过UUID检索外设 |
|
|
|
let peripherals = centralManager.retrievePeripherals(withIdentifiers: [uuid]) |
|
|
|
// 从指定的视图控制器显示设备选择器 |
|
|
|
func showDevicePickerFromViewController(_ viewController: UIViewController) { |
|
|
|
#if canImport(AccessorySetupKit) && swift(>=5.9) |
|
|
|
if #available(iOS 18.0, *) { |
|
|
|
os_log("从视图控制器显示设备选择器...", log: logger, type: .debug) |
|
|
|
|
|
|
|
if let peripheral = peripherals.first { |
|
|
|
os_log("连接到UUID对应的设备: %{public}@", log: logger, type: .info, peripheral.name ?? uuid.uuidString) |
|
|
|
service.connect(to: peripheral) |
|
|
|
safeShowDevicePickerWithUI(presentingVC: viewController) |
|
|
|
} else { |
|
|
|
os_log("未找到UUID对应的设备: %{public}@", log: logger, type: .error, uuid.uuidString) |
|
|
|
os_log("当前iOS版本不支持AccessorySetupKit", log: logger, type: .info) |
|
|
|
} |
|
|
|
#else |
|
|
|
os_log("构建环境不支持AccessorySetupKit", log: logger, type: .info) |
|
|
|
#endif |
|
|
|
} |
|
|
|
|
|
|
|
// 带UI上下文的安全显示选择器 |
|
|
|
private func safeShowDevicePickerWithUI(presentingVC: UIViewController) { |
|
|
|
#if canImport(AccessorySetupKit) && swift(>=5.9) |
|
|
|
if #available(iOS 18.0, *) { |
|
|
|
resetSession() |
|
|
|
|
|
|
|
initializeSessionAndRun { [weak self, weak presentingVC] in |
|
|
|
guard let self = self, let presentingVC = presentingVC else { return } |
|
|
|
|
|
|
|
if let wrapper = self.accessorySessionWrapper as? AccessorySessionWrapper { |
|
|
|
os_log("会话已激活,准备显示选择器", log: self.logger, type: .info) |
|
|
|
|
|
|
|
// 从主线程创建和显示 |
|
|
|
DispatchQueue.main.async { [weak self] in |
|
|
|
guard let self = self else { return } |
|
|
|
|
|
|
|
os_log("创建设备描述符...", log: self.logger, type: .debug) |
|
|
|
let descriptor = ASDiscoveryDescriptor() |
|
|
|
|
|
|
|
// 设置服务UUID和制造商ID |
|
|
|
// let serviceUUID = "0000abc0-0000-1111-2222-123456789abc" |
|
|
|
// os_log("设置服务UUID: %{public}@", log: self.logger, type: .debug, serviceUUID) |
|
|
|
// descriptor.bluetoothServiceUUID = CBUUID(string: serviceUUID) |
|
|
|
|
|
|
|
os_log("设置公司ID: 0xABEF", log: self.logger, type: .debug) |
|
|
|
// descriptor.setValue(UInt16(0xABEF), forKey: "bluetoothCompanyIdentifier") |
|
|
|
descriptor.bluetoothCompanyIdentifier = ASBluetoothCompanyIdentifier(0xEFAB) |
|
|
|
|
|
|
|
// 创建默认图像 |
|
|
|
let deviceImage = UIImage(systemName: "headphones") ?? UIImage() |
|
|
|
|
|
|
|
// 创建显示项 |
|
|
|
let displayItem = ASPickerDisplayItem( |
|
|
|
name: "语音助理耳机", |
|
|
|
productImage: deviceImage, |
|
|
|
descriptor: descriptor |
|
|
|
) |
|
|
|
|
|
|
|
// 检查权限,但不创建CBCentralManager |
|
|
|
os_log("iOS 18环境下不检查蓝牙状态,避免创建CBCentralManager", log: self.logger, type: .debug) |
|
|
|
|
|
|
|
// 显示选择器 |
|
|
|
wrapper.session.showPicker(for: [displayItem]) { [weak self] error in |
|
|
|
guard let self = self else { return } |
|
|
|
|
|
|
|
if let error = error { |
|
|
|
os_log("设备选择器显示错误: %{public}@", log: self.logger, type: .error, error.localizedDescription) |
|
|
|
|
|
|
|
// 错误码解析 |
|
|
|
if let nsError = error as? NSError { |
|
|
|
os_log("错误域: %{public}@, 错误码: %{public}d", log: self.logger, type: .error, nsError.domain, nsError.code) |
|
|
|
|
|
|
|
// 解析常见错误 |
|
|
|
switch nsError.code { |
|
|
|
case 550: |
|
|
|
os_log("错误550: CBCentralManager冲突 - 进程中已存在使用全局权限的CBCentralManager", log: self.logger, type: .error) |
|
|
|
case 500...599: |
|
|
|
os_log("错误%{public}d: 可能是AccessorySetupKit内部错误", log: self.logger, type: .error, nsError.code) |
|
|
|
default: |
|
|
|
os_log("未知错误码", log: self.logger, type: .error) |
|
|
|
} |
|
|
|
|
|
|
|
if let info = nsError.userInfo as? [String: Any] { |
|
|
|
os_log("错误信息: %{public}@", log: self.logger, type: .error, info.description) |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
os_log("设备选择器显示已触发", log: self.logger, type: .info) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
#endif |
|
|
|
} |
|
|
|
|
|
|
|
// 检查蓝牙是否可用,不使用CBCentralManager |
|
|
|
func isBluetoothAvailable() -> Bool { |
|
|
|
#if canImport(AccessorySetupKit) && swift(>=5.9) |
|
|
|
if #available(iOS 18.0, *) { |
|
|
|
// iOS 18+环境下不进行蓝牙状态检查 |
|
|
|
os_log("iOS 18环境下,跳过蓝牙状态检查", log: logger, type: .debug) |
|
|
|
return true |
|
|
|
} |
|
|
|
#endif |
|
|
|
|
|
|
|
// 使用AVAudioSession检测是否有蓝牙设备连接 |
|
|
|
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 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 没有检测到蓝牙设备,但不确定蓝牙是否已打开 |
|
|
|
return false |
|
|
|
} |
|
|
|
} |