You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
344 lines
14 KiB
344 lines
14 KiB
import Foundation
|
|
import CoreBluetooth
|
|
import os.log
|
|
import AVFoundation
|
|
import AccessorySetupKit
|
|
import UIKit
|
|
|
|
class BleAccessorySetup: NSObject {
|
|
// 单例实现
|
|
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 onSessionActivated: (() -> Void)?
|
|
typealias DeviceDiscoveredCallback = (UUID) -> Void
|
|
private var onDeviceDiscovered: DeviceDiscoveredCallback?
|
|
|
|
private override init() {
|
|
super.init()
|
|
}
|
|
|
|
// 设置BLE服务实例
|
|
func setBleService(_ service: Any) {
|
|
self.bleService = service as? BleService
|
|
}
|
|
|
|
// 设置设备发现回调
|
|
func setDeviceDiscoveredCallback(_ callback: @escaping (UUID) -> Void) {
|
|
self.onDeviceDiscovered = callback
|
|
}
|
|
|
|
// 初始化设备发现会话
|
|
func initializeDiscoverySession() {
|
|
os_log("开始初始化设备发现会话", log: logger, type: .info)
|
|
|
|
if isInitializing || isSessionActivated {
|
|
os_log("会话已在初始化或已激活,跳过初始化", log: logger, type: .debug)
|
|
return
|
|
}
|
|
|
|
isInitializing = true
|
|
|
|
do {
|
|
os_log("创建ASAccessorySession", log: logger, type: .debug)
|
|
session = try ASAccessorySession()
|
|
|
|
os_log("激活会话", log: logger, type: .debug)
|
|
session?.activate(on: .main) { [weak self] event in
|
|
guard let self = self else { return }
|
|
|
|
os_log("收到会话事件: %{public}@", log: self.logger, type: .debug, String(describing: event.eventType))
|
|
|
|
switch event.eventType {
|
|
case .activated:
|
|
os_log("会话已激活", log: self.logger, type: .info)
|
|
self.isSessionActivated = true
|
|
DispatchQueue.main.async(execute: DispatchWorkItem(block: {
|
|
self.onSessionActivated?()
|
|
self.onSessionActivated = nil
|
|
}))
|
|
|
|
case .accessoryAdded:
|
|
if let accessory = event.accessory, let uuid = accessory.bluetoothIdentifier {
|
|
os_log("发现设备: %{public}@", log: self.logger, type: .info, uuid.uuidString)
|
|
|
|
// 记录设备详细信息
|
|
let name = "设备_\(uuid.uuidString.prefix(8))"
|
|
os_log("设备名称: %{public}@", log: self.logger, type: .debug, name)
|
|
// 直接获取公司ID无需使用可选链
|
|
let descriptor = accessory.descriptor
|
|
let companyIdentifier = descriptor.bluetoothCompanyIdentifier.rawValue
|
|
os_log("设备公司ID: 0x%{public}x", log: self.logger, type: .debug, companyIdentifier)
|
|
|
|
self.onDeviceDiscovered?(uuid)
|
|
} else {
|
|
os_log("发现设备但缺少UUID信息", log: self.logger, type: .error)
|
|
if let accessory = event.accessory {
|
|
os_log("设备信息: %{public}@", log: self.logger, type: .debug, String(describing: accessory))
|
|
}
|
|
}
|
|
|
|
case .accessoryRemoved:
|
|
os_log("设备已移除", log: self.logger, type: .debug)
|
|
if let accessory = event.accessory, let uuid = accessory.bluetoothIdentifier {
|
|
os_log("移除设备UUID: %{public}@", log: self.logger, type: .debug, uuid.uuidString)
|
|
}
|
|
|
|
case .accessoryChanged:
|
|
os_log("设备已变更", log: self.logger, type: .debug)
|
|
|
|
case .pickerSetupFailed:
|
|
os_log("选择器设置失败: %{public}@", log: self.logger, type: .error, event.error?.localizedDescription ?? "未知错误")
|
|
|
|
default:
|
|
os_log("未处理的事件类型: %{public}@", log: self.logger, type: .debug, String(describing: event.eventType))
|
|
break
|
|
}
|
|
}
|
|
|
|
// 处理现有授权设备
|
|
if let accessories = session?.accessories {
|
|
os_log("检查当前已授权设备:共%d个", log: logger, type: .debug, accessories.count)
|
|
for accessory in accessories {
|
|
if let uuid = accessory.bluetoothIdentifier {
|
|
os_log("处理已授权设备:%@", log: logger, type: .info, uuid.uuidString)
|
|
let name = "设备_\(uuid.uuidString.prefix(8))"
|
|
os_log("已授权设备名称:%@", log: logger, type: .debug, name)
|
|
self.onDeviceDiscovered?(uuid)
|
|
} else {
|
|
os_log("发现已授权设备但无UUID", log: logger, type: .error)
|
|
}
|
|
}
|
|
} else {
|
|
os_log("无已授权设备", log: logger, type: .debug)
|
|
}
|
|
} catch {
|
|
// 出错时重置状态
|
|
os_log("初始化会话失败:%@", log: logger, type: .error, error.localizedDescription)
|
|
isInitializing = false
|
|
session = nil
|
|
|
|
DispatchQueue.main.async(execute: DispatchWorkItem(block: {
|
|
self.onSessionActivated?()
|
|
self.onSessionActivated = nil
|
|
}))
|
|
}
|
|
|
|
isInitializing = false
|
|
os_log("设备发现会话初始化完成", log: logger, type: .debug)
|
|
}
|
|
|
|
// 初始化会话并在完成后执行回调
|
|
func initializeSessionAndRun(completion: @escaping () -> Void) {
|
|
// 如果会话已存在且已激活
|
|
if session != nil && isSessionActivated {
|
|
completion()
|
|
return
|
|
}
|
|
|
|
// 如果会话存在但未激活,设置回调
|
|
if session != nil && !isSessionActivated {
|
|
self.onSessionActivated = completion
|
|
return
|
|
}
|
|
|
|
// 需要从头创建会话
|
|
self.onSessionActivated = completion
|
|
initializeDiscoverySession()
|
|
}
|
|
|
|
// 重置会话状态
|
|
func resetSession() {
|
|
// 取消可能挂起的回调
|
|
onSessionActivated = nil
|
|
|
|
// 清理现有会话
|
|
session?.invalidate()
|
|
|
|
// 重置状态
|
|
session = nil
|
|
isSessionActivated = false
|
|
isInitializing = false
|
|
}
|
|
|
|
// 安全显示设备选择器
|
|
func safeShowDevicePicker() {
|
|
resetSession()
|
|
|
|
initializeSessionAndRun { [weak self] in
|
|
guard let self = self else { return }
|
|
self.showDevicePicker()
|
|
}
|
|
}
|
|
|
|
// 显示设备选择器
|
|
func showDevicePicker() {
|
|
os_log("尝试显示设备选择器", log: logger, type: .info)
|
|
|
|
// 检查会话是否已初始化
|
|
if session == nil {
|
|
os_log("会话未初始化,重新安全初始化", log: logger, type: .debug)
|
|
safeShowDevicePicker()
|
|
return
|
|
}
|
|
|
|
// 检查会话是否已激活
|
|
if !isSessionActivated {
|
|
os_log("会话尚未激活,等待激活后再显示选择器", log: logger, type: .debug)
|
|
onSessionActivated = { [weak self] in
|
|
self?.showDevicePicker()
|
|
}
|
|
return
|
|
}
|
|
|
|
// 创建设备描述符和显示项
|
|
os_log("创建设备描述符", log: logger, type: .debug)
|
|
let descriptor = ASDiscoveryDescriptor()
|
|
descriptor.bluetoothCompanyIdentifier = ASBluetoothCompanyIdentifier(0xEFAB)
|
|
os_log("设置公司标识符:0x%x", log: logger, type: .debug, 0xEFAB)
|
|
|
|
// 检查和记录发现设置
|
|
if let session = self.session {
|
|
os_log("当前会话设置已激活: %{public}@", log: logger, type: .debug,
|
|
String(describing: self.isSessionActivated))
|
|
|
|
let currentAccessories = session.accessories
|
|
os_log("当前会话有%d个已知设备", log: logger, type: .debug, currentAccessories.count)
|
|
}
|
|
|
|
// 创建默认图像
|
|
let deviceImage = UIImage(systemName: "headphones") ?? UIImage()
|
|
|
|
// 创建显示项
|
|
let displayItem = ASPickerDisplayItem(
|
|
name: "语音助理耳机",
|
|
productImage: deviceImage,
|
|
descriptor: descriptor
|
|
)
|
|
os_log("创建显示项:语音助理耳机", log: logger, type: .debug)
|
|
|
|
// 显示选择器
|
|
os_log("即将显示设备选择器", log: logger, type: .debug)
|
|
DispatchQueue.main.async(execute: DispatchWorkItem(block: {
|
|
self.session?.showPicker(for: [displayItem]) { error in
|
|
if let error = error {
|
|
os_log("设备选择器错误: %{public}@", log: self.logger, type: .error,
|
|
error.localizedDescription)
|
|
} else {
|
|
os_log("设备选择器已显示", log: self.logger, type: .debug)
|
|
}
|
|
}
|
|
}))
|
|
}
|
|
|
|
// 处理发现的设备
|
|
func handleDiscoveredAccessory(uuid: UUID) {
|
|
guard let service = bleService else {
|
|
return
|
|
}
|
|
|
|
service.connectToAccessory(uuid: uuid)
|
|
}
|
|
|
|
// 从指定的视图控制器显示设备选择器
|
|
func showDevicePickerFromViewController(_ viewController: UIViewController) {
|
|
safeShowDevicePickerWithUI(presentingVC: viewController)
|
|
}
|
|
|
|
// 带UI上下文的安全显示选择器
|
|
private func safeShowDevicePickerWithUI(presentingVC: UIViewController) {
|
|
os_log("准备从视图控制器显示设备选择器", log: logger, type: .info)
|
|
resetSession()
|
|
|
|
initializeSessionAndRun { [weak self, weak presentingVC] in
|
|
guard let self = self, let presentingVC = presentingVC else {
|
|
os_log("视图控制器或self已被释放", log: self?.logger ?? OSLog.default, type: .error)
|
|
return
|
|
}
|
|
|
|
if self.session != nil && self.isSessionActivated {
|
|
os_log("会话已激活,准备显示设备选择器UI", log: self.logger, type: .debug)
|
|
DispatchQueue.main.async {
|
|
let descriptor = ASDiscoveryDescriptor()
|
|
descriptor.bluetoothCompanyIdentifier = ASBluetoothCompanyIdentifier(0xEFAB)
|
|
os_log("设置公司标识符:0x%x", log: self.logger, type: .debug, 0xEFAB)
|
|
|
|
os_log("描述符配置: %{public}@", log: self.logger, type: .debug,
|
|
String(describing: descriptor))
|
|
|
|
let deviceImage = UIImage(systemName: "headphones") ?? UIImage()
|
|
|
|
let displayItem = ASPickerDisplayItem(
|
|
name: "语音助理耳机",
|
|
productImage: deviceImage,
|
|
descriptor: descriptor
|
|
)
|
|
|
|
os_log("显示设备选择器UI", log: self.logger, type: .debug)
|
|
self.session?.showPicker(for: [displayItem]) { error in
|
|
os_log("设备选择器UI回调", log: self.logger, type: .debug)
|
|
|
|
if let error = error {
|
|
os_log("设备选择器返回错误: %{public}@", log: self.logger, type: .error,
|
|
error.localizedDescription)
|
|
|
|
let nsError = error as NSError
|
|
os_log("错误域: %{public}@, 错误码: %d", log: self.logger, type: .error,
|
|
nsError.domain, nsError.code)
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
os_log("会话未激活或为nil,无法显示设备选择器", log: self.logger, type: .error)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 检查蓝牙是否可用
|
|
func isBluetoothAvailable() -> Bool {
|
|
// 日志输出
|
|
os_log("检查蓝牙可用性", log: logger, type: .debug)
|
|
|
|
// 默认返回true,适用于iOS 18环境
|
|
if bleService == nil {
|
|
os_log("BleService未设置,默认返回true", log: logger, type: .info)
|
|
return true
|
|
}
|
|
|
|
// 尝试类型转换
|
|
guard let _ = bleService as? BleService else {
|
|
os_log("BleService类型转换失败", log: logger, type: .debug)
|
|
return true
|
|
}
|
|
|
|
// 使用AVAudioSession检查蓝牙连接
|
|
do {
|
|
let audioSession = AVAudioSession.sharedInstance()
|
|
let outputs = audioSession.currentRoute.outputs
|
|
|
|
// 检查蓝牙音频输出设备
|
|
for output in outputs {
|
|
if output.portType == .bluetoothA2DP ||
|
|
output.portType == .bluetoothHFP ||
|
|
output.portType == .bluetoothLE {
|
|
os_log("发现蓝牙音频输出设备", log: logger, type: .debug)
|
|
return true
|
|
}
|
|
}
|
|
|
|
// 未找到蓝牙设备
|
|
os_log("未找到蓝牙音频设备", log: logger, type: .debug)
|
|
} catch {
|
|
os_log("检查音频会话出错: %{public}@", log: logger, type: .error, error.localizedDescription)
|
|
}
|
|
|
|
// 默认返回true,避免阻止功能
|
|
return true
|
|
}
|
|
}
|