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.

268 lines
9.4 KiB

import Foundation
import CoreBluetooth
import os.log
import AVFoundation
import AccessorySetupKit
import UIKit
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 let deviceCompanyIdentifier: UInt16 = 0xEFAB
private let deviceDisplayName = "语音助理耳机"
typealias DeviceDiscoveredCallback = (UUID) -> Void
private var onDeviceDiscovered: DeviceDiscoveredCallback?
// MARK: - 初始化
private override init() { super.init() }
// MARK: - 公共方法
func setBleService(_ service: Any) {
self.bleService = service as? BleService
os_log("已设置BLE服务实例", log: logger, type: .debug)
}
func setDeviceDiscoveredCallback(_ callback: @escaping (UUID) -> Void) {
self.onDeviceDiscovered = callback
}
// 处理发现的设备并连接
func connectToAccessory(uuid: UUID) {
guard let service = bleService else {
os_log("BleService未设置,无法连接到设备", log: logger, type: .error)
return
}
// 连接设备
service.connectToAccessory(uuid: uuid)
}
// 从视图控制器显示设备选择器 - 主要入口点
func showDevicePickerFromViewController(_ viewController: UIViewController) {
// 初始化会话(如果尚未初始化)
initializeSession()
}
// 清除蓝牙状态(仅用于测试或重置,不会清除系统授权)
func resetBluetoothState() {
if let service = bleService {
service.disconnect()
}
}
// MARK: - 会话管理
func initializeSession() {
// 检查是否已在初始化或已初始化
if isInitializing || (session != nil && isSessionActivated) {
os_log("会话已在初始化或已激活", log: logger, type: .debug)
return
}
// 开始初始化过程
isInitializing = true
os_log("初始化设备发现会话", log: logger, type: .info)
do {
session = try ASAccessorySession()
session?.activate(on: .main) { [weak self] event in
guard let self = self else { return }
self.handleSessionEvent(event)
}
} catch {
handleSessionInitError(error)
}
}
func resetSession() {
session?.invalidate()
session = nil
isSessionActivated = false
isInitializing = false
os_log("会话已重置", log: logger, type: .debug)
}
// MARK: - 设备选择器显示
private func showDevicePicker() {
guard let session = session, isSessionActivated else {
os_log("会话未激活,无法显示设备选择器", log: logger, type: .error)
return
}
// 创建设备描述符
let descriptor = ASDiscoveryDescriptor()
descriptor.bluetoothCompanyIdentifier = ASBluetoothCompanyIdentifier(deviceCompanyIdentifier)
// 创建显示项
let deviceImage = UIImage(systemName: "headphones") ?? UIImage()
let displayItem = ASPickerDisplayItem(
name: deviceDisplayName,
productImage: deviceImage,
descriptor: descriptor
)
os_log("显示设备选择器", log: logger, type: .info)
DispatchQueue.main.async {
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}@, 域: %{public}@, 错误码: %d",
log: self.logger, type: .error,
error.localizedDescription, nsError.domain, nsError.code)
} else {
os_log("设备选择器已显示", log: self.logger, type: .debug)
}
}
}
}
// MARK: - 私有辅助方法
private func handleSessionEvent(_ event: ASAccessoryEvent) {
switch event.eventType {
case .activated:
handleSessionActivated()
case .accessoryAdded:
handleAccessoryAdded(event.accessory)
case .accessoryRemoved:
handleAccessoryRemoved(event.accessory)
case .accessoryChanged:
os_log("设备已变更", log: logger, type: .debug)
case .pickerDidPresent:
os_log("选择器已显示", log: logger, type: .info)
case .pickerDidDismiss:
os_log("选择器已关闭", log: logger, type: .info)
case .pickerSetupFailed:
os_log("选择器设置失败: %{public}@", log: logger, type: .error,
event.error?.localizedDescription ?? "未知错误")
default:
os_log("未处理的事件类型: %d", log: logger, type: .debug, event.eventType.rawValue)
}
}
private func handleSessionActivated() {
os_log("会话已激活", log: logger, type: .info)
isSessionActivated = true
isInitializing = false
// 检查是否有已授权设备
if let accessories = session?.accessories, !accessories.isEmpty {
// 二次进入:已有授权设备,可以先创建Central再连接
os_log("发现%d个已授权设备", log: logger, type: .info, accessories.count)
// 初始化BleService的Central
if let service = bleService {
// 确保CBCentralManager已初始化
let _ = service.getCentralManager()
// 连接所有授权设备
for accessory in accessories {
if let uuid = accessory.bluetoothIdentifier {
os_log("连接已授权设备: %{public}@", log: logger, type: .info, uuid.uuidString)
onDeviceDiscovered?(uuid)
connectToAccessory(uuid: uuid)
}
}
}
} else {
// 首次配对:不创建Central,直接显示设备选择器
os_log("没有已授权设备,显示设备选择器", log: logger, type: .info)
showDevicePicker()
}
}
private func handleAccessoryAdded(_ accessory: ASAccessory?) {
os_log("发现已授权设备: %{public}@", log: logger, type: .info, accessory?.bluetoothIdentifier?.uuidString ?? "Unknown")
guard let accessory = accessory, let uuid = accessory.bluetoothIdentifier else {
os_log("发现设备但缺少UUID信息", log: logger, type: .error)
return
}
// 通知发现设备并连接
onDeviceDiscovered?(uuid)
connectToAccessory(uuid: uuid)
}
private func handleAccessoryRemoved(_ accessory: ASAccessory?) {
guard let accessory = accessory, let uuid = accessory.bluetoothIdentifier else {
return
}
os_log("设备已移除授权: %{public}@", log: logger, type: .info, uuid.uuidString)
// 断开连接
if let service = bleService {
service.disconnect()
}
}
private func handleSessionInitError(_ error: Error) {
os_log("初始化会话失败: %{public}@", log: logger, type: .error, error.localizedDescription)
isInitializing = false
session = nil
}
// MARK: - 蓝牙状态检查
func isBluetoothAvailable() -> Bool {
// 使用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 {
return true
}
}
os_log("未检测到蓝牙音频设备", log: logger, type: .debug)
} catch {
os_log("检查蓝牙状态出错: %{public}@", log: logger, type: .error, error.localizedDescription)
}
// 默认返回true避免阻止功能
return true
}
// MARK: - 清除配对关系
func clearAssociations() {
os_log("清除设备配对关系", log: logger, type: .info)
// 先断开当前连接
resetBluetoothState()
// 清除会话授权关系
#if canImport(AccessorySetupKit) && swift(>=5.9)
if #available(iOS 18.0, *) {
// 方法1: 重置整个会话
resetSession()
// 重新初始化会话
initializeSession()
os_log("会话已重置,所有授权关系已清除", log: logger, type: .info)
}
#endif
}
}