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.
305 lines
10 KiB
305 lines
10 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 = "语音助理耳机"
|
|
|
|
// 用于存储配对设备UUID的UserDefaults键
|
|
private let pairedDeviceUUIDKey = "com.yunqiinnovation.ble_service.paired_device_uuid"
|
|
|
|
typealias DeviceDiscoveredCallback = (UUID) -> Void
|
|
private var onDeviceDiscovered: DeviceDiscoveredCallback?
|
|
|
|
// MARK: - 初始化
|
|
private override init() { super.init() }
|
|
|
|
// MARK: - 公共方法
|
|
func setBleService(_ service: Any) {
|
|
self.bleService = service as? BleService
|
|
}
|
|
|
|
func setDeviceDiscoveredCallback(_ callback: @escaping (UUID) -> Void) {
|
|
self.onDeviceDiscovered = callback
|
|
}
|
|
|
|
// 处理发现的设备并连接
|
|
func connectToAccessory(uuid: UUID) {
|
|
guard let service = bleService else {
|
|
return
|
|
}
|
|
|
|
service.connectToAccessory(uuid: uuid)
|
|
}
|
|
|
|
func addToReconnectQueue(uuid: UUID) {
|
|
guard let service = bleService else {
|
|
return
|
|
}
|
|
|
|
service.addToReconnectQueue(uuid: uuid)
|
|
}
|
|
|
|
// 从视图控制器显示设备选择器 - 主要入口点
|
|
func showDevicePickerFromViewController(_ viewController: UIViewController) {
|
|
initializeSession()
|
|
}
|
|
|
|
// 清除蓝牙状态(仅用于测试或重置,不会清除系统授权)
|
|
func resetBluetoothState() {
|
|
if let service = bleService {
|
|
service.disconnect()
|
|
}
|
|
}
|
|
|
|
// 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()
|
|
|
|
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
|
|
|
|
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
|
|
}
|
|
|
|
// MARK: - 设备选择器显示
|
|
private func showDevicePicker() {
|
|
guard let session = session, isSessionActivated else {
|
|
os_log("无法显示配对框:会话未激活", log: logger, type: .error)
|
|
return
|
|
}
|
|
|
|
os_log("准备显示设备配对框", log: logger, type: .info)
|
|
|
|
let descriptor = ASDiscoveryDescriptor()
|
|
descriptor.bluetoothCompanyIdentifier = ASBluetoothCompanyIdentifier(deviceCompanyIdentifier)
|
|
|
|
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 }
|
|
|
|
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()
|
|
|
|
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 .accessoryChanged:
|
|
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 .pickerSetupFailed:
|
|
os_log("选择器设置失败: %{public}@", log: logger, type: .error,
|
|
event.error?.localizedDescription ?? "未知错误")
|
|
|
|
default:
|
|
os_log("未处理的会话事件: %d", log: logger, type: .info, event.eventType.rawValue)
|
|
break
|
|
}
|
|
}
|
|
|
|
private func handleSessionActivated() {
|
|
isSessionActivated = true
|
|
isInitializing = false
|
|
|
|
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()
|
|
}
|
|
}
|
|
|
|
private func handleAccessoryAdded(_ accessory: ASAccessory?) {
|
|
guard let accessory = accessory, let uuid = accessory.bluetoothIdentifier else {
|
|
return
|
|
}
|
|
|
|
// 保存配对设备的UUID
|
|
savePairedMacAddress(uuid)
|
|
|
|
onDeviceDiscovered?(uuid)
|
|
connectToAccessory(uuid: uuid)
|
|
}
|
|
|
|
private func handleAccessoryRemoved(_ accessory: ASAccessory?) {
|
|
guard let accessory = accessory, let uuid = accessory.bluetoothIdentifier else {
|
|
return
|
|
}
|
|
|
|
if let service = bleService {
|
|
service.disconnect()
|
|
}
|
|
}
|
|
|
|
private func handleSessionInitError(_ error: Error) {
|
|
isInitializing = false
|
|
session = nil
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// MARK: - 清除配对关系
|
|
func clearAssociations() {
|
|
resetBluetoothState()
|
|
|
|
// 清除保存的配对设备UUID
|
|
clearPairedMacAddress()
|
|
|
|
#if canImport(AccessorySetupKit) && swift(>=5.9)
|
|
if #available(iOS 18.0, *) {
|
|
resetSession()
|
|
initializeSession()
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// 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
|
|
/// - 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)
|
|
}
|
|
}
|